Every domain with an access_control rule was reachable by any Authelia
user by default (the existing catch-all *.${AUTHELIA_DOMAIN} rule) — no
way to restrict a specific service to a subset of users without hand-
editing configuration.yml and users.yml directly.
_authelia_scope_access(SERVICE_ID, DOMAIN) is a new generic, reusable
helper: call it after any service finishes being protected by Authelia
(forward_auth gate or native OIDC alike — it only cares about the domain).
Offers universal vs. specific-users access; if scoped, creates a
"<service_id>-only" group, adds every listed username to it (creating
accounts on the fly for names that don't exist yet, via the new
_authelia_create_user_noninteractive — a non-interactive sibling to
add_authelia_user, same extraction pattern already used for
_authelia_provision_oidc_client), and inserts an allow+deny rule pair
above the general catch-all. Idempotent on rerun.
_authelia_report_access_scope() (new menu option 6) is the read side —
lists who has universal vs. service-scoped access, and offers to promote
a scoped user back to universal by removing their "-only" group
membership.
services/gitea.sh's _gitea_offer_authelia_sso() is the reference
integration, calling _authelia_scope_access after successfully wiring up
Gitea's OIDC login. The other services with a plain "Protect X with
Authelia?" prompt (magicmirror, wolf-pair, js99er, drum-rhythm-game,
iopaint, paintplus, stirling-pdf, wolf) are natural follow-ups once this
is confirmed working live — each just needs one added call.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YEQNc4NfBST1m9NtCZVYa8
Gitea Actions is Gitea's own CI, largely GitHub-Actions-workflow-compatible
(.gitea/workflows/*.yml). Off by default; this Gitea install is otherwise
just a passive GitHub pull mirror, so the main value here is resilience —
.gitea/workflows/*.yml can still run something like a GitHub Actions build
if GitHub itself is ever unreachable.
_gitea_offer_actions_runner(), offered on fresh installs and Update reruns
(idempotent — no-ops if already set up):
- Enables GITEA__actions__ENABLED / DEFAULT_ACTIONS_URL in the compose
file's environment, restarts to apply
- Generates a runner registration token via `gitea actions
generate-runner-token`
- Appends an act_runner service to the same docker-compose.yml, using
the host's Docker socket to launch a fresh container per job — the
same pattern this repo already uses for portainer/watchtower/
uptimekuma/beszel/traccar's autoheal
- Falls back to printing manual setup instructions if token generation
fails, rather than losing the attempt silently
_gitea_fix_ownership()'s data/-exclusion (added when we fixed the earlier
SQLite readonly-database bug) now also skips runner-data/, so a future
reinstall/update doesn't clobber the runner's own state the same way.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YEQNc4NfBST1m9NtCZVYa8
Gitea has its own built-in login, so it was never wired into the
forward_auth/Caddy pattern the rest of this repo uses to gate apps with
no auth of their own — that's still correct and unchanged. But Gitea
also supports adding an OAuth2/OpenID Connect authentication source
natively, and Authelia can act as an OIDC provider — a genuinely
different, additive integration: an extra "Sign in with Authelia" button
on Gitea's own login page, alongside local login, not a Caddy-level gate.
Refactored services/authelia.sh's _authelia_add_oidc_client() to split
out its non-interactive core as _authelia_provision_oidc_client() — same
behavior for the existing ActualBudget/Vaultwarden/Immich/custom-app menu
flow, but now callable directly by other services with explicit args
instead of walking a human through the menu, returning the plaintext
secret and Authelia's domain via out-params.
services/gitea.sh's new _gitea_offer_authelia_sso() uses that to fully
automate both sides when accepted: registers Gitea as an OIDC client in
Authelia, then runs `gitea admin auth add-oauth` itself to add Authelia
as an authentication source — no manual web-UI copy-paste on either side,
matching how this installer already avoids manual wizards for the admin
account/token. Falls back to printing the values for a manual add if the
Gitea-side CLI call fails. Offered on fresh installs and on Update
reruns (default no, so a plain Update stays silent), so it can be added
later without a full reinstall.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YEQNc4NfBST1m9NtCZVYa8
Root cause of "attempt to write a readonly database (1544)" on repo
creation: Gitea's container always runs internally as UID 1000
(USER_UID/USER_GID are fixed in docker-compose.yml, independent of
whoever's running this installer) — the image chowns /data to that UID
itself at startup. install_gitea()'s three ensure_docker_dir_ownership
calls recursively chown the *entire* service directory, data/ included,
to $ACTUAL_USER. On a box where the installer runs as root directly
(ACTUAL_USER=root), that resets a live data/ back to UID 0. If the
container doesn't happen to restart right after — confirmed live: Update
mode against an already-running container just no-ops instead of
restarting — nothing ever re-fixes it, and every subsequent write to
Gitea's own SQLite DB fails.
Added _gitea_fix_ownership(), which chowns everything in the service
directory except data/, and swapped it in at all three call sites. The
container continues to own data/'s permissions exclusively, as it always
has on first boot; this installer no longer fights it on every rerun.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YEQNc4NfBST1m9NtCZVYa8
Root cause of the "Failed to reach Gitea API" / 403 errors on every retry:
`gitea admin user change-password` (used in the already-exists branch to
sync the account's password to what the user just entered) defaults to
setting must_change_password=true, unlike `user create` which was already
pinned to --must-change-password=false. Once set, Gitea rejects every API
call — including the sync script's own token-authenticated calls — with
403 "You must change your password", even though the token itself and
GITEA_URL were both completely correct. Confirmed live via a direct curl
against /api/v1/user.
Pin the same flag on change-password that create already used.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YEQNc4NfBST1m9NtCZVYa8
Live symptom: pasting the GitHub PAT over SSH showed the token text
landing on the terminal *after* "No GitHub token entered" had already
printed — the prompt's read() returned empty a beat before the paste
actually arrived (a paste/Enter race that isn't specific to this box,
just common over higher-latency SSH sessions). A single empty answer
was treated as "user has no token" and the install moved on silently.
Both token prompts (GitHub token, and the Gitea-token manual fallback)
now retry up to 3 times interactively before giving up, and strip
whitespace from what was captured in case the paste carried a stray
leading/trailing newline. Unattended installs still take one shot, same
as before, since nobody's there to retry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YEQNc4NfBST1m9NtCZVYa8
The sync direction step only ever set up the timer (or printed manual
instructions) — there was no way to actually confirm tokens/config work
without waiting for the first scheduled run or invoking the script by
hand afterward. Add a post-configure prompt: dry-run preview (--list),
run for real right now, or skip. Defaults to dry-run interactively;
defaults to skip under UNATTENDED so a headless install with no GitHub
token configured doesn't spam preflight errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YEQNc4NfBST1m9NtCZVYa8
generate-access-token used a fixed --token-name "sync", which Gitea
rejects on a second call for the same user (e.g. a retry against an
already-existing admin account, now common after the readiness-wait
fix). The failure was silent: it fell through to a manually-labeled
"Paste the Gitea token here" prompt appearing immediately before the
real "GitHub token:" prompt, so a pasted GitHub PAT could land on the
wrong prompt and leave GITHUB_TOKEN empty with no clear reason why.
Token name now includes a timestamp so it's always unique, and the
fallback prompt is relabeled to make clear it wants a Gitea token, not
the GitHub one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YEQNc4NfBST1m9NtCZVYa8
Previously the admin account was silently auto-generated (username =
ACTUAL_USER, random password) and only created after a fixed 60s
readiness probe — a slow first boot (SQLite init on a slower disk) timed
the whole install out with no account ever created, leaving the user to
create one by hand with a raw docker exec.
Now the install prompts for admin username/password up front, then folds
account creation into the same retry loop used to detect readiness (up to
2 minutes), so a slow-but-eventually-successful boot no longer dead-ends
the install. A retry against a partially-completed prior run (account
already exists) is treated as success and syncs the password instead of
failing outright.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YEQNc4NfBST1m9NtCZVYa8
Gitea previously only existed bundled inside the full ai-stack service
(Ollama/ComfyUI/InvokeAI/etc. all together) — no way to get just a git
server without the rest of that heavy stack. This adds it as its own
lightweight service, reusing the vendored gitea-github-sync.sh but not
any of ai-stack's other components.
- Auto-creates a Gitea admin account and API token via the container's
own CLI (no manual web setup wizard).
- Asks GitHub token, sync direction (GitHub->Gitea / Gitea->GitHub /
both), and whether to install a systemd timer for automatic sync —
prints manual commands instead if declined.
- Own systemd unit for the timer rather than the vendor script's
built-in --install-timer, since that always runs both directions
with no way to pin a single direction.