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
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
- 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
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
: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
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
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
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
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
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
Fix all undefined variable references and logic errors that would
prevent runner registration and daemon startup from working.
- rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh: fix GITEA_USER
typo (missing $ before SERVICE_USER); define 9 previously undefined
variables: RUNNER_IP_ADDRESS, RUNNER_CONFIG_DEFAULT, RUNNER_DEFAULT_HOME,
RUNNER_CONFIG_NAME, RUNNER_LOG_FILE, RUNNER_DAEMON_LOG, RUNNER_CACHE_HOST,
CACHE_CONFIG_FILE, CACHE_LOG_FILE; swap ctime/waitTime local declarations
so ctime is defined before waitTime uses it; quote act_runner daemon and
cache-server --config paths
rootfs/usr/local/etc/docker/init.d/zz-act_runner.sh
Update the embedded entrypoint copies in rootfs/ to match the
upstream template change. Internal state files renamed to dotfiles
so they're not matched by `/run/*.pid` cleanup globs:
- /run/init.d/entrypoint.pid -> /run/.entrypoint.pid
- /run/no_exit.pid -> /run/.no_exit.pid
- /run/backup.pid -> /run/.backup.pid
- /run/__start_init_scripts.pid -> /run/.start_init_scripts.pid
Per-service PIDs in /run/init.d/ are unchanged.
rootfs/usr/local/bin/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
rootfs/usr/local/share/template-files/config/env/default.sample
rootfs/usr/local/share/template-files/config/env/examples/zz-entrypoint.sh