Commit Graph
107 Commits
Author SHA1 Message Date
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 5b65812d0d 📖 Replace runbook AI.md with standardized app master spec 📖
AI.md is now the standardized application-image specification from
claudemgr/docker/CASJAYSDEVDOCKER.md — PARTs 0–8 covering critical
rules and org mapping, repo model, template system with the OCI label
canon (image.url https://hub.docker.com/r/casjaysdevdocker/gitea),
tooling reference, .env.scripts, runtime system, README layout with the
hand-crafted README exception, CI/CD, and verification gates. The old
file's bootstrap/update runbook moved to the dockersrc-bootstrap agent;
the spec stays declarative.

- AI.md: replaced with the CASJAYSDEVDOCKER.md master template
2026-08-14 15:21:30 -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 87d9e2d50f 📝 Update project spec and documentation 📝
- CLAUDE.md
- IDEA.md
2026-08-04 23:18:28 -04:00
casjay 566d1e8166 📝 Update project spec and documentation 📝
- AI.md
2026-08-03 16:23:18 -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
casjay 4787512bf0 🗃️ Update codebase 🗃️
rootfs/usr/local/etc/docker/functions/entrypoint.sh
2026-08-02 20:43:07 -04:00
casjay 5d85668c17 🔧 repo cleanup 🔧
.claude/settings.local.json
2026-07-30 18:25:38 -04:00
casjay c9593bd0b6 🐛 Fix entrypoint hang and lost-interpreter bugs 🐛
- rootfs/usr/local/bin/entrypoint.sh:
  1. The "start all services" gate
     (`if [ "$START_SERVICES" = "yes" ] || [ -z "$1" ]`) always evaluated
     true on a first-run container regardless of $1, because
     START_SERVICES is force-set to "yes" whenever no PID file exists yet.
     Any command passed to `docker run` — `exec ...`, `sh -c ...`,
     `shell`, or an arbitrary program — was swallowed into the
     service-start+monitor branch before reaching the `case "$1"`
     statement that already handles those subcommands, hanging the
     container as a daemon instead of running the given command. Changed
     the condition to `if [ -z "$1" ]` so the daemon branch only fires
     when no command was given at all.
  2. The `*/bin/sh | */bin/bash | bash | sh | shell)` case branch
     unconditionally shifted $1 before `__exec_command "$@"` (a bare
     `exec "$@"`). For `docker run image sh -c 'cmd'` this turned the
     exec into `exec -c cmd` (command not found, exit 127) instead of
     `exec sh -c 'cmd'`. Split the branch: real interpreter names
     (*/bin/sh, */bin/bash, bash, sh) now pass through unshifted; the
     "shell" keyword (not a real interpreter) gets its own branch that
     shifts and prepends "sh" to any remaining args, or falls back to a
     bare `__exec_command` (exec bash -l) when none remain.
  Verified `bash -n` passes. Found and fixed upstream in
  dockersrc/go, confirmed identical in this repo's generated
  entrypoint.sh, and mechanically applied here with the same patch.
2026-07-27 23:19:25 -04:00
casjay edcf07e9ea 🗃️ Update codebase 🗃️
rootfs/usr/local/etc/docker/functions/entrypoint.sh
2026-06-26 23:50:19 -04:00
casjay 11b50e49bc 🐛 Fix __create_service_env to write .local.sh to disk 🐛
The .local.sh block defined stub functions in memory but never
wrote the file to disk, so __file_exists_with_content always
failed and __create_service_env returned non-zero on every run.
Fix: use a heredoc to write the stub functions into .local.sh.
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: write .local.sh
via heredoc in __create_service_env; bump version to 202606261600-git

rootfs/usr/local/etc/docker/init.d/08-gitea.sh
2026-06-26 21:02:54 -04:00
casjay 1d198090c0 🐛 Fix __create_service_env to write .local.sh to disk 🐛
The .local.sh block defined stub functions in memory but never
wrote the file to disk, so __file_exists_with_content always
failed and __create_service_env returned non-zero on every run.
Fix: use a heredoc to write the stub functions into .local.sh.
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: write .local.sh
via heredoc in __create_service_env; bump version to 202606261600-git

rootfs/usr/local/etc/docker/init.d/05-dockerd.sh
rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
2026-06-26 21:00:32 -04:00
casjay ee93310f88 🐛 Fix duplicate 2>/dev/null in entrypoint.sh /etc/hosts line 🐛
The grep -vF line that appends /usr/local/etc/hosts entries had a
duplicate 2>/dev/null redirect after the append operator, which is
a no-op but clutters the script. Synced from template.
- rootfs/usr/local/bin/entrypoint.sh: remove duplicate 2>/dev/null
from grep -vF /etc/hosts append line; bump version to 202606261500-git

rootfs/usr/local/bin/entrypoint.sh
2026-06-26 18:44:20 -04:00
casjay 5a46d8f868 🐛 Respect DOMAIN env var for gitea server name 🐛
REPLACE_SERVER_NAME and the dynamic ROOT_URL/DOMAIN/SSH_DOMAIN
re-stamps were using $HOSTNAME directly, ignoring the DOMAIN env
var passed in docker run. SERVER_NAME is already set to
${DOMAIN:-$HOSTNAME}; feed it back into FULL_DOMAIN_NAME so
__initialize_replace_variables picks it up, and switch the sed
re-stamps to use $SERVER_NAME.
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: export
FULL_DOMAIN_NAME from SERVER_NAME; use SERVER_NAME in
ROOT_URL/DOMAIN/SSH_DOMAIN sed re-stamps

rootfs/usr/local/etc/docker/init.d/08-gitea.sh
2026-06-09 19:04:04 -04:00
casjay 42d0439d94 🐛 Use marker file to gate config seeding — never re-seed on restart 🐛
Replace the fragile grep-for-REPLACE_ re-seed check with a
.initialized marker written after __update_conf_files completes.
The seed (cp from /etc/ → /config/) only runs when the marker
is absent, so secrets and tokens are generated exactly once and
never overwritten on container restart.
To force a full re-initialisation: delete /config/$svc/.initialized
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: seed on missing
marker; write marker at end of __update_conf_files
- rootfs/usr/local/etc/docker/init.d/05-dockerd.sh: same
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: same

rootfs/usr/local/etc/docker/init.d/05-dockerd.sh
rootfs/usr/local/etc/docker/init.d/08-gitea.sh
rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
2026-06-09 18:57:12 -04:00
casjay 2a5f7bdbc9 🐛 Re-seed config if REPLACE_ tokens remain from a broken prior run 🐛
The previous guard (check for missing primary config file) left
stale app.ini/daemon.json/default_config.yaml files in place when
the volume had an unprocessed copy from a broken earlier run.
Gitea then read REPLACE_DATABASE_DIR as a literal path, failed to
open the SQLite DB, and redirected to the install wizard.
Now also re-copy from /etc if the existing config file still
contains any REPLACE_ token, ensuring a clean template is always
in place before variable substitution runs.
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: re-seed if
REPLACE_ tokens present in app.ini
- rootfs/usr/local/etc/docker/init.d/05-dockerd.sh: re-seed if
REPLACE_ tokens present in daemon.json
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: re-seed if
REPLACE_ tokens present in default_config.yaml

rootfs/usr/local/etc/docker/init.d/05-dockerd.sh
rootfs/usr/local/etc/docker/init.d/08-gitea.sh
rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
2026-06-09 16:07:12 -04:00
casjay 6b8527c19b 🐛 Fix config seed check and container env var mapping 🐛
Guard the /etc→/config seed on the primary config file rather
than dir-empty so a pre-existing volume with only subdirs (e.g.
custom/) does not prevent app.ini/daemon.json/default_config.yaml
from being seeded — fixing the gitea install-page regression.
Also wire CONTAINER_DEFAULT_DATABASE_TYPE, CONTAINER_PROTOCOL,
CONTAINER_WEB_SERVER_PROTOCOL, WEB_PORT/ENV_PORTS, and
DATABASE_DIR_SQLITE to the correct gitea init variables so all
REPLACE_* tokens in app.ini are substituted on first start.
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: seed guard
checks for app.ini; map CONTAINER_DEFAULT_DATABASE_TYPE →
GITEA_SQL_TYPE; map CONTAINER_PROTOCOL/WEB_PORT → SERVICE_*;
fix DATABASE_DIR and CUSTOM_PATH
- rootfs/usr/local/etc/docker/init.d/05-dockerd.sh: seed guard
checks for daemon.json
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: seed guard
checks for default_config.yaml

rootfs/usr/local/etc/docker/init.d/05-dockerd.sh
rootfs/usr/local/etc/docker/init.d/08-gitea.sh
rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
2026-06-09 15:52:09 -04:00
casjay 963dbf8ab5 🐛 Map container env vars to gitea init variables 🐛
Wire CONTAINER_DEFAULT_DATABASE_TYPE, CONTAINER_PROTOCOL,
CONTAINER_WEB_SERVER_PROTOCOL, WEB_PORT, ENV_PORTS, and
DATABASE_DIR_SQLITE to the correct gitea init variables so
all REPLACE_* tokens in app.ini are substituted correctly
on first container start.
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: map
CONTAINER_DEFAULT_DATABASE_TYPE → DATABASE_SERVICE_TYPE +
GITEA_SQL_TYPE (sqlite→sqlite3, postgres, mysql, mssql);
map CONTAINER_PROTOCOL/CONTAINER_WEB_SERVER_PROTOCOL →
SERVICE_PROTOCOL; map WEB_PORT/ENV_PORTS → SERVICE_PORT;
initialise DATABASE_DIR from DATABASE_DIR_SQLITE env var;
fix CUSTOM_PATH export to point at $CONF_DIR/custom

.claude/settings.local.json
rootfs/usr/local/etc/docker/init.d/08-gitea.sh
2026-06-09 15:39:38 -04:00
casjay c7b724bf4b ♻️ symlink ETC_DIR to CONF_DIR instead of dual-copy ♻️
- Replace copy-on-first-run + `__initialize_system_etc` sync loop with a symlink: after seeding `$CONF_DIR`, remove `$ETC_DIR` and point it at `$CONF_DIR` so both paths always resolve to the same config
- Drop `$ETC_DIR` from `__initialize_replace_variables` calls since the symlink makes it redundant
- Switch daemon.json, app.ini, and runner yaml config paths from `$ETC_DIR` to `$CONF_DIR` references
- Remove unused `__init_config_etc` function from entrypoint.sh

rootfs/usr/local/etc/docker/functions/entrypoint.sh
rootfs/usr/local/etc/docker/init.d/05-dockerd.sh
rootfs/usr/local/etc/docker/init.d/08-gitea.sh
rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
2026-06-09 15:08:17 -04:00
casjay ead2bd2e19 🗃️ Update codebase 🗃️
rootfs/usr/local/etc/docker/init.d/05-dockerd.sh
rootfs/usr/local/etc/docker/init.d/08-gitea.sh
rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
2026-06-09 14:38:20 -04:00
casjay 36854be70b 🐛 Update entrypoint.sh and functions library from current template 🐛
Stale copies called __initialize_default_templates, __initialize_config_dir,
and __initialize_data_dir which are not in the old functions library,
causing container startup failures. Replaced with current template
versions (202606041210-git) which no longer call those missing functions.
- rootfs/usr/local/bin/entrypoint.sh: update to current template
- rootfs/usr/local/etc/docker/functions/entrypoint.sh: update to current template

.claude/settings.local.json
Dockerfile
.env.scripts
rootfs/usr/local/bin/entrypoint.sh
2026-06-05 18:24:48 -04:00
casjay 056270d60c 🐛 Update entrypoint.sh and functions library from current template 🐛
The gitea repo had stale copies of both files. The old entrypoint.sh
called __initialize_default_templates, __initialize_config_dir, and
__initialize_data_dir which don't exist in the old functions library,
causing container startup failures.
Replace both with the current template versions (202606041210-git).
The new entrypoint.sh no longer calls those missing functions.
Set CONTAINER_NAME=gitea and description to match the service.
- rootfs/usr/local/bin/entrypoint.sh: update to 202606041210-git template
- rootfs/usr/local/etc/docker/functions/entrypoint.sh: update to current template

.claude/settings.local.json
rootfs/usr/local/bin/entrypoint.sh
rootfs/usr/local/etc/docker/functions/entrypoint.sh
2026-06-05 18:06:48 -04:00
casjay c420852e0b 🐛 Set WORK_DIR for act_runner to avoid /root permission issues 🐛
act_runner runs as the git user which has no access to /root (Docker's
WORKDIR). Setting WORK_DIR=/data/act_runner ensures the service starts
from a directory the git user can access, preventing any git or file
operations from inadvertently referencing /root.
05-dockerd.sh runs as root so no fix needed there.
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: set WORK_DIR=/data/act_runner

rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
2026-06-05 17:41:48 -04:00
casjay 0ff1923d3e 🐛 Fix git fatal error reading /root/.git at gitea startup 🐛
Docker's WORKDIR is /root and the git user has no read permission on
that directory. When gitea starts via gosu git, git inherits /root as
the working directory, calls getcwd(), constructs /root/.git, and
fails with "fatal: error reading '/root/.git'" — crashing gitea.
Setting WORK_DIR=/data/gitea causes the init functions library to cd
to /data/gitea before launching the service, so git never sees /root.
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: set WORK_DIR=/data/gitea

rootfs/usr/local/etc/docker/init.d/08-gitea.sh
2026-06-05 17:40:04 -04:00
casjay 7e6f4f5a84 🐛 Fix chmod error on empty ssh directory at first run 🐛
chmod 0600 $DATA_DIR/ssh/* fails with "cannot access" when the
directory is empty (first boot before keys are generated, or when
keys are symlinks in /config/ssh). Replace the glob with find -type f
which silently handles empty directories.
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: use find -exec chmod instead of glob for ssh key permissions

rootfs/usr/local/etc/docker/init.d/08-gitea.sh
2026-06-05 17:38:17 -04:00
casjay 94263dc03c 📝 Document auto-detected arch runner labels in README 📝
Add runner labels table showing what gets registered per architecture,
and clarify that all jobs run in containers (no host execution).
Document full language image label list available on both arches.
- README.md: add runner labels table and arch auto-detection note

README.md
2026-06-05 17:08:23 -04:00
casjay 1cc1624187 🐛 Remove :host runner labels — all jobs must run in containers 🐛
:host labels run jobs directly on the container filesystem with no
isolation. Replace all arch-specific :host labels with
:docker://ubuntu:latest so every job runs inside its own container
regardless of the runner host architecture.
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: amd64/arm64/linux labels use docker://ubuntu:latest not :host
- README.md: remove :host from external runner label examples

README.md
rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
2026-06-05 17:06:16 -04:00
casjay 910cf5be9b 📝 Document external runner setup for native arch matrix builds 📝
Add "Adding external runners" section covering:
- Getting a registration token from Gitea UI or API
- Installing the act_runner binary with arch auto-detection
- Registering with arch-specific labels (arm64:host, linux/arm64:host)
- Running as a systemd service
- Matrix workflow example targeting amd64 and arm64 natively
- README.md: add external runner setup section

README.md
2026-06-05 17:03:20 -04:00
casjay e624d7c219 Add arch-specific runner labels for native amd64/arm64 matrix builds
Detect the host architecture at container startup and prepend
arch-specific labels to RUNNER_LABELS so matrix workflows can target
native runners by architecture:
runs-on: amd64 → dispatched to x86_64 runners
runs-on: arm64 → dispatched to aarch64 runners
runs-on: linux/amd64 / runs-on: linux/arm64 (OCI-style)
On x86_64: adds amd64:host and linux/amd64:host
On aarch64: adds arm64:host and linux/arm64:host
This enables a dedicated ARM64 server running the same image to register
native arm64 runners against the same Gitea instance, allowing full
multi-arch matrix CI without emulation.
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: detect arch, prepend arch labels to RUNNER_LABELS

rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
2026-06-05 17:01:24 -04:00
casjay 2e20b3b5b8 Enable Actions and forks by default; document SSH port 7833
- DEFAULT_REPO_UNITS and DEFAULT_FORK_REPO_UNITS: all units including
repo.actions so every repo and fork has everything on by default
(opt-out, not opt-in) — ready for GitHub migration
- README: clarify SSH port — internal 22 maps to external 7833 since
host port 22 is reserved for sshd
- rootfs/tmp/etc/gitea/app.ini: DEFAULT_FORK_REPO_UNITS with full unit list
- README.md: document SSH external port 7833

README.md
2026-06-05 16:51:41 -04:00
casjay 51a57cfeb4 Add DEFAULT_FORK_REPO_UNITS — full feature set for forks too
Forks should get the same full set of units as new repos since the
intent is a full GitHub migration. Matches DEFAULT_REPO_UNITS exactly.
- rootfs/tmp/etc/gitea/app.ini: add DEFAULT_FORK_REPO_UNITS with all units

rootfs/tmp/etc/gitea/app.ini
2026-06-05 16:49:07 -04:00
casjay 85ccdabe5d Enable Actions by default on all new repos
Add DEFAULT_REPO_UNITS to [repository] in app.ini with repo.actions
included so every new repo has Actions enabled out of the box.
Users can still disable it per-repo — opt-out rather than opt-in.
- rootfs/tmp/etc/gitea/app.ini: add DEFAULT_REPO_UNITS with repo.actions included

rootfs/tmp/etc/gitea/app.ini
2026-06-05 16:47:18 -04:00
casjay cd977be93d 🐛 Re-apply resolv.conf in init.d phase to beat Docker's async rewrite 🐛
Docker writes /etc/resolv.conf at container start before PID1 launches,
and again asynchronously when the network finishes initializing. The
entrypoint's early copy gets overwritten by Docker's second write.
Re-applying the custom resolv.conf in __run_precopy (init.d phase)
happens after Docker's network setup is complete, so the search . and
options ndots:0 settings stick for the full container lifetime.
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: copy custom resolv.conf in __run_precopy

rootfs/usr/local/etc/docker/init.d/08-gitea.sh
2026-06-05 15:44:44 -04:00
casjay ea11289300 🔧 update resolv.conf in rootfs 🔧
- Update DNS resolver configuration in rootfs/usr/local/etc/resolv.conf

rootfs/usr/local/etc/resolv.conf
2026-06-05 14:40:27 -04:00
casjay ddc318052c 📝 Fix compose example to follow conventions 📝
- Add x-logging anchor with json-file driver
- Add pull_policy: always
- Add logging: *default-logging
- Add network block (gitea / external: false)
- Switch environment from list to map format
- Add CONTAINER_NAME to environment
- Use HOSTNAME: ${BASE_HOST_NAME:-...} pattern
- Switch volumes from absolute paths to ./volumes relative paths
- Bind ports to 172.17.0.1 (not 0.0.0.0)
- Add cgroupns_mode: private
- Add port comment at top of file
- README.md: bring compose example into line with dockerfile_conventions.md

README.md
2026-06-05 13:42:50 -04:00
casjay 958439b9ee 🐛 Fix DATABASE_DIR_SQLITE override; 📝 correct docker run example 🐛
DATABASE_DIR fix:
The previous override hardcoded $DATA_DIR/db/sqlite, ignoring
DATABASE_DIR_SQLITE when it was explicitly set via env var. Changed
to respect DATABASE_DIR_SQLITE and only fall back to $DATA_DIR/db/sqlite
when the env var is not provided.
README:
- docker run and compose examples updated to match actual working flags:
--cgroupns private, --tty, --cap-add CHOWN/SYS_TIME/SYS_ADMIN,
--hostname FQDN, --domainname, GITEA_PROTO, DATABASE_DIR_SQLITE
with a separate sqlite volume mount
- Removed non-functional vars from examples (CONTAINER_PROTOCOL,
CONTAINER_DEFAULT_DATABASE_TYPE, DATABASE_BASE_DIR, WEB_PORT)
- Added DATABASE_DIR_SQLITE to the database env var table
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: respect DATABASE_DIR_SQLITE env var
- README.md: fix docker run/compose examples, add DATABASE_DIR_SQLITE to table

README.md
rootfs/usr/local/etc/docker/init.d/08-gitea.sh
2026-06-05 13:41:26 -04:00
casjay b276c3ee0b 📝 Add missing env vars to README 📝
Add GITEA_HOSTNAME, FULL_DOMAIN_NAME, GITEA_ROOT_USER_NAME,
GITEA_ROOT_PASS_WORD, GITEA_USER_NAME, GITEA_USER_PASS_WORD,
GITEA_WORK_DIR, GITEA_SQL_DB_HOST, RUNNER_CACHE_PORT,
RUNNER_IP_ADDRESS, RUNNER_DEFAULT_HOME, RUNNER_CONFIG_NAME.
Reorganise the env var table into labelled groups (General, Server,
Users, Mail, Database, act_runner) for easier scanning.
- README.md: add missing env vars, group table by category

README.md
2026-06-05 13:25:10 -04:00
casjay cce3b4528d 🐛 Fix sqlite DATABASE_DIR path; 📝 rewrite README 🐛
DATABASE_DIR fix (08-gitea.sh):
DATABASE_SERVICE_TYPE="sqlite" triggers a generic block that appends
/$SERVER_NAME to /data/db/sqlite. Before the SERVER_NAME fix that was
empty giving /data/db/sqlite//gitea.db; even after it would be
/data/db/sqlite/<hostname> not under DATA_DIR. Re-pin DATABASE_DIR to
$DATA_DIR/db/sqlite after the generic block.
README rewrite:
- Full env var reference table (GITEA_SERVER, GITEA_PROTO, GITEA_NAME,
GITEA_ADMIN, GITEA_EMAIL_*, GITEA_SQL_*, ACT_RUNNER_FALLBACK_VERSION,
RUNNERS_START, DOMAIN, DEBUGGER)
- Volume and port tables
- Production notes: --privileged required, GITEA_SERVER must be set,
mailer disabled by default, DNS override explained
- Canonical section order: Docker → Development → License
- README.md: full rewrite with env vars, volumes, ports, and production notes
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: fix DATABASE_DIR to always use $DATA_DIR/db/sqlite

README.md
rootfs/usr/local/etc/docker/init.d/08-gitea.sh
2026-06-05 13:20:18 -04:00
casjay bd301103c4 🐛 Fix REPLACE_* token substitution in config files 🐛
Two substitution bugs fixed:
act_runner (zz-act_runner.sh):
- REPLACE_RUNNER_* tokens were only substituted inside the registration
block (guarded by SYS_AUTH_TOKEN + runners file absence). If gitea
wasn't ready on first boot, the file was copied with tokens intact
and never substituted on subsequent boots.
- Fix: substitute tokens immediately after copy, unconditionally.
Registration logic remains gated on auth token availability.
gitea app.ini (08-gitea.sh):
- REPLACE_SERVER_NAME and REPLACE_SERVER_PROTO had no matching env
vars — the script used HOSTNAME and SERVICE_PROTOCOL instead, so
__initialize_replace_variables left those tokens unsubstituted.
- Fix: export SERVER_NAME="${DOMAIN:-$HOSTNAME}" and
SERVER_PROTO="${SERVICE_PROTOCOL:-http}" as aliases after the
HOSTNAME chain is resolved.
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: add SERVER_NAME and SERVER_PROTO aliases for token substitution
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: move REPLACE_ substitution outside registration guard

rootfs/usr/local/etc/docker/init.d/08-gitea.sh
rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
2026-06-05 13:14:39 -04:00
casjay 541e3398a0 🐛 Fix container DNS: ship resolv.conf without search domain 🐛
Hosts with a search domain (e.g. search casjay.dev) cause containers
to inherit it. When that zone has a wildcard AAAA record, public
hostnames like github.com resolve to the host's own IPv6 address
instead of the real server, breaking all HTTPS (and any other
protocol) from inside the container.
The entrypoint already has a hook: if /usr/local/etc/resolv.conf
exists it replaces /etc/resolv.conf at container startup. Ship a
clean resolv.conf with Cloudflare + Google DNS and no search domain
so container DNS is always correct regardless of host configuration.
- rootfs/usr/local/etc/resolv.conf: new file — clean DNS, no search domain

rootfs/usr/local/etc/resolv.conf
2026-06-05 12:22:20 -04:00
casjay dd8fa3d25c 🐛 Add retry logic to gitea and act_runner downloads 🐛
gitea.com was returning 502 Bad Gateway from the build host, causing
the act_runner download to fail immediately with no retry. Added
--retry 5 --retry-delay 10 --retry-all-errors to both download curl
calls so transient gateway errors don't abort the build.
- rootfs/root/docker/setup/05-custom.sh: add retry flags to gitea and act_runner curl download calls

rootfs/root/docker/setup/05-custom.sh
2026-06-04 17:30:26 -04:00
casjay 65c2ec64f6 🐛 Fix act_runner download: repo renamed, new binary names 🐛
The gitea/act_runner repo was renamed to gitea/runner and binary
filenames changed from act_runner-{ver}-linux-{arch} to
gitea-runner-{ver}-linux-{arch} (with version stripping the leading
'v'). The old URLs returned 404 causing every build to fail.
Also adds resilience for builds where gitea.com is unreachable:
- 30s connect timeout / 45s max-time on API calls
- Pinned fallback version (v1.0.8) used when API returns nothing
- Fallback direct URL constructed from version tag without API
- Empty-URL guard before curl invocation prevents blank-argument error
- rootfs/root/docker/setup/05-custom.sh: fix repo path gitea/act_runner → gitea/runner, fix binary name pattern, add fallback version, add fallback URL construction, add empty-URL guard

rootfs/root/docker/setup/05-custom.sh
2026-06-04 17:13:51 -04:00
casjay 5349e92de9 🐛 Disable act_runner cache — external_secret required when enabled 🐛
default_config.yaml had cache.enabled=true but no external_secret,
dir, host, or port configured. act_runner refuses to start the cache
server without external_secret, logging an error on every boot.
cache_server.yaml already has cache disabled — align default_config
to match.
- rootfs/tmp/etc/act_runner/default_config.yaml: cache.enabled false

rootfs/tmp/etc/act_runner/default_config.yaml
2026-06-04 16:54:06 -04:00