Merge pull request #375 from outis1one/claude/frigate-authelia-openid-0l1htj

Claude/frigate authelia openid 0l1htj
This commit is contained in:
Outis
2026-08-21 17:55:27 -04:00
committed by GitHub
8 changed files with 538 additions and 35 deletions
+96 -5
View File
@@ -191,13 +191,25 @@ pip_user_install PACKAGE... # pip3 --user with --break-system-packages o
### Caddy reverse proxy ### Caddy reverse proxy
```bash ```bash
configure_caddy_for_service "Display Name" "PORT" "default-subdomain" ["extra-block"] configure_caddy_for_service "Display Name" "PORT" "default-subdomain" ["extra-block"] ["reverse_proxy-extra"]
``` ```
Prompts the user for a domain, appends a site block to the Caddyfile, and Prompts the user for a domain, appends a site block to the Caddyfile, and
reloads Caddy. No-ops silently if Caddy isn't installed. The fourth argument reloads Caddy. No-ops silently if Caddy isn't installed. The fourth argument
is an optional string inserted verbatim inside the Caddy site block (use it is an optional string inserted verbatim inside the Caddy site block, before
for `import authelia` or custom matchers). `reverse_proxy` (use it for `import authelia` or custom matchers). The fifth
argument is a different thing — an optional string inserted **inside** the
`reverse_proxy` block itself, as sub-directives (e.g.
`" header_up X-Proxy-Secret abc123"`), for a backend that needs a
header only `reverse_proxy`'s own `header_up` can set — the fourth
argument's block runs *before* `reverse_proxy` and can't reach into it.
`services/frigate.sh` is the reference caller: Frigate's `proxy` auth mode
trusts `Remote-User`/`Remote-Groups` headers from Authelia's forward_auth,
but only if a matching `X-Proxy-Secret` header is also present — otherwise
those headers could be spoofed by a request that reaches Frigate's
published host port directly, bypassing Caddy/Authelia entirely. Omit the
fifth argument and the generated `reverse_proxy` line is the same bare form
as before — every other caller is unaffected.
The function places that block **before** `reverse_proxy` in the generated The function places that block **before** `reverse_proxy` in the generated
site block — don't reorder this. `forward_auth` (what `import authelia` site block — don't reorder this. `forward_auth` (what `import authelia`
@@ -245,14 +257,19 @@ forward_auth https://auth.example.com {
This only affects the remote-Authelia path — same-machine `authelia:9091` This only affects the remote-Authelia path — same-machine `authelia:9091`
snippets (`services/authelia.sh`) are a single hop and don't need it. snippets (`services/authelia.sh`) are a single hop and don't need it.
Sets two out-params (not `local` — read them after the call returns) so the Sets three out-params (not `local` — read them after the call returns) so
caller can tell whether Caddy actually ended up fronting the service: the caller can tell whether Caddy actually ended up fronting the service:
```bash ```bash
CADDY_SERVICE_CONFIGURED # true/false CADDY_SERVICE_CONFIGURED # true/false
CADDY_SERVICE_MODE # "local" or "remote" (only meaningful if configured) CADDY_SERVICE_MODE # "local" or "remote" (only meaningful if configured)
CADDY_SERVICE_DOMAIN # the domain actually configured (only meaningful if configured)
``` ```
`CADDY_SERVICE_DOMAIN` is what `_authelia_scope_access()` (see below) wants
as its `DOMAIN` argument — read it right after the call instead of
recomputing/guessing the domain a second time.
Use this to skip opening a host firewall port for a service Caddy already Use this to skip opening a host firewall port for a service Caddy already
fronts *locally* (it reaches the service over `host.docker.internal`, not fronts *locally* (it reaches the service over `host.docker.internal`, not
the network) — but still open it when `CADDY_SERVICE_MODE` is `"remote"`, the network) — but still open it when `CADDY_SERVICE_MODE` is `"remote"`,
@@ -461,6 +478,80 @@ for Authelia to protect. Removed from this list; if it grows a web UI in
the future, add it back and wire up the same prompt other services here the future, add it back and wire up the same prompt other services here
use. use.
**`frigate` — a third pattern, neither of the two above.** Frigate *does*
have built-in auth (username/password, `admin`/`viewer` roles, on by
default) so it isn't "no built-in auth" — but unlike the has-built-in-auth
list, that auth is designed to be handed off to an upstream proxy instead
of just living alongside it. Frigate has its own `proxy` auth mode built
specifically for Authelia/Authentik/oauth2_proxy/traefik-forward-auth:
given trusted `Remote-User`/`Remote-Groups` headers it can skip its own
login screen entirely (`auth.enabled: False`), rather than showing a
second, independently-expiring login *after* Authelia's. `services/frigate.sh`
wires this up: `import authelia` (fourth arg) plus a
`header_up X-Proxy-Secret <secret>` (fifth arg, see
`configure_caddy_for_service` above) into the reverse_proxy block, with
the matching `proxy.auth_secret`/`header_map`/`default_role: admin` block
written into `config/config.yml` — and only written at all once
`CADDY_SERVICE_CONFIGURED` confirms Caddy actually ended up fronting the
domain, so Frigate's own login is never disabled with nothing else in
front of it. `default_role: admin` (default in this repo's install) means
anyone who passes Authelia gets full access, same as the login it
replaces; use `proxy.role_map`/Authelia groups instead if some users
should be view-only. Reuses the same `FRIGATE_PROXY_AUTH_SECRET` on
reinstall (from `.env` via `ENV_MAP`, the same array `_frigate_parse_existing`
already builds) rather than rotating it and breaking the existing Caddy
pairing.
**`gitea` and `uptimekuma` — two more "disable/bypass built-in login,
Authelia is the only gate" integrations, each with its own trust model.**
Both are opt-in extras layered on top of the has-built-in-auth entries
those services already had; neither replaces the existing behavior for
anyone who doesn't ask for it.
- `gitea`'s `_gitea_offer_reverse_proxy_auth()` is a *second*, stronger
Authelia integration alongside the OIDC "Sign in with Authelia" button
(`_gitea_offer_authelia_sso()`, unchanged): Gitea's own
`ENABLE_REVERSE_PROXY_AUTHENTICATION` mode auto-logs in as whatever
username arrives in a trusted header — no click, no separate Gitea
session with its own expiry. Unlike Frigate, Gitea's own login page
isn't disabled — it stays as a fallback for anyone not arriving through
the trusted path, so there's no "native login off with nothing gating
it" failure mode to guard against here. The trust boundary is
`REVERSE_PROXY_TRUSTED_PROXIES` (an IP range), not a shared secret —
Gitea's own Docker image has shipped this wildcarded before (a real CVE,
GHSA-f75j-4cw6-rmx4: any source IP could set `X-WEBAUTH-USER` and log in
as anyone), so this always computes the range from caddy_net's actual
subnet (`docker network inspect ... --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'`,
the same lookup `ufw_allow_from_caddy_net` uses) and refuses to enable
the feature at all if that can't be determined — never falls back to a
permissive default. `REVERSE_PROXY_AUTHENTICATION_USER`/`_EMAIL` are set
to `Remote-User`/`Remote-Email` to match Authelia's `import authelia`
snippet's own `copy_headers` output directly, rather than renaming
headers in Caddy to match Gitea's own `X-WEBAUTH-USER` default. Gitea
currently reaches Caddy over its published host port
(`host.docker.internal:PORT`), not caddy_net, because it predates this
feature — enabling it rewires Gitea onto caddy_net (like every other
locally-Caddy-fronted service) and re-points Caddy's upstream at
`gitea:3000`, replacing the old site block via
`configure_caddy_for_service`'s own existing "already exists —
overwrite?" prompt. Local Caddy only; a remote Caddy machine's source
address isn't a stable, narrowly-scopeable range the way caddy_net's
bridge subnet is.
- `uptimekuma`'s equivalent is much simpler: Uptime Kuma's `DISABLE_AUTH=true`
env var turns its own login off *completely*, with no IP-range or secret
check left at all — once set, anything that can reach its port is in, no
questions asked. That makes it the one of these three where getting the
ordering wrong is worst: `services/uptimekuma.sh` only ever sets
`DISABLE_AUTH=true` after `configure_caddy_for_service "Uptime Kuma" "uptime-kuma:3001" "uptime" " import authelia"`
confirms `CADDY_SERVICE_CONFIGURED` — the same never-disable-native-auth-
without-a-confirmed-gate rule Frigate follows. Uptime Kuma already joined
caddy_net unconditionally before this (see its own `_CADDY_NET_BLOCK`),
so no networking change was needed here, just the env var and the
Authelia-gated Caddy call happening earlier (before `docker-compose.yml`
is written) instead of the plain unconditional call this file already
had at the end — which now only runs as a fallback when the Authelia
path wasn't used or wasn't completed.
For services without built-in auth, prompt the user before calling For services without built-in auth, prompt the user before calling
`configure_caddy_for_service` and pass `import authelia` as the extra block `configure_caddy_for_service` and pass `import authelia` as the extra block
if Authelia is installed and the user wants SSO protection: if Authelia is installed and the user wants SSO protection:
+20 -3
View File
@@ -841,11 +841,19 @@ find_free_coturn_range() {
} }
# ── Caddy reverse-proxy wiring (shared by every web service) ───────────────── # ── Caddy reverse-proxy wiring (shared by every web service) ─────────────────
# Usage: configure_caddy_for_service "Name" "UPSTREAM" "default-subdomain" ["extra"] # Usage: configure_caddy_for_service "Name" "UPSTREAM" "default-subdomain" ["extra"] ["reverse_proxy-extra"]
# UPSTREAM: container:port for caddy_net routing (e.g. "filebrowser:80"), # UPSTREAM: container:port for caddy_net routing (e.g. "filebrowser:80"),
# or plain port number for localhost fallback (e.g. "8085"). # or plain port number for localhost fallback (e.g. "8085").
# The optional 5th arg is inserted as sub-directives *inside* the
# reverse_proxy block itself (e.g. " header_up X-Proxy-Secret abc123")
# — for the rare case a backend needs a header only reverse_proxy's own
# header_up can set, as opposed to EXTRA_CONFIG's auth-gate directives that
# run before reverse_proxy entirely. See services/frigate.sh's Authelia
# integration for the reference caller (pins X-Proxy-Secret so Frigate's
# proxy-auth trust can't be spoofed by a request that reaches it directly,
# bypassing Caddy/Authelia).
configure_caddy_for_service() { configure_caddy_for_service() {
local SERVICE_NAME="$1" SERVICE_UPSTREAM="$2" DEFAULT_SUBDOMAIN="$3" EXTRA_CONFIG="${4:-}" local SERVICE_NAME="$1" SERVICE_UPSTREAM="$2" DEFAULT_SUBDOMAIN="$3" EXTRA_CONFIG="${4:-}" REVERSE_PROXY_EXTRA="${5:-}"
# Out-params (not `local` — callers read these after the call returns) so # Out-params (not `local` — callers read these after the call returns) so
# a caller can tell whether Caddy actually ended up fronting the service # a caller can tell whether Caddy actually ended up fronting the service
@@ -941,6 +949,15 @@ configure_caddy_for_service() {
_BLOCK_UPSTREAM="${_THIS_IP}:${_DISPLAY_PORT}" _BLOCK_UPSTREAM="${_THIS_IP}:${_DISPLAY_PORT}"
fi fi
# Bare "reverse_proxy upstream" unless a caller needs sub-directives
# (header_up, etc.) inside it — see the REVERSE_PROXY_EXTRA comment above.
local _REVERSE_PROXY_LINE="reverse_proxy ${_BLOCK_UPSTREAM}"
if [ -n "$REVERSE_PROXY_EXTRA" ]; then
_REVERSE_PROXY_LINE="reverse_proxy ${_BLOCK_UPSTREAM} {
${REVERSE_PROXY_EXTRA}
}"
fi
local _SITE_BLOCK local _SITE_BLOCK
_SITE_BLOCK="$(cat << CADDY_BLOCK _SITE_BLOCK="$(cat << CADDY_BLOCK
@@ -954,7 +971,7 @@ ${SERVICE_DOMAIN} {
# after it would be dead code that never runs — full bypass regardless # after it would be dead code that never runs — full bypass regardless
# of what the auth server's own rules say. # of what the auth server's own rules say.
${EXTRA_CONFIG} ${EXTRA_CONFIG}
reverse_proxy ${_BLOCK_UPSTREAM} ${_REVERSE_PROXY_LINE}
# Security headers # Security headers
header { header {
+39 -1
View File
@@ -2241,8 +2241,46 @@ install_asterisk() {
local _EXISTING_DOMAIN _EXISTING_PORT local _EXISTING_DOMAIN _EXISTING_PORT
_EXISTING_DOMAIN="$(grep -E '^DOMAIN_NAME=' .env | cut -d= -f2-)" _EXISTING_DOMAIN="$(grep -E '^DOMAIN_NAME=' .env | cut -d= -f2-)"
_EXISTING_PORT="$(grep -E '^WEB_ADMIN_PORT=' .env | cut -d= -f2-)" _EXISTING_PORT="$(grep -E '^WEB_ADMIN_PORT=' .env | cut -d= -f2-)"
# A domain was set at some point (DOMAIN_NAME in .env) but
# Caddy never ended up with a site block for it — declined
# at install time, DNS wasn't ready yet, or Caddy itself was
# reinstalled/reset since. "update" never re-asks the
# domain/networking/firewall questions (see this branch's
# own comment above), but leaving a configured-but-unwired
# domain broken forever with no way back short of a full
# reinstall (which rotates coturn/TURN credentials — see the
# "fresh" branch's own warning below) defeats the point of
# "update" being the safe, no-side-effects path.
# _asterisk_configure_caddy_public() only ever touches the
# Caddyfile and .env's WEB_ADMIN_AUTH_DISABLED line — never
# coturn, extensions, or anything a full reinstall would put
# at risk — so it's safe to offer here even though nothing
# else in "update" touches Caddy.
local _CADDY_JUST_CONFIGURED=false
if [[ -n "$_EXISTING_DOMAIN" ]] && [[ -d "$DOCKER_DIR/caddy" ]] \
&& ! grep -q "^${_EXISTING_DOMAIN}" "$DOCKER_DIR/caddy/Caddyfile" 2>/dev/null; then
echo ""
log_warning "DOMAIN_NAME (${_EXISTING_DOMAIN}) is set, but Caddy has no site"
log_warning "block for it — nothing is actually serving that domain."
local _FIX_CADDY=""
prompt_yn " Configure Caddy for ${_EXISTING_DOMAIN} now? (y/n):" "y" _FIX_CADDY
if [[ "$_FIX_CADDY" =~ ^[Yy]$ ]]; then
local _CURRENT_PUBLIC_IP=""
_CURRENT_PUBLIC_IP="$(curl -fsS --max-time 2 http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address 2>/dev/null || true)"
[[ -z "$_CURRENT_PUBLIC_IP" ]] && _CURRENT_PUBLIC_IP="$(curl -fsS --max-time 3 https://ifconfig.me 2>/dev/null || true)"
[[ -z "$_CURRENT_PUBLIC_IP" ]] && _CURRENT_PUBLIC_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
_asterisk_configure_caddy_public "$_EXISTING_DOMAIN" "${_EXISTING_PORT:-8081}" "$_CURRENT_PUBLIC_IP"
_CADDY_JUST_CONFIGURED=true
fi
fi
echo "" echo ""
log_success "Existing .env, firewall rules, and Caddy/Authelia config were left untouched." if [[ "$_CADDY_JUST_CONFIGURED" == true ]]; then
log_success "Existing .env and firewall rules were left untouched; Caddy was just configured above."
else
log_success "Existing .env, firewall rules, and Caddy/Authelia config were left untouched."
fi
if [[ -n "$_EXISTING_DOMAIN" ]]; then if [[ -n "$_EXISTING_DOMAIN" ]]; then
echo " Web admin: https://${_EXISTING_DOMAIN}/" echo " Web admin: https://${_EXISTING_DOMAIN}/"
else else
+90 -8
View File
@@ -83,7 +83,7 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
} }
configure_caddy_for_service() { configure_caddy_for_service() {
local _name="$1" _upstream="$2" _subdomain="$3" _extra="${4:-}" local _name="$1" _upstream="$2" _subdomain="$3" _extra="${4:-}" _rp_extra="${5:-}"
local _caddy_dir="$DOCKER_DIR/caddy" local _caddy_dir="$DOCKER_DIR/caddy"
local _caddyfile="$_caddy_dir/Caddyfile" local _caddyfile="$_caddy_dir/Caddyfile"
local _display_port="${_upstream##*:}" local _display_port="${_upstream##*:}"
@@ -126,12 +126,23 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
_block_upstream="${CADDY_REMOTE_HOST}:${_display_port}" _block_upstream="${CADDY_REMOTE_HOST}:${_display_port}"
fi fi
local _rp_line="reverse_proxy ${_block_upstream}"
if [[ -n "$_rp_extra" ]]; then
_rp_line="reverse_proxy ${_block_upstream} {
${_rp_extra}
}"
fi
local _site_block local _site_block
_site_block="$(cat << CBLOCK _site_block="$(cat << CBLOCK
# $_name # $_name
${_domain} { ${_domain} {
reverse_proxy ${_block_upstream} # Auth (if any) must come before reverse_proxy — see lib/common.sh's
# configure_caddy_for_service for why (reverse_proxy first would answer
# every request itself, making an auth block after it dead code).
${_extra}
${_rp_line}
header { header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
@@ -144,7 +155,6 @@ ${_domain} {
output file /var/log/caddy/${_domain}.log output file /var/log/caddy/${_domain}.log
format json format json
} }
${_extra}
} }
CBLOCK CBLOCK
)" )"
@@ -526,6 +536,10 @@ install_frigate() {
echo " - Prompt to add cameras interactively (RTSP creds go in .env)" echo " - Prompt to add cameras interactively (RTSP creds go in .env)"
echo " or write a starter config.yml if none are added" echo " or write a starter config.yml if none are added"
echo " - Offer a Caddy reverse proxy and to start the container" echo " - Offer a Caddy reverse proxy and to start the container"
echo " - If Authelia is installed: offer to protect Frigate with it —"
echo " disables Frigate's own login (auth.enabled: False) and pins a"
echo " proxy.auth_secret/X-Proxy-Secret handshake so only Caddy can"
echo " satisfy Frigate's proxy-auth trust"
return 0 return 0
fi fi
@@ -646,6 +660,51 @@ FRIGATE_COMPOSE
mkdir -p config mkdir -p config
mkdir -p "$FRIGATE_MEDIA" mkdir -p "$FRIGATE_MEDIA"
# Authelia SSO — decided (and, if accepted, wired into Caddy) before
# config.yml is written, so the auth block baked into config.yml only
# ever reflects a gate that's actually in place (never "native login
# disabled, but nothing put in front of it instead"). Frigate has its
# own built-in login (username/password) separate from Authelia's —
# left alone it would show *after* Authelia's forward_auth already
# gated the domain: a redundant second login, and worse, a second
# session that can expire independently and force a re-login on its
# own schedule regardless of Authelia's "remember me" duration. The
# proxy.auth_secret/X-Proxy-Secret handshake (pinned into the Caddy
# reverse_proxy block) stops that trust from being spoofed by a
# request that reaches Frigate's published host port directly,
# bypassing Caddy/Authelia entirely.
local FRIGATE_USE_AUTHELIA="n" FRIGATE_PROXY_SECRET="" AUTH_CONFIG_BLOCK=""
if [ -d "$DOCKER_DIR/authelia" ]; then
echo ""
prompt_yn "Protect Frigate with Authelia SSO (disables Frigate's own login)? (y/n):" "y" FRIGATE_USE_AUTHELIA
fi
if [[ "$FRIGATE_USE_AUTHELIA" =~ ^[Yy]$ ]]; then
FRIGATE_PROXY_SECRET="${ENV_MAP[FRIGATE_PROXY_AUTH_SECRET]:-$(generate_password 32)}"
configure_caddy_for_service "Frigate" "frigate:5000" "frigate" \
" import authelia" \
" header_up X-Proxy-Secret ${FRIGATE_PROXY_SECRET}"
if [ "${CADDY_SERVICE_CONFIGURED:-false}" = true ]; then
AUTH_CONFIG_BLOCK="auth:
enabled: False # Authelia already gates the whole domain — its own login would be redundant
proxy:
auth_secret: \"{FRIGATE_PROXY_AUTH_SECRET}\" # must match the X-Proxy-Secret header Caddy sends
header_map:
user: remote-user
role: remote-groups
default_role: admin # anyone who passes Authelia gets full access, same as the disabled local login did
"
declare -F _authelia_scope_access >/dev/null 2>&1 && _authelia_scope_access "frigate" "$CADDY_SERVICE_DOMAIN"
else
log_warning "Caddy wasn't configured for Frigate — leaving Frigate's own login enabled (nothing else is gating access)."
FRIGATE_PROXY_SECRET=""
fi
else
configure_caddy_for_service "Frigate" "frigate:5000" "frigate"
fi
# Credentials/IPs go in .env as FRIGATE_* variables; Frigate substitutes # Credentials/IPs go in .env as FRIGATE_* variables; Frigate substitutes
# any {FRIGATE_VAR} placeholder in config.yml from its container env at # any {FRIGATE_VAR} placeholder in config.yml from its container env at
# startup, so RTSP secrets never need to be typed into the YAML directly. # startup, so RTSP secrets never need to be typed into the YAML directly.
@@ -654,12 +713,12 @@ FRIGATE_COMPOSE
if [ "${#CAM_NAME[@]}" -eq 0 ]; then if [ "${#CAM_NAME[@]}" -eq 0 ]; then
# No cameras entered — write a starter config the operator edits by hand. # No cameras entered — write a starter config the operator edits by hand.
cat > config/config.yml << 'FRIGATE_CONFIG' cat > config/config.yml << FRIGATE_CONFIG
# Frigate Configuration — Docs: https://docs.frigate.video # Frigate Configuration — Docs: https://docs.frigate.video
# #
# ⚠️ YOU MUST EDIT THIS FILE to add your cameras before starting Frigate. # ⚠️ YOU MUST EDIT THIS FILE to add your cameras before starting Frigate.
mqtt: ${AUTH_CONFIG_BLOCK}mqtt:
enabled: false # Set to true and configure if you use Home Assistant enabled: false # Set to true and configure if you use Home Assistant
cameras: cameras:
@@ -696,7 +755,7 @@ FRIGATE_CONFIG
# RTSP credentials/IPs come from .env — Frigate substitutes {FRIGATE_VAR} # RTSP credentials/IPs come from .env — Frigate substitutes {FRIGATE_VAR}
# placeholders below from the container's environment at startup. # placeholders below from the container's environment at startup.
mqtt: ${AUTH_CONFIG_BLOCK}mqtt:
enabled: false # Set to true and configure if you use Home Assistant enabled: false # Set to true and configure if you use Home Assistant
go2rtc: go2rtc:
@@ -724,6 +783,7 @@ FRIGATE_CONFIG
cat > .env << FRIGATE_ENV cat > .env << FRIGATE_ENV
FRIGATE_MEDIA=$FRIGATE_MEDIA FRIGATE_MEDIA=$FRIGATE_MEDIA
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
FRIGATE_PROXY_AUTH_SECRET=$FRIGATE_PROXY_SECRET
${ENV_CAM_VARS} ${ENV_CAM_VARS}
FRIGATE_ENV FRIGATE_ENV
chmod 600 .env chmod 600 .env
@@ -732,7 +792,29 @@ FRIGATE_ENV
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$FRIGATE_MEDIA" 2>/dev/null || true chown -R "$ACTUAL_USER:$ACTUAL_USER" "$FRIGATE_MEDIA" 2>/dev/null || true
log_success "Frigate configured at $FRIGATE_DIR" log_success "Frigate configured at $FRIGATE_DIR"
configure_caddy_for_service "Frigate" "frigate:5000" "frigate" local AUTH_README_SECTION=""
if [ -n "$AUTH_CONFIG_BLOCK" ]; then
AUTH_README_SECTION="
## Authelia SSO
Frigate's own login is disabled (\`auth.enabled: False\` in
\`config/config.yml\`) — Authelia gates the whole domain instead via Caddy's
\`import authelia\` plus a \`proxy.auth_secret\`/\`X-Proxy-Secret\` handshake
(the secret lives in \`.env\` as \`FRIGATE_PROXY_AUTH_SECRET\`) so that trust
can't be spoofed by a request that reaches Frigate's published port
directly, bypassing Caddy.
Everyone who passes Authelia gets full (admin) access to Frigate —
adjust \`config/config.yml\`'s \`proxy.role_map\`/\`default_role\` plus
Authelia's own group assignments if you want to give some users
view-only access instead.
To stop Authelia asking for a login again on repeat visits (e.g. from a
phone) for as long as possible, increase its \"remember me\" session
duration: \`sudo ./setup.sh authelia\` → \"Change 'remember me' session
duration\" (this affects every domain that instance protects, not just
Frigate).
"
fi
write_readme "$FRIGATE_DIR" << MD write_readme "$FRIGATE_DIR" << MD
# Frigate NVR # Frigate NVR
@@ -746,7 +828,7 @@ security cameras. Detects people, cars, animals, and more.
- Recordings: \`$FRIGATE_MEDIA\` - Recordings: \`$FRIGATE_MEDIA\`
- Config: \`config/config.yml\` — cameras configured during install (${#CAM_NAME[@]} total) - Config: \`config/config.yml\` — cameras configured during install (${#CAM_NAME[@]} total)
- Credentials: \`.env\` — RTSP user/pass/IP per camera as FRIGATE_* variables - Credentials: \`.env\` — RTSP user/pass/IP per camera as FRIGATE_* variables
${AUTH_README_SECTION}
## Manage ## Manage
\`\`\`bash \`\`\`bash
cd $FRIGATE_DIR cd $FRIGATE_DIR
+105
View File
@@ -240,6 +240,92 @@ _gitea_offer_authelia_sso() {
declare -F _authelia_scope_access >/dev/null 2>&1 && _authelia_scope_access "gitea" "$GITEA_OIDC_DOMAIN" declare -F _authelia_scope_access >/dev/null 2>&1 && _authelia_scope_access "gitea" "$GITEA_OIDC_DOMAIN"
} }
# Offers Gitea's OTHER Authelia integration — not the OIDC button above, but
# ENABLE_REVERSE_PROXY_AUTHENTICATION: Gitea auto-logs in as whatever user
# name arrives in a trusted header, no click and no separate Gitea session
# to expire on its own schedule. This is genuinely stronger than the OIDC
# button (which still shows a login page, just with an extra option on it)
# and matches the pattern services/frigate.sh uses — except Gitea's own
# login form stays available as a fallback for anyone NOT arriving from a
# trusted source, so there's no "native login disabled with nothing gating
# it" failure mode to guard against here the way Frigate's had.
#
# The security boundary is REVERSE_PROXY_TRUSTED_PROXIES, not a shared
# secret: Gitea only honors the identity header from source IPs inside that
# range. Gitea's own Docker image shipped this wildcarded (GHSA-f75j-4cw6-
# rmx4 — any IP could set X-WEBAUTH-USER and log in as anyone), so this is
# always computed from caddy_net's real subnet (same lookup
# ufw_allow_from_caddy_net uses) and refuses to enable the feature at all if
# that can't be determined — never falls back to a permissive default.
#
# Requires Gitea to actually be reachable from an address inside that range,
# which means joining caddy_net like every other locally-Caddy-fronted
# service in this repo (Gitea currently reaches Caddy via its published
# host port instead — host.docker.internal upstream — because it predates
# this feature). Local Caddy only: a remote Caddy machine's source address
# isn't a stable, narrowly-scopeable range the way caddy_net's bridge subnet
# is, so this skips remote mode rather than guess at a trust range worth
# getting wrong.
_gitea_offer_reverse_proxy_auth() {
local DIR="$1"
[ -d "$DOCKER_DIR/authelia" ] || return 0
[ -d "$DOCKER_DIR/caddy" ] || return 0
if grep -q 'ENABLE_REVERSE_PROXY_AUTHENTICATION=true' "$DIR/docker-compose.yml" 2>/dev/null; then
log_info "Gitea's zero-click Authelia login (reverse-proxy auth) is already enabled — skipping."
return 0
fi
echo ""
local USE_RP=""
prompt_yn " Skip Gitea's own login entirely for anyone arriving via Authelia — fully transparent, no click, no separate Gitea session to re-expire? Rewires Gitea onto Caddy's internal network (Caddy must be on this same machine). (y/n):" "n" USE_RP
[[ "$USE_RP" =~ ^[Yy]$ ]] || return 0
local _subnet
_subnet="$(docker network inspect "${SITE_CADDY_NET:-caddy_net}" \
--format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' 2>/dev/null)"
if [ -z "$_subnet" ]; then
log_warning "Couldn't determine ${SITE_CADDY_NET:-caddy_net}'s subnet — refusing to enable"
log_warning "reverse-proxy auth without a scoped trust range. An unscoped default lets ANY"
log_warning "client impersonate ANY Gitea user via a spoofed header (this was a real Gitea"
log_warning "CVE — GHSA-f75j-4cw6-rmx4). Skipping."
return 1
fi
log_info "Wiring Gitea onto caddy_net and enabling reverse-proxy authentication..."
sed -i "/GITEA__security__INSTALL_LOCK=true/a\\ - GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION=true\\n - GITEA__service__ENABLE_REVERSE_PROXY_AUTO_REGISTRATION=true\\n - GITEA__service__ENABLE_REVERSE_PROXY_EMAIL=true\\n - GITEA__security__REVERSE_PROXY_AUTHENTICATION_USER=Remote-User\\n - GITEA__security__REVERSE_PROXY_AUTHENTICATION_EMAIL=Remote-Email\\n - GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES=${_subnet}" \
"$DIR/docker-compose.yml"
cat >> "$DIR/docker-compose.yml" << EOF
networks:
- caddy_net
networks:
caddy_net:
external: true
name: ${SITE_CADDY_NET:-caddy_net}
EOF
_gitea_fix_ownership "$DIR"
(cd "$DIR" && docker compose up -d) \
&& log_success "Gitea restarted on caddy_net (trusted range: ${_subnet})." \
|| { log_warning "Restart failed — check: docker compose -f $DIR/docker-compose.yml logs"; return 1; }
# Re-point Caddy at the container (gitea:3000, now reachable over
# caddy_net) instead of the host-published port, with the auth gate in
# front. This replaces the plain block set up earlier in this install —
# configure_caddy_for_service's own "already exists — overwrite?" prompt
# covers that; nothing here bypasses it.
configure_caddy_for_service "Gitea" "gitea:3000" "git" " import authelia"
if [ "${CADDY_SERVICE_CONFIGURED:-false}" = true ]; then
log_success "Gitea now signs in transparently via Authelia at https://${CADDY_SERVICE_DOMAIN} — its own login page is still there for anyone reaching it another way."
declare -F _authelia_scope_access >/dev/null 2>&1 && _authelia_scope_access "gitea" "$CADDY_SERVICE_DOMAIN"
else
log_warning "Caddy wasn't reconfigured — env vars are set, but nothing is routing Gitea through Authelia yet."
log_warning "Point Gitea's Caddy entry at gitea:3000 (not the old host.docker.internal upstream) with 'import authelia' in front, or just re-run this offer."
fi
}
# Offers to enable Gitea Actions (Gitea's own CI, largely GitHub-Actions- # Offers to enable Gitea Actions (Gitea's own CI, largely GitHub-Actions-
# workflow-compatible) with a local runner — mainly useful as a fallback so # workflow-compatible) with a local runner — mainly useful as a fallback so
# .gitea/workflows/*.yml can still run something like a GitHub Actions build # .gitea/workflows/*.yml can still run something like a GitHub Actions build
@@ -445,6 +531,8 @@ install_gitea() {
echo "[DRY-RUN] to install a systemd timer for automatic sync, or print manual instructions" echo "[DRY-RUN] to install a systemd timer for automatic sync, or print manual instructions"
echo "[DRY-RUN] Would offer to run a sync now (dry-run preview or for real), off-schedule" echo "[DRY-RUN] Would offer to run a sync now (dry-run preview or for real), off-schedule"
echo "[DRY-RUN] Would offer \"Sign in with Authelia\" (OIDC) if Authelia is installed" echo "[DRY-RUN] Would offer \"Sign in with Authelia\" (OIDC) if Authelia is installed"
echo "[DRY-RUN] Would offer zero-click Authelia login (reverse-proxy auth) if Authelia"
echo "[DRY-RUN] and local Caddy are both installed — rewires Gitea onto caddy_net"
echo "[DRY-RUN] Would offer to enable Gitea Actions (CI) with a local act_runner container" echo "[DRY-RUN] Would offer to enable Gitea Actions (CI) with a local act_runner container"
echo "[DRY-RUN] Would write $DIR/README.md" echo "[DRY-RUN] Would write $DIR/README.md"
return 0 return 0
@@ -473,6 +561,7 @@ install_gitea() {
|| log_warning "Restart failed — check: docker compose -f $DIR/docker-compose.yml logs" || log_warning "Restart failed — check: docker compose -f $DIR/docker-compose.yml logs"
_gitea_run_sync_direction_step "$DIR" _gitea_run_sync_direction_step "$DIR"
_gitea_offer_authelia_sso "$DIR" _gitea_offer_authelia_sso "$DIR"
_gitea_offer_reverse_proxy_auth "$DIR"
_gitea_offer_actions_runner "$DIR" _gitea_offer_actions_runner "$DIR"
log_success "Existing .env (tokens) and web/SSH ports were left untouched." log_success "Existing .env (tokens) and web/SSH ports were left untouched."
return 0 return 0
@@ -643,6 +732,7 @@ ENV
configure_caddy_for_service "Gitea" "host.docker.internal:${WEB_PORT}" "git" configure_caddy_for_service "Gitea" "host.docker.internal:${WEB_PORT}" "git"
_gitea_offer_authelia_sso "$DIR" _gitea_offer_authelia_sso "$DIR"
_gitea_offer_reverse_proxy_auth "$DIR"
_gitea_offer_actions_runner "$DIR" _gitea_offer_actions_runner "$DIR"
write_readme "$DIR" << MD write_readme "$DIR" << MD
@@ -685,6 +775,21 @@ on Gitea's own login page. Local admin login keeps working exactly as
before — this is additive, not a replacement. Managed in Gitea under before — this is additive, not a replacement. Managed in Gitea under
Site Administration -> Authentication Sources (source name: \`authelia\`). Site Administration -> Authentication Sources (source name: \`authelia\`).
## Zero-click Authelia login (optional, stronger)
A second, separate Authelia integration: instead of an extra button on
Gitea's login page, Gitea auto-logs in as whoever Authelia says you are —
no click, and no separate Gitea session that can expire on its own and
force a re-login later. Re-run \`sudo ./setup.sh gitea\` (Update mode) and
answer yes to the "Skip Gitea's own login entirely..." prompt. Requires
Authelia and Caddy on this same machine — it moves Gitea onto Caddy's
internal Docker network (\`caddy_net\`) and Gitea only trusts the identity
header from that network's address range, not from the internet or from
its own host-published port. Gitea's own login page keeps working for
anyone who reaches it any other way (e.g. directly on its port). New
users arriving this way get an ordinary (non-admin) Gitea account created
automatically the first time they show up.
## Gitea Actions (CI) — optional local runner ## Gitea Actions (CI) — optional local runner
Re-run \`sudo ./setup.sh gitea\` (Update mode is fine) and answer yes to Re-run \`sudo ./setup.sh gitea\` (Update mode is fine) and answer yes to
+84 -1
View File
@@ -1956,6 +1956,79 @@ def ea_reload_voicemail():
run_sudo(["docker", "exec", ASTERISK_EA_CONTAINER, "asterisk", "-rx", "module reload app_voicemail.so"]) run_sudo(["docker", "exec", ASTERISK_EA_CONTAINER, "asterisk", "-rx", "module reload app_voicemail.so"])
def _ea_endpoint_stanza_bounds(lines, ext):
"""Line-index range (start, end-exclusive) of the `[ext]\\ntype=endpoint`
PJSIP stanza for one extension, or None if not found. pjsip.conf reuses
the same [ext] bracket name for three separate stanzas per device
(type=endpoint, type=auth, type=aor — see easy-asterisk-v0.10.0.sh's
add_device()), so matching on the bracket alone would land in the wrong
one; this only matches the occurrence immediately followed by
"type=endpoint", bounded by the next blank line or next [section] the
same way lib/common.sh's _remove_caddy_site_block is bounded for Caddy
blocks — never an unbounded scan past this one device's own stanza."""
target = "[%s]" % ext
i, n = 0, len(lines)
while i < n:
if lines[i].strip() == target and i + 1 < n and lines[i + 1].strip() == "type=endpoint":
j = i + 1
while j < n and lines[j].strip() != "" and not lines[j].strip().startswith("["):
j += 1
return i, j
i += 1
return None
def _ea_set_endpoint_mailboxes(ext, enabled):
"""Adds/updates (enabled) or removes (disabled) the extension's PJSIP
`mailboxes=` line, so a phone can actually SUBSCRIBE for MWI (the "new
voicemail" notice) on this extension.
Confirmed live: nothing anywhere in this repo or the vendored
easy-asterisk script ever sets this. add_device()'s own device_config
template (easy-asterisk-v0.10.0.sh) never writes it, and until this,
write_voicemail() below only ever touched voicemail.conf — so recording
a voicemail worked fine (voicemail.conf + the dialplan's VoiceMail()
call), but no phone ever actually subscribed to be told about it,
regardless of whether the voicemail flag was on. `mailboxes=<ext>@default`
matches the "default" context name voicemail.conf's [default] section
uses (see _asterisk_write_voicemail_conf in services/asterisk.sh)
same context, just referenced from the endpoint side instead of the
dialplan side."""
path = _ea_pjsip_host_path()
if not path or not os.path.isfile(path):
return False, "No pjsip.conf found"
with open(path) as f:
lines = f.readlines()
bounds = _ea_endpoint_stanza_bounds(lines, ext)
if not bounds:
return False, "No PJSIP endpoint found for extension %s" % ext
start, end = bounds
existing_idx = None
for k in range(start, end):
if lines[k].lstrip().startswith("mailboxes="):
existing_idx = k
break
if enabled:
mailbox_line = "mailboxes=%s@default\n" % ext
if existing_idx is not None:
lines[existing_idx] = mailbox_line
else:
lines.insert(end, mailbox_line)
elif existing_idx is not None:
del lines[existing_idx]
else:
return True, ""
ok, err = ea_docker_write(EA_PJSIP_CONTAINER_PATH, "".join(lines))
if not ok:
return False, err
ea_reload_pjsip()
return True, ""
def write_voicemail(ext, enabled): def write_voicemail(ext, enabled):
"""Sets/clears the voicemail flag for one extension, then regenerates """Sets/clears the voicemail flag for one extension, then regenerates
voicemail.conf and reloads app_voicemail so the change takes effect voicemail.conf and reloads app_voicemail so the change takes effect
@@ -1968,7 +2041,12 @@ def write_voicemail(ext, enabled):
pstn-permissions.conf even after disabling — toggling it off and back on pstn-permissions.conf even after disabling — toggling it off and back on
later reuses the same PIN instead of silently changing it on the user. later reuses the same PIN instead of silently changing it on the user.
Independent of pstn_installed() the same way messaging is: voicemail has Independent of pstn_installed() the same way messaging is: voicemail has
no PSTN/trunk dependency.""" no PSTN/trunk dependency.
Also wires up (or tears down) MWI via _ea_set_endpoint_mailboxes() — the
extension's PJSIP endpoint needs its own `mailboxes=` line for a phone
to ever be told about a new voicemail; voicemail.conf alone is only
enough for the recording itself, not the notification."""
if not ASTERISK_CONFIG_DIR: if not ASTERISK_CONFIG_DIR:
return False, "No Asterisk install detected on this box" return False, "No Asterisk install detected on this box"
ext = str(ext).strip() ext = str(ext).strip()
@@ -1990,6 +2068,11 @@ def write_voicemail(ext, enabled):
return True, "Saved, but voicemail.conf couldn't be regenerated: %s" % err return True, "Saved, but voicemail.conf couldn't be regenerated: %s" % err
ea_reload_voicemail() ea_reload_voicemail()
mok, merr = _ea_set_endpoint_mailboxes(ext, enabled)
if not mok:
return True, "Saved, but couldn't wire up the phone's voicemail notification (MWI): %s" % merr
return True, "Saved" return True, "Saved"
+53 -14
View File
@@ -629,17 +629,11 @@ CBLOCK
_sms_write_readme() { _sms_write_readme() {
local _url="$1" _relay_domain="$2" local _url="$1" _relay_domain="$2"
write_readme "$SMS_APP_DIR" << MD
# Inbound SMS → Sipnetic (via AMI)
Gets SMS sent to one of your PSTN DIDs delivered into Asterisk as a SIP local _url_section
MESSAGE, landing in Sipnetic the same way internal texting already does — if [ -n "$_url" ]; then
not a push notification, a real message in the softphone. _url_section="In the provider portal, open the DID's SMS settings and paste this into the
\"Forward to URL\" field (on Anveo: Phone Numbers → the DID → SMS tab, tick
## The URL to paste into your DID provider
In the provider portal, open the DID's SMS settings and paste this into the
"Forward to URL" field (on Anveo: Phone Numbers → the DID → SMS tab, tick
the checkbox, paste, press SAVE — RETURN discards): the checkbox, paste, press SAVE — RETURN discards):
\`\`\` \`\`\`
@@ -652,7 +646,21 @@ query parameters; with the message last, everything after it can be read back
verbatim. verbatim.
Treat this URL like a password — anyone holding it can trigger a message Treat this URL like a password — anyone holding it can trigger a message
delivery into your Asterisk. delivery into your Asterisk."
else
_url_section="**Not set up yet — no public domain was entered.** Re-run \`sudo ./setup.sh sms-inbound\` and choose \"Full reinstall\" once DNS for the webhook's domain points at this box; nothing here works until then."
fi
write_readme "$SMS_APP_DIR" << MD
# Inbound SMS → Sipnetic (via AMI)
Gets SMS sent to one of your PSTN DIDs delivered into Asterisk as a SIP
MESSAGE, landing in Sipnetic the same way internal texting already does —
not a push notification, a real message in the softphone.
## The URL to paste into your DID provider
${_url_section}
## How delivery is decided ## How delivery is decided
@@ -770,8 +778,24 @@ install_sms-inbound() {
&& log_success "Relay refreshed and restarted." \ && log_success "Relay refreshed and restarted." \
|| log_warning "Restart failed — check: journalctl -u sms-inbound -n 50" || log_warning "Restart failed — check: journalctl -u sms-inbound -n 50"
echo "" echo ""
log_success "Settings, Caddy and firewall rules were left untouched." # A missing/placeholder domain here means an earlier run was
echo " Provider URL: ${SMS_FORWARD_URL}" # left with no real webhook URL (RELAY_DOMAIN entered blank,
# or DNS wasn't ready yet) — "update" mode never re-prompts
# for the domain (by design, same as every other service's
# non-destructive update path), so silently repeating that
# broken URL forever, looking like nothing is wrong, is worse
# than saying so plainly. Confirmed live: this is exactly
# what a DID provider like Anveo rejects — "<your-domain>"
# isn't a resolvable hostname.
if [[ -z "${SMS_RELAY_DOMAIN:-}" || "${SMS_FORWARD_URL:-}" == *"<your-domain>"* ]]; then
log_warning "No real webhook domain was ever set for this install — the stored"
log_warning "provider URL is a placeholder, not something a DID provider can use."
log_warning "Re-run 'sudo ./setup.sh sms-inbound' and choose \"2) Full reinstall\""
log_warning "to be asked for the domain again (needs DNS pointed at this box first)."
else
log_success "Settings, Caddy and firewall rules were left untouched."
echo " Provider URL: ${SMS_FORWARD_URL}"
fi
echo "" echo ""
return 0 return 0
;; ;;
@@ -898,7 +922,14 @@ install_sms-inbound() {
ensure_ufw_enabled ensure_ufw_enabled
fi fi
local FORWARD_URL="https://${RELAY_DOMAIN:-<your-domain>}/sms/${RELAY_TOKEN}?from=\$[from]\$&to=\$[to]\$&message=\$[message]\$" # Empty (not a "<your-domain>" placeholder) when no domain was entered —
# a placeholder here used to get persisted to settings.env and silently
# re-served as-is on every later "update" run (which never re-prompts
# for the domain, by design), looking like a valid webhook URL right up
# until a DID provider like Anveo rejected it as an unresolvable host.
# Confirmed live.
local FORWARD_URL=""
[ -n "$RELAY_DOMAIN" ] && FORWARD_URL="https://${RELAY_DOMAIN}/sms/${RELAY_TOKEN}?from=\$[from]\$&to=\$[to]\$&message=\$[message]\$"
# ── Persist settings ────────────────────────────────────────────────────── # ── Persist settings ──────────────────────────────────────────────────────
# Single-quoted values: this file gets `source`d again on the next # Single-quoted values: this file gets `source`d again on the next
@@ -929,6 +960,14 @@ ENV
# ── Summary ─────────────────────────────────────────────────────────────── # ── Summary ───────────────────────────────────────────────────────────────
echo "" echo ""
if [ -z "$FORWARD_URL" ]; then
log_warning "Inbound SMS relay is running, but nothing can reach it yet — no domain was entered."
log_warning "Point an A record at this box, then re-run 'sudo ./setup.sh sms-inbound' and"
log_warning "choose \"2) Full reinstall\" to be asked for the domain again and get a real"
log_warning "\"Forward to URL\" to paste into your DID provider."
echo ""
return 0
fi
log_success "Inbound SMS → Sipnetic configured." log_success "Inbound SMS → Sipnetic configured."
echo "" echo ""
echo " 1. In your DID provider's portal, open the number's SMS settings and" echo " 1. In your DID provider's portal, open the number's SMS settings and"
+51 -3
View File
@@ -206,6 +206,9 @@ install_uptimekuma() {
if [ "$DRY_RUN" = true ]; then if [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] Would create $UPTIME_DIR" echo "[DRY-RUN] Would create $UPTIME_DIR"
echo "[DRY-RUN] Would auto-scan for a free host port" echo "[DRY-RUN] Would auto-scan for a free host port"
echo "[DRY-RUN] If Authelia is installed: would offer to protect Uptime Kuma with it —"
echo "[DRY-RUN] sets DISABLE_AUTH=true (Kuma's own login off) only once Caddy's"
echo "[DRY-RUN] 'import authelia' gate is actually confirmed in front of it"
return 0 return 0
fi fi
@@ -241,6 +244,39 @@ networks:
" "
fi fi
# Authelia SSO — decided (and, if accepted, wired into Caddy) before
# docker-compose.yml is written, so DISABLE_AUTH only ever gets set once
# Caddy's "import authelia" gate is actually confirmed in front of Kuma.
# Unlike Frigate/Gitea, Uptime Kuma with DISABLE_AUTH=true has NO
# internal check left at all — it's not IP-scoped (Gitea) or secret-
# pinned (Frigate), just fully open to whatever reaches its port, so
# this is the one place getting the ordering wrong is worst: a login-
# disabled Kuma with nothing gating it is wide open to anyone who can
# reach the port, not just spoofable.
local UPTIME_USE_AUTHELIA="n" UPTIME_ENV_BLOCK="" _uptime_caddy_done=false
if [ -d "$DOCKER_DIR/authelia" ]; then
echo ""
prompt_yn "Protect Uptime Kuma with Authelia SSO (disables Kuma's own login entirely)? (y/n):" "y" UPTIME_USE_AUTHELIA
fi
if [[ "$UPTIME_USE_AUTHELIA" =~ ^[Yy]$ ]]; then
configure_caddy_for_service "Uptime Kuma" "uptime-kuma:3001" "uptime" " import authelia"
if [ "${CADDY_SERVICE_CONFIGURED:-false}" = true ]; then
UPTIME_ENV_BLOCK=" - DISABLE_AUTH=true"
_uptime_caddy_done=true
declare -F _authelia_scope_access >/dev/null 2>&1 && _authelia_scope_access "uptimekuma" "$CADDY_SERVICE_DOMAIN"
else
log_warning "Caddy wasn't configured — leaving Uptime Kuma's own login enabled (nothing else would be gating access)."
fi
fi
local UPTIME_ENV_SECTION=""
if [ -n "$UPTIME_ENV_BLOCK" ]; then
UPTIME_ENV_SECTION=" environment:
${UPTIME_ENV_BLOCK}
"
fi
cat > docker-compose.yml << UPTIME_COMPOSE cat > docker-compose.yml << UPTIME_COMPOSE
name: uptime-kuma name: uptime-kuma
@@ -250,7 +286,7 @@ services:
container_name: uptime-kuma container_name: uptime-kuma
hostname: uptime-kuma hostname: uptime-kuma
restart: unless-stopped restart: unless-stopped
volumes: ${UPTIME_ENV_SECTION} volumes:
- ./data:/app/data - ./data:/app/data
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
ports: ports:
@@ -282,6 +318,15 @@ Docker containers.
If Caddy is installed, you can expose this via the prompt during install If Caddy is installed, you can expose this via the prompt during install
(see configure_caddy_for_service). Default subdomain: uptime. (see configure_caddy_for_service). Default subdomain: uptime.
## Authelia SSO (optional)
If Authelia is installed, the installer offers to protect Uptime Kuma with
it instead of Kuma's own login — this sets \`DISABLE_AUTH=true\` (Kuma's own
account/login screen goes away entirely) and puts Caddy's \`import authelia\`
gate in front instead, so Authelia is the only thing checking who you are.
This only gets set once Caddy confirms it's actually fronting the domain —
never with nothing else gating access. Re-run \`sudo ./setup.sh uptimekuma\`
to add or change this later.
## Manage ## Manage
\`\`\` \`\`\`
cd $UPTIME_DIR cd $UPTIME_DIR
@@ -291,8 +336,11 @@ docker compose logs -f # logs
\`\`\` \`\`\`
MD MD
# Configure Caddy reverse proxy before starting # Configure Caddy reverse proxy before starting (skip if the Authelia
configure_caddy_for_service "Uptime Kuma" "uptime-kuma:3001" "uptime" # step above already did it)
if [ "$_uptime_caddy_done" != true ]; then
configure_caddy_for_service "Uptime Kuma" "uptime-kuma:3001" "uptime"
fi
local START_UPTIME="" local START_UPTIME=""
prompt_yn "Start Uptime Kuma now? (y/n):" "y" START_UPTIME prompt_yn "Start Uptime Kuma now? (y/n):" "y" START_UPTIME