fix(gtea): UPGRADE_SECRET_PREP hook — pre-insert lfs_jwt_secret with correct 43-char format
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
Blocker 4 fix: abra `secret generate --all` uses .env.sample for length hints; the lfs-plain-gitea PR has SECRET_LFS_JWT_SECRET_VERSION=v1 COMMENTED OUT, so abra produces a wrong-length secret. gitea requires exactly 43 chars (32 bytes base64 URL-safe); wrong length → gitea fatals trying to save the JWT secret to the read-only Docker Config app.ini → health check fails → swarm rolls back. Fix: new UPGRADE_SECRET_PREP hook (meta.py) called before `abra secret generate --all` in the upgrade path. abra's `--all` is idempotent (skips existing secrets), so the correctly pre-inserted secret survives. gitea's recipe_meta.py implements the hook using `docker secret create` directly to guarantee correct format regardless of .env.sample. Also consumes machine-docs/BUILDER-INBOX.md (Adversary Blocker 4 digest). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -60,6 +60,45 @@ def UPGRADE_EXTRA_ENV(ctx):
|
||||
}
|
||||
|
||||
|
||||
def UPGRADE_SECRET_PREP(ctx):
|
||||
"""Pre-insert lfs_jwt_secret with the correct 43-char base64 URL-safe format before
|
||||
`abra secret generate --all` runs. The lfs-plain-gitea PR's .env.sample has the
|
||||
SECRET_LFS_JWT_SECRET_VERSION=v1 spec COMMENTED OUT, so abra uses a wrong default length;
|
||||
gitea requires exactly 43 chars (32 bytes) or it fatals on the read-only app.ini."""
|
||||
if not _lfs_enabled():
|
||||
return
|
||||
import base64
|
||||
import subprocess
|
||||
|
||||
env_path = _os.path.expanduser(f"~/.abra/servers/default/{ctx.domain}.env")
|
||||
stack_name = None
|
||||
try:
|
||||
with open(env_path) as fh:
|
||||
for line in fh:
|
||||
if line.startswith("STACK_NAME="):
|
||||
stack_name = line.split("=", 1)[1].strip().strip('"').strip("'")
|
||||
except OSError:
|
||||
pass
|
||||
if not stack_name:
|
||||
raise RuntimeError(f"UPGRADE_SECRET_PREP: STACK_NAME not found in {env_path}")
|
||||
|
||||
docker_secret = f"{stack_name}_lfs_jwt_secret_v1"
|
||||
value = base64.urlsafe_b64encode(_os.urandom(32)).rstrip(b"=").decode()
|
||||
|
||||
subprocess.run(["docker", "secret", "rm", docker_secret], capture_output=True)
|
||||
result = subprocess.run(
|
||||
["docker", "secret", "create", docker_secret, "-"],
|
||||
input=value,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(
|
||||
f"UPGRADE_SECRET_PREP: docker secret create {docker_secret}: {result.stderr.strip()}"
|
||||
)
|
||||
print(f" gitea upgrade: pre-created {docker_secret} (43-char lfs_jwt_secret)", flush=True)
|
||||
|
||||
|
||||
def EXTRA_ENV(ctx):
|
||||
lfs = _lfs_enabled()
|
||||
compose_file = "compose.yml:compose.sqlite3.yml"
|
||||
|
||||
@ -65,6 +65,7 @@ def test_missing_meta_yields_spec_baseline(tmp_path):
|
||||
assert meta.DEPS == []
|
||||
assert meta.WARM_CANONICAL is False
|
||||
assert meta.SCREENSHOT is None
|
||||
assert meta.UPGRADE_SECRET_PREP is None
|
||||
assert meta_mod.non_default(meta) == {}
|
||||
|
||||
|
||||
@ -73,9 +74,9 @@ def test_registry_field_set_matches_dataclass():
|
||||
import dataclasses
|
||||
|
||||
assert [f.name for f in dataclasses.fields(RecipeMeta)] == [k.name for k in KEYS]
|
||||
# the 14 final keys, no more (the 3 P2-deleted legacy keys are gone from the registry,
|
||||
# the 15 final keys, no more (the 3 P2-deleted legacy keys are gone from the registry,
|
||||
# so any recipe_meta still setting them hard-fails the typo gate)
|
||||
assert len(KEYS) == 14
|
||||
assert len(KEYS) == 15
|
||||
assert not [k for k in KEYS if k.deprecated]
|
||||
for gone in ("CHAOS_BASE_DEPLOY", "OIDC_AT_INSTALL", "SKIP_GENERIC"):
|
||||
assert gone not in {k.name for k in KEYS}
|
||||
|
||||
Reference in New Issue
Block a user