Commit Graph
84 Commits
Author SHA1 Message Date
jason 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
jason fc3e148bb2 🐛 Create /config/env directory before services write into it 🐛
gitea / release-gitea (push) Failing after 41s
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
jason 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
jason 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
jason 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
jason 6a3a9bde14 🚀 Enable shared act_runner cache-server 🚀
gitea / release-gitea (push) Failing after 48s
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
jason 4787512bf0 🗃️ Update codebase 🗃️
gitea / release-gitea (push) Failing after 1m31s
rootfs/usr/local/etc/docker/functions/entrypoint.sh
2026-08-02 20:43:07 -04:00
jason c9593bd0b6 🐛 Fix entrypoint hang and lost-interpreter bugs 🐛
gitea / release-gitea (push) Failing after 1m50s
- 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
jason edcf07e9ea 🗃️ Update codebase 🗃️
gitea / release-gitea (push) Failing after 2s
rootfs/usr/local/etc/docker/functions/entrypoint.sh
2026-06-26 23:50:19 -04:00
jason 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
jason 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
jason ee93310f88 🐛 Fix duplicate 2>/dev/null in entrypoint.sh /etc/hosts line 🐛
gitea / release-gitea (push) Failing after 13m48s
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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason 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
jason ab8d2b9ffc ♻️ Migrate gitea to /config/ source-of-truth architecture ♻️
Migrate gitea Docker image to the new build-time config architecture.
- rootfs/root/docker/setup/03-files.sh: rewrite to canonical form with /tmp/bin, /tmp/var, /tmp/etc, /tmp/usr handlers; remove template-files copy block
- rootfs/usr/local/etc/docker/functions/entrypoint.sh: update to latest template with __init_service_conf, __find_php_ini, __find_php_bin helpers
- rootfs/usr/local/etc/docker/init.d/*.sh: fix $(basename) UUOC → ${var##*/}; move inline comments above code lines; remove commented-out dead code
- rootfs/usr/local/share/template-files/: delete entire directory; config files now deployed via /tmp/etc/ at build time

rootfs/root/docker/setup/03-files.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
rootfs/usr/local/share/template-files/config/env/default.sample
rootfs/usr/local/share/template-files/config/env/examples/00-directory.sh
rootfs/usr/local/share/template-files/config/env/examples/addresses.sh
rootfs/usr/local/share/template-files/config/env/examples/certbot.sh
rootfs/usr/local/share/template-files/config/env/examples/couchdb.sh
rootfs/usr/local/share/template-files/config/env/examples/dockerd.sh
rootfs/usr/local/share/template-files/config/env/examples/global.sh
rootfs/usr/local/share/template-files/config/env/examples/healthcheck.sh
rootfs/usr/local/share/template-files/config/env/examples/mariadb.sh
rootfs/usr/local/share/template-files/config/env/examples/mongodb.sh
rootfs/usr/local/share/template-files/config/env/examples/networking.sh
rootfs/usr/local/share/template-files/config/env/examples/other.sh
rootfs/usr/local/share/template-files/config/env/examples/php.sh
rootfs/usr/local/share/template-files/config/env/examples/postgres.sh
rootfs/usr/local/share/template-files/config/env/examples/redis.sh
rootfs/usr/local/share/template-files/config/env/examples/services.sh
rootfs/usr/local/share/template-files/config/env/examples/ssl.sh
rootfs/usr/local/share/template-files/config/env/examples/supabase.sh
rootfs/usr/local/share/template-files/config/env/examples/webservers.sh
rootfs/usr/local/share/template-files/config/env/examples/zz-entrypoint.sh
rootfs/usr/local/share/template-files/config/.gitkeep
rootfs/usr/local/share/template-files/data/.gitkeep
rootfs/usr/local/share/template-files/defaults/.gitkeep
2026-06-04 14:41:57 -04:00
jason 98640213a2 🐛 Fall back to hostname -f for GITEA_SERVER when unset 🐛
HOSTNAME resolution now tries hostname -f (FQDN) before falling back
to the short $HOSTNAME, giving a usable ROOT_URL out of the box on
hosts where the FQDN is set in /etc/hosts or DNS.
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: HOSTNAME fallback
chain: GITEA_SERVER → GITEA_HOSTNAME → FULL_DOMAIN_NAME →
hostname -f → $HOSTNAME

