Compare commits

...

32 Commits

Author SHA1 Message Date
41eacfec02 Typo fix. 2025-12-02 20:39:25 +00:00
0a0748b920 Disable byte range locking for smbfs. 2025-12-02 20:38:48 +00:00
d6e0e09e87 Update flake. 2025-11-28 13:00:17 +00:00
61c3020a5e Update flake. 2025-11-25 18:53:43 +00:00
972b973f58 Update flake. 2025-11-25 14:05:10 +00:00
8c5a7b78c6 Update flake. 2025-11-24 13:33:04 +00:00
675204816a Even more RAM for Plex. 2025-11-23 20:10:58 +00:00
3bb82dbc6b Initial config. 2025-11-23 08:55:38 +00:00
0f6233c3ec More RAM. 2025-11-23 07:54:04 +00:00
43fa56bf35 Bind on all addresses and rely on firewall for blocking public ssh.
Otherwise, sshd will try and fail to bind on the tailscale IP before
tailscale is up.
2025-11-23 07:24:09 +00:00
50c930eeaf Add flaresolverr, disable bazarr, tweak resources. 2025-11-22 19:27:37 +00:00
8dde15b8ef Add prowlarr, recyclarr, and jellyseerr. 2025-11-22 17:32:14 +00:00
6100d8dc69 Fix override. 2025-11-21 16:43:39 +00:00
a92f0fcb28 Tighten up security. 2025-11-21 16:39:45 +00:00
bd4604cdcc Auth docs. 2025-11-21 14:12:19 +00:00
31db372b43 Remove now unused authentik config. 2025-11-21 14:00:47 +00:00
360e776745 Set up ollama. 2025-11-17 22:33:44 +00:00
5a819f70bb Static port for claude code accessibility. 2025-11-17 19:05:17 +00:00
b2c055ffb2 MCP server for tiddlywiki. 2025-11-17 17:56:05 +00:00
6e0b34843b Allow claude-code to read&write. 2025-11-16 21:07:58 +00:00
e8485e3bb7 Update flake. 2025-11-12 15:10:11 +00:00
e8cd970960 Make it an exit node. 2025-11-05 16:50:05 +00:00
78b59cec4f Put PHP port back on 9000, where the rest of the stuff expects it. 2025-11-05 15:54:46 +00:00
e6d40a9f7e Set an actual password. 2025-11-04 20:26:50 +00:00
7733a1be46 yet another replication fix. 2025-11-04 19:57:52 +00:00
a5df98bc5a Update docs. 2025-11-04 19:08:27 +00:00
fb9b0dd2f5 Move NFS server to sparky. 2025-11-04 19:00:18 +00:00
0dc214069c Fix curl-induced failures. 2025-11-04 18:59:50 +00:00
a6c4be9530 Use clone source for btrfs send. 2025-11-04 17:51:34 +00:00
6e338e6d65 Stop replicating to c1. 2025-11-04 14:03:49 +00:00
41f16fa0b8 Make sparky a standby again. 2025-11-04 12:58:34 +00:00
1b05728817 Switch to Pocket ID. 2025-11-04 12:58:15 +00:00
33 changed files with 543 additions and 302 deletions

View File

