Compare commits
4 Commits
d44546ef29
...
10cfc6e505
| Author | SHA1 | Date | |
|---|---|---|---|
| 10cfc6e505 | |||
| 733f6c54d5 | |||
| 65aa8b0894 | |||
| 07a05788ad |
@@ -247,8 +247,11 @@ Design: **Authentik authenticates once for the whole domain; Caddy authorizes pe
|
|||||||
the matching access on next login; removing it removes it.
|
the matching access on next login; removing it removes it.
|
||||||
copyparty additionally requires a header with a secret name that only Caddy adds (`idp-h-key`,
|
copyparty additionally requires a header with a secret name that only Caddy adds (`idp-h-key`,
|
||||||
`COPYPARTY_IDP_KEY` in `.env`), so identity headers cannot be forged even if copyparty were reached directly.
|
`COPYPARTY_IDP_KEY` in `.env`), so identity headers cannot be forged even if copyparty were reached directly.
|
||||||
Logout: copyparty's logout link and the live portal's "Sign out" go to `/outpost.goauthentik.io/sign_out`,
|
Logout: copyparty's logout link, the live portal's "Sign out" and the dock's "sign out" go through
|
||||||
which ends the Authentik session (a local app logout alone would be undone on the next request).
|
`/outpost.goauthentik.io/sign_out`. Set the provider's **Invalidation flow** to
|
||||||
|
`default-provider-invalidation-flow` so that also ends the Authentik login: every logout then forces a full
|
||||||
|
Discord re-login on the next visit (and picks up group changes). Discord itself may approve silently if
|
||||||
|
you are still logged into Discord in that browser.
|
||||||
5. Debug: `https://HOST/whoami` prints the username/groups headers exactly as the apps receive them.
|
5. Debug: `https://HOST/whoami` prints the username/groups headers exactly as the apps receive them.
|
||||||
Group changes in Authentik apply on the next login — log out at `https://auth.HOST/` and back in.
|
Group changes in Authentik apply on the next login — log out at `https://auth.HOST/` and back in.
|
||||||
6. Test in a private window: `https://live.HOST/` → Discord login → portal; `https://bot.HOST/` → 403 unless
|
6. Test in a private window: `https://live.HOST/` → Discord login → portal; `https://bot.HOST/` → 403 unless
|
||||||
|
|||||||
@@ -154,7 +154,8 @@ AUTHENTIK FORWARD-AUTH (gates https://$LIVE/ and https://$BOT/; later the main h
|
|||||||
1. Applications -> Outposts -> edit "authentik Embedded Outpost" -> config: authentik_host: https://$AUTH
|
1. Applications -> Outposts -> edit "authentik Embedded Outpost" -> config: authentik_host: https://$AUTH
|
||||||
2. Applications -> Create with provider -> Application "Vanguard sites"; Proxy Provider, authorization
|
2. Applications -> Create with provider -> Application "Vanguard sites"; Proxy Provider, authorization
|
||||||
flow implicit-consent, mode "Forward auth (domain level)", authentication URL https://$AUTH,
|
flow implicit-consent, mode "Forward auth (domain level)", authentication URL https://$AUTH,
|
||||||
cookie domain ${BASE} -> then edit the Embedded Outpost -> Applications: add it -> Update
|
cookie domain ${BASE}, Invalidation flow default-provider-invalidation-flow (logout = full re-login)
|
||||||
|
-> then edit the Embedded Outpost -> Applications: add it -> Update
|
||||||
3. Per-host access is enforced by Caddy from X-Authentik-Groups: .env BOT_REQUIRE_GROUP=bot-users
|
3. Per-host access is enforced by Caddy from X-Authentik-Groups: .env BOT_REQUIRE_GROUP=bot-users
|
||||||
(default), LIVE_REQUIRE_GROUP= (empty = any logged-in member). ./configure.py apply regenerates.
|
(default), LIVE_REQUIRE_GROUP= (empty = any logged-in member). ./configure.py apply regenerates.
|
||||||
4. Discord roles -> Authentik groups: Directory -> Groups: create bot-users (keep streamers/editors/admins);
|
4. Discord roles -> Authentik groups: Directory -> Groups: create bot-users (keep streamers/editors/admins);
|
||||||
|
|||||||
33
check.sh
Executable file
33
check.sh
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Static checks for everything in the bundle. Run before committing / deploying.
|
||||||
|
set -uo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fail=0
|
||||||
|
say() { printf '%-28s %s\n' "$1" "$2"; }
|
||||||
|
|
||||||
|
for f in install.sh addCommunityMediaServer.sh installJellyfinPlugin.sh registerGiteaRunner.sh createGiteaDb.sh deployLive.sh firewall/firewall.sh bin/*.sh home/*.sh bot/entrypoint.sh mariadb/init/*.sh; do
|
||||||
|
[[ -f $f ]] || continue
|
||||||
|
bash -n "$f" && say "$f" ok || { say "$f" "BASH SYNTAX"; fail=1; }
|
||||||
|
done
|
||||||
|
for f in configure.py bin/dumpd.py; do
|
||||||
|
python3 -m py_compile "$f" && say "$f" ok || { say "$f" "PYTHON SYNTAX"; fail=1; }
|
||||||
|
done
|
||||||
|
rm -rf __pycache__ bin/__pycache__
|
||||||
|
python3 -c "import yaml,sys; [yaml.safe_load(open(f)) for f in sys.argv[1:]]" docker-compose.yml mediamtx.example.yml && say "yaml" ok || { say "yaml" "INVALID"; fail=1; }
|
||||||
|
node -e "
|
||||||
|
const fs=require('fs');const html=fs.readFileSync('dock/index.html','utf8');
|
||||||
|
for (const m of html.matchAll(/<script(?:[^>]*)>([\s\S]*?)<\/script>/g)) new Function(m[1]);" && say "dock/index.html JS" ok || { say "dock/index.html JS" "SYNTAX ERROR"; fail=1; }
|
||||||
|
# generated Caddyfile / mediamtx / copyparty from a dry configure run must at least be produced + parse
|
||||||
|
T=$(mktemp -d); cp configure.py "$T/"; ( cd "$T" && VG_DRY=1 python3 -c "
|
||||||
|
import configure as c, yaml
|
||||||
|
c.write_caddy('ovh1.example.com','auth.example.com','watch.example.com','git.example.com','bot.example.com','live.example.com')
|
||||||
|
c.write_mediamtx('ovh1.example.com'); yaml.safe_load(open('mediamtx.yml')); c.write_copyparty()
|
||||||
|
assert 'authMethod: http' in open('mediamtx.yml').read(); assert 'idp-h-usr' in open('copyparty.conf').read()
|
||||||
|
" ) && say "configure.py generators" ok || { say "configure.py generators" "FAILED"; fail=1; }
|
||||||
|
rm -rf "$T"
|
||||||
|
if command -v caddy >/dev/null; then
|
||||||
|
T=$(mktemp -d); cp configure.py "$T/"; ( cd "$T" && VG_DRY=1 python3 -c "import configure as c; c.write_caddy('ovh1.example.com','a.example.com','w.example.com','g.example.com','b.example.com','l.example.com')" && caddy validate --adapter caddyfile --config Caddyfile >/dev/null 2>&1 ) && say "Caddyfile (caddy validate)" ok || { say "Caddyfile (caddy validate)" "INVALID"; fail=1; }
|
||||||
|
rm -rf "$T"
|
||||||
|
fi
|
||||||
|
[[ -d live/node_modules ]] && ( cd live && npx --no-install vite build --logLevel error >/dev/null 2>&1 ) && say "live portal build" ok || say "live portal build" "(skipped or failed — run npm run build in live/)"
|
||||||
|
exit $fail
|
||||||
@@ -127,7 +127,7 @@ async function mint(){
|
|||||||
}
|
}
|
||||||
let showTok = false;
|
let showTok = false;
|
||||||
function renderManual(t){
|
function renderManual(t){
|
||||||
const c = cfg(); const title = t ?? buildTitle($("title").value) || "TITLE";
|
const c = cfg(); const title = (t ?? buildTitle($("title").value)) || "TITLE";
|
||||||
const user = me ? me.user : "<you>", tok = me ? me.token : "<token>";
|
const user = me ? me.user : "<you>", tok = me ? me.token : "<token>";
|
||||||
$("m_rtmp").textContent = `rtmp://${c.host||location.hostname}:1935/`;
|
$("m_rtmp").textContent = `rtmp://${c.host||location.hostname}:1935/`;
|
||||||
$("m_key").textContent = `${user}/${title}?user=${user}&pass=${showTok ? tok : "••••••••"}`;
|
$("m_key").textContent = `${user}/${title}?user=${user}&pass=${showTok ? tok : "••••••••"}`;
|
||||||
@@ -212,7 +212,9 @@ $("signout").onclick = async e => {
|
|||||||
// Authentik's sign-out redirects to the site root (copyparty); do it in the background, then reload the dock,
|
// Authentik's sign-out redirects to the site root (copyparty); do it in the background, then reload the dock,
|
||||||
// which sends us through the Discord login and back here.
|
// which sends us through the Discord login and back here.
|
||||||
try { await fetch("/outpost.goauthentik.io/sign_out", {credentials:"same-origin", mode:"no-cors", redirect:"follow"}); } catch(e){}
|
try { await fetch("/outpost.goauthentik.io/sign_out", {credentials:"same-origin", mode:"no-cors", redirect:"follow"}); } catch(e){}
|
||||||
setTimeout(() => location.replace("/dock/"), 500);
|
// also end the Authentik login itself (otherwise the next visit re-authorises silently); the flow returns to this host
|
||||||
|
const auth = "https://" + location.hostname.replace(/^[^.]+\./, "auth.") + "/if/flow/default-invalidation-flow/?next=" + encodeURIComponent(location.origin + "/dock/");
|
||||||
|
setTimeout(() => location.replace(auth), 400);
|
||||||
};
|
};
|
||||||
whoami(); renderPreview();
|
whoami(); renderPreview();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user