Commit Graph
16 Commits
Author SHA1 Message Date
casjay 8fa9547fb1 🔧 Remove DISABLE_QUERY_AUTH_TOKEN pin from app.ini 🔧
gitea / release-gitea (push) Successful in 13m58s
Gitea 28 still defaults the setting to false, so dropping the explicit
pin changes no behaviour and removes its startup warning. Existing
installs keep whatever they already have; the startup script never
touched this key.

- rootfs/tmp/etc/gitea/app.ini: remove DISABLE_QUERY_AUTH_TOKEN and its explanatory comment from [security]
- TODO.AI.md: note the removal in the Gitea 28 migration entry

Claude-Session: https://claude.ai/code/session_01Kae22tmhzhmfHyqc34PZbx
2026-10-03 20:30:18 -04:00
casjay 13e9ea9e16 🔧 Migrate app.ini for Gitea 28 and match live settings 🔧
Audited the template against Gitea 28.0.0 (source plus the binary's
own startup log) and fixed everything it flags. Existing configs are
migrated on startup without touching user values.

- rootfs/tmp/etc/gitea/app.ini: ALLOWED_HOST_LIST "*" -> private,loopback with explicit EGRESS_MODE=lax; [migration] -> [migrations] with ALLOWED_HOST_LIST; drop deprecated/dead keys and the non-existent [notify.email]; add WORK_PATH and LOCAL_ROOT_URL; DISABLE_REGISTRATION=true
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: add __gitea_ini_get/__gitea_ini_unset and migrate existing configs (invalid "*" host list, [webhook] key moved to [security], [migrations] host list, EGRESS_MODE)
- TODO.AI.md: record the audit, the verification, and the behaviour changes needing owner confirmation

Claude-Session: https://claude.ai/code/session_01Kae22tmhzhmfHyqc34PZbx
2026-10-03 20:24:39 -04:00
casjay 246fb4494c 🐛 Never overwrite existing config when seeding /config 🐛
gitea / release-gitea (push) Successful in 18m49s
Without the .initialized marker, startup copied the baked template
over existing files in /config, resetting app.ini and regenerating
secrets. Seeding now only adds files that are missing.

- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: seed copy uses cp -Rn instead of cp -Rf
- rootfs/usr/local/etc/docker/init.d/05-dockerd.sh: same change, protects a user-edited daemon.json
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: same change, protects user-edited runner config
- TODO.AI.md: record the bug, the verification, and the upstream template follow-up

Claude-Session: https://claude.ai/code/session_01Kae22tmhzhmfHyqc34PZbx
2026-10-03 20:12:40 -04:00
casjay 5b4079dc41 🔧 Set sane Gitea retention and cleanup defaults 🔧
Shorter Actions retention and enable Gitea's built-in cleanup cron
tasks. Existing configs receive missing keys only; user-set values
are never overwritten.

- rootfs/tmp/etc/gitea/app.ini: artifacts 30d, logs 90d, runs 180d; enable gc_lfs, git_gc_repos, delete_old_actions, delete_old_system_notices; explicit cleanup_actions/cleanup_action_runs
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: add __gitea_ini_default and apply retention/cron defaults to existing configs when missing
- README.md: document retention defaults
- TODO.AI.md: record the change and its verification

Claude-Session: https://claude.ai/code/session_01Kae22tmhzhmfHyqc34PZbx
2026-10-03 11:28:28 -04:00
casjay cb16b1659b 🐛 Fix passkeys behind TLS reverse proxy 🐛
Gitea now always listens on plain http while ROOT_URL carries the
public https origin, per Gitea's reverse-proxy docs. Fixes fresh
containers failing to start (PROTOCOL = https with no cert) and
passkey origin mismatches behind a proxy.

- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: add __gitea_ini_set helper; stamp [server] PROTOCOL = http, ROOT_URL from new GITEA_ROOT_URL (trailing slash), and [session] COOKIE_SECURE from ROOT_URL scheme on every startup
- rootfs/tmp/etc/gitea/app.ini: default PROTOCOL to http instead of the public protocol placeholder
- README.md: document GITEA_PROTO as the public protocol (default https), GITEA_ROOT_URL, and reverse-proxy/passkey requirements; compose example uses https
- TODO.AI.md: record the fix and log the generated __create_ssl_cert crash

Claude-Session: https://claude.ai/code/session_01Kae22tmhzhmfHyqc34PZbx
2026-10-03 11:23:03 -04:00
casjay 6e24727c29 ✨ Add act_runner cleanup and apply full runner config ✨
Prune leftovers from CI jobs in the nested dockerd on a schedule, and
make runner-N daemons use the full default_config.yaml instead of a
cache-only config.