@@ -8,42 +8,35 @@ NixOS cluster configuration using flakes. Homelab infrastructure with Nomad/Cons
├── common/
│ ├── global/ # Applied to all hosts (backup, sops, users, etc.)
│ ├── minimal-node.nix # Base (ssh, user, boot, impermanence)
│ ├── cluster-member.nix # Consul + storage clients (NFS/CIFS/GlusterFS)
│ ├── cluster-member.nix # Consul agent + storage mounts (NFS/CIFS)
│ ├── nomad-worker.nix # Nomad client (runs jobs) + Docker + NFS deps
│ ├── nomad-server.nix # Enables Consul + Nomad server mode
│ ├── cluster-tools.nix # Just CLI tools (nomad, wander, damon)
│ ├── workstation-node.nix # Dev tools (wget, deploy-rs, docker, nix-ld)
│ ├── desktop-node.nix # Hyprland + GUI environment
│ ├── nfs-services-server.nix # NFS server + btrfs replication (zippy)
│ └── nfs-services-standby.nix # NFS standby + receive replication (c1)
├── hosts/
│ ├── c1/, c2/, c3/ # Cattle nodes (quorum + workers)
│ ├── zippy/ # Primary storage + NFS server + worker (not quorum)
│ ├── chilly/ # Home Assistant VM + cluster member (Consul only)
│ ├── sparky/ # Desktop + cluster member (Consul only)
│ ├── fractal/ # (Proxmox, will become NixOS storage node)
│ └── sunny/ # (Standalone ethereum node, not in cluster)
│ ├── nfs-services-server.nix # NFS server + btrfs replication
│ └── nfs-services-standby.nix # NFS standby + receive replication
├── hosts/ # Host configs - check imports for roles
├── docs/
│ ├── CLUSTER_REVAMP.md # Master plan for architecture changes
│ ├── MIGRATION_TODO.md # Tracking checklist for migration
── NFS_FAILOVER.md # NFS failover procedures
── NFS_FAILOVER.md # NFS failover procedures
│ └── AUTH_SETUP.md # Authentication (Pocket ID + Traefik OIDC)
└── services/ # Nomad job specs (.hcl files)
```
## Current Architecture
### Storage Mounts
- `/data/services` - NFS from `data-services.service.consul` (zippy primary, c1 standby)
- `/data/media` - CIFS from fractal (existing, unchanged)
- `/data/shared` - CIFS from fractal (existing, unchanged)
- `/data/services` - NFS from `data-services.service.consul` (check nfs-services-server.nix for primary)
- `/data/media` - CIFS from fractal
- `/data/shared` - CIFS from fractal
### Hosts
- **c1, c2, c3**: Cattle nodes, run most workloads, Nomad/Consul quorum members
- **zippy**: Primary NFS server, runs workloads (affinity), NOT quorum, replicates to c1 every 5min
- **chilly**: Home Assistant VM, cluster member (Consul agent + CLI tools), no workloads
- **sparky**: Desktop/laptop, cluster member (Consul agent + CLI tools), no workloads
- **fractal**: Storage node (Proxmox/ZFS), will join quorum after GlusterFS removed
- **sunny**: Standalone ethereum staking node (not in cluster)
### Cluster Roles (check hosts/*/default.nix for each host's imports)
- **Quorum**: hosts importing `nomad-server.nix` (3 expected for consensus)
- **Workers**: hosts importing `nomad-worker.nix` (run Nomad jobs)
- **NFS server**: host importing `nfs-services-server.nix` (affinity for direct disk access like DBs)
- **Standby**: hosts importing `nfs-services-standby.nix` (receive replication)
## Config Architecture
@@ -58,19 +51,22 @@ NixOS cluster configuration using flakes. Homelab infrastructure with Nomad/Cons
- `workstation-node.nix` - Dev tools (deploy-rs, docker, nix-ld, emulation)
- `desktop-node.nix` - Extends workstation + Hyprland/GUI
**Host composition examples**:
- c1/c2/c3: `cluster-member + nomad-worker + nomad-server` (quorum + runs jobs)
- zippy: `cluster-member + nomad-worker` (runs jobs, not quorum)
- chilly/sparky: `cluster-member + cluster-tools` (Consul + CLI only)
**Composition patterns**:
- Quorum member: `cluster-member + nomad-worker + nomad-server`
- Worker only: `cluster-member + nomad-worker`
- CLI only: `cluster-member + cluster-tools` (Consul agent, no Nomad service)
- NFS primary: `cluster-member + nomad-worker + nfs-services-server`
- Standalone: `minimal-node` only (no cluster membership)
**Key insight**: Profiles (workstation/desktop) no longer imply cluster membership. Hosts explicitly declare roles via imports.
**Key insight**: Profiles (workstation/desktop) don't imply cluster roles. Check imports for actual roles.
## Key Patterns
**NFS Server/Standby**:
- Primary (zippy): imports `nfs-services-server.nix`, sets `standbys = ["c1"]`
- Standby (c1): imports `nfs-services-standby.nix`, sets `replicationKeys = [...]`
- Primary: imports `nfs-services-server.nix`, sets `standbys = [...]`
- Standby: imports `nfs-services-standby.nix`, sets `replicationKeys = [...]`
- Replication: btrfs send/receive every 5min, incremental with fallback to full
- Check host configs for current primary/standby assignments
**Backups**:
- Kopia client on all nodes → Kopia server on fractal
@@ -81,6 +77,12 @@ NixOS cluster configuration using flakes. Homelab infrastructure with Nomad/Cons
- SOPS for secrets, files in `secrets/`
- Keys managed per-host
**Authentication**:
- Pocket ID (OIDC provider) at `pocket-id.v.paler.net`
- Traefik uses `traefik-oidc-auth` plugin for SSO
- Services add `middlewares=oidc-auth@file` tag to protect
- See `docs/AUTH_SETUP.md` for details
## Migration Status
**Phase 3 & 4**: COMPLETE! GlusterFS removed, all services on NFS
@@ -92,7 +94,7 @@ See `docs/MIGRATION_TODO.md` for detailed checklist.
**Deploy a host**: `deploy -s '.#hostname'`
**Deploy all**: `deploy`
**Check replication**: `ssh zippy journalctl -u replicate-services-to-c1.service -f`
**Check replication**: Check NFS primary host, then `ssh <primary> journalctl -u replicate-services-to-*.service -f`
**NFS failover**: See `docs/NFS_FAILOVER.md`
**Nomad jobs**: `services/*.hcl` - service data stored at `/data/services/<service-name>`
@@ -106,8 +108,8 @@ See `docs/MIGRATION_TODO.md` for detailed checklist.
## Important Files
- `common/global/backup.nix` - Kopia backup configuration
- `hosts/zippy/default.nix` - NFS server config, replication targets
- `hosts/c1/default.nix` - NFS standby config, authorized replication keys
- `common/nfs-services-server.nix` - NFS server role (check hosts for which imports this)
- `common/nfs-services-standby.nix` - NFS standby role (check hosts for which imports this)
- `flake.nix` - Host definitions, nixpkgs inputs
---

View File

@@ -1,7 +1,7 @@
{ pkgs, ... }:
let
# this line prevents hanging on network split
automount_opts = "x-systemd.automount,noauto,x-systemd.idle-timeout=60,x-systemd.mount-timeout=5s";
automount_opts = "x-systemd.automount,noauto,x-systemd.idle-timeout=60,x-systemd.mount-timeout=5s,nobrl";
in
{
environment.systemPackages = [ pkgs.cifs-utils ];

View File

@@ -133,13 +133,15 @@ in
echo "Attempting incremental send from $(basename $PREV_LOCAL) to ${standby}"
# Try incremental send, if it fails (e.g., parent missing on receiver), fall back to full
if btrfs send -p "$PREV_LOCAL" "$SNAPSHOT_PATH" | \
# Use -c to help with broken Received UUID chains
if btrfs send -p "$PREV_LOCAL" -c "$PREV_LOCAL" "$SNAPSHOT_PATH" | \
ssh -i "$SSH_KEY" -o StrictHostKeyChecking=accept-new root@${standby} \
"btrfs receive /persist/services-standby"; then
echo "Incremental send completed successfully"
REPLICATION_SUCCESS=1
else
echo "Incremental send failed (likely missing parent on receiver), falling back to full send"
# Plain full send without clone source (receiver may have no snapshots)
btrfs send "$SNAPSHOT_PATH" | \
ssh -i "$SSH_KEY" -o StrictHostKeyChecking=accept-new root@${standby} \
"btrfs receive /persist/services-standby"
@@ -163,7 +165,7 @@ in
SNAPSHOT_COUNT=$(ls -1d /persist/services@* 2>/dev/null | wc -l)
# Push metrics to Prometheus pushgateway
cat <<METRICS | curl --data-binary @- http://pushgateway.service.consul:9091/metrics/job/nfs_replication/instance/${standby}
cat <<METRICS | curl -s --data-binary @- http://pushgateway.service.consul:9091/metrics/job/nfs_replication/instance/${standby} || true
# TYPE nfs_replication_last_success_timestamp gauge
nfs_replication_last_success_timestamp $END_TIME
# TYPE nfs_replication_duration_seconds gauge

View File

@@ -54,7 +54,7 @@ in
SNAPSHOT_COUNT=$(ls -1d /persist/services-standby/services@* 2>/dev/null | wc -l)
# Push metrics to Prometheus pushgateway
cat <<METRICS | curl --data-binary @- http://pushgateway.service.consul:9091/metrics/job/nfs_standby_cleanup/instance/$(hostname)
cat <<METRICS | curl -s --data-binary @- http://pushgateway.service.consul:9091/metrics/job/nfs_standby_cleanup/instance/$(hostname) || true
# TYPE nfs_standby_snapshot_count gauge
nfs_standby_snapshot_count $SNAPSHOT_COUNT
# TYPE nfs_standby_cleanup_last_run_timestamp gauge

55
docs/AUTH_SETUP.md Normal file
View File

@@ -0,0 +1,55 @@
# Authentication Setup
SSO for homelab services using OIDC.
## Architecture
**Pocket ID** (`pocket-id.v.paler.net`) - Lightweight OIDC provider, data in `/data/services/pocket-id`
**Traefik** - Uses `traefik-oidc-auth` plugin (v0.16.0) to protect services
- Plugin downloaded from GitHub at startup, cached in `/data/services/traefik/plugins-storage`
- Middleware config in `/data/services/traefik/rules/middlewares.yml`
- Protected services add tag: `traefik.http.routers.<name>.middlewares=oidc-auth@file`
## Flow
1. User hits protected service → Traefik intercepts
2. Redirects to Pocket ID for login
3. Pocket ID returns OIDC token
4. Traefik validates and forwards with `X-Oidc-Username` header
## Protected Services
Use `oidc-auth@file` middleware (grep codebase for full list):
- Wikis (TiddlyWiki instances)
- Media stack (Radarr, Sonarr, Plex, etc.)
- Infrastructure (Traefik dashboard, Loki, Jupyter, Unifi)
## Key Files
- `services/pocket-id.hcl` - OIDC provider
- `services/traefik.hcl` - Plugin declaration
- `/data/services/traefik/rules/middlewares.yml` - Middleware definitions (oidc-auth, simple-auth fallback)
## Cold Start Notes
- Traefik needs internet to download plugin on first start
- Pocket ID needs `/data/services` NFS mounted
- Pocket ID down = all protected services inaccessible
## Troubleshooting
**Infinite redirects**: Check `TRUST_PROXY=true` on Pocket ID
**Plugin not loading**: Clear cache in `/data/services/traefik/plugins-storage/`, restart Traefik
**401 after login**: Verify client ID/secret in middlewares.yml matches Pocket ID client config
## Migration History
- Previous: Authentik with forwardAuth (removed Nov 2024)
- Current: Pocket ID + traefik-oidc-auth (simpler, lighter)
---
*Manage users/clients via Pocket ID UI. Basic auth fallback available via `simple-auth` middleware.*

View File

@@ -1,5 +1,6 @@
* remote docker images used, can't come up if internet is down
* local docker images pulled from gitea, can't come up if gitea isn't up (yet)
* traefik-oidc-auth plugin downloaded from GitHub at startup (cached in /data/services/traefik/plugins-storage)
* renovate system of some kind
* vector (or other log ingestion) everywhere, consider moving it off docker if possible
* monitor backup-persist success/fail

126
flake.lock generated
View File

@@ -62,11 +62,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1761938838,
"narHash": "sha256-0gvCxO8/jpfN1vFeAd0gM07wIUuRkcXgDxeQk0o4Duw=",
"lastModified": 1763766218,
"narHash": "sha256-CM694zS6IeO/tFvUW7zhlb8t67+6L9QfvCDgQy0nVyQ=",
"owner": "nix-community",
"repo": "browser-previews",
"rev": "9f9fbff0aa9d628e737ea36286d343518153effc",
"rev": "04f8550aa62ccda42a6eb839a4ccf6cdcf3d953d",
"type": "github"
},
"original": {
@@ -84,11 +84,11 @@
"utils": "utils"
},
"locked": {
"lastModified": 1756719547,
"narHash": "sha256-N9gBKUmjwRKPxAafXEk1EGadfk2qDZPBQp4vXWPHINQ=",
"lastModified": 1762286984,
"narHash": "sha256-9I2H9x5We6Pl+DBYHjR1s3UT8wgwcpAH03kn9CqtdQc=",
"owner": "serokell",
"repo": "deploy-rs",
"rev": "125ae9e3ecf62fb2c0fd4f2d894eb971f1ecaed2",
"rev": "9c870f63e28ec1e83305f7f6cb73c941e699f74f",
"type": "github"
},
"original": {
@@ -105,11 +105,11 @@
]
},
"locked": {
"lastModified": 1741473158,
"narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=",
"lastModified": 1762521437,
"narHash": "sha256-RXN+lcx4DEn3ZS+LqEJSUu/HH+dwGvy0syN7hTo/Chg=",
"owner": "numtide",
"repo": "devshell",
"rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0",
"rev": "07bacc9531f5f4df6657c0a02a806443685f384a",
"type": "github"
},
"original": {
@@ -125,11 +125,11 @@
]
},
"locked": {
"lastModified": 1761899396,
"narHash": "sha256-XOpKBp6HLzzMCbzW50TEuXN35zN5WGQREC7n34DcNMM=",
"lastModified": 1764110879,
"narHash": "sha256-xanUzIb0tf3kJ+PoOFmXEXV1jM3PjkDT/TQ5DYeNYRc=",
"owner": "nix-community",
"repo": "disko",
"rev": "6f4cf5abbe318e4cd1e879506f6eeafd83f7b998",
"rev": "aecba248f9a7d68c5d1ed15de2d1c8a4c994a3c5",
"type": "github"
},
"original": {
@@ -141,7 +141,6 @@
"ethereum-nix": {
"inputs": {
"devshell": "devshell",
"flake-compat": "flake-compat_2",
"flake-parts": "flake-parts",
"flake-utils": "flake-utils_2",
"foundry-nix": "foundry-nix",
@@ -153,11 +152,11 @@
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1762083251,
"narHash": "sha256-ZK8w1vsvWHKHVdf+p2TRuUTjtH6uM+zEZmLa2bv+h8A=",
"lastModified": 1764174664,
"narHash": "sha256-CYAjcXbI6RzQ3cWKiW/u3ZiJCeVX9PQd2J0+V8zX7c8=",
"owner": "nix-community",
"repo": "ethereum.nix",
"rev": "59693a43a8754ead13fed0d0705fb182df5ac508",
"rev": "e3a1e2d86a6bc1ef25bdb395d9c770b471d53e7f",
"type": "github"
},
"original": {
@@ -183,21 +182,6 @@
}
},
"flake-compat_2": {
"locked": {
"lastModified": 1746162366,
"narHash": "sha256-5SSSZ/oQkwfcAz/o/6TlejlVGqeK08wyREBQ5qFFPhM=",
"owner": "nix-community",
"repo": "flake-compat",
"rev": "0f158086a2ecdbb138cd0429410e44994f1b7e4b",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_3": {
"flake": false,
"locked": {
"lastModified": 1747046372,
@@ -218,11 +202,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1762040540,
"narHash": "sha256-z5PlZ47j50VNF3R+IMS9LmzI5fYRGY/Z5O5tol1c9I4=",
"lastModified": 1762980239,
"narHash": "sha256-8oNVE8TrD19ulHinjaqONf9QWCKK+w4url56cdStMpM=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "0010412d62a25d959151790968765a70c436598b",
"rev": "52a2caecc898d0b46b2b905f058ccc5081f842da",
"type": "github"
},
"original": {
@@ -239,11 +223,11 @@
]
},
"locked": {
"lastModified": 1760948891,
"narHash": "sha256-TmWcdiUUaWk8J4lpjzu4gCGxWY6/Ok7mOK4fIFfBuU4=",
"lastModified": 1763759067,
"narHash": "sha256-LlLt2Jo/gMNYAwOgdRQBrsRoOz7BPRkzvNaI/fzXi2Q=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "864599284fc7c0ba6357ed89ed5e2cd5040f0c04",
"rev": "2cccadc7357c0ba201788ae99c4dfa90728ef5e0",
"type": "github"
},
"original": {
@@ -324,11 +308,11 @@
]
},
"locked": {
"lastModified": 1759569036,
"narHash": "sha256-FuxbXLDArxD1NeRR8zNnsb8Xww5/+qdMwzN1m8Kow/M=",
"lastModified": 1762247499,
"narHash": "sha256-dPBqjoBcP3yczY7EUQP6BXf58wauRl+lZVZ/fabgq3E=",
"owner": "shazow",
"repo": "foundry.nix",
"rev": "47ba6d3b02bf3faaa857d3572df82ff186d5279a",
"rev": "ae6473c7190edea0e505f433293688014b556b29",
"type": "github"
},
"original": {
@@ -368,11 +352,11 @@
]
},
"locked": {
"lastModified": 1758463745,
"narHash": "sha256-uhzsV0Q0I9j2y/rfweWeGif5AWe0MGrgZ/3TjpDYdGA=",
"lastModified": 1763992789,
"narHash": "sha256-WHkdBlw6oyxXIra/vQPYLtqY+3G8dUVZM8bEXk0t8x4=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "3b955f5f0a942f9f60cdc9cacb7844335d0f21c3",
"rev": "44831a7eaba4360fb81f2acc5ea6de5fde90aaa3",
"type": "github"
},
"original": {
@@ -732,11 +716,11 @@
]
},
"locked": {
"lastModified": 1762055842,
"narHash": "sha256-Pu1v3mlFhRzZiSxVHb2/i/f5yeYyRNqr0RvEUJ4UgHo=",
"lastModified": 1763870992,
"narHash": "sha256-NPyc76Wxmv/vAsXJ8F+/8fXECHYcv2YGSqdiSHp/F/A=",
"owner": "nix-community",
"repo": "nix-index-database",
"rev": "359ff6333a7b0b60819d4c20ed05a3a1f726771f",
"rev": "d7423982c7a26586aa237d130b14c8b302c7a367",
"type": "github"
},
"original": {
@@ -747,11 +731,11 @@
},
"nixos-hardware": {
"locked": {
"lastModified": 1762179181,
"narHash": "sha256-T4+TNfXlF/gHbcNCC2HY7sMGBKgqNzyYeMBWmcbH7/o=",
"lastModified": 1764328224,
"narHash": "sha256-hFyF1XQd+XrRx7WZCrGJp544dykexD8Q5SrJJZpEQYg=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "256770618502d2eda892af3ae91da5e386ce9586",
"rev": "d62603a997438e19182af69d3ce7be07565ecad4",
"type": "github"
},
"original": {
@@ -763,11 +747,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1761999846,
"narHash": "sha256-IYlYnp4O4dzEpL77BD/lj5NnJy2J8qbHkNSFiPBCbqo=",
"lastModified": 1763948260,
"narHash": "sha256-dY9qLD0H0zOUgU3vWacPY6Qc421BeQAfm8kBuBtPVE0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3de8f8d73e35724bf9abef41f1bdbedda1e14a31",
"rev": "1c8ba8d3f7634acac4a2094eef7c32ad9106532c",
"type": "github"
},
"original": {
@@ -809,11 +793,11 @@
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1761880412,
"narHash": "sha256-QoJjGd4NstnyOG4mm4KXF+weBzA2AH/7gn1Pmpfcb0A=",
"lastModified": 1763191728,
"narHash": "sha256-esRhOS0APE6k40Hs/jjReXg+rx+J5LkWw7cuWFKlwYA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a7fc11be66bdfb5cdde611ee5ce381c183da8386",
"rev": "1d4c88323ac36805d09657d13a5273aea1b34f0c",
"type": "github"
},
"original": {
@@ -825,11 +809,11 @@
},
"nixpkgs-unstable_2": {
"locked": {
"lastModified": 1762111121,
"narHash": "sha256-4vhDuZ7OZaZmKKrnDpxLZZpGIJvAeMtK6FKLJYUtAdw=",
"lastModified": 1764242076,
"narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b3d51a0365f6695e7dd5cdf3e180604530ed33b4",
"rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4",
"type": "github"
},
"original": {
@@ -865,11 +849,11 @@
"systems": "systems_5"
},
"locked": {
"lastModified": 1762207388,
"narHash": "sha256-+FvGHB57ZuJIYbI35qcyGsxhvKdeKlX7AomVD6M5sIg=",
"lastModified": 1764238240,
"narHash": "sha256-7Znm3koZ4sF+O41Y7rJqf651BPEbjIUYF3r9H23GRGw=",
"owner": "nix-community",
"repo": "nixvim",
"rev": "de1760ddfd3e67aa5d2251d7df9e6bad30c36692",
"rev": "f1e07ba53abd0fb4872a365cba45562144ad6130",
"type": "github"
},
"original": {
@@ -913,11 +897,11 @@
]
},
"locked": {
"lastModified": 1760558991,
"narHash": "sha256-E8MMVwy7QNURBtCLiCjFXfv7uZUEg6QVSZLu4q9YGpk=",
"lastModified": 1762999930,
"narHash": "sha256-uKyxLwiN6sD6EmRSno66y1a8oqISr1XiWxbWHoMJT7I=",
"owner": "henrysipp",
"repo": "omarchy-nix",
"rev": "fba993c589920fbe68d9f7918e52903c476adad2",
"rev": "308e0f85a0deb820c01cfbe1b4faee1daab4da12",
"type": "github"
},
"original": {
@@ -928,7 +912,7 @@
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat_3",
"flake-compat": "flake-compat_2",
"gitignore": "gitignore",
"nixpkgs": [
"omarchy-nix",
@@ -974,11 +958,11 @@
]
},
"locked": {
"lastModified": 1760998189,
"narHash": "sha256-ee2e1/AeGL5X8oy/HXsZQvZnae6XfEVdstGopKucYLY=",
"lastModified": 1764021963,
"narHash": "sha256-1m84V2ROwNEbqeS9t37/mkry23GBhfMt8qb6aHHmjuc=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "5a7d18b5c55642df5c432aadb757140edfeb70b3",
"rev": "c482a1c1bbe030be6688ed7dc84f7213f304f1ec",
"type": "github"
},
"original": {
@@ -1085,11 +1069,11 @@
]
},
"locked": {
"lastModified": 1761311587,
"narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=",
"lastModified": 1762938485,
"narHash": "sha256-AlEObg0syDl+Spi4LsZIBrjw+snSVU4T8MOeuZJUJjM=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc",
"rev": "5b4ee75aeefd1e2d5a1cc43cf6ba65eba75e83e4",
"type": "github"
},
"original": {

View File

@@ -1,4 +1,4 @@
{ pkgs, inputs, ... }:
{ pkgs, lib, inputs, ... }:
{
imports = [
../../common/global
@@ -12,4 +12,27 @@
networking.hostName = "alo-cloud-1";
services.tailscaleAutoconnect.authkey = "tskey-auth-kbdARC7CNTRL-pNQddmWV9q5C2sRV3WGep5ehjJ1qvcfD";
services.tailscale = {
enable = true;
useRoutingFeatures = lib.mkForce "server"; # enables IPv4/IPv6 forwarding + loose rp_filter
extraUpFlags = [ "--advertise-exit-node" ];
};
networking.nat = {
enable = true;
externalInterface = "enp1s0";
internalInterfaces = [ "tailscale0" ];
};
networking.firewall = {
enable = lib.mkForce true;
allowedTCPPorts = [ 80 443 ]; # Public web traffic only
allowedUDPPorts = [ 41641 ]; # Tailscale
trustedInterfaces = [ "tailscale0" ]; # Full access via VPN
};
services.openssh = {
settings.PasswordAuthentication = false; # Keys only
};
}

View File

@@ -23,8 +23,8 @@
networking.hostName = "c1";
services.tailscaleAutoconnect.authkey = "tskey-auth-k2nQ771YHM11CNTRL-YVpoumL2mgR6nLPG51vNhRpEKMDN7gLAi";
# NFS standby configuration: accept replication from zippy
nfsServicesStandby.replicationKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHyTKsMCbwCIlMcC/aopgz5Yfx/Q9QdlWC9jzMLgYFAV root@zippy-replication"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO5s73FSUiysHijWRGYCJY8lCtZkX1DGKAqp2671REDq root@sparky-replication"
];
}

View File

@@ -5,6 +5,11 @@
../../common/global
../../common/cluster-member.nix
../../common/nomad-worker.nix
../../common/nfs-services-server.nix
# To move NFS server role to another host:
# 1. Follow procedure in docs/NFS_FAILOVER.md
# 2. Replace above line with: ../../common/nfs-services-standby.nix
# 3. Add nfsServicesStandby.replicationKeys with the new server's public key
./hardware.nix
];
@@ -16,4 +21,6 @@
networking.hostName = "sparky";
services.tailscaleAutoconnect.authkey = "tskey-auth-k6VC79UrzN11CNTRL-rvPmd4viyrQ261ifCrfTrQve7c2FesxrG";
nfsServicesServer.standbys = [ "c1" ];
}

View File

@@ -5,12 +5,6 @@
../../common/global
../../common/cluster-member.nix # Consul + storage clients
../../common/nomad-worker.nix # Nomad client (runs jobs)
# NOTE: zippy is NOT a server - no nomad-server.nix import
../../common/nfs-services-server.nix # NFS server for /data/services
# To move NFS server role to another host:
# 1. Follow procedure in docs/NFS_FAILOVER.md
# 2. Replace above line with: ../../common/nfs-services-standby.nix
# 3. Add nfsServicesStandby.replicationKeys with the new server's public key
./hardware.nix
];
@@ -22,8 +16,4 @@
networking.hostName = "zippy";
services.tailscaleAutoconnect.authkey = "tskey-auth-ktKyQ59f2p11CNTRL-ut8E71dLWPXsVtb92hevNX9RTjmk4owBf";
nfsServicesServer.standbys = [
"c1"
];
}

View File

@@ -1,4 +1,4 @@
kopia: ENC[AES256_GCM,data:FrvSs1th,iv:GnoJ9ec26Wx8rH/G5yuN2CwmBp2ITD2C264cYQ2t6io=,tag:zn67Rikn7PXS2jOTf+KQ3Q==,type:str]
kopia: ENC[AES256_GCM,data:/6jqArNgeBoGnEdJ1eshrsG8RJs=,iv:2nNdrKczus70QDdvO/MC2wJubGnAf3M8PtzSe1aoBF4=,tag:aOoktsqhQLXr0YkjYZq4OQ==,type:str]
sops:
age:
- recipient: age1df9ukkmg9yn9cjeheq9m6wspa420su8qarmq570rdvf2de3rl38saqauwn
@@ -19,7 +19,7 @@ sops:
R21jYU96SGVHOUxmZjlldS96K2VqbWcKC28wLdT/zx6yHluCLqB/cFRmc0Alq6AH
DqmAaxRhOg/SI5ljCX1gE5BB9rNIJ1Gq8+li7wCpsdfLMr5Yy/HAsw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-10-29T15:41:56Z"
mac: ENC[AES256_GCM,data:AM5Srw03yvk6gKVf3KF/N5ilYKgF0KKObA98N4KbLGNsxsAagvmQtvzWurgDmOvihbKyBlNyOCjBVCHrKwfzzdCHj0+9lcuCtZ5CC/zOy9a7LMFJvpElj0pQUxpODU+6HcGtdrQQpsfEkzrMBzw1wsJhJ9vC1rp0YdUqK7+wS5g=,iv:M0mTIlXZPdyiTUf/8vYJvmDTMB9bOwH2BKTexPpS/2Q=,tag:CFylsJEP9mePMcRoxrxgwA==,type:str]
lastmodified: "2025-11-04T20:25:17Z"
mac: ENC[AES256_GCM,data:llS+R5Pj51ZUkU8FkJx2KqqE4D42Uno3Btn31FadIl4kFamnrL6uJjbiNEJpFFO+SchXD3l7VCatbBhMSoxsPYd+rdDRT2klq+iIcZU/k413GC87xdmHIwWE+L2pujv36iBjtM+HJTSvXI0xOxjUmzH4FPdEa1r3Z5yGNnCI+Q4=,iv:ld6pSEzvKTSZtBb+QjHyqqj2VT05YegxBrUR2yxhjKY=,tag:7/vYBh8lDOcVXJL3esTIZQ==,type:str]
unencrypted_suffix: _unencrypted
version: 3.11.0

View File

@@ -27,7 +27,7 @@ job "adminer" {
tags = [
"traefik.enable=true",
"traefik.http.routers.adminer.entryPoints=websecure",
"traefik.http.routers.adminer.middlewares=authentik@file",
"traefik.http.routers.adminer.middlewares=oidc-auth@file",
]
}
}

View File

@@ -1,118 +0,0 @@
job "authentik" {
datacenters = ["alo"]
group "auth" {
network {
port "http" {
# traefik forwardAuth hardcodes this port
static = 9000
}
port "https" {
to = 9443
}
port "metrics" {
to = 9300
}
}
task "server" {
driver = "docker"
config {
image = "ghcr.io/goauthentik/server:${var.authentik_version}"
ports = [
"http",
"https",
"metrics"
]
command = "server"
}
env {
AUTHENTIK_REDIS__HOST = "redis.service.consul"
AUTHENTIK_POSTGRESQL__HOST = "postgres.service.consul"
AUTHENTIK_POSTGRESQL__NAME = "${var.pg_db}"
AUTHENTIK_POSTGRESQL__USER = "${var.pg_user}"
AUTHENTIK_POSTGRESQL__PASSWORD = "${var.pg_password}"
AUTHENTIK_SECRET_KEY = "${var.secret_key}"
AUTHENTIK_EMAIL__HOST = "192.168.1.1"
AUTHENTIK_EMAIL__FROM = "authentik@paler.net"
}
resources {
cpu = 2000
memory = 1024
}
service {
name = "authentik"
port = "http"
tags = [
"traefik.enable=true",
# Main UI
"traefik.http.routers.authentik.entryPoints=websecure",
"traefik.http.routers.authentik.rule=Host(`authentik.v.paler.net`) || Host(`authentik.alo.land`)",
# Embedded outpost for forward auth
"traefik.http.routers.authentik-palernet.entryPoints=websecure",
"traefik.http.routers.authentik-palernet.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.v.paler.net`) && PathPrefix(`/outpost.goauthentik.io/`)",
"traefik.http.routers.authentik-aloland.entryPoints=websecure",
"traefik.http.routers.authentik-aloland.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.alo.land`) && PathPrefix(`/outpost.goauthentik.io/`)",
]
}
service {
name = "authentik-metrics"
port = "metrics"
tags = [ "metrics" ]
}
}
task "worker" {
driver = "docker"
config {
image = "ghcr.io/goauthentik/server:${var.authentik_version}"
command = "worker"
}
env {
AUTHENTIK_REDIS__HOST = "redis.service.consul"
AUTHENTIK_POSTGRESQL__HOST = "postgres.service.consul"
AUTHENTIK_POSTGRESQL__NAME = "${var.pg_db}"
AUTHENTIK_POSTGRESQL__USER = "${var.pg_user}"
AUTHENTIK_POSTGRESQL__PASSWORD = "${var.pg_password}"
AUTHENTIK_SECRET_KEY = "${var.secret_key}"
AUTHENTIK_EMAIL__HOST = "192.168.1.1"
AUTHENTIK_EMAIL__FROM = "authentik@paler.net"
}
resources {
memory = 600
}
}
}
}
variable "pg_user" {
type = string
default = "authentik"
}
variable "pg_password" {
type = string
default = "aQueiquuo6aiyah5eoch"
}
variable "pg_db" {
type = string
default = "authentik"
}
variable "secret_key" {
type = string
default = "uUzCYhGV93Z8wKLAScuGFqBskxyzSfG4cz6bnXq6McM67Ho7p9"
}
variable "authentik_version" {
type = string
default = "2025.6"
}

View File

@@ -37,7 +37,7 @@ job "beancount" {
tags = [
"traefik.enable=true",
"traefik.http.routers.finances.entryPoints=websecure",
"traefik.http.routers.finances.middlewares=authentik@file",
"traefik.http.routers.finances.middlewares=oidc-auth@file",
]
}

View File

@@ -49,7 +49,7 @@ job "evcc" {
tags = [
"traefik.enable=true",
"traefik.http.routers.evcc.entryPoints=websecure",
"traefik.http.routers.evcc.middlewares=authentik@file",
"traefik.http.routers.evcc.middlewares=oidc-auth@file",
]
}
}

View File

@@ -25,19 +25,22 @@ job "grafana" {
GF_SERVER_ROOT_URL = "https://grafana.v.paler.net"
GF_AUTH_BASIC_ENABLED = "false"
GF_AUTH_GENERIC_OAUTH_ENABLED = "true"
GF_AUTH_GENERIC_OAUTH_NAME = "authentik"
GF_AUTH_GENERIC_OAUTH_CLIENT_ID = "E78NG1AZeW6FaAox0mUhaTSrHeqFgNkWG12My2zx"
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET = "N7u2RfFZ5KVLdEkhlpUTzymGxeK5rLo9SYZLSGGBXJDr46p5g5uv1qZ4Jm2d1rP4aJX4PSzauZlxHhkG2byiBFMbdo6K742KXcEimZsOBFiNKeWOHxofYerBnPuoECQW"
GF_AUTH_GENERIC_OAUTH_SCOPES = "openid profile email offline_access"
GF_AUTH_GENERIC_OAUTH_AUTH_URL = "https://authentik.v.paler.net/application/o/authorize/"
GF_AUTH_GENERIC_OAUTH_TOKEN_URL = "https://authentik.v.paler.net/application/o/token/"
GF_AUTH_GENERIC_OAUTH_API_URL = "https://authentik.v.paler.net/application/o/userinfo/"
GF_AUTH_SIGNOUT_REDIRECT_URL = "https://authentik.v.paler.net/application/o/grafana/end-session/"
GF_AUTH_GENERIC_OAUTH_NAME = "Pocket ID"
GF_AUTH_GENERIC_OAUTH_CLIENT_ID = "99e44cf2-ecc6-4e82-8882-129c017f8a4a"
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET = "NjJ9Uro4MK7siqLGSmkiQmjFuESulqQN"
GF_AUTH_GENERIC_OAUTH_SCOPES = "openid profile email groups"
GF_AUTH_GENERIC_OAUTH_AUTH_URL = "https://pocket-id.v.paler.net/authorize"
GF_AUTH_GENERIC_OAUTH_TOKEN_URL = "https://pocket-id.v.paler.net/api/oidc/token"
GF_AUTH_GENERIC_OAUTH_API_URL = "https://pocket-id.v.paler.net/api/oidc/userinfo"
GF_AUTH_SIGNOUT_REDIRECT_URL = "https://pocket-id.v.paler.net/logout"
# Optionally enable auto-login (bypasses Grafana login screen)
GF_AUTH_OAUTH_AUTO_LOGIN = "true"
# Optionally map user groups to Grafana roles
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH = "contains(groups[*], 'Grafana Admins') && 'Admin' || contains(groups[*], 'Grafana Editors') && 'Editor' || 'Viewer'"
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH = "contains(groups[*], 'admins') && 'Admin' || contains(groups[*], 'residents') && 'Editor' || 'Viewer'"
GF_AUTH_GENERIC_OAUTH_USE_REFRESH_TOKEN = "true"
GF_AUTH_GENERIC_OAUTH_EMAIL_ATTRIBUTE_PATH = "email"
GF_AUTH_GENERIC_OAUTH_LOGIN_ATTRIBUTE_PATH = "preferred_username"
GF_AUTH_GENERIC_OAUTH_NAME_ATTRIBUTE_PATH = "name"
#GF_LOG_LEVEL = "debug"
}

50
services/homepage.hcl Normal file
View File

@@ -0,0 +1,50 @@
job "homepage" {
datacenters = ["alo"]
group "app" {
network {
port "http" { to = 3000 }
}
task "homepage" {
driver = "docker"
config {
image = "ghcr.io/gethomepage/homepage:latest"
ports = [ "http" ]
volumes = [
"/data/services/homepage:/app/config",
]
}
env {
PUID = 1000
PGID = 1000
HOMEPAGE_ALLOWED_HOSTS = "homepage.v.paler.net"
}
resources {
cpu = 200
memory = 256
}
service {
name = "homepage"
port = "http"
tags = [
"traefik.enable=true",
"traefik.http.routers.homepage.entryPoints=websecure",
"traefik.http.routers.homepage.middlewares=oidc-auth@file",
]
check {
type = "http"
path = "/"
interval = "10s"
timeout = "5s"
}
}
}
}
}

View File

@@ -38,7 +38,7 @@ job "jupyter" {
tags = [
"traefik.enable=true",
"traefik.http.routers.jupyter.entryPoints=websecure",
"traefik.http.routers.jupyter.middlewares=authentik@file",
"traefik.http.routers.jupyter.middlewares=oidc-auth@file",
]
}
}

View File

@@ -126,7 +126,7 @@ EOH
tags = [
"traefik.enable=true",
"traefik.http.routers.loki.entryPoints=websecure",
"traefik.http.routers.loki.middlewares=authentik@file",
"traefik.http.routers.loki.middlewares=oidc-auth@file",
"metrics",
]
}

View File

@@ -65,7 +65,7 @@ job "maps" {
to = 80
}
port "php" {
static = 9001
static = 9000
}
}

View File

@@ -7,9 +7,12 @@ job "media" {
group "servers" {
network {
port "radarr" { to = 7878 }
port "sonarr" { to = 8989 }
port "radarr" { static = 7878 }
port "sonarr" { static = 8989 }
port "bazarr" { to = 6767 }
port "prowlarr" { static = 9696 }
port "jellyseerr" { static = 5055 }
port "flaresolverr" { static = 8191 }
port "pms" { static = 32400 }
port "qbt_ui" { static = 8080 }
port "qbt_torrent" { static = 51413 }
@@ -34,7 +37,7 @@ job "media" {
}
resources {
cpu = 200
cpu = 1000
}
service {
@@ -44,7 +47,7 @@ job "media" {
tags = [
"traefik.enable=true",
"traefik.http.routers.radarr.entryPoints=websecure",
"traefik.http.routers.radarr.middlewares=authentik@file",
"traefik.http.routers.radarr.middlewares=oidc-auth@file",
]
}
}
@@ -68,7 +71,8 @@ job "media" {
}
resources {
cpu = 200
cpu = 1000
memory = 500
}
service {
@@ -78,20 +82,54 @@ job "media" {
tags = [
"traefik.enable=true",
"traefik.http.routers.sonarr.entryPoints=websecure",
"traefik.http.routers.sonarr.middlewares=authentik@file",
"traefik.http.routers.sonarr.middlewares=oidc-auth@file",
]
}
}
task "bazarr" {
# task "bazarr" {
# driver = "docker"
#
# config {
# image = "ghcr.io/hotio/bazarr:latest"
# ports = [ "bazarr" ]
# volumes = [
# "/data/services/media/bazarr:/config",
# "/data/media/media:/data/media",
# ]
# }
#
# env {
# PUID = 1000
# PGID = 1000
# TZ = "Europe/Lisbon"
# }
#
# resources {
# cpu = 200
# memory = 500
# }
#
# service {
# name = "bazarr"
# port = "bazarr"
#
# tags = [
# "traefik.enable=true",
# "traefik.http.routers.bazarr.entryPoints=websecure",
# "traefik.http.routers.bazarr.middlewares=oidc-auth@file",
# ]
# }
# }
task "prowlarr" {
driver = "docker"
config {
image = "ghcr.io/hotio/bazarr:latest"
ports = [ "bazarr" ]
image = "ghcr.io/hotio/prowlarr:latest"
ports = [ "prowlarr" ]
volumes = [
"/data/services/media/bazarr:/config",
"/data/media/media:/data/media",
"/data/services/media/prowlarr:/config",
]
}
@@ -106,17 +144,93 @@ job "media" {
}
service {
name = "bazarr"
port = "bazarr"
name = "prowlarr"
port = "prowlarr"
tags = [
"traefik.enable=true",
"traefik.http.routers.bazarr.entryPoints=websecure",
"traefik.http.routers.bazarr.middlewares=authentik@file",
"traefik.http.routers.prowlarr.entryPoints=websecure",
"traefik.http.routers.prowlarr.middlewares=oidc-auth@file",
]
}
}
task "jellyseerr" {
driver = "docker"
config {
image = "fallenbagel/jellyseerr:latest"
ports = [ "jellyseerr" ]
volumes = [
"/data/services/media/jellyseerr:/app/config",
]
}
env {
TZ = "Europe/Lisbon"
}
resources {
cpu = 200
}
service {
name = "jellyseerr"
port = "jellyseerr"
tags = [
"traefik.enable=true",
"traefik.http.routers.jellyseerr.entryPoints=websecure",
"traefik.http.routers.jellyseerr.middlewares=oidc-auth@file",
]
}
}
task "flaresolverr" {
driver = "docker"
config {
image = "ghcr.io/flaresolverr/flaresolverr:latest"
ports = [ "flaresolverr" ]
}
env {
LOG_LEVEL = "info"
TZ = "Europe/Lisbon"
}
resources {
cpu = 500
memory = 1024
}
service {
name = "flaresolverr"
port = "flaresolverr"
}
}
task "recyclarr" {
driver = "docker"
config {
image = "ghcr.io/recyclarr/recyclarr:latest"
volumes = [
"/data/services/media/recyclarr:/config",
]
}
env {
TZ = "Europe/Lisbon"
CRON_SCHEDULE = "0 0 * * *" # Daily at midnight
}
resources {
cpu = 100
memory = 256
}
}
task "plex" {
driver = "docker"
@@ -138,7 +252,7 @@ job "media" {
resources {
cpu = 2000
memory = 1000
memory = 2000
}
service {
@@ -148,7 +262,7 @@ job "media" {
tags = [
"traefik.enable=true",
"traefik.http.routers.plex.entryPoints=websecure",
"traefik.http.routers.plex.middlewares=authentik@file",
"traefik.http.routers.plex.middlewares=oidc-auth@file",
]
}
}
@@ -177,7 +291,7 @@ job "media" {
resources {
cpu = 2000
memory = 1000
memory = 1500
}
service {
@@ -187,7 +301,7 @@ job "media" {
tags = [
"traefik.enable=true",
"traefik.http.routers.torrent.entryPoints=websecure",
"traefik.http.routers.torrent.middlewares=authentik@file",
"traefik.http.routers.torrent.middlewares=oidc-auth@file",
]
}
}

View File

@@ -39,10 +39,10 @@ job "netbox" {
REMOTE_AUTH_ENABLED = "true"
REMOTE_AUTH_BACKEND = "social_core.backends.open_id_connect.OpenIdConnectAuth"
SOCIAL_AUTH_OIDC_ENDPOINT = "https://authentik.v.paler.net/application/o/netbox/"
SOCIAL_AUTH_OIDC_KEY = "XiPhZmWy2mp8hQyHLXCwk7njRNPSLTp2vSHhvWYI"
SOCIAL_AUTH_OIDC_SECRET = "Kkop2dStx0gN52V1LfPnoxcaemuur6zMsvRnqpWSDe2qSngJVcqWfvFXaNeTbdURRB6TPwjlaNJ5BXR2ChcSmokWGTGargu84Ox1D6M2zXTsfLFj9B149Mhblos4mJL1"
LOGOUT_REDIRECT_URL = "https://authentik.v.paler.net/application/o/netbox/end-session/"
SOCIAL_AUTH_OIDC_ENDPOINT = "https://pocket-id.v.paler.net/"
SOCIAL_AUTH_OIDC_KEY = "6ce1f1bb-d5e8-4ba5-b136-2643dc8bcbcf"
SOCIAL_AUTH_OIDC_SECRET = "Af7sJvCn9BuijoJXrB5aWv6fTmEqLCAf"
LOGOUT_REDIRECT_URL = "https://pocket-id.v.paler.net/logout"
}
resources {

39
services/ollama.hcl Normal file
View File

@@ -0,0 +1,39 @@
job "ollama" {
datacenters = ["alo"]
type = "service"
group "ollama" {
network {
port "http" {
static = 11434
}
}
task "server" {
driver = "docker"
config {
image = "ollama/ollama:latest"
ports = ["http"]
volumes = ["/data/services/ollama:/root/.ollama"]
}
service {
name = "ollama"
port = "http"
check {
type = "http"
path = "/"
interval = "30s"
timeout = "5s"
}
}
resources {
cpu = 8000
memory = 2048
}
}
}
}

View File

@@ -91,15 +91,15 @@ job "postgres" {
PGADMIN_CONFIG_OAUTH2_AUTO_CREATE_USER = "True"
PGADMIN_CONFIG_OAUTH2_CONFIG = <<EOH
[{
'OAUTH2_NAME' : 'authentik',
'OAUTH2_NAME' : 'pocket-id',
'OAUTH2_DISPLAY_NAME' : 'SSO',
'OAUTH2_CLIENT_ID' : 'o4p3B03ayTQ2kpwmM7GswbcfO78JHCTdoZqKJEut',
'OAUTH2_CLIENT_SECRET' : '7UYHONOCVdjpRMK9Ojwds0qPPpxCiztbIRhK7FJ2IFBpUgN6tnmpEjlkPYimiGKfaHLhy4XE7kQm7Et1Jm0hgyia0iB1VIlp623ckppbwkM6IfpTE1LfEmTMtPrxSngx',
'OAUTH2_TOKEN_URL' : 'https://authentik.v.paler.net/application/o/token/',
'OAUTH2_AUTHORIZATION_URL' : 'https://authentik.v.paler.net/application/o/authorize/',
'OAUTH2_API_BASE_URL' : 'https://authentik.v.paler.net/',
'OAUTH2_USERINFO_ENDPOINT' : 'https://authentik.v.paler.net/application/o/userinfo/',
'OAUTH2_SERVER_METADATA_URL' : 'https://authentik.v.paler.net/application/o/pgadmin/.well-known/openid-configuration',
'OAUTH2_CLIENT_ID' : '180133da-1bd7-4cde-9c18-2f277e962dab',
'OAUTH2_CLIENT_SECRET' : 'ELYNAfiWSGYJQUXUDOdpm7tTtyLbrs4E',
'OAUTH2_TOKEN_URL' : 'https://pocket-id.v.paler.net/api/oidc/token',
'OAUTH2_AUTHORIZATION_URL' : 'https://pocket-id.v.paler.net/authorize',
'OAUTH2_API_BASE_URL' : 'https://pocket-id.v.paler.net/',
'OAUTH2_USERINFO_ENDPOINT' : 'https://pocket-id.v.paler.net/api/oidc/userinfo',
'OAUTH2_SERVER_METADATA_URL' : 'https://pocket-id.v.paler.net/.well-known/openid-configuration',
'OAUTH2_SCOPE' : 'openid email profile',
'OAUTH2_ICON' : 'fa-database',
'OAUTH2_BUTTON_COLOR' : '#00ff00'

View File

@@ -54,7 +54,7 @@ job "prometheus" {
tags = [
"traefik.enable=true",
"traefik.http.routers.prometheus.entryPoints=websecure",
"traefik.http.routers.prometheus.middlewares=authentik@file",
"traefik.http.routers.prometheus.middlewares=oidc-auth@file",
]
check {

View File

@@ -0,0 +1,82 @@
job "tiddlywiki-mcp" {
datacenters = ["alo"]
group "captainslog" {
network {
port "http" {
static = 3500
}
}
volume "services" {
type = "host"
source = "services"
read_only = false
}
volume "nix-store" {
type = "host"
source = "nix-store"
read_only = true
}
volume "sw" {
type = "host"
source = "sw"
read_only = true
}
task "mcp-server" {
driver = "exec"
config {
command = "/sw/bin/node"
args = ["/data/services/tiddlywiki-mcp/dist/index.js"]
}
env {
MCP_TRANSPORT = "http"
MCP_PORT = "${NOMAD_PORT_http}"
CONSUL_SERVICE = "captainslog.service.consul"
AUTH_HEADER = "X-Oidc-Username"
AUTH_USER = "claude-code"
}
volume_mount {
volume = "services"
destination = "/data/services"
read_only = false
}
volume_mount {
volume = "nix-store"
destination = "/nix/store"
read_only = true
}
volume_mount {
volume = "sw"
destination = "/sw"
read_only = true
}
service {
name = "tiddlywiki-mcp-captainslog"
port = "http"
check {
type = "http"
path = "/health"
interval = "10s"
timeout = "2s"
}
}
resources {
memory = 256
}
user = "ppetru"
}
}
}

View File

@@ -34,7 +34,7 @@ job "traefik" {
tags = [
"traefik.enable=true",
"traefik.http.routers.api.entryPoints=websecure",
"traefik.http.routers.api.middlewares=authentik@file",
"traefik.http.routers.api.middlewares=oidc-auth@file",
"traefik.http.routers.api.rule=Host(`traefik.v.paler.net`)",
"traefik.http.routers.api.service=api@internal",
]
@@ -63,6 +63,7 @@ job "traefik" {
volumes = [
"local/traefik.yml:/etc/traefik/traefik.yml",
"/data/services/traefik:/config",
"/data/services/traefik/plugins-storage:/plugins-storage",
]
}
@@ -75,6 +76,12 @@ global:
#log:
# level: debug
experimental:
plugins:
traefik-oidc-auth:
moduleName: "github.com/sevensolutions/traefik-oidc-auth"
version: "v0.16.0"
api:
dashboard: true

View File

@@ -69,7 +69,7 @@ job "unifi" {
tags = [
"traefik.enable=true",
"traefik.http.routers.unifi.entryPoints=websecure",
"traefik.http.routers.unifi.middlewares=authentik@file",
"traefik.http.routers.unifi.middlewares=oidc-auth@file",
"traefik.http.services.unifi.loadbalancer.server.scheme=https",
]
}

View File

@@ -39,7 +39,7 @@ job "urbit" {
tags = [
"traefik.enable=true",
"traefik.http.routers.urbit.entryPoints=websecure",
"traefik.http.routers.urbit.middlewares=authentik@file",
"traefik.http.routers.urbit.middlewares=oidc-auth@file",
]
}

View File

@@ -73,7 +73,7 @@ EOH
tags = [
"traefik.enable=true",
"traefik.http.routers.webodm.entryPoints=websecure",
"traefik.http.routers.webodm.middlewares=authentik@file",
"traefik.http.routers.webodm.middlewares=oidc-auth@file",
]
}
}
@@ -97,7 +97,7 @@ EOH
tags = [
"traefik.enable=true",
"traefik.http.routers.clusterodm.entryPoints=websecure",
"traefik.http.routers.clusterodm.middlewares=authentik@file",
"traefik.http.routers.clusterodm.middlewares=oidc-auth@file",
]
}

View File

@@ -22,7 +22,7 @@ job "whoami" {
"traefik.enable=true",
"traefik.http.routers.whoami.rule=Host(`test.alo.land`)",
"traefik.http.routers.whoami.entryPoints=websecure",
"traefik.http.routers.whoami.middlewares=authentik@file",
"traefik.http.routers.whoami.middlewares=oidc-auth@file",
]
}
}

View File

@@ -36,9 +36,9 @@ job "wiki" {
"--listen",
"host=0.0.0.0",
"port=${NOMAD_PORT_captainslog}",
"authenticated-user-header=X-authentik-username",
"readers=ppetru",
"writers=ppetru",
"authenticated-user-header=X-Oidc-Username",
"readers=ppetru,claude-code",
"writers=ppetru,claude-code",
"admin=ppetru",
]
}
@@ -64,7 +64,7 @@ job "wiki" {
tags = [
"traefik.enable=true",
"traefik.http.routers.captainslog.entryPoints=websecure",
"traefik.http.routers.captainslog.middlewares=authentik@file",
"traefik.http.routers.captainslog.middlewares=oidc-auth@file",
]
}
@@ -85,7 +85,7 @@ job "wiki" {
"--listen",
"host=0.0.0.0",
"port=${NOMAD_PORT_alo}",
"authenticated-user-header=X-authentik-username",
"authenticated-user-header=X-Oidc-Username",
"readers=ppetru,ines",
"writers=ppetru,ines",
"admin=ppetru",
@@ -112,7 +112,7 @@ job "wiki" {
"traefik.enable=true",
"traefik.http.routers.alowiki.rule=Host(`wiki.alo.land`)",
"traefik.http.routers.alowiki.entryPoints=websecure",
"traefik.http.routers.alowiki.middlewares=authentik@file",
"traefik.http.routers.alowiki.middlewares=oidc-auth@file",
]
}
@@ -133,7 +133,7 @@ job "wiki" {
"--listen",
"host=0.0.0.0",
"port=${NOMAD_PORT_pispace}",
"authenticated-user-header=X-authentik-username",
"authenticated-user-header=X-Oidc-Username",
"readers=ppetru,ines",
"writers=ppetru,ines",
"admin=ppetru",
@@ -160,7 +160,7 @@ job "wiki" {
"traefik.enable=true",
"traefik.http.routers.pispace.rule=Host(`pi.paler.net`)",
"traefik.http.routers.pispace.entryPoints=websecure",
"traefik.http.routers.pispace.middlewares=authentik@file",
"traefik.http.routers.pispace.middlewares=oidc-auth@file",
]
}
@@ -181,7 +181,7 @@ job "wiki" {
"--listen",
"host=0.0.0.0",
"port=${NOMAD_PORT_grok}",
"authenticated-user-header=X-authentik-username",
"authenticated-user-header=X-Oidc-Username",
"readers=ppetru",
"writers=ppetru",
"admin=ppetru",
@@ -207,7 +207,7 @@ job "wiki" {
tags = [
"traefik.enable=true",
"traefik.http.routers.groktw.entryPoints=websecure",
"traefik.http.routers.groktw.middlewares=authentik@file",
"traefik.http.routers.groktw.middlewares=oidc-auth@file",
]
}