rootfs/usr/local/etc/docker/init.d/08-gitea.sh
2026-05-31 15:36:55 -04:00
jason 7cc09e7cfc 🐛 Fix persistent deprecated settings and ROOT_URL mismatch 🐛
Two issues with existing persistent volumes:
1. Deprecated app.ini settings ([cors].X_FRAME_OPTIONS,
[picture].DISABLE_GRAVATAR, [picture].ENABLE_FEDERATED_AVATAR) were
baked into the volume config from the old template and never removed
on subsequent container starts.
2. ROOT_URL/DOMAIN/SSH_DOMAIN were substituted once on first run from
the container hostname and never updated when GITEA_SERVER/GITEA_PROTO
env vars changed, causing the "Mismatched ROOT_URL" warning.
Fix: add migration + dynamic resync in __update_conf_files (08-gitea.sh)
that runs on every startup against both the persistent (/config/gitea)
and runtime (/etc/gitea) copies of app.ini, so changes take effect
immediately without a second restart.
Set GITEA_PROTO=https and GITEA_SERVER=git.casjay.work to populate
the correct ROOT_URL at container start.
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: add dynamic ROOT_URL
resync and deprecated-setting removal to __update_conf_files

rootfs/usr/local/etc/docker/init.d/08-gitea.sh
2026-05-31 15:34:40 -04:00
jason a3296f475f 🐛 Fix SSL errors for repository migrations 🐛
SSL interception on this network presents certificates without SANs
for the target hostname, breaking git clone operations during Gitea
migrations. Add migration and git-level TLS bypass settings.
- rootfs/tmp/etc/gitea/app.ini: add [migration] section with SKIP_TLS_VERIFY=true
- rootfs/tmp/etc/gitea/app.ini: add [git.config] http.sslVerify=false

rootfs/tmp/etc/gitea/app.ini
2026-05-31 15:25:56 -04:00
jason 0abc43bffc 🐛 Fix act_runner registration order 🐛
Runners were launched in parallel subshells with only a 2-second gap
between them. act_runner register is a network call; if Gitea was
still warming up any registration could race past an earlier one,
causing Gitea to assign IDs out of sequence (1,3,2,5,4 instead of
1,2,3,4,5).
Split into two phases: register all runners sequentially first so IDs
are assigned in the correct order, then launch all daemons in parallel
once every runner is confirmed registered.
- rootfs/usr/local/bin/start-runners: split __start_runner into
__register_runner (sequential, phase 1) and __start_runner_daemon
(parallel, phase 2); remove the sleep 2 workaround

rootfs/usr/local/bin/start-runners
2026-05-24 21:37:50 -04:00
jason 755b3db301 🐛 Fix all Gitea v1.26 deprecated config settings 🐛
Resolve all deprecation warnings emitted at runtime by updating
app.ini to use current key names and locations.
- rootfs/tmp/etc/gitea/app.ini: move X_FRAME_OPTIONS from [cors] to [security] (deprecatedSetting v1.26.0)
- rootfs/tmp/etc/gitea/app.ini: remove [picture].DISABLE_GRAVATAR (deprecatedSettingDB since v1.18, use admin panel)
- rootfs/tmp/etc/gitea/app.ini: remove [picture].ENABLE_FEDERATED_AVATAR (deprecatedSettingDB since v1.18, use admin panel)
- rootfs/tmp/etc/gitea/app.ini: rename [lfs].LFS_CONTENT_PATH → PATH (was silently ignored; correct key is PATH)
- rootfs/tmp/etc/gitea/app.ini: rename [git].MAX_GIT_DIFF_LINE_CHARACTER_COUNT → MAX_GIT_DIFF_LINE_CHARACTERS (correct struct field name)
- rootfs/tmp/etc/gitea/app.ini: remove LOG_ROTATE/DAILY_ROTATE/MAX_DAYS from [log] root section (only valid in file-writer sub-sections; dead config with MODE=console)

