Skip to content

fix(deploy): create /data/git/.ssh with 0700 before processgit starts#139

Merged
rg4444 merged 1 commit into
mainfrom
fix/git-ssh-dir-perms
May 23, 2026
Merged

fix(deploy): create /data/git/.ssh with 0700 before processgit starts#139
rg4444 merged 1 commit into
mainfrom
fix/git-ssh-dir-perms

Conversation

@rg4444
Copy link
Copy Markdown
Contributor

@rg4444 rg4444 commented May 23, 2026

fix(deploy): create /data/git/.ssh with 0700 before processgit starts

What went wrong on v0.1.2

End-to-end install test on v0.1.2 (the install-wizard-skip fix from #138 is working — Gitea boots past the wizard, runs migrations, initializes storage), then dies with:

[F] code.gitea.io/gitea/services/asymkey.RewriteAllPublicKeys(ctx) failed:
    open /data/git/.ssh/authorized_keys.tmp: permission denied

RewriteAllPublicKeys writes an authorized_keys file containing all registered SSH keys (zero on a fresh install — but it still writes the empty file). The parent dir /data/git/.ssh must:

  1. Exist
  2. Be owned by the gitea user (uid 1000)
  3. Have mode 0700 (SSH refuses to use it otherwise)

The standard gitea/gitea image's startup chain handles this. Our custom /etc/s6/gitea/run override bypasses it — the same s6-overlay v3 mismatch that broke env-var-to-app.ini and necessitated the init-config service in #138.

Fix

Two lines of substance in deploy/bootstrap/init-config.sh:

   /data/git/repositories \
-  /data/git/lfs
+  /data/git/lfs \
+  /data/git/.ssh
+
+chmod 0700 /data/git/.ssh

Plus a 4-line doc comment so this isn't surprising six months from now when someone wonders why we chmod a directory.

init-config runs as 1000:1000, so the dir ends up owned by the right user automatically — no chown needed.

Why this wasn't in #138

Honestly: I didn't think of it. The pre-bootstrap mental model was "create the dirs Gitea writes config to" and I enumerated the obvious ones from the data tree. SSH auth-keys storage is a less-obvious one because Gitea writes there only on startup as a side effect of RewriteAllPublicKeys, not as part of any visible config path.

The end-to-end test is what surfaced this. This is the value of actually running the install — neither of these papercuts (install-wizard, ssh-dir) appeared in any of the unit tests or CI checks we've run. Every release we tag without a fresh-install validation pass is a potential bug parked in production.

What init-config.sh now ensures (full list)

/data/gitea/conf          (app.ini lives here)
/data/gitea/log
/data/gitea/attachments
/data/gitea/avatars
/data/gitea/repo-avatars
/data/gitea/sessions
/data/gitea/indexers
/data/git/repositories
/data/git/lfs
/data/git/.ssh           ← NEW, with mode 0700

Plus /data/git/ implicitly via mkdir -p.

Could there be more missing dirs?

Maybe. Gitea creates /app/gitea/data/{repo-archive,packages,actions_log,actions_artifacts} lazily at startup — those work because /app/gitea/data/ is writable. The next install attempt will tell us if anything else needs to be pre-provisioned. If so, we add it in v0.1.4. The pattern is now established: pre-bootstrap container, idempotent, owned by 1000:1000.

After merge

Tag v0.1.3 against new main HEAD. Operator pulls v0.1.3's compose, runs docker compose up -d, and processgit should become healthy on its own.

Second papercut surfaced by the end-to-end install test on v0.1.2.
Init-config now successfully writes app.ini and Gitea boots past the
install wizard. It runs SQLite migrations, initializes the storage
modules, then dies with:

  [F] code.gitea.io/gitea/services/asymkey.RewriteAllPublicKeys(ctx)
      failed: open /data/git/.ssh/authorized_keys.tmp: permission
      denied

RewriteAllPublicKeys writes an authorized_keys file containing all
registered SSH keys (zero on a fresh install — but it still writes
the empty file). The parent dir /data/git/.ssh must:

  1. Exist
  2. Be owned by the gitea user (uid 1000)
  3. Have mode 0700 (SSH refuses to use it otherwise)

The standard gitea/gitea image's startup chain creates this. Our
custom /etc/s6/gitea/run override bypasses it (same s6-overlay v3
mismatch that broke env-var-to-app.ini conversion and necessitated
the init-config service in v0.1.2 — see #138).

Fix: add /data/git/.ssh to init-config's mkdir block and chmod 0700.
init-config runs as 1000:1000, so the dir ends up owned by the right
user automatically.

Two lines of substance, plus a doc comment explaining the SSH perms
requirement so this isn't surprising in three months.

Testing path: v0.1.3 release → fresh install → processgit should
become healthy on first boot.

Co-authored-by: Claude <noreply@anthropic.com>
@rg4444 rg4444 merged commit c3ab0f6 into main May 23, 2026
23 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 13d0ab7ff3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

/data/git/repositories \
/data/git/lfs
/data/git/lfs \
/data/git/.ssh
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Run SSH directory provisioning before lock short-circuit

This new /data/git/.ssh creation path only executes when app.ini is regenerated, but the script exits early whenever INSTALL_LOCK=true (deploy/bootstrap/init-config.sh lines 24–27). That means users upgrading from the broken v0.1.2 state (where app.ini already exists and startup failed on missing/invalid .ssh) will still skip these new lines and keep failing at Gitea startup. In practice, the fix works for fresh installs but not for the upgrade/recovery path it is intended to unblock.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant