diff --git a/.cursor/rules/interview-first-planning.mdc b/.cursor/rules/interview-first-planning.mdc new file mode 100644 index 0000000..36d2e8e --- /dev/null +++ b/.cursor/rules/interview-first-planning.mdc @@ -0,0 +1,16 @@ +--- +description: Interview-first planning for ADHD — applies to plans/ and task briefs in this repo +globs: plans/**/* +alwaysApply: false +--- + +# Interview-first planning (this repo) + +When working on files under `plans/`: + +- Lead with conversation questions, not dense specs +- Each task brief's **Talk first** section is the real interface +- Record outcomes in **What we decided** after chatting with Matt +- Dropping a task is success — mark Decision: dropped + +See also: user rule `interview-first-planning` (global, always apply). diff --git a/Caddyfile b/Caddyfile index 7f13e1d..7c8e361 100644 --- a/Caddyfile +++ b/Caddyfile @@ -31,6 +31,11 @@ } } +# Clients on LAN, Docker, or tailnet (same ranges as internal_only). +(remote_internal) { + remote_ip 192.168.1.0/24 172.16.0.0/12 100.64.0.0/10 127.0.0.1 +} + # ============================================================= # FOUNDRY VTT — public # ============================================================= @@ -87,6 +92,9 @@ sonarr.ginnoir.com { import internal_only route { import authentik_outpost + handle /api/* { + reverse_proxy sonarr:8989 + } handle { import authentik_forward_auth reverse_proxy sonarr:8989 @@ -98,6 +106,9 @@ radarr.ginnoir.com { import internal_only route { import authentik_outpost + handle /api/* { + reverse_proxy radarr:7878 + } handle { import authentik_forward_auth reverse_proxy radarr:7878 @@ -109,6 +120,9 @@ bazarr.ginnoir.com { import internal_only route { import authentik_outpost + handle /api/* { + reverse_proxy bazarr:6767 + } handle { import authentik_forward_auth reverse_proxy bazarr:6767 @@ -120,6 +134,9 @@ prowlarr.ginnoir.com { import internal_only route { import authentik_outpost + handle /api/* { + reverse_proxy prowlarr:9696 + } handle { import authentik_forward_auth reverse_proxy prowlarr:9696 @@ -178,6 +195,9 @@ whisparr.ginnoir.com { import internal_only route { import authentik_outpost + handle /api/* { + reverse_proxy whisparr:6969 + } handle { import authentik_forward_auth reverse_proxy whisparr:6969 @@ -324,6 +344,13 @@ portainer.ginnoir.com { uptime.ginnoir.com { route { import authentik_outpost + @uptime_mcp { + path /api/* /socket.io/* + import remote_internal + } + handle @uptime_mcp { + reverse_proxy uptime-kuma:3001 + } handle { import authentik_forward_auth reverse_proxy uptime-kuma:3001 diff --git a/scripts/freshrss-debug-auth.php b/scripts/freshrss-debug-auth.php new file mode 100644 index 0000000..00910df --- /dev/null +++ b/scripts/freshrss-debug-auth.php @@ -0,0 +1,7 @@ + $v) { + if (str_starts_with($k, 'HTTP_') || str_contains($k, 'REMOTE')) { + echo "$k=$v\n"; + } +} diff --git a/stacks/authentik/scripts/preview-portainer-userinfo.py b/stacks/authentik/scripts/preview-portainer-userinfo.py new file mode 100644 index 0000000..cb99c74 --- /dev/null +++ b/stacks/authentik/scripts/preview-portainer-userinfo.py @@ -0,0 +1,16 @@ +"""Print userinfo payload Portainer receives (ak shell: exec(open(...).read())).""" +from authentik.core.models import User +from authentik.providers.oauth2.models import OAuth2Provider, AccessToken +from authentik.providers.oauth2.views.userinfo import UserInfoView +from django.test import RequestFactory + +u = User.objects.get(username="ginnoir") +p = OAuth2Provider.objects.get(name="portainer") +t = AccessToken.objects.create(user=u, provider=p, scope="openid email profile") +req = RequestFactory().get( + "/application/o/userinfo/", + HTTP_AUTHORIZATION=f"Bearer {t.token}", +) +resp = UserInfoView.as_view()(req) +print("status", resp.status_code) +print(resp.content.decode())