- rootfs/usr/local/bin/cleanup-runners: new script that prunes stopped containers, networks, build cache, act-* volumes, dangling images, all unused images above a disk threshold, and stale ~/.cache/act clones
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: add RUNNER_CLEANUP_* settings, run cleanup-runners via __cron, export RUNNERS_CONFIG_BASE
- rootfs/usr/local/bin/start-runners: generate per-runner config.yaml from RUNNERS_CONFIG_BASE with its own runner.file; keep cache-only config as fallback
- README.md: document RUNNER_CLEANUP_* variables, per-runner config, correct RUNNER_IP_ADDRESS default
- TODO.AI.md: record both fixes and log newly found open issues

Claude-Session: https://claude.ai/code/session_01Kae22tmhzhmfHyqc34PZbx
2026-10-03 11:08:05 -04:00
casjay 4a954b0eb3 🐛 Fix CI job execution, restart races, and act_runner duplication 🐛
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.
2026-09-03 16:00:45 -04:00
casjay a328be0655 📝 Update project spec and documentation 📝
- AI.md
- TODO.AI.md
2026-09-03 01:39:53 -04:00
casjay 534d2802dc 🧹 Remove forbidden OCI labels from Dockerfile 🧹
Brought the Dockerfile's LABEL block back in line with AI.md's OCI
label standard (lines 58-87).

- Dockerfile: removed `org.opencontainers.image.base.name` (belongs
  on the base image, not the app image) and
  `org.opencontainers.image.schema-version` (non-spec, redundant with
  `version`); removed the duplicate `authors="${LICENSE}"` line and
  the duplicate `source="https://docker.io/..."` line, replacing the
  former with the correct `org.opencontainers.image.licenses="${LICENSE}"`
  label and keeping a single `source` pointing at the github.com repo
- TODO.AI.md: marked this finding fixed
2026-08-05 23:23:19 -04:00
casjay 61a4e38cdb 🐛 Add version stamp and grep -- guards in zz-act_runner.sh 🐛
Added `VERSION="202608031200-git"` matching the existing `##@Version`
header, and added `--` before the pattern argument on all 15 grep
invocations in the file (the header/version mismatch and several of
the missing `--` guards were flagged by the `script-lint` agent;
completeness check found the pattern repeated across the whole file,
not just the originally-flagged subset). Also quoted the previously
bare `grep` pattern at the former line 544 (`grep -v grep` ->
`grep -v -- 'grep'`).

- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: version stamp
  + grep -- fixes
- TODO.AI.md: marked this finding fixed
2026-08-05 23:22:45 -04:00
casjay 3fc96e70e2 🐛 Fix UUOC and missing grep guard in start-runners 🐛
Replaced `echo "$SERVER_ADDRESS" | grep -q '://'` with the native bash
`[[ "$SERVER_ADDRESS" != *"://"* ]]` test, eliminating a useless
subshell/pipe and the missing `--` before the grep pattern that came
with it. Found by the `script-lint` agent while auditing the
act_runner cache-server integration.

- rootfs/usr/local/bin/start-runners: line 24 UUOC fix
- TODO.AI.md: marked the start-runners lint finding fixed; logged a
  new, separate line-length violation on line 36 (`RUNNER_LABELS`
  default is 781 chars) discovered during the same lint pass but not
  yet actioned
2026-08-05 23:22:20 -04:00
casjay fc3e148bb2 🐛 Create /config/env directory before services write into it 🐛
App-breaking bug found during full runtime verification of the act_runner
cache-server feature: `/config/env` was never explicitly created. It only
came into existence as a side effect of `__create_env_file()` copying
`/usr/local/etc/docker/env/default.sample` into it — but that sample file
does not exist in this image's rootfs, so `__create_env_file()` returns
early without creating the directory. `05-dockerd.sh` and
`zz-act_runner.sh` then failed writing `/config/env/docker.local.sh` and
`/config/env/act_runner.local.sh` directly, logging
`No such file or directory` (non-fatal, but a real bug).

- rootfs/usr/local/bin/entrypoint.sh: added
  `mkdir -p "/config/env" 2>/dev/null || true` alongside the other
  `/config/*` directory creation lines
- TODO.AI.md: logged the fix and the upstream-template-sync follow-up

Verified: full end-to-end run with `--privileged --cap-add CHOWN
--cap-add SYS_TIME --cap-add SYS_ADMIN` (per README) shows no more
"No such file or directory" errors, gitea starts on port 80, dockerd
starts, act_runner cache server logs "Cache server has been started and
is listening on 44015", and both configured runners register
successfully ("Runner registered successfully." x2, RUNNERS_START=2).
`runners-cache.yaml` is generated with real (non-REPLACE_) cache config
values.
2026-08-05 01:52:09 -04:00
casjay 6ebe62790a 🐛 Fix __format_variables returning a single space for empty input 🐛
App-breaking bug found during full runtime verification of the act_runner
cache-server feature: gitea failed to start with `Command error: unknown
command: /config/gitea/app.ini` because its `--port` argument was empty.

