Merge pull request #375 from outis1one/claude/frigate-authelia-openid-0l1htj
Claude/frigate authelia openid 0l1htj
This commit is contained in:
@@ -191,13 +191,25 @@ pip_user_install PACKAGE... # pip3 --user with --break-system-packages o
|
||||
### Caddy reverse proxy
|
||||
|
||||
```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
|
||||
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
|
||||
for `import authelia` or custom matchers).
|
||||
is an optional string inserted verbatim inside the Caddy site block, before
|
||||
`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
|
||||
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`
|
||||
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
|
||||
caller can tell whether Caddy actually ended up fronting the service:
|
||||
Sets three out-params (not `local` — read them after the call returns) so
|
||||
the caller can tell whether Caddy actually ended up fronting the service:
|
||||
|
||||
```bash
|
||||
CADDY_SERVICE_CONFIGURED # true/false
|
||||
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
|
||||
fronts *locally* (it reaches the service over `host.docker.internal`, not
|
||||
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
|
||||
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
|
||||
`configure_caddy_for_service` and pass `import authelia` as the extra block
|
||||
if Authelia is installed and the user wants SSO protection:
|
||||
|
||||
+20
-3
@@ -841,11 +841,19 @@ find_free_coturn_range() {
|
||||
}
|
||||
|
||||
# ── 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"),
|
||||
# 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() {
|
||||
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
|
||||
# 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}"
|
||||
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
|
||||
_SITE_BLOCK="$(cat << CADDY_BLOCK
|
||||
|
||||
@@ -954,7 +971,7 @@ ${SERVICE_DOMAIN} {
|
||||
# after it would be dead code that never runs — full bypass regardless
|
||||
# of what the auth server's own rules say.
|
||||
${EXTRA_CONFIG}
|
||||
reverse_proxy ${_BLOCK_UPSTREAM}
|
||||
${_REVERSE_PROXY_LINE}
|
||||
|
||||
# Security headers
|
||||
header {
|
||||
|
||||
+39
-1
@@ -2241,8 +2241,46 @@ install_asterisk() {
|
||||
local _EXISTING_DOMAIN _EXISTING_PORT
|
||||
_EXISTING_DOMAIN="$(grep -E '^DOMAIN_NAME=' .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 ""
|
||||
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
|
||||
echo " Web admin: https://${_EXISTING_DOMAIN}/"
|
||||
else
|
||||
|
||||
+90
-8
@@ -83,7 +83,7 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
}
|
||||
|
||||
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 _caddyfile="$_caddy_dir/Caddyfile"
|
||||
local _display_port="${_upstream##*:}"
|
||||
@@ -126,12 +126,23 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
_block_upstream="${CADDY_REMOTE_HOST}:${_display_port}"
|
||||
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
|
||||
_site_block="$(cat << CBLOCK
|
||||
|
||||
# $_name
|
||||
${_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 {
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
@@ -144,7 +155,6 @@ ${_domain} {
|
||||
output file /var/log/caddy/${_domain}.log
|
||||
format json
|
||||
}
|
||||
${_extra}
|
||||
}
|
||||
CBLOCK
|
||||
)"
|
||||
@@ -526,6 +536,10 @@ install_frigate() {
|
||||
echo " - Prompt to add cameras interactively (RTSP creds go in .env)"
|
||||
echo " or write a starter config.yml if none are added"
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -646,6 +660,51 @@ FRIGATE_COMPOSE
|
||||
mkdir -p config
|
||||
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
|
||||
# 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.
|
||||
@@ -654,12 +713,12 @@ FRIGATE_COMPOSE
|
||||
|
||||
if [ "${#CAM_NAME[@]}" -eq 0 ]; then
|
||||
# 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
|
||||
#
|
||||
# ⚠️ 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
|
||||
|
||||
cameras:
|
||||
@@ -696,7 +755,7 @@ FRIGATE_CONFIG
|
||||
# RTSP credentials/IPs come from .env — Frigate substitutes {FRIGATE_VAR}
|
||||
# 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
|
||||
|
||||
go2rtc:
|
||||
@@ -724,6 +783,7 @@ FRIGATE_CONFIG
|
||||
cat > .env << FRIGATE_ENV
|
||||
FRIGATE_MEDIA=$FRIGATE_MEDIA
|
||||
CADDY_NET=$SITE_CADDY_NET
|
||||
FRIGATE_PROXY_AUTH_SECRET=$FRIGATE_PROXY_SECRET
|
||||
${ENV_CAM_VARS}
|
||||
FRIGATE_ENV
|
||||
chmod 600 .env
|
||||
@@ -732,7 +792,29 @@ FRIGATE_ENV
|
||||
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$FRIGATE_MEDIA" 2>/dev/null || true
|
||||
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
|
||||
# Frigate NVR
|
||||
@@ -746,7 +828,7 @@ security cameras. Detects people, cars, animals, and more.
|
||||
- Recordings: \`$FRIGATE_MEDIA\`
|
||||
- Config: \`config/config.yml\` — cameras configured during install (${#CAM_NAME[@]} total)
|
||||
- Credentials: \`.env\` — RTSP user/pass/IP per camera as FRIGATE_* variables
|
||||
|
||||
${AUTH_README_SECTION}
|
||||
## Manage
|
||||
\`\`\`bash
|
||||
cd $FRIGATE_DIR
|
||||
|
||||
@@ -240,6 +240,92 @@ _gitea_offer_authelia_sso() {
|
||||
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-
|
||||
# workflow-compatible) with a local runner — mainly useful as a fallback so
|
||||
# .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] 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 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 write $DIR/README.md"
|
||||
return 0
|
||||
@@ -473,6 +561,7 @@ install_gitea() {
|
||||
|| log_warning "Restart failed — check: docker compose -f $DIR/docker-compose.yml logs"
|
||||
_gitea_run_sync_direction_step "$DIR"
|
||||
_gitea_offer_authelia_sso "$DIR"
|
||||
_gitea_offer_reverse_proxy_auth "$DIR"
|
||||
_gitea_offer_actions_runner "$DIR"
|
||||
log_success "Existing .env (tokens) and web/SSH ports were left untouched."
|
||||
return 0
|
||||
@@ -643,6 +732,7 @@ ENV
|
||||
configure_caddy_for_service "Gitea" "host.docker.internal:${WEB_PORT}" "git"
|
||||
|
||||
_gitea_offer_authelia_sso "$DIR"
|
||||
_gitea_offer_reverse_proxy_auth "$DIR"
|
||||
_gitea_offer_actions_runner "$DIR"
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
Re-run \`sudo ./setup.sh gitea\` (Update mode is fine) and answer yes to
|
||||
|
||||
@@ -1956,6 +1956,79 @@ def ea_reload_voicemail():
|
||||
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):
|
||||
"""Sets/clears the voicemail flag for one extension, then regenerates
|
||||
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
|
||||
later reuses the same PIN instead of silently changing it on the user.
|
||||
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:
|
||||
return False, "No Asterisk install detected on this box"
|
||||
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
|
||||
|
||||
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"
|
||||
|
||||
|
||||
|
||||
+53
-14
@@ -629,17 +629,11 @@ CBLOCK
|
||||
|
||||
_sms_write_readme() {
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
local _url_section
|
||||
if [ -n "$_url" ]; then
|
||||
_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 checkbox, paste, press SAVE — RETURN discards):
|
||||
|
||||
\`\`\`
|
||||
@@ -652,7 +646,21 @@ query parameters; with the message last, everything after it can be read back
|
||||
verbatim.
|
||||
|
||||
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
|
||||
|
||||
@@ -770,8 +778,24 @@ install_sms-inbound() {
|
||||
&& log_success "Relay refreshed and restarted." \
|
||||
|| log_warning "Restart failed — check: journalctl -u sms-inbound -n 50"
|
||||
echo ""
|
||||
log_success "Settings, Caddy and firewall rules were left untouched."
|
||||
echo " Provider URL: ${SMS_FORWARD_URL}"
|
||||
# A missing/placeholder domain here means an earlier run was
|
||||
# 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 ""
|
||||
return 0
|
||||
;;
|
||||
@@ -898,7 +922,14 @@ install_sms-inbound() {
|
||||
ensure_ufw_enabled
|
||||
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 ──────────────────────────────────────────────────────
|
||||
# Single-quoted values: this file gets `source`d again on the next
|
||||
@@ -929,6 +960,14 @@ ENV
|
||||
|
||||
# ── Summary ───────────────────────────────────────────────────────────────
|
||||
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."
|
||||
echo ""
|
||||
echo " 1. In your DID provider's portal, open the number's SMS settings and"
|
||||
|
||||
+51
-3
@@ -206,6 +206,9 @@ install_uptimekuma() {
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
echo "[DRY-RUN] Would create $UPTIME_DIR"
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -241,6 +244,39 @@ networks:
|
||||
"
|
||||
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
|
||||
name: uptime-kuma
|
||||
|
||||
@@ -250,7 +286,7 @@ services:
|
||||
container_name: uptime-kuma
|
||||
hostname: uptime-kuma
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
${UPTIME_ENV_SECTION} volumes:
|
||||
- ./data:/app/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
ports:
|
||||
@@ -282,6 +318,15 @@ Docker containers.
|
||||
If Caddy is installed, you can expose this via the prompt during install
|
||||
(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
|
||||
\`\`\`
|
||||
cd $UPTIME_DIR
|
||||
@@ -291,8 +336,11 @@ docker compose logs -f # logs
|
||||
\`\`\`
|
||||
MD
|
||||
|
||||
# Configure Caddy reverse proxy before starting
|
||||
configure_caddy_for_service "Uptime Kuma" "uptime-kuma:3001" "uptime"
|
||||
# Configure Caddy reverse proxy before starting (skip if the Authelia
|
||||
# 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=""
|
||||
prompt_yn "Start Uptime Kuma now? (y/n):" "y" START_UPTIME
|
||||
|
||||
Reference in New Issue
Block a user