Full production-readiness pass: fixed every issue found during exhaustive functional and restart-stability testing (admin/user/repo/fork/mirror+sync, webhook delivery, real Gitea Actions CI job runs, paced and rapid-burst container restarts), then brought all touched scripts into full lint compliance. Most significant: Gitea Actions CI jobs failed 100% of the time due to a nested-overlayfs Docker-in-Docker mount conflict, despite runners appearing registered and online — only an actual job run surfaced it. Now verified end-to-end across two full passes: a real workflow reaches `status: success`, the full restart-stability suite (3x paced + 4x rapid-burst restarts) shows identical runner UUIDs with no duplicate registrations, and a final focused smoke test confirmed the lint-only edits (exit-code and trap-line changes included) introduced zero regressions. - rootfs/tmp/etc/docker/daemon.json: added `storage-driver: fuse-overlayfs` to the baked default config. The inner (DinD) dockerd's default overlayfs driver conflicted with the outer container's own overlay-backed root filesystem, causing every job container creation to fail with `failed to mount ... fstype: overlay ... err: invalid argument`. fuse-overlayfs (already bundled in the image) mounts entirely in userspace via FUSE, avoiding the kernel-level conflict; no new run-flag requirement since `--privileged`/`SYS_ADMIN` already covers it. - rootfs/usr/local/etc/docker/init.d/05-dockerd.sh: same `storage-driver: fuse-overlayfs` fix added to both daemon.json generation branches (with/without registry) as defense-in-depth for the case where the baked `/etc/docker` seed directory is absent; also clears a stale `/tmp/docker.pid` before each start attempt so dockerd doesn't refuse to start after a restart when the PID namespace reset lets an unrelated early-boot process reuse the old PID number; fixed a call to the non-existent `symlink` function (missing its `__` prefix) that silently no-op'd the `/var/lib/docker` symlink setup on every run — corrected to `__symlink`, the actual function defined in functions/entrypoint.sh and used consistently everywhere else in this codebase; also updated its `su_cmd touch "$SERVICE_PID_FILE"` call site to `__su_cmd` to match the function rename in functions/entrypoint.sh (see that entry below). Lint pass: added `VERSION="..."` assignment and refreshed the `##@Version` header timestamp; added `--` before 8 grep search patterns; wrapped 2 unconditional emoji echoes in the existing NO_COLOR conditional pattern. - rootfs/usr/local/etc/docker/init.d/08-gitea.sh: updated the one `su_cmd touch "$SERVICE_PID_FILE"` call site to `__su_cmd`, matching the function rename in functions/entrypoint.sh (see that entry above) — otherwise this script would call an undefined function on every service start. Since this required touching the file anyway, brought it into the same lint compliance as the other 5 scripts in this pass: added `VERSION="..."` assignment and refreshed the `##@Version` header timestamp (this script previously had neither); added `--` before 8 grep search patterns. - rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: removed the legacy single "gitea"-named runner registration and daemon-start blocks, which duplicated the runner set already owned by start-runners/RUNNERS_START; removed the second unconditional `__post_execute` invocation at the bottom of the script that duplicated runner registration on every start; switched RUNNER_IP_ADDRESS to loopback instead of the detected external IP4_ADDRESS, which was transient/wrong under Docker-in-Docker networking and caused "no route to host" registration failures; redirected the cache-server and start-runners background job output to real log files (instead of inheriting the __post_execute pipe) and disowned both jobs, since a long-running background process inheriting that pipe's write end would never let the reading `tee` see EOF, hanging __run_start_script forever; also updated its `su_cmd touch "$SERVICE_PID_FILE"` call site to `__su_cmd` to match the function rename in functions/entrypoint.sh (see that entry below). Lint pass: refreshed the stale `##@Version`/`VERSION=` timestamp since the file was substantially edited this pass; broke one 207-char line into multiple lines without changing behavior; confirmed the 2 existing emoji echoes are already NO_COLOR-guarded. - rootfs/usr/local/bin/start-runners: refactored the default RUNNER_LABELS value into an array joined with a comma, avoiding one very long hardcoded line. Lint pass: added the missing standard script header block and a `VERSION="..."` assignment (this script previously had neither); wrapped the fatal-error trap's emoji echo in a NO_COLOR conditional, matching the existing pattern used elsewhere in the repo. - rootfs/usr/local/bin/entrypoint.sh: the stale-PID-file restart guard now also checks the live process's own cmdline for "entrypoint.sh" before treating the recorded PID as still running, since the PID namespace resets on every `docker restart` while `/run` persists, letting an unrelated early-boot process coincidentally reuse the old PID number and cause the guard to wrongly skip __start_init_scripts on a real restart. Lint pass: added `VERSION="..."` assignment and refreshed the `##@Version` header timestamp; added `--` before 6 grep search patterns (including one missed on the first fix pass); replaced 2 bare `exit` statements with explicit exit codes. - rootfs/usr/local/etc/docker/functions/entrypoint.sh: same PID-reuse false-positive fix applied to `__no_exit`'s monitor-loop guard, via a `__no_exit_monitor_loop` marker embedded in the exec'd process's own cmdline so a future restart can tell a genuine still-running monitor loop apart from an unrelated process that reused its PID; also renamed the 6 `su_cmd()` helper definitions inside `__switch_to_user()` to `__su_cmd()` to follow this codebase's `__`-prefix naming convention for internal functions, and updated all 3 call sites (05-dockerd.sh, 08-gitea.sh, zz-act_runner.sh — all `su_cmd touch "$SERVICE_PID_FILE"`) so the shared function rename doesn't silently break any of the three init.d scripts that source it. Lint pass: added `VERSION="..."` assignment and refreshed the `##@Version` header timestamp; added `--` before 15 grep search patterns; wrapped 4 unconditional emoji echoes in the existing NO_COLOR conditional pattern; broke one 200-char line into multiple lines without changing behavior. - rootfs/tmp/etc/gitea/app.ini: moved `ALLOWED_HOST_LIST` from the deprecated `[webhook]` section to `[security]`, removing a `[E] Deprecation:` warning logged on every gitea startup and admin-CLI invocation (`[webhook].ALLOWED_HOST_LIST` is deprecated and slated for removal in Gitea v28.0.0). - TODO.AI.md: documented every bug found and fixed this pass, including full verification details (exact error messages, root-cause tracing, live confirmation steps, and regression-suite results) for each.
22 KiB
TODO.AI.md
Lint cleanup done — UUOC fixed (start-runners)
Verified clean by script-lint agent after fix.
rootfs/usr/local/bin/start-runners: line 24 UUOC (echo | grep -q '://') replaced with[[ "$SERVER_ADDRESS" != *"://"* ]]; grep call removed entirely so the missing--no longer applies.
Lint cleanup done — version stamp and grep -- fixed (zz-act_runner.sh)
Verified clean by script-lint agent after fix.
rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: addedVERSION="202608031200-git"matching the existing##@Versionheader; added--before the pattern argument on all 15 grep invocations in the file (not just the subset originally enumerated); quoted the baregreppattern at the former line 544 (nowgrep -v -- 'grep').
Lint cleanup done — line-length violation fixed (start-runners)
rootfs/usr/local/bin/start-runners: the 781-charRUNNER_LABELS="${RUNNER_LABELS:-...}"default literal was replaced with a_default_runner_labelsarray joined viaIFS=,, only applied whenRUNNER_LABELSis unset. Verified withbash -nand a line-length scan (no line exceeds 180 chars).
App-breaking bug fixed — DEBUGGER guard pattern under set -e (functions/entrypoint.sh)
Needs syncing back to the upstream template in casjay-dotfiles/scripts per the Docker Template
Update Runbook in AI.md — functions/entrypoint.sh is normally regenerated, not hand-edited.
- 26x occurrences of
[ "$DEBUGGER" = "on" ] && echo/printf/__service_banner "..."used as a bare statement: underset -e, this aborts the whole script silently whenever$DEBUGGER!= "on" (the default). This was the root cause of the container dying immediately after printing only the startup banner. Fixed by appending|| trueto all 26 occurrences.
App-breaking bug fixed — __random_password() SIGPIPE (functions/entrypoint.sh)
Needs syncing back to the upstream template in casjay-dotfiles/scripts per the Docker Template
Update Runbook in AI.md — functions/entrypoint.sh is normally regenerated, not hand-edited.
__random_password()(~line 333):tr | head -cpipeline died underset -eo pipefailon SIGPIPE. Fixed by wrapping in{ ... } || true.
App-breaking bug fixed — __format_variables() whitespace-only input (functions/entrypoint.sh)
Needs syncing back to the upstream template per AI.md's runbook.
__format_variables()(~line 187):printf '%s\n' $input | sort -Ru | tr '\n' ' 'always emits at least one line even when$inputword-splits to zero words (whitespace-only), becauseprintfwith a format containing%sruns once even with no args. This madeENV_PORTS/WEB_SERVER_PORTSresolve to a single space" "instead of empty when no port env vars were set, which madeSERVICE_PORTin08-gitea.shbecome" "— passing the-ntest but rendering as an empty--portarg togitea web, which broke gitea's CLI argument parsing entirely (Command error: unknown command: /config/gitea/app.ini). Fixed by replacing the[ -z "$input" ]check with[[ "$input" =~ [^[:space:]] ]] || return 0.
App-breaking bug fixed — missing /config/env directory (bin/entrypoint.sh)
/config/envdirectory was never explicitly created. It only came into existence as a side effect of__create_env_file()(functions/entrypoint.sh) copying/usr/local/etc/docker/env/default.sampleinto it — but that sample file/dir does not exist in this image's rootfs, so__create_env_file()returns early (line 960) without creating the directory.05-dockerd.sh's__create_service_env()then fails writing/config/env/docker.local.shdirectly (cat <<'EOF' >"/config/env/....local.sh", noteesuppression) withNo such file or directory;zz-act_runner.shhits the same error writing/config/env/act_runner.local.sh. Fixed by addingmkdir -p "/config/env" 2>/dev/null || truealongside the other/config/*directory creation lines (~line 241) inrootfs/usr/local/bin/entrypoint.sh. Needs syncing to the upstream template per AI.md's runbook.
OCI label cleanup done — forbidden labels removed from Dockerfile
- Removed
org.opencontainers.image.base.name(belongs on the base image, not the app image) andorg.opencontainers.image.schema-version(non-spec, redundant withversion). - Removed the duplicate
org.opencontainers.image.authors="${LICENSE}"line and duplicateorg.opencontainers.image.source="https://docker.io/..."line; the license value now correctly populates the (previously missing)org.opencontainers.image.licenseslabel per AI.md's OCI label standard (lines 58-87), andsourcekeeps the single github.com URL.
App-breaking bug fixed — act_runner init.d hang + duplicate runner registration (zz-act_runner.sh)
Needs syncing back to the upstream template in casjay-dotfiles/scripts per the Docker Template
Update Runbook in AI.md — the outer hooks (__post_execute, __run_start_script) are normally
generated, not hand-edited, but this repo's app-specific runner logic already lives inline in
them rather than in the *_local() stubs, so the fix was applied in place.
- Root cause of the hang: because
EXEC_CMD_BIN=''for this service,__run_start_script(called at the script's tail) internally calls__post_executesynchronously through__post_execute | tee -p -a "/data/logs/init.txt". Inside__post_execute's own backgrounded subshell,/usr/local/bin/start-runners &was launched without redirecting its stdout/stderr, so it (and the long-runningact_runner daemonprocesses itexecs, which never exit) inherited the write end of thatteepipe. Since that write end never closed,teenever saw EOF, so it never exited, so__run_start_scriptnever returned, so__start_init_scripts's( source "$init" )for this script never completed — the init.d loop hung forever after all services were actually up, and/data/logs/start.log's finalprintf(the completion marker) was never reached. Fixed by redirectingstart-runners's stdout/stderr to"$LOG_DIR/runners.log"at its invocation site instead of inheriting the pipe. - The script also called
__post_executea second time explicitly (__post_execute ... | tee ... &near the end of the script), duplicating the call__run_start_scriptalready makes via its empty-EXEC_CMD_BINbranch. Removed the redundant explicit call. - The script additionally registered and started a hardcoded, always-on single runner named
"gitea"(in__run_pre_execute_checksand__post_execute) independent of and in addition to theRUNNERS_START-driven runners fromstart-runners— soRUNNERS_START=2produced 3 registered runners (gitea,runner-1,runner-2) instead of 2. Removed both the legacy registration block and its matching daemon-start block;start-runners(already idempotent per-runner via its.runner-file check) is now the sole runner registration/startup path. - Verified with
bash -nand a live rebuild/retest: the init.d loop now completes and/data/logs/start.loggets its completion marker; onlyrunner-1/runner-2are registered (nogitearunner).
App-breaking bug fixed — act_runner registered against wrong/unreachable IP (zz-act_runner.sh)
Needs syncing back to the upstream template per AI.md's runbook (same file/hooks as above).
RUNNER_IP_ADDRESSdefaulted to$IP4_ADDRESS(the container's externally-detected IP, captured once very early inbin/entrypoint.shvia__get_ip4). In live testing this value did not match the container's actualeth0address by the timeact_runner daemontried to connect, causing every runner to fail its first RPC withdial tcp <ip>:80: connect: no route to hostand exit immediately (act_runner does not retry a failed initial connection). Since act_runner registers against gitea running in the very same container/network namespace, the detected external IP was never the right thing to use. Fixed by defaultingRUNNER_IP_ADDRESSto127.0.0.1instead (still overridable via theRUNNER_IP_ADDRESSenv var).- Verified live: after the fix,
curl http://127.0.0.1/inside the container succeeds immediately. A first retest still showed runners connecting to172.17.0.2:80, but this was a false alarm caused by stale test volumes: a previous test run's.runnerregistration files (with the old baked-in address) were still present, and__register_runner's idempotency check correctly skipped re-registering over them. After fully removing the test volumes and starting a genuinely fresh container, runner registration and connectivity succeeded (Runner registered successfully./declare successfullyfor bothrunner-1andrunner-2, no connection errors).
App-breaking bug fixed — cache-server fd leak hangs init.d loop (zz-act_runner.sh)
Needs syncing back to the upstream template per AI.md's runbook (same file/hooks as above).
- Same fd-leak class as the
start-runnershang fixed above, but foract_runner cache-server:act_runner cache-server --config "$CACHE_CONFIG_FILE" 2>>/dev/stderr >>"$CACHE_LOG_FILE" &used2>>/dev/stderr, which duplicates the process's current stderr fd — at that point in__post_execute's backgrounded subshell, still the write end of thetee -p -a "/data/logs/init.txt"pipe used by the synchronous__post_executecall in__run_start_script. Sincecache-serveris a long-running daemon that never exits, that pipe's write end never closed, soteenever saw EOF, hanging__run_start_script(and therefore the whole init.d loop) forever even though dockerd/gitea/act_runner/cache-server were all actually up and working. Fixed by redirecting to"$CACHE_LOG_FILE" 2>&1instead of/dev/stderr. - The redirect fix alone did not fully resolve the hang: the subshell launching
start-runners/cache-server(the left side of__post_execute's pipe totee -p -a /data/logs/init.txt) was still observed blocked indo_waiton its long-running child even with output redirected to a real file, preventing the pipe from ever seeing EOF. Added an explicitdisown "$!"immediately after each background launch (act_runner cache-serverand/usr/local/bin/start-runners) to fully detach them from the subshell's job table. - Verified live on a genuinely fresh container (volumes removed first):
/data/logs/start.lognow gets its completion marker, no orphanedtee -p -a /data/logs/init.txtprocesses remain, and bothrunner-1/runner-2register and declare successfully against gitea (runners.logshowsRunner registered successfully.anddeclare successfullyfor both, no connection errors).
App-breaking bug fixed — dockerd fails to restart under rapid restart cycling (05-dockerd.sh)
Found while directly testing the user's "restart container many times" requirement with a rapid 4x-restart loop.
docker restartreuses the same container filesystem (unlike a freshrun), so/tmp/docker.pidfrom the previous dockerd instance survives the restart. The PID namespace itself resets on every restart, so the low PID number written into that file (e.g.461) can coincidentally be reused by an unrelated early-boot process in the new namespace. dockerd's own startup check saw/proc/<pid>exist and refused to start:failed to start daemon, ensure docker is not running or delete /tmp/docker.pid: process with PID 461 is still running.- Observed impact:
05-dockerd.shlogged❌ Service dockerd failed to start - check logs, but gitea and act_runner still reported starting successfully; the entrypoint logged⚠️ Warning: 1 critical service(s) reported failures/ℹ️ Continuing with 1 failure(s) - container may still be functionaland kept going in a degraded state, then the whole container crashed anyway roughly 2 minutes later (ExitCode=1). - Fixed by removing
/tmp/docker.pidunconditionally at the top of__run_pre_execute_checksin05-dockerd.sh, before dockerd is started. This init script is the sole owner of the dockerd lifecycle (a separate real overlap-guard already exists viaSERVICE_PID_FILE), so clearing docker's own pidfile before every start attempt is safe. - Verified live on a rebuilt image: no
failed to start daemon/ stale-pidfile error appeared in the logs across either a 3x normally-paced restart loop or a 4x rapid back-to-back restart burst (see the verification note on the PID-reuse fix below — both fixes were tested together in the same runs).
App-breaking bug fixed — PID-reuse false positive kills container after restart (entrypoint.sh, functions/entrypoint.sh)
Found and fixed immediately after the dockerd stale-pidfile fix above, while retesting restart
robustness on the rebuilt image. Same root bug class (a PID recorded in a file that persists
across docker restart gets coincidentally reused by an unrelated process once the PID namespace
resets), but hitting two different guards this time, one of which actively took down the whole
container:
functions/entrypoint.sh's__no_exit(): guarded re-entry with[ -f /run/.no_exit.pid ] && kill -0 "$no_exit_pid". After a restart, an unrelated early-boot process could reuse that old PID number, making the check wrongly believe the monitor loop was already running.__no_exitthenreturned 0 instead ofexecing the actual monitor loop, sobin/entrypoint.shfell straight through toexit $?— the whole entrypoint process exited cleanly (ExitCode=0), killing the container roughly 2 minutes after a normal, correctly-paced restart, well after dockerd/gitea/act_runner had all logged successful starts. This is almost certainly the actual root cause behind earlier restart-loop crashes previously attributed only to the dockerd stale-pidfile bug.bin/entrypoint.sh'sENTRYPOINT_PID_FILEcheck (~line 433) has the identical hazard in the opposite direction: a false-positive "still alive" match setsSTART_SERVICES=no, which would silently skip__start_init_scriptsentirely on a genuine restart (not observed in this test run, but reachable by the same mechanism).- Fixed both by requiring the live PID's own
/proc/<pid>/cmdlineto actually match the expected process, not justkill -0succeeding:__no_exit's exec'd monitor loop now embeds a__no_exit_monitor_loopmarker comment in its ownbash -ccommand text (visible in its own cmdline), and the re-check greps for it;ENTRYPOINT_PID_FILE's check greps the candidate PID's cmdline forentrypoint.sh. - Verified live on a rebuilt image (fresh volumes): a 3x normally-paced restart loop (each
followed by a 90s post-init settle, well past the previously-observed ~2min crash window) and a
4x rapid back-to-back restart burst (3s apart, no waiting for init between them) both completed
with the container remaining
running/ExitCode=0throughout — no clean-exit crash, and nofailed to start daemon ... still runningdockerd error in the logs. Runner UUIDs for all 5 registered runners were identical before and after both test sequences (/config/act_runner/reg/still holds exactly 5 directories, no duplicates), confirming the "same id, not new" requirement holds even under rapid restart cycling.
Config cleanup done — deprecated [webhook].ALLOWED_HOST_LIST moved to [security] (rootfs/tmp/etc/gitea/app.ini)
- Every
gitea admin user create(and, by extension, every gitea startup) logged:[E] Deprecation: config option [webhook].ALLOWED_HOST_LIST present, please use [security].ALLOWED_HOST_LIST instead because this fallback will be/has been removed in v28.0.0. - Root cause: the shipped
app.initemplate setALLOWED_HOST_LIST = *under[webhook]only; no[security]equivalent existed, so gitea used the deprecated fallback on every run. - Fix: added
ALLOWED_HOST_LIST = *to[security]and removed it from[webhook]. app.iniis a staged config file (rootfs/tmp/etc/gitea/app.ini→/config/gitea/app.ini), not agen-dockerfile-generated file, so this is a normal in-repo edit, not a template/generation issue.- Verified live on a rebuilt image:
gitea admin user createno longer emits the deprecation warning, and admin login/API access still works normally after the move. - Re-ran the full functional + restart-stability regression suite against this rebuilt image on fresh
volumes: admin user creation, non-admin user creation, repo creation, fork (
forker/testrepo), and GitHub mirror creation + manualmirror-synctrigger (mirror_last_sync_atmoved off epoch) all passed. A 3x paced restart loop (90s settle each) and a 4x rapid back-to-back restart burst (3s apart) both left the containerrunning/ExitCode=0throughout, with dockerd,gitea web, and all 5act_runnerdaemons (+ cache-server) alive afterward, and all 5 runner UUIDs byte-for-byte identical before and after (/config/act_runner/reg/still exactly 5 directories, no duplicates).
App-breaking bug fixed — act_runner CI jobs cannot start: nested overlayfs mount failure (05-dockerd.sh)
- Every Gitea Actions job failed within ~13s with, both via act_runner and via a plain manual
docker runinside the container:failed to mount ... fstype: overlay ... err: invalid argument. Reproduced withdocker exec gitea-test docker run --rm <any image> ...— 100% failure rate, not specific to any one image or to act_runner's job-container logic. - Root cause: the inner (DinD) dockerd defaulted to the
overlayfsstorage driver, same as the outer host/container's own root filesystem. Nesting anoverlay2-driver dockerd inside a container whose own root is itself an overlay filesystem is a well-known Docker-in-Docker failure mode — the kernel rejects the resulting overlay-on-overlay mount withinvalid argumenton this kernel/host combination, even withindex=offalready set. - Impact: the entire act_runner CI subsystem was non-functional — runners registered fine and showed as online, but every single job would fail immediately at the "Starting job container" step, regardless of workflow, label, or target image. This would not have been caught by container-startup/registration testing alone; it required actually running a real workflow to surface.
- Fix: added
"storage-driver": "fuse-overlayfs"to both branches of the/config/docker/daemon.jsongeneration in__run_pre_execute_checks_local(with-registry and without-registry cases).fuse-overlayfsis already bundled in the image (/usr/bin/fuse-overlayfs,/dev/fusepresent) and avoids the kernel-level overlay-on-overlay conflict by mounting entirely in userspace via FUSE. The existing--privileged/SYS_ADMINrequirement (already documented in README.md as required for Docker-in-Docker) covers fuse-overlayfs's own requirements — no new run-flag requirement introduced. - Verified live: manually patched
/config/docker/daemon.jsonon the running container, restarted the inner dockerd, and confirmeddocker inforeportsStorage Driver: fuse-overlayfsanddocker run(bothcasjaysdev/alpine:latestandubuntu:latest) now creates and starts containers successfully with no mount error, where it previously failed 100% of the time. - Follow-up root cause found: the initial
05-dockerd.shheredoc fix alone did not take effect on a fresh container/fresh volumes. Traced to__run_precopy's baked-/etc-seeding step: on first run it copies the whole baked/etc/dockerdirectory (staged fromrootfs/tmp/etc/docker/daemon.jsonvia03-files.sh) into/config/docker/BEFORE__run_pre_execute_checks_local's[ ! -f "/config/docker/daemon.json" ]guard ever runs — so the guard always saw the file already present and the heredoc (containing the fix) never executed. Fixed by adding"storage-driver": "fuse-overlayfs"to the actual baked source file,rootfs/tmp/etc/docker/daemon.json, in addition to keeping the05-dockerd.shheredoc fix as defense-in-depth for any case where the baked/etc/dockerdir is absent. - Rebuilt the image and recreated the container on completely fresh volumes: confirmed
/config/docker/daemon.jsonnow contains"storage-driver": "fuse-overlayfs"anddocker inforeportsStorage Driver: fuse-overlayfsvia the normal init path (not a manual patch). Confirmeddocker exec gitea-test docker run --rm ubuntu:latest echo ...succeeds with no mount error. Pushed a realruns-on: alpineGitea Actions workflow via the contents API and confirmed the job reached"status":"success"in ~3 seconds viaGET .../actions/tasks— full end-to-end confirmation, not just container-creation succeeding. This bug is now fully resolved and verified. - Re-ran the full restart-stability + functional regression suite on this fix, on the same fresh
container: 3x paced restarts (90s settle each) + 4x rapid-burst restarts (3s apart) — container
stayed
running/ExitCode:0/healthythroughout, all 5 runner UUIDs identical before/after (no duplicate registrations), andStorage Driver: fuse-overlayfs+ prior admin user/repo state all survived the restarts intact. Also verified admin user creation, repo creation, fork (into a secondforkeruser account), and mirror+sync (mirror_updatedtimestamp advanced from mirror-registration time to a fresh sync time) all still work with no regressions from this fix.
Non-issue — confirmed intentional (.gitea/workflows/docker.yaml)
- Uses a stale/unpinned action pattern (
@v2-@v4, DockerHub-only,catthehacker/ubuntu:act-latest). AI.md PART 7 explicitly documents this as the legacy hand-crafted workflow: "Never overwrite it, and never use it as a template for new work — it uses tag-pinned actions and retired secret names. All new/updated workflows come fromgen-dockerfile actions." Nobuild.ymlexists yet in this repo; generating one is a separate task (runninggen-dockerfile actions), not a fix to this file.