Root cause: `__format_variables()` ran `printf '%s\n' $input | sort -Ru |
tr '\n' ' '` unconditionally. When `$input` word-splits to zero words
(whitespace-only, e.g. no port env vars set), `printf` with a `%s` format
still runs once with a missing arg, emitting a blank line — so the
function returned a single space `" "` instead of empty. That made
`ENV_PORTS` / `WEB_SERVER_PORTS` resolve to `" "`, which made
`SERVICE_PORT` in `08-gitea.sh` become `" "` — non-empty per `[ -n ... ]`
but rendering as an empty `--port` value to `gitea web`.

- rootfs/usr/local/etc/docker/functions/entrypoint.sh: replaced
  `[ -z "$input" ] && return 0` with
  `[[ "$input" =~ [^[:space:]] ]] || return 0` so whitespace-only input
  is treated as empty before reaching the `printf` pipeline
- TODO.AI.md: logged the fix and the upstream-template-sync follow-up

Verified: rebuilt the image and confirmed `gitea will be running on port
80` / `gitea web --port 80 ...` in a full container run (previously
`--port` with no value).
2026-08-05 01:51:13 -04:00
casjay 8990a72bd7 🐛 Fix __random_password SIGPIPE abort under pipefail 🐛
App-breaking bug found during full runtime verification of the act_runner
cache-server feature: `__random_password()`'s `tr | head -c` pipeline
could be killed by SIGPIPE once `head -c` closes its input early, and
under `set -eo pipefail` this aborted the whole script.

- rootfs/usr/local/etc/docker/functions/entrypoint.sh: wrapped the
  `tr -dc ... | head -c...` pipeline in `{ ... } || true` so a SIGPIPE
  from `head` closing early no longer aborts the script
- TODO.AI.md: logged the fix and the upstream-template-sync follow-up
2026-08-05 01:50:16 -04:00
casjay 97270cfe91 🐛 Fix silent set -e abort on unguarded DEBUGGER echo pattern 🐛
App-breaking bug found during full runtime verification of the act_runner
cache-server feature: the container died immediately on every startup,
printing only the initial banner line, with no error message.

Root cause: 26 occurrences of `[ "$DEBUGGER" = "on" ] && echo/printf/
__service_banner "..."` used as a bare (non-if-guarded) statement. Under
`set -eo pipefail`, when `$DEBUGGER` is not "on" (the default), the test
fails and the statement's exit status is nonzero, aborting the whole
script silently. Confirmed via `bash -x` trace pinpointing the exact
crash line.

- rootfs/usr/local/etc/docker/functions/entrypoint.sh: appended `|| true`
  to all 26 occurrences of the pattern
- TODO.AI.md: logged the fix and the upstream-template-sync follow-up
  (functions/entrypoint.sh is normally regenerated from casjay-dotfiles,
  not hand-edited)
2026-08-05 01:49:20 -04:00
casjay 6a3a9bde14 🚀 Enable shared act_runner cache-server 🚀
Turned on the act_runner Actions cache instead of leaving it disabled.
The standalone `cache-server` process was already being launched
unconditionally by zz-act_runner.sh but had no `external_secret`, so it
was refusing to start (the binary requires a non-empty secret even
though its own `cache.enabled` field is unused). Wired a shared,
randomly generated secret through the existing REPLACE_* templating
pattern so the "gitea" runner, the cache-server, and the extra
runner-N daemons spawned by start-runners all share one cache backend.

- rootfs/tmp/etc/act_runner/default_config.yaml: cache.enabled: true;
  added external_server (pointed at the local cache-server) and
  external_secret placeholders
- rootfs/tmp/etc/act_runner/cache_server.yaml: cache.enabled: true;
  added external_secret placeholder (required for the binary to start)
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: added
  __gen_cache_secret() (mirrors __gen_auth_token's persisted-token
  pattern, stored at $CONF_DIR/tokens/cache_secret); substitutes
  REPLACE_RUNNER_CACHE_SECRET into both the runner config and the
  cache-server config; exports RUNNER_CACHE_HOST/PORT/SECRET for
  start-runners; bumped version stamp
- rootfs/usr/local/bin/start-runners: generates a shared
  runners-cache.yaml from the exported cache env vars and passes
  --config to both `act_runner register` and `act_runner daemon` for
  every runner-N instance, so they use the same external cache server
  instead of an unshared per-process local cache
- TODO.AI.md: logged pre-existing script-lint findings (missing `--`
  before grep queries throughout zz-act_runner.sh, a UUOC in
  start-runners, an unpinned/stale docker.yaml CI workflow, and
  forbidden OCI labels in the Dockerfile) surfaced incidentally by the
  lint pass for this change but out of scope for it
2026-08-03 11:57:10 -04:00