diff --git a/CHANGELOG.md b/CHANGELOG.md index 22ea31e..99d5429 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to KiteStacks Homelab are documented here. +## [v1.4.0] — 2026-06-12 + +### Changed +- **OpenProject replaced by OSticket** — OpenProject CE 15 blocked SSO behind Enterprise license; replaced with OSticket (campbellsoftwaresolutions/osticket + MariaDB). Cloudflare Tunnel updated to `http://osticket:8080`. Both monk and kscloud1 running. Admin: kenpat7177. Portal card updated to "OSticket / Help Desk" with new icon. +- **Portainer SSO live** — Authentik OAuth configured via API on both monk and kscloud1. Portal card changed from coming-soon to live link (`https://portainer.kitestacks.com`). kscloud1 Portainer freshly deployed. +- **OAuth2 code TTL bumped 1min → 10min** — fixes `invalid_grant` / "Code does not exist" errors that occurred when monk reconnects after being offline (Cloudflare splits auth and token requests across reconnecting instances during the ~5-min startup window). +- **Karakeep redirect_uri re-fixed** — `_redirect_uris` in shared Authentik Postgres reverted to proxy URIs; fixed back to `https://links.kitestacks.com/api/auth/callback/custom`. + +### Fixed +- SSO errors (Redirect URI Error + invalid_grant) visible when accessing services from class/away from home. Root cause: Karakeep redirect_uri wrong + auth codes expiring during monk reconnect. + +### Removed +- OpenProject provider and application deleted from Authentik shared DB + ## [v1.3.922] — 2026-06-09 ### Added diff --git a/RUNBOOK.md b/RUNBOOK.md index 11a8d38..af9b0fd 100644 --- a/RUNBOOK.md +++ b/RUNBOOK.md @@ -1,6 +1,6 @@ # KiteStacks Homelab — Complete Setup Runbook -**Last Updated:** 2026-06-11 +**Last Updated:** 2026-06-12 **Status:** Production (monk primary, kscloud1 Hetzner cloud replica) **Maintainer:** kenpat (kenpat7177@gmail.com) @@ -32,14 +32,15 @@ Tailscale overlay network (VPN mesh): | www.kitestacks.com | homepage (nginx portal) | | | auth.kitestacks.com | authentik | | | gitforge.kitestacks.com | forgejo | | -| tasks.kitestacks.com | openproject | | +| tasks.kitestacks.com | osticket (nginx proxy → osticket-app) | 8080 | +| portainer.kitestacks.com | portainer | 9443 (HTTPS) | | ai.kitestacks.com | kite-openwebui | | | links.kitestacks.com | karakeep | | | kavita.kitestacks.com | kavita | | | grafana.kitestacks.com | grafana | | | status.kitestacks.com | uptime-kuma | | -**Important — active-active data model:** monk and kscloud1 each run their own copies of all stateful apps (Forgejo, Kavita, OpenProject, etc.) with independent databases. Data is intentionally NOT synced between them (except for Authentik, which shares a single Postgres+Redis on kscloud1 over Tailscale). If kscloud1 serves a request, the user sees kscloud1's database. This is the accepted tradeoff for guaranteed uptime. +**Important — active-active data model:** monk and kscloud1 each run their own copies of all stateful apps (Forgejo, Kavita, OSticket, etc.) with independent databases. Data is intentionally NOT synced between them (except for Authentik, which shares a single Postgres+Redis on kscloud1 over Tailscale). If kscloud1 serves a request, the user sees kscloud1's database. This is the accepted tradeoff for guaranteed uptime. --- @@ -181,7 +182,8 @@ Still in the tunnel config, add one public hostname per subdomain. Use the conta | www.kitestacks.com | `http://homepage:` | | auth.kitestacks.com | `http://authentik:` | | gitforge.kitestacks.com | `http://forgejo:` | -| tasks.kitestacks.com | `http://openproject:` | +| tasks.kitestacks.com | `http://osticket:8080` | +| portainer.kitestacks.com | `https://portainer:9443` (enable "No TLS Verify") | | ai.kitestacks.com | `http://kite-openwebui:` | | links.kitestacks.com | `http://karakeep:` | | kavita.kitestacks.com | `http://kavita:` | @@ -607,50 +609,101 @@ networks: - Client Secret: (96-char hex from Authentik) - Enabled: true, ProviderName: `authentik` -### 5.8 OpenProject (Task Management) +### 5.8 OSticket (Help Desk) — replaced OpenProject 2026-06-12 -`~/kitestacks-live/docker/openproject/docker-compose.yml`: +OSticket is a PHP-based help desk ticketing system. It runs behind an nginx proxy container that Cloudflare Tunnel reaches at `http://osticket:8080`. + +**Architecture:** `osticket` (nginx:alpine, port 8080) → `osticket-app` (campbellsoftwaresolutions/osticket, port 80) + `osticket-db` (mariadb:10.11) + +`~/kitestacks-live/docker/osticket/docker-compose.yml`: ```yaml services: - openproject: - image: openproject/openproject:15 - container_name: openproject + osticket: + image: nginx:alpine + container_name: osticket restart: unless-stopped - environment: - - OPENPROJECT_SECRET_KEY_BASE=${OPENPROJECT_SECRET_KEY_BASE} - - OPENPROJECT_HOST__NAME=tasks.kitestacks.com - - OPENPROJECT_HTTPS=false - - OPENPROJECT_OPENID__CONNECT_AUTHENTIK_IDENTIFIER=openproject - - OPENPROJECT_OPENID__CONNECT_AUTHENTIK_SECRET=${OPENPROJECT_OIDC_SECRET} - - OPENPROJECT_OPENID__CONNECT_AUTHENTIK_DISPLAY__NAME=Authentik - - OPENPROJECT_OPENID__CONNECT_AUTHENTIK_SCOPE=["openid","email","profile"] - - OPENPROJECT_OPENID__CONNECT_AUTHENTIK_ISSUER=https://auth.kitestacks.com/application/o/openproject/ - - OPENPROJECT_OPENID__CONNECT_AUTHENTIK_AUTHORIZATION__ENDPOINT=https://auth.kitestacks.com/application/o/authorize/ - - OPENPROJECT_OPENID__CONNECT_AUTHENTIK_TOKEN__ENDPOINT=https://auth.kitestacks.com/application/o/token/ - - OPENPROJECT_OPENID__CONNECT_AUTHENTIK_USERINFO__ENDPOINT=https://auth.kitestacks.com/application/o/userinfo/ - - OPENPROJECT_OPENID__CONNECT_AUTHENTIK_END__SESSION__ENDPOINT=https://auth.kitestacks.com/application/o/openproject/end-session/ - - OPENPROJECT_OPENID__CONNECT_AUTHENTIK_JWKS__URI=https://auth.kitestacks.com/application/o/openproject/jwks/ - ports: - - ":" + depends_on: + - osticket-app volumes: - - ./pgdata:/var/openproject/pgdata - - openproject_assets:/var/openproject/assets + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + ports: + - ":8080" networks: - default - kitestacks + osticket-app: + image: campbellsoftwaresolutions/osticket + container_name: osticket-app + restart: unless-stopped + depends_on: + - osticket-db + environment: + - INSTALL_NAME=KiteStacks Help Desk + - INSTALL_EMAIL=helpdesk@kitestacks.com + - INSTALL_URL=https://tasks.kitestacks.com/ + - ADMIN_FIRSTNAME=Ken + - ADMIN_LASTNAME=Pat + - ADMIN_EMAIL= + - ADMIN_USERNAME= + - ADMIN_PASSWORD=${OSTICKET_ADMIN_PASS} + - INSTALL_SECRET=${OSTICKET_INSTALL_SECRET} + - MYSQL_HOST=osticket-db + - MYSQL_DATABASE=osticket + - MYSQL_USER=osticket + - MYSQL_PASSWORD=${OSTICKET_DB_PASS} + volumes: + - osticket_uploads:/data/upload/include/i18n + networks: + - default + + osticket-db: + image: mariadb:10.11 + container_name: osticket-db + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=${OSTICKET_DB_ROOT} + - MYSQL_DATABASE=osticket + - MYSQL_USER=osticket + - MYSQL_PASSWORD=${OSTICKET_DB_PASS} + volumes: + - osticket_db:/var/lib/mysql + networks: + - default + volumes: - openproject_assets: + osticket_uploads: + osticket_db: networks: kitestacks: external: true ``` -`.env` keys: `OPENPROJECT_SECRET_KEY_BASE`, `OPENPROJECT_OIDC_SECRET` +`nginx.conf`: +```nginx +server { + listen 8080; + location / { + proxy_pass http://osticket-app:80; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-Host $host; + proxy_read_timeout 120s; + } +} +``` -> **Known blocker:** OpenProject CE (Community Edition) 15 gates all SSO/OmniAuth strategies behind an Enterprise Edition license. The OIDC config above is correct and the provider record will be seeded in the DB, but the SSO button does NOT appear on `/login` in CE. Options: purchase EE license, or put a forward-auth proxy (oauth2-proxy or Authentik outpost) in front of OpenProject. Currently deferred. +`.env` keys: `OSTICKET_DB_PASS`, `OSTICKET_DB_ROOT`, `OSTICKET_ADMIN_PASS`, `OSTICKET_INSTALL_SECRET` + +**First boot:** The install script runs automatically, connects to MariaDB, and seeds the database. Wait for `Database installation successful` in `docker logs osticket-app` before testing. The same `.env` values are used on kscloud1 so both instances share the same admin credentials (separate databases). + +**Cloudflare Tunnel:** Route `tasks.kitestacks.com → http://osticket:8080` in the tunnel config. + +**kscloud1 note:** Same compose, host port `:8080` (8090 is used since port 80 is taken by caddy). ### 5.9 Grafana + Prometheus + Node Exporter @@ -824,20 +877,41 @@ networks: external: true ``` -**SSO for Portainer (pending):** Authentik OAuth2 provider has been created (Client ID: `portainer`). Two manual steps remain: -1. Add `portainer.kitestacks.com` as a Cloudflare Tunnel public hostname → `https://portainer:` (No TLS Verify enabled) -2. In Portainer UI → Settings → Authentication → OAuth → Custom: - - Client ID: `portainer` - - Client Secret: `` - - Authorization URL: `https://auth.kitestacks.com/application/o/authorize/` - - Access Token URL: `https://auth.kitestacks.com/application/o/token/` - - Resource URL: `https://auth.kitestacks.com/application/o/userinfo/` - - Redirect URL: `https://portainer.kitestacks.com` - - Logout URL: `https://auth.kitestacks.com/application/o/portainer/end-session/` - - Scopes: `openid email profile`, User identifier: `email` -3. After SSO works, update the portal's Portainer card from `data-coming-soon="1"` to a real href on all 3 portal copies. +**SSO — CONFIGURED (2026-06-12):** Authentik OAuth2 provider (Client ID: `portainer`) is live. OAuth configured via the Portainer API on both monk and kscloud1. Portal card updated to a live link on all 3 copies. -Access is restricted to the `homelab-admin` Authentik group via a PolicyBinding. +- Cloudflare Tunnel: `portainer.kitestacks.com → https://portainer:9443` (No TLS Verify) — **add this in CF dashboard if not yet present** +- Access restricted to `homelab-admin` Authentik group via PolicyBinding + +**To re-configure OAuth via Portainer API** (if settings get reset): +```bash +JWT=$(curl -sk -X POST https://localhost:9443/api/auth \ + -H "Content-Type: application/json" \ + -d '{"Username":"admin","Password":""}' | python3 -c "import sys,json; print(json.load(sys.stdin)['jwt'])") + +curl -sk -X PUT https://localhost:9443/api/settings \ + -H "Authorization: Bearer $JWT" \ + -H "Content-Type: application/json" \ + -d '{ + "AuthenticationMethod": 3, + "OAuthSettings": { + "ClientID": "portainer", + "ClientSecret": "", + "AuthorizationURI": "https://auth.kitestacks.com/application/o/authorize/", + "AccessTokenURI": "https://auth.kitestacks.com/application/o/token/", + "ResourceURI": "https://auth.kitestacks.com/application/o/userinfo/", + "RedirectURI": "https://portainer.kitestacks.com", + "UserIdentifier": "email", + "Scopes": "openid email profile", + "OAuthAutoCreateUsers": true, + "SSO": true, + "LogoutURI": "https://auth.kitestacks.com/application/o/portainer/end-session/" + } + }' +``` + +**Password reset for monk Portainer** (if admin password is lost): use a Go container with bbolt to patch BoltDB directly — see DEBUG-DOCUMENTATION.md for the full procedure. + +**kscloud1 Portainer:** admin username `kenpat7177`, password in `.env` (same as OSticket admin). OAuth also configured via API. ### 5.12 Kite AI (LiteLLM + Open WebUI) @@ -983,10 +1057,13 @@ Log in to `https://auth.kitestacks.com` → Admin Interface. Create one OAuth2/O | Open WebUI | `open-webui` | `https://ai.kitestacks.com/oauth/oidc/callback` | | Forgejo | `forgejo` | `https://gitforge.kitestacks.com/user/oauth2/authentik/callback` | | Karakeep | `karakeep` | `https://links.kitestacks.com/api/auth/callback/custom` | -| Kavita | `kavita` | `https://kavita.kitestacks.com/api/Account/OIDCCallback` | -| OpenProject | `openproject` | `https://tasks.kitestacks.com/auth/oidc/callback` | +| Kavita | `kavita` | `https://kavita.kitestacks.com/signin-oidc` | | Portainer | `portainer` | `https://portainer.kitestacks.com` | +> **Note:** OpenProject was removed 2026-06-12 and replaced by OSticket. The OpenProject Authentik provider and application have been deleted from the shared DB. OSticket does not currently have an Authentik SSO provider (local auth only). + +> **Auth code TTL:** All OAuth2 providers have `access_code_validity = minutes=10` (bumped from 1 min on 2026-06-12) to prevent `invalid_grant` errors during monk reconnect. The default 60-second window was too short for monk's ~5-minute startup after going offline. + For each provider, create a matching Application (slug = Client ID, launch URL = `https://.kitestacks.com`). ### 6.2 Portainer — restrict to homelab-admin group @@ -1142,11 +1219,11 @@ The portal lives at `~/kitestacks-live/docker/kitestacks-portal/public/index.htm | Panel | Color | Cards | |-------|-------|-------| -| INFRASTRUCTURE | cyan | Portainer (coming-soon), Authentik, Cloudflare | +| INFRASTRUCTURE | cyan | Portainer (live), Authentik, Cloudflare | | MONITORING | magenta | Grafana, Prometheus (coming-soon), Node Exporter (coming-soon), Uptime Kuma | | AI & AUTOMATION | purple | Kite AI (coming-soon), LiteLLM (coming-soon), OpenRouter (coming-soon), FluxCD (coming-soon) | | KNOWLEDGE BASE | pink | Kavita, Karakeep | -| DEVELOPMENT | cyan | Forgejo, OpenProject | +| DEVELOPMENT | cyan | Forgejo, OSticket | | SYSTEM STATUS | — | live metrics from /api/metrics | | RECENT ACTIVITY | — | last 8 commits from Forgejo via /api/activity | | COMMUNITY | — | Discord invite link | @@ -1296,7 +1373,7 @@ kscloud1 (Hetzner) is planned to be replaced by an Oracle Cloud VPS. When the ne |---------------|------| | `authentik/.env` | `AUTHENTIK_SECRET_KEY`, `PG_PASS` | | `grafana/.env` | `GRAFANA_OAUTH_CLIENT_SECRET` | -| `openproject/.env` | `OPENPROJECT_OIDC_SECRET`, `OPENPROJECT_SECRET_KEY_BASE` | +| `osticket/.env` | `OSTICKET_DB_PASS`, `OSTICKET_DB_ROOT`, `OSTICKET_ADMIN_PASS`, `OSTICKET_INSTALL_SECRET` | | `kite-ai/.env` | `OPENROUTER_API_KEY`, `LITELLM_MASTER_KEY`, `WEBUI_SECRET_KEY`, `OPENWEBUI_OAUTH_CLIENT_SECRET` | | `karakeep/.env` | `KARAKEEP_VERSION`, `NEXTAUTH_SECRET`, `NEXTAUTH_URL`, `MEILI_MASTER_KEY`, `OAUTH_CLIENT_ID`, `OAUTH_CLIENT_SECRET`, `OAUTH_WELLKNOWN_URL`, `OAUTH_PROVIDER_NAME`, `OAUTH_ALLOW_DANGEROUS_EMAIL_ACCOUNT_LINKING` | | `cloudflared/docker-compose.yml` | `TUNNEL_TOKEN` (inline) |