.claude/settings.local.json
rootfs/tmp/etc/gitea/app.ini
2026-05-24 21:18:57 -04:00
jason ae8a7583a8 🔧 Harden gitea binary download in 05-custom.sh 🔧
Fix SSL and rate-limit failures when downloading gitea during docker build.
The GitHub REST API is rate-limited at 60 req/hour for unauthenticated
requests from Docker BuildKit's outgoing IP. Additionally, BuildKit resolves
github.com via the host DNS which may return an IPv6 address served by a
transparent proxy, causing TLS cert verification failures (error 60: "no
alternative certificate subject name matches target hostname 'github.com'").
Changes:
- rootfs/root/docker/setup/05-custom.sh: replace JSON API version lookup
with a redirect-follow approach (curl -4sfL -o /dev/null -w %{url_effective})
that avoids the rate-limited /releases/latest API endpoint entirely
- rootfs/root/docker/setup/05-custom.sh: add -4 (IPv4-only) flag to all
github.com curl calls to bypass intercepted IPv6 DNS resolutions
- rootfs/root/docker/setup/05-custom.sh: add explicit ca-certificates
install and update-ca-certificates before any HTTPS downloads, since
the base image cert bundle may be stale after system upgrade

rootfs/root/docker/setup/05-custom.sh
2026-05-24 20:53:40 -04:00
jason 7668154e67 🐛 Fix silent download failure and cosmetic naming bugs 🐛
Fixes three bugs discovered during live container testing.
The critical bug was a bash post-increment no-op: `exitCode=$((exitCode++))`
assigns the *old* value back to the variable, so exitCode stays 0 even
when a download fails. This caused the Docker build to succeed silently
when the gitea binary download failed, publishing a broken image to Docker Hub.
- rootfs/root/docker/setup/05-custom.sh: change exitCode=$((exitCode++)) to
exitCode=$((exitCode + 1)) in both the gitea and act_runner failure handlers
- rootfs/usr/local/bin/entrypoint.sh: change CONTAINER_NAME and description
from "archlinux" (copied template default) to "gitea"
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: remove leading space from
[ -d " /config/ssh" ] path test so the directory existence check is correct

.claude/
rootfs/root/docker/setup/05-custom.sh
rootfs/usr/local/bin/entrypoint.sh
rootfs/usr/local/etc/docker/init.d/08-gitea.sh
2026-05-24 19:58:15 -04:00
jason 895520eb77 🐛 Fix runner config filenames, cache-server, and registration log 🐛
Three more bugs found during full component verification:
1. RUNNER_CONFIG_DEFAULT pointed to config.yaml but the actual
template filename is default_config.yaml — gitea named runner
never registered because the file-existence check always failed.
2. CACHE_CONFIG_FILE pointed to cache.yaml but the actual template
filename is cache_server.yaml — cache-server launch always
skipped silently.
3. act_runner v1.0.6 requires cache.external_secret on both the
cache-server and any runner using external_server; neither config
had it, so cache-server exited immediately and runner registration
failed when external_server was present in default_config.yaml.
4. Registration log redirect was 2>/dev/stdout >>"$log" (stderr went
to original stdout, not the log); corrected to >>"$log" 2>&1.
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh:
- RUNNER_CONFIG_DEFAULT: config.yaml → default_config.yaml
- CACHE_CONFIG_FILE: cache.yaml → cache_server.yaml
- fix registration log redirect: >>"$RUNNER_LOG_FILE" 2>&1
- rootfs/tmp/etc/act_runner/default_config.yaml: remove
external_server (no shared secret configured; each runner uses
its own internal cache)
- rootfs/tmp/etc/act_runner/cache_server.yaml: set enabled: false
(cache-server requires external_secret; disabled until a proper
shared-secret setup is added)
Verified: 6 daemons on fresh start (1 gitea named + 5 runners), all
with 22 labels; clean reconnect on restart with no re-registration.

rootfs/tmp/etc/act_runner/cache_server.yaml
rootfs/tmp/etc/act_runner/default_config.yaml
rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
2026-05-24 15:16:16 -04:00
jason e8021e29e2 🐛 Fix act_runner token generation and gitea auto-install 🐛
Runner registration was broken by three compounding bugs:
1. Token generated at script init time (before gitea was ready)
2. gitea CLI missing --work-path/--custom-path flags, writing fatal
log messages to stdout which got captured as the token value
3. Token assignment inside a piped subshell didn't propagate back
to the parent shell, leaving SYS_AUTH_TOKEN empty at runtime
4. INSTALL_LOCK=false in app.ini template caused gitea to start in
install-wizard mode, making generate-runner-token always fail
- rootfs/tmp/etc/gitea/app.ini: set INSTALL_LOCK=true so gitea
auto-initializes the SQLite DB on first run
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh:
- defer SYS_AUTH_TOKEN: initialize to empty at global scope,
generate in __run_pre_execute_checks after gitea is confirmed up
- add --work-path/--custom-path to gitea CLI call; filter output
with grep -oE '[A-Za-z0-9]{20,}' to extract only the token
- guard token generation on INSTALL_LOCK=true in app.ini
- read token back from $CONF_DIR/tokens/system after the piped
__run_pre_execute_checks call returns (subshell escape)
Tested: fresh start registers all 5 runners with full 22-label set;
restart skips re-registration and reconnects all 5 daemons cleanly.

rootfs/tmp/etc/gitea/app.ini
rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
2026-05-24 14:11:11 -04:00
jason 3a21cd6091 🐛 Fix generated start script: empty su_exec produces '' command prefix 🐛
When su_exec is empty (service runs as root, no user-switching needed),
printf '%q ' $su_exec expands to the literal string '' which gets embedded
in the generated start script as a command prefix, causing bash to try
executing a program named '' and failing immediately. Also add explicit
PATH and HOME exports to the RESET_ENV=no generated script so services
are not dependent on environment inheritance.
- rootfs/usr/local/etc/docker/init.d/05-dockerd.sh: fix _q_su assignment
in both RESET_ENV branches to use ${su_exec:+...} so it's empty string
(not '') when su_exec is empty; fix format string %s%s (no space between
su and cmd, su already carries trailing space); add PATH and HOME exports
to RESET_ENV=no generated script
- rootfs/usr/local/etc/docker/init.d/08-gitea.sh: same fixes
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: same fixes

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-05-24 13:00:24 -04:00
jason e599f1edc8 🐛 Fix start-runners: wrong --config arg kept all runners permanently offline 🐛
The daemon was called with --config pointing at the .runner registration
state file (JSON), not a YAML config. act_runner rejected it immediately
on every start, so all runners were always offline and never reconnected.
Also fix log truncation and stale fallback labels.
- rootfs/usr/local/bin/start-runners: remove --config from act_runner
daemon invocation (act_runner finds .runner in CWD automatically after
cd "$runner_dir"); fix __log to append (>>) instead of truncate (>);
update fallback RUNNER_LABELS to match the full label set defined in
zz-act_runner.sh

rootfs/usr/local/bin/start-runners
2026-05-24 12:32:25 -04:00
jason 1f84972d9d 🗑️ Remove scaffolding template 00-server01.sh 🗑️
Was added as a reference artifact during framework migration work
and should not live in the repo.
- rootfs/usr/local/etc/docker/init.d/00-server01.sh: deleted

rootfs/usr/local/etc/docker/init.d/00-server01.sh
2026-05-24 12:24:36 -04:00