chore: initial homelab stack config and sync tooling

Mirror the three production files (docker-compose.yml, .env, Caddyfile) that live on valhalla, plus the push/pull PowerShell scripts, CLAUDE.md, .gitignore/.gitattributes, and .claude/skills for ssh/apply/sync.
This commit is contained in:
ginnoir
2026-06-02 22:12:07 -05:00
commit 56d834ede9
11 changed files with 1426 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
---
name: homelab-apply
description: Push local config changes (docker-compose.yml, .env, Caddyfile) to the valhalla server and apply them to the live Docker stack. Use after editing homelab config to deploy it. This mutates the running production stack.
---
# homelab-apply
Deploys local repo changes to the live stack on valhalla. **This mutates production** — sync and review your diff first (see the `homelab-sync` skill).
## Run it
```powershell
powershell -File apply-compose.ps1 # push .env + Caddyfile + compose, then apply
powershell -File apply-compose.ps1 -EnvFile # .env only
powershell -File apply-compose.ps1 -Caddy # Caddyfile only + hot-reload Caddy
powershell -File apply-compose.ps1 -Compose # docker-compose.yml only + dc pull + dc up -d
```
Flags combine (e.g. `-Caddy -Compose`); no flags pushes & applies all three.
## What each step does
- **`.env` / compose** → `scp` to `~/htpc-download-box/`, then `dc pull` (latest images) + `dc up -d` (recreates only containers whose resolved config changed).
- **Caddyfile** → `scp` to `/config/caddy/Caddyfile`, then `dc exec caddy caddy reload …` — a hot reload with no downtime. If the new Caddyfile is invalid the reload fails and the old config keeps running; read the output.
## After applying, verify
```powershell
ssh ginnoir@valhalla "bash -ic 'dc ps'"
ssh ginnoir@valhalla "bash -ic 'dc logs --tail=50 <service>'"
```
## Notes
- Compose is **v1.27.4** (`docker-compose`, hyphenated); `dc` runs over `bash -ic` to load the alias.
- Editing config without applying does nothing — the server only sees pushed files.
- Cosmetic/whitespace-only compose edits are no-ops to running containers; `dc up -d` won't recreate them.
+51
View File
@@ -0,0 +1,51 @@
---
name: homelab-ssh
description: Connect to and run commands on the valhalla homelab server (ginnoir@valhalla) over SSH, including docker-compose operations through the `dc` alias. Use when inspecting the live stack, tailing logs, restarting a service, reloading Caddy, or checking container/file state on the server.
---
# homelab-ssh
The production homelab runs on a headless Ubuntu host reachable at `ssh ginnoir@valhalla` (key-based auth; resolves and works from this Windows host directly).
## Connect (non-interactive / safe for automation)
Always pass `-o BatchMode=yes` so a missing key or password prompt fails fast instead of hanging:
```powershell
ssh -o BatchMode=yes -o ConnectTimeout=8 ginnoir@valhalla "<command>"
```
The first `Bash` command of a session is gated by a GateGuard hook — state the user request + what the command does, then retry.
## docker-compose via the `dc` alias
The stack is Docker Compose **v1.27.4** (`docker-compose`, hyphenated — `docker compose` v2 is not installed). On the server, `dc` is aliased in `~/.bashrc` to:
```
docker-compose -f ~/htpc-download-box/docker-compose.yml --env-file ~/htpc-download-box/.env
```
It's a shell alias, so it only exists in an interactive shell. Invoke it with `bash -ic`:
```powershell
ssh ginnoir@valhalla "bash -ic 'dc ps'"
ssh ginnoir@valhalla "bash -ic 'dc logs -f --tail=200 <service>'"
ssh ginnoir@valhalla "bash -ic 'dc restart <service>'"
ssh ginnoir@valhalla "bash -ic 'dc up -d'"
ssh ginnoir@valhalla "bash -ic 'dc exec caddy caddy reload --config /etc/caddy/Caddyfile'"
```
Use the compose **service name** (`sonarr`, `caddy`, `app`, …). Without `bash -ic`, `dc` is "command not found".
## Key paths on the server
- `~/htpc-download-box/docker-compose.yml`, `~/htpc-download-box/.env` — stack definition
- `/config/caddy/Caddyfile` — Caddy config (mounted into the `caddy` container at `/etc/caddy/Caddyfile`)
- `/config/<service>/` — per-service persisted config; `/storage1/` — media & data; plus named Docker volumes
- `~/htpc-download-box/` also holds unrelated legacy files (`*.bak`, old `.git`, `README.md`, `Vagrantfile`) — don't modify them
## Notes
- Read-only exploration of `/config`, `/storage1`, and container state is fine for answering questions.
- Avoid destructive commands against the live stack without explicit confirmation.
- To push config changes and apply them, use the `homelab-apply` skill; to pull prod config into the repo, use `homelab-sync`.
+38
View File
@@ -0,0 +1,38 @@
---
name: homelab-sync
description: Pull the live production configs (docker-compose.yml, .env, Caddyfile) from the valhalla server into this repo so local matches what's deployed. Use at the start of any session that will edit homelab deployment config, or to check for drift between repo and production.
---
# homelab-sync
Production (valhalla) is the source of truth. Pull before editing so you never edit a stale copy.
## Run it
```powershell
powershell -File sync-prod.ps1
```
This `scp`s three files from valhalla, **overwriting** the local copies:
| From valhalla | Into repo |
|---------------|-----------|
| `~/htpc-download-box/docker-compose.yml` | `docker-compose.yml` |
| `~/htpc-download-box/.env` | `.env` |
| `/config/caddy/Caddyfile` | `Caddyfile` |
It overwrites local working copies — commit or stash anything you want to keep first.
## Check drift without overwriting
To see how local differs from prod before deciding direction (git is installed):
```powershell
$tmp = Join-Path $env:TEMP 'valhalla-compose.yml'
scp -o BatchMode=yes "ginnoir@valhalla:~/htpc-download-box/docker-compose.yml" $tmp
git --no-pager diff --no-index -- $tmp .\docker-compose.yml
```
(`.env` and `Caddyfile` can be compared the same way.)
After syncing, review `git status` / `git diff` to see what changed on the server since your last sync. To push local changes the other direction, use the `homelab-apply` skill.
+99
View File
@@ -0,0 +1,99 @@
# =============================================================
# BASE CONFIG
# =============================================================
# Timezone: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
TZ=America/Chicago
# UNIX PUID and PGID — find with: id $USER
PUID=1000
PGID=1000
# Root path prefix for volume mounts
ROOT=/
# =============================================================
# FOUNDRY VTT
# =============================================================
FOUNDRY_USERNAME=ginnoir
FOUNDRY_PASSWORD=agt-UAU!uky_ykh3juy
FOUNDRY_ADMIN_KEY=fuckyouben
FOUNDRY_PATCH_URLS=https://gist.githubusercontent.com/ginnoir/898b152a09369f9089770daa71887262/raw/a354e4284f2b1feb616910f65bfe65a4ffdb5473/plutonium-12.x.sh
# =============================================================
# OWNCLOUD
# =============================================================
OWNCLOUD_DOMAIN=files.ginnoir.com
OWNCLOUD_DB_USER=ginnoir
OWNCLOUD_DB_PASSWORD=d0fet0th3x
OWNCLOUD_ADMIN_USER=ginnoir
OWNCLOUD_ADMIN_PASSWORD=d0fet0th3x
# =============================================================
# MARIADB
# =============================================================
MYSQL_ROOT_PASSWORD=d0fet0th3x
# =============================================================
# REACTIVE RESUME
# =============================================================
POSTGRES_PASSWORD=postgres
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
CHROME_TOKEN=chrome_token
# IMPORTANT: Replace this before exposing resume.ginnoir.com publicly.
# Generate with: openssl rand -hex 64
AUTH_SECRET=CHANGE_ME_USE_openssl_rand_hex_64
# =============================================================
# FRESHRSS
# =============================================================
FRESHRSS_API_PASSWORD=d0fet0th3x
FRESHRSS_PASSWORD=d0fet0th3x
FRESHRSS_EMAIL=3nigma.matt@gmail.com
AUTH_SECRET=b4c032c8475cdd21e9138f2178c862f79cb6302ebf90e561cdea4cd6efcfe61e4ee03f5a4b3405c3c99239b734c8f387c6c3547d6c5b8f1c7d7db2b81c01ce0b
# ── famapp ────────────────────────────────────────────────────────────────────
FAMAPP_IMAGE=ghcr.io/ginnoir/famapp:latest
FAMAPP_PULL_POLICY=always
AUTHENTIK_IMAGE_TAG=2024.12.3
RUN_MIGRATIONS=true
AUTH_URL=https://fam.ginnoir.com
FAMAPP_DB_USER=famapp
FAMAPP_DB_PASSWORD=a9a999d16dd5cdc521bb62eff331265d3c24
FAMAPP_DB_NAME=famapp
AUTH_SECRET=CJsICgBs78YxxSdJfS8KU9Zn9k+QytRoVui9kVtuHtI=
# Fill in after Authentik bootstrap
AUTH_OIDC_ISSUER=https://auth.ginnoir.com/application/o/famapp/
AUTH_OIDC_CLIENT_ID=6uOZ8yjTR9Z2m1RZU1cLHYXHV2Lv6zZtfuJcrsdq
AUTH_OIDC_CLIENT_SECRET=CPcNM8jXaJ2jHFKxRwGuuKyMKJsT502l3FztO1AOcFqDzWqlWu5x7Ox52Q6zyBa2v33YzoA9igrXW8FFjd1Gp7wrZV4PGTwvXmOd7pIMOY93gNotAgJaewT8WHW1F3YD
VAPID_PUBLIC_KEY=BNED_UgfPhyoG6_JOabhpQpN3nr6QM5tO0PDiAadMGCcZROn8LM7tnsBrKaB1E5kfuDFb1rC_M86cT3uCvBqSBk
VAPID_PRIVATE_KEY=QyJiyCI2TJa_QWVXznfj4vjpWTBsyFfSCtn1brpE9aI
VAPID_SUBJECT=mailto:3nigma.matt@gmail.com
NTFY_URL=https://ntfy.ginnoir.com
NTFY_TOPIC=famapp
AUTHENTIK_DB_USER=authentik
AUTHENTIK_DB_PASSWORD=ebb7d34ecbc4fc8f95cd16ef491d2162cdf8
AUTHENTIK_DB_NAME=authentik
AUTHENTIK_SECRET_KEY=C33S/oXOVOLrlLnWGMRYadgH9zfEC7Bm1vHtbczkFxHcTOj9j6io397c/OGb
# MinIO object storage (used for plant/container image uploads)
famapp_MINIO_ENDPOINT=http://minio:9000
famapp_MINIO_ROOT_USER=famapp
famapp_MINIO_ROOT_PASSWORD=d0fet0th3x
famapp_MINIO_BUCKET=garden
famapp_OPENPLANTBOOK_CLIENT_ID=5JdQLab69RhVaF4er7b7D29BCrkf3fo3OEiDsOQa
famapp_OPENPLANTBOOK_CLIENT_SECRET=kxPTrdQPAVta7sjVuIRf5EGIJ1fKvlMlqRSJpQtFxRftJhg7oeGnXzwH7ls6tluQLFUYVDYRMwISiAuKZGZLvkuJzUwFGCwhZw7UtW8OvcTGy4717WeNUinvuCDSsNXP
# =============================================================
# CLOUDFLARE DNS (for Caddy DNS-01 TLS challenge)
# =============================================================
# Create a scoped API token at: https://dash.cloudflare.com/profile/api-tokens
# Required permissions: Zone > DNS > Edit (for ginnoir.com zone only)
# This is NOT the Zone ID or Account ID — it's the API Token secret value.
CF_API_TOKEN=cfut_ijIT9HDjXWKgd0dtnyLqxv8L3deB7hSDivSqquwp905a6c51
+8
View File
@@ -0,0 +1,8 @@
# These files are deployed to a Linux host via scp (a byte-for-byte copy).
# Force LF so a Windows checkout never introduces CRLF -- stray \r in .env
# values or shell snippets would break on the server.
* text=auto eol=lf
*.ps1 text eol=lf
*.yml text eol=lf
.env text eol=lf
Caddyfile text eol=lf
+12
View File
@@ -0,0 +1,12 @@
# NOTE: .env is intentionally TRACKED in this repo. ginnoir/homelabstack is a
# private repo and the stack's secrets are versioned with it. Do NOT add .env here.
# OS / editor cruft
.DS_Store
Thumbs.db
*.swp
*~
# Local scratch
*.tmp
*.log
+95
View File
@@ -0,0 +1,95 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this repo is
Deployment configuration for a **live, single-host Docker homelab** running on a headless Ubuntu server reachable at `ssh ginnoir@valhalla`. This repo is **not application code** — it is a version-controlled mirror of three files that live on the server. Editing here changes nothing until the files are pushed to valhalla and the stack is re-applied.
Tracked files ↔ where they live on valhalla:
| Repo file | On valhalla | Consumed by |
|-----------|-------------|-------------|
| `docker-compose.yml` | `~/htpc-download-box/docker-compose.yml` | the whole stack |
| `.env` | `~/htpc-download-box/.env` | compose variable substitution |
| `Caddyfile` | `/config/caddy/Caddyfile` | the `caddy` container, mounted at `/etc/caddy/Caddyfile` |
`.env` is committed **intentionally** — the GitHub repo `ginnoir/homelabstack` is private and the stack's secrets are versioned with it. Do not scrub or gitignore it.
The server's `~/htpc-download-box/` also holds unrelated legacy files (`*.bak`, a 2020 `.git`, an old 42 KB `README.md`, `Vagrantfile`, `apache/`, `proxy/`): this stack was grafted onto the original *htpc-download-box* project. Leave that cruft alone — only the three files above are managed from here.
## The core workflow
Production is the source of truth. **Sync before editing, apply after editing:**
```powershell
powershell -File sync-prod.ps1 # pull prod -> repo (OVERWRITES local copies)
# ...edit docker-compose.yml / Caddyfile / .env...
powershell -File apply-compose.ps1 # push repo -> prod and apply (mutates the LIVE stack)
```
`apply-compose.ps1` flags (combine freely; no flags = all three):
- `-EnvFile` — push `.env` only
- `-Caddy` — push `Caddyfile`, then hot-reload Caddy (no downtime)
- `-Compose` — push `docker-compose.yml`, then `dc pull` + `dc up -d`
Caddy changes hot-reload; on `dc up -d` only containers whose resolved config changed are recreated. Cosmetic/whitespace-only compose edits are therefore no-ops to the running containers.
## Running commands on valhalla
SSH is key-based and works from this Windows host directly. In automation, pass `-o BatchMode=yes` so it fails fast instead of hanging on a prompt.
All compose operations go through the **`dc` alias** in the server's `~/.bashrc`:
```
dc = docker-compose -f ~/htpc-download-box/docker-compose.yml --env-file ~/htpc-download-box/.env
```
It's a shell alias, so it only exists in an **interactive** shell — invoke it over SSH with `bash -ic`:
```powershell
ssh ginnoir@valhalla "bash -ic 'dc ps'"
ssh ginnoir@valhalla "bash -ic 'dc logs -f --tail=100 sonarr'"
ssh ginnoir@valhalla "bash -ic 'dc restart caddy'"
ssh ginnoir@valhalla "bash -ic 'dc exec caddy caddy reload --config /etc/caddy/Caddyfile'"
```
Address services by their **compose service name** (`sonarr`, `caddy`, `app`, …) — this works regardless of how the container is named.
## docker-compose.yml — read before editing
- **Compose v1.27.4.** The binary is hyphenated `docker-compose`; `docker compose` (v2) is **not** installed. Project name is `htpc-download-box` (from the directory).
- **Container names:** services with an explicit `container_name:` (most of the media stack, `owncloud_*`, `ntfy`, `freshrss`, `hbbr`/`hbbs`) use that literal name. Services without one get the v1 pattern `htpc-download-box_<service>_1` (e.g. `caddy`, `foundry`, `app`, `postgres`, the `authentik-*`/`famapp-*` sets). Prefer `dc <cmd> <service>` so you don't have to track which is which.
- **`${ROOT}`** (`.env`, default `/`) prefixes some media-stack volume mounts; other services hardcode `/config` and `/storage1`. Both forms resolve to the same real paths.
- **Networks segment the stack:** `foundry`, `media`, `media_external`, `owncloud`, `resume`, `famapp`, `authentik`. `caddy` is the only container on all of them — a new service that Caddy must proxy has to share a network with `caddy`.
- **Persistence lives on the server, not in this repo:** container state is in `/config/<service>` bind mounts, under `/storage1`, or in named volumes (`postgres_data`, `minio_data`, `authentik_db_data`, `garden_uploads`, …).
## Caddyfile
- TLS uses the Cloudflare DNS-01 challenge (`acme_dns cloudflare {env.CF_API_TOKEN}`), so certs need no inbound ports. Most sites are `<name>.ginnoir.com → reverse_proxy <service>:<port>`.
- The `(internal_only)` snippet 403s any client outside `192.168.1.0/24`. `import internal_only` is how admin UIs (sonarr, qbittorrent, …) stay LAN-only while public sites omit it.
- Reverse-proxy upstreams use the compose **service name + container-internal port** (e.g. `qbittorrent:3232`, `stash:6970`), not the host-published port.
## Adding a service (the common change)
1. Add the service to `docker-compose.yml`; put it on a network `caddy` is also on if it needs proxying; bind config to `/config/<service>`.
2. Add any secrets/vars to `.env`.
3. Add a site block to `Caddyfile` (`reverse_proxy <service>:<port>`); add `import internal_only` for LAN-only access.
4. Run `apply-compose.ps1` (or `-Compose -Caddy`), then verify with `dc ps` / `dc logs <service>`.
## Known quirks / gotchas
- A **GateGuard hook blocks the first use of `Bash`, and every `Write`/`Edit`, until you state the required facts** (the user request + what the operation does/affects). State them, then retry the same call.
- `Caddyfile` proxies `matrix.ginnoir.com → localhost:8008`, but there is **no Matrix/Synapse service in compose** — it's external/legacy. Likewise `dev.ginnoir.com → 192.168.1.74:3000` points at a different LAN host.
- Reactive Resume's `app` reads `${RESUME_AUTH_SECRET}`, which is **not defined in `.env`** (resolves empty). `.env` also defines `AUTH_SECRET` three times — last definition wins (famapp's).
- `watchtower` auto-updates `:latest` images, so a running image can drift ahead of what the last `apply` pulled.
- Line endings: a `.gitattributes` forces **LF** so files stay Unix-clean. Pushing CRLF (especially in `.env`) to the Linux host would append stray `\r` to values and break things.
## Skills
Project skills in `.claude/skills/` wrap the recurring operations:
- `homelab-sync` — pull prod configs into the repo
- `homelab-apply` — push config + apply to the live stack
- `homelab-ssh` — run commands / inspect state on valhalla
+225
View File
@@ -0,0 +1,225 @@
# =============================================================
# GLOBAL OPTIONS
# =============================================================
{
acme_dns cloudflare {env.CF_API_TOKEN}
}
# =============================================================
# SNIPPETS
# =============================================================
# Reusable matcher — blocks anything not on the LAN.
# Usage: import internal_only inside any site block.
(internal_only) {
@blocked not remote_ip 192.168.1.0/24
respond @blocked "Access denied" 403
}
# =============================================================
# FOUNDRY VTT — public
# =============================================================
foundry.ginnoir.com {
reverse_proxy foundry:30000
}
foundry2.ginnoir.com {
reverse_proxy foundry2:30000
}
# =============================================================
# TABLETOP TOOLS — public
# =============================================================
5etools.ginnoir.com {
reverse_proxy 5etools:80
}
pf2e.ginnoir.com {
root * /srv/aon
file_server
}
# =============================================================
# FILE STORAGE — public
# =============================================================
files.ginnoir.com {
reverse_proxy owncloud:8080
}
# =============================================================
# STATIC SITES — public
# =============================================================
ffttsystems.ginnoir.com {
root * /srv/ffttsystems
file_server
}
# =============================================================
# MEDIA REQUESTS — public
# =============================================================
requests.ginnoir.com {
reverse_proxy overseerr:5055
}
# =============================================================
# MEDIA MANAGEMENT — internal only
# =============================================================
sonarr.ginnoir.com {
import internal_only
reverse_proxy sonarr:8989
}
radarr.ginnoir.com {
import internal_only
reverse_proxy radarr:7878
}
bazarr.ginnoir.com {
import internal_only
reverse_proxy bazarr:6767
}
jackett.ginnoir.com {
import internal_only
reverse_proxy jackett:9117
}
prowlarr.ginnoir.com {
import internal_only
reverse_proxy prowlarr:9696
}
tautulli.ginnoir.com {
import internal_only
reverse_proxy tautulli:8181
}
hydra.ginnoir.com {
import internal_only
reverse_proxy nzbhydra2:5076
}
# =============================================================
# DOWNLOAD CLIENTS — internal only
# =============================================================
qbittorrent.ginnoir.com {
import internal_only
reverse_proxy qbittorrent:3232
}
deluge.ginnoir.com {
import internal_only
reverse_proxy deluge:8112
}
nzbget.ginnoir.com {
import internal_only
reverse_proxy nzbget:6789
}
whisparr.ginnoir.com {
import internal_only
reverse_proxy whisparr:6969
}
stash.ginnoir.com {
import internal_only
reverse_proxy stash:6970
}
# =============================================================
# NOTIFICATIONS & RSS — public
# =============================================================
ntfy.ginnoir.com, http://ntfy.ginnoir.com {
reverse_proxy ntfy:80
@httpget {
protocol http
method GET
path_regexp ^/([-_a-z0-9]{0,64}$|docs/|static/)
}
redir @httpget https://{host}{uri}
}
freshrss.ginnoir.com {
reverse_proxy freshrss:80
@httpget {
protocol http
method GET
path_regexp ^/([-_a-z0-9]{0,64}$|docs/|static/)
}
redir @httpget https://{host}{uri}
}
# =============================================================
# RESUME / PORTFOLIO — public
# =============================================================
resume.ginnoir.com, https://resume.ginnoir.com {
reverse_proxy app:3000
@httpget {
protocol http
method GET
path_regexp ^/([-_a-z0-9]{0,64}$|docs/|static/)
}
redir @httpget https://{host}{uri}
}
j-costa.com, https://j-costa.com {
tls {
issuer acme {
disable_tlsalpn_challenge
}
}
redir * https://resume.ginnoir.com/ginnoir/resume permanent
}
storage.j-costa.com, https://storage.j-costa.com {
tls {
issuer acme {
disable_tlsalpn_challenge
}
}
reverse_proxy resume-minio:9000
@httpget {
protocol http
method GET
path_regexp ^/([-_a-z0-9]{0,64}$|docs/|static/)
}
redir @httpget https://{host}{uri}
}
# =============================================================
# MINIO CONSOLE — internal only
# =============================================================
minio.ginnoir.com {
import internal_only
reverse_proxy resume-minio:9001
}
# =============================================================
# MATRIX — public
# =============================================================
ginnoir.com {
header /.well-known/matrix/* Content-Type application/json
header /.well-known/matrix/* Access-Control-Allow-Origin *
respond /.well-known/matrix/server `{"m.server": "matrix.ginnoir.com:443"}`
respond /.well-known/matrix/client `{"m.homeserver":{"base_url":"https://matrix.ginnoir.com"},"m.identity_server":{"base_url":"https://identity.ginnoir.com"}}`
}
matrix.ginnoir.com {
reverse_proxy /_matrix/* localhost:8008
reverse_proxy /_synapse/client/* localhost:8008
}
# =============================================================
# FAMAPP & AUTH — public
# =============================================================
fam.ginnoir.com {
reverse_proxy famapp:3000
}
auth.ginnoir.com {
reverse_proxy authentik-server:9000
}
dev.ginnoir.com {
reverse_proxy 192.168.1.74:3000
}
+55
View File
@@ -0,0 +1,55 @@
# Push .env, Caddyfile, and docker-compose.yml to valhalla, then apply them
# to the live Docker stack.
#
# On valhalla, `dc` is aliased (in ~/.bashrc) to:
# docker-compose -f ~/htpc-download-box/docker-compose.yml --env-file ~/htpc-download-box/.env
# It's a shell alias, so we invoke it over SSH with `bash -ic` to load it.
#
# Usage (flags combine; no flags = push & apply all three):
# powershell -File apply-compose.ps1 # .env + Caddyfile + compose
# powershell -File apply-compose.ps1 -EnvFile # .env only
# powershell -File apply-compose.ps1 -Caddy # Caddyfile only + hot-reload Caddy
# powershell -File apply-compose.ps1 -Compose # docker-compose.yml only + pull + up -d
param(
[switch]$Compose,
[switch]$Caddy,
[switch]$EnvFile
)
$ErrorActionPreference = "Stop"
# Make a non-zero exit from scp/ssh abort the script instead of silently continuing.
$PSNativeCommandUseErrorActionPreference = $true
$server = "ginnoir@valhalla"
$composeLocal = Join-Path $PSScriptRoot "docker-compose.yml"
$caddyLocal = Join-Path $PSScriptRoot "Caddyfile"
$envLocal = Join-Path $PSScriptRoot ".env"
# Default (no flags): push everything.
if (-not $Compose -and -not $Caddy -and -not $EnvFile) {
$EnvFile = $true; $Caddy = $true; $Compose = $true
}
if ($EnvFile) {
Write-Host "Pushing .env ..."
scp $envLocal "${server}:~/htpc-download-box/.env"
}
if ($Caddy) {
Write-Host "Pushing Caddyfile ..."
scp $caddyLocal "${server}:/config/caddy/Caddyfile"
Write-Host "Reloading Caddy ..."
ssh $server "bash -ic 'dc exec caddy caddy reload --config /etc/caddy/Caddyfile'"
}
if ($Compose) {
Write-Host "Pushing docker-compose.yml ..."
scp $composeLocal "${server}:~/htpc-download-box/docker-compose.yml"
Write-Host "Pulling latest images ..."
ssh $server "bash -ic 'dc pull'"
Write-Host "Applying compose changes (recreates only changed containers) ..."
ssh $server "bash -ic 'dc up -d'"
}
Write-Host "Done."
+781
View File
@@ -0,0 +1,781 @@
services:
# ============================================================
# FOUNDRY VTT
# ============================================================
foundry:
image: felddy/foundryvtt:latest
hostname: valhalla-primary
init: true
restart: unless-stopped
networks:
- foundry
volumes:
- /storage1/foundry:/data
- /storage1/foundry/updatemain.bash:/home/foundry/updatemain.bash
- /storage1/Books/Tabletop:/data/Data/storage
environment:
- FOUNDRY_USERNAME=${FOUNDRY_USERNAME}
- FOUNDRY_PASSWORD=${FOUNDRY_PASSWORD}
- FOUNDRY_ADMIN_KEY=${FOUNDRY_ADMIN_KEY}
- CONTAINER_VERBOSE=true
- FOUNDRY_HOSTNAME=foundry.ginnoir.com
- FOUNDRY_PROXY_PORT=443
- FOUNDRY_PROXY_SSL=true
- CONTAINER_PRESERVE_OWNER=/data/Data/storage
- FOUNDRY_UID=1000
- FOUNDRY_GID=1000
- CONTAINER_PATCH_URLS=${FOUNDRY_PATCH_URLS}
ports:
- "30000:30000"
foundry2:
image: felddy/foundryvtt:latest
hostname: valhalla-secondary
init: true
restart: unless-stopped
networks:
- foundry
volumes:
- /storage1/foundry2:/data
- /storage1/foundry/updatemain.bash:/home/foundry/updatemain.bash
- /storage1/Books/Tabletop:/data/Data/storage
environment:
- FOUNDRY_USERNAME=${FOUNDRY_USERNAME}
- FOUNDRY_PASSWORD=${FOUNDRY_PASSWORD}
- FOUNDRY_ADMIN_KEY=${FOUNDRY_ADMIN_KEY}
- CONTAINER_VERBOSE=true
- FOUNDRY_HOSTNAME=foundry2.ginnoir.com
- FOUNDRY_PROXY_PORT=443
- FOUNDRY_PROXY_SSL=true
- CONTAINER_PRESERVE_OWNER=/data/Data/storage
- FOUNDRY_UID=1000
- FOUNDRY_GID=1000
- CONTAINER_PATCH_URLS=${FOUNDRY_PATCH_URLS}
ports:
- "30001:30000"
# ============================================================
# CADDY (reverse proxy)
# serfriz/caddy-cloudflare-ddns bundles:
# - caddy-dns/cloudflare (DNS-01 ACME challenge)
# - caddy-cloudflare-ip (trust Cloudflare proxy IPs for real client IPs)
# - caddy-dynamicdns (keeps DNS records current if WAN IP changes)
# ============================================================
caddy:
image: serfriz/caddy-cloudflare-ddns:latest
restart: unless-stopped
networks:
- foundry
- owncloud
- resume
- media_external
- media
- famapp
- authentik
ports:
- "80:80"
- "443:443"
environment:
- CF_API_TOKEN=${CF_API_TOKEN}
volumes:
- /config/caddy/Caddyfile:/etc/caddy/Caddyfile
- /config/caddy/site:/srv
- /config/caddy/data:/data
- /config/caddy/config:/config
- /storage1/Books:/srv/Books
# ============================================================
# MEDIA STACK
# ============================================================
deluge:
container_name: deluge
image: lscr.io/linuxserver/deluge:latest
restart: "no"
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ${ROOT}/storage1/torrents:/downloads
- ${ROOT}/config/deluge:/config
ports:
- "8112:8112"
nzbget:
container_name: nzbget
image: lscr.io/linuxserver/nzbget:latest
restart: "no"
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ${ROOT}/storage1/complete:/downloads
- ${ROOT}/config/nzbget:/config
ports:
- "6789:6789"
sonarr:
container_name: sonarr
image: lscr.io/linuxserver/sonarr:latest
restart: "no"
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- /etc/localtime:/etc/localtime:ro
- ${ROOT}/config/sonarr:/config
- ${ROOT}/storage1/Media/TV:/tv
- ${ROOT}/storage1/Media/Anime:/anime
- ${ROOT}/storage1/complete:/downloads
ports:
- "8989:8989"
radarr:
container_name: radarr
image: lscr.io/linuxserver/radarr:latest
restart: "no"
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- /etc/localtime:/etc/localtime:ro
- ${ROOT}/config/radarr:/config
- ${ROOT}/storage1/Media/Movies:/movies
- ${ROOT}/storage1/complete:/downloads
ports:
- "7878:7878"
bazarr:
container_name: bazarr
image: lscr.io/linuxserver/bazarr:latest
restart: "no"
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
- UMASK_SET=022
volumes:
- ${ROOT}/config/bazarr:/config
- ${ROOT}/storage1/Media/Movies:/movies
- ${ROOT}/storage1/Media/TV:/tv
- ${ROOT}/storage1/Media/Anime:/anime
ports:
- "6767:6767"
jackett:
container_name: jackett
image: ghcr.io/hotio/jackett:latest
restart: unless-stopped
networks:
- media
environment:
- PUID=1000
- PGID=1000
- UMASK=002
- TZ=${TZ}
volumes:
- /config/jackett:/config
ports:
- "9117:9117"
prowlarr:
container_name: prowlarr
image: ghcr.io/hotio/prowlarr:latest
restart: unless-stopped
networks:
- media
environment:
- PUID=1000
- PGID=1000
- UMASK=002
- TZ=${TZ}
volumes:
- /config/prowlarr:/config
ports:
- "9696:9696"
overseerr:
container_name: overseerr
image: sctx/overseerr:latest
networks:
- media_external
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- UMASK=002
- TZ=${TZ}
volumes:
- /config/overseerr:/app/config
ports:
- "5055:5055"
# ============================================================
# MEDIA UTILITIES
# ============================================================
tautulli:
image: ghcr.io/linuxserver/tautulli:latest
container_name: tautulli
restart: "no"
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ${ROOT}/config/tautulli:/config
- ${ROOT}/storage1/PMS:/logs
ports:
- "8181:8181"
nzbhydra2:
image: ghcr.io/linuxserver/nzbhydra2:latest
container_name: nzbhydra2
restart: "no"
networks:
- media
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ${ROOT}/config/nzbhydra:/config
- ${ROOT}/storage1/complete:/downloads
ports:
- "5076:5076"
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
restart: unless-stopped
networks:
- media
environment:
- PUID=1000
- PGID=1000
- TZ=${TZ}
- WEBUI_PORT=3232
- TORRENTING_PORT=6881
volumes:
- /config/qbittorrent:/config
- /storage1/complete:/downloads
ports:
- "3232:3232"
- "6881:6881"
- "6881:6881/udp"
whisparr:
container_name: whisparr
image: ghcr.io/hotio/whisparr:latest
restart: unless-stopped
networks:
- media
environment:
- PUID=1000
- PGID=1000
- UMASK=002
- TZ=${TZ}
- WEBUI_PORTS=6969/tcp
volumes:
- /config/whisparr:/config
- /storage1:/data
ports:
- "6969:6969"
stash:
image: stashapp/stash:latest
container_name: stash
restart: unless-stopped
networks:
- media
logging:
driver: json-file
options:
max-file: "10"
max-size: "2m"
environment:
- PUID=1000
- PGID=1000
- STASH_STASH=/data/
- STASH_GENERATED=/generated/
- STASH_METADATA=/metadata/
- STASH_CACHE=/cache/
- STASH_PORT=6970
volumes:
- /etc/localtime:/etc/localtime:ro
- /config/stash:/root/.stash
- /storage1/LinuxISOs:/data
- /storage1/stash/metadata:/metadata
- /storage1/stash/cache:/cache
- /storage1/stash/blobs:/blobs
- /storage1/stash/generated:/generated
ports:
- "6971:6970"
# ============================================================
# OWNCLOUD STACK
# ============================================================
owncloud:
image: owncloud/server:latest
container_name: owncloud_server
restart: always
networks:
- owncloud
ports:
- "8080:8080"
depends_on:
mariadb:
condition: service_healthy
redis:
condition: service_healthy
environment:
- OWNCLOUD_DOMAIN=${OWNCLOUD_DOMAIN}
- OWNCLOUD_DB_TYPE=mysql
- OWNCLOUD_DB_NAME=owncloud
- OWNCLOUD_DB_USERNAME=${OWNCLOUD_DB_USER}
- OWNCLOUD_DB_PASSWORD=${OWNCLOUD_DB_PASSWORD}
- OWNCLOUD_DB_HOST=mariadb
- OWNCLOUD_ADMIN_USERNAME=${OWNCLOUD_ADMIN_USER}
- OWNCLOUD_ADMIN_PASSWORD=${OWNCLOUD_ADMIN_PASSWORD}
- OWNCLOUD_MYSQL_UTF8MB4=true
- OWNCLOUD_REDIS_ENABLED=true
- OWNCLOUD_REDIS_HOST=redis
healthcheck:
test: ["CMD", "/usr/bin/healthcheck"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- /config/owncloud:/mnt/data
- /storage1/Books:/mnt/books
- /mnt/dropbox:/mnt/data/files/dropbox
- /storage1/ROMs:/mnt/roms
mariadb:
image: mariadb:latest
container_name: owncloud_mariadb
restart: always
networks:
- owncloud
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_USER=${OWNCLOUD_DB_USER}
- MYSQL_PASSWORD=${OWNCLOUD_DB_PASSWORD}
- MYSQL_DATABASE=owncloud
command: ["--max-allowed-packet=128M", "--innodb-log-file-size=64M"]
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-u", "root", "--password=${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- /config/mysql:/var/lib/mysql
redis:
image: redis:latest
container_name: owncloud_redis
restart: always
networks:
- owncloud
command: ["--databases", "1"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- /config/redis:/data
# ============================================================
# REACTIVE RESUME STACK
# ============================================================
postgres:
image: postgres:16-alpine
restart: unless-stopped
networks:
- resume
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 10s
timeout: 5s
retries: 5
resume-minio:
image: minio/minio:latest
restart: unless-stopped
networks:
- resume
command: server /data --console-address :9001
ports:
- "9000:9000"
- "9004:9001"
volumes:
- minio_data:/data
environment:
- MINIO_ROOT_USER=${MINIO_ROOT_USER}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:9000/minio/health/live || exit 1"]
interval: 30s
timeout: 20s
retries: 3
start_period: 30s
chrome:
image: ghcr.io/browserless/chromium:latest
restart: unless-stopped
networks:
- resume
environment:
- TIMEOUT=10000
- CONCURRENT=10
- TOKEN=${CHROME_TOKEN}
- EXIT_ON_HEALTH_FAILURE=true
- PRE_REQUEST_HEALTH_CHECK=true
app:
image: amruthpillai/reactive-resume:latest
restart: unless-stopped
networks:
- resume
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
resume-minio:
condition: service_healthy
chrome:
condition: service_started
environment:
- PORT=3000
- NODE_ENV=production
- APP_URL=https://resume.ginnoir.com
- STORAGE_URL=https://storage.j-costa.com/default
- PRINTER_ENDPOINT=http://chrome:3000
- PRINTER_TOKEN=${CHROME_TOKEN}
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres
- AUTH_SECRET=${RESUME_AUTH_SECRET}
- MAIL_FROM=noreply@localhost
- STORAGE_ENDPOINT=resume-minio
- STORAGE_PORT=9000
- STORAGE_REGION=us-east-1
- STORAGE_BUCKET=default
- STORAGE_ACCESS_KEY=${MINIO_ROOT_USER}
- STORAGE_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- STORAGE_USE_SSL=false
- STORAGE_SKIP_BUCKET_CHECK=false
# ============================================================
# NOTIFICATIONS & RSS
# ============================================================
ntfy:
image: binwiederhier/ntfy:latest
container_name: ntfy
command:
- serve
restart: unless-stopped
environment:
- TZ=${TZ}
- NTFY_DEFAULT_HOST=https://ntfy.ginnoir.com
- NTFY_BASE_URL=https://ntfy.ginnoir.com
- NTFY_CACHE_FILE=/var/lib/ntfy/cache.db
- NTFY_AUTH_FILE=/var/lib/ntfy/auth.db
- NTFY_AUTH_DEFAULT_ACCESS=read-write
- NTFY_BEHIND_PROXY=true
- NTFY_ATTACHMENT_CACHE_DIR=/var/lib/ntfy/attachments
- NTFY_ENABLE_LOGIN=true
user: 1000:1000
networks:
- famapp
volumes:
- /config/ntfy/var/cache/ntfy:/var/cache/ntfy
- /config/ntfy/etc/ntfy:/etc/ntfy
- /config/ntfy/var/lib/ntfy:/var/lib/ntfy
ports:
- "8000:80"
healthcheck:
test:
[
"CMD-SHELL",
"wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1",
]
interval: 60s
timeout: 10s
retries: 3
start_period: 40s
freshrss:
image: freshrss/freshrss:latest
container_name: freshrss
hostname: freshrss
restart: unless-stopped
networks:
- famapp
logging:
options:
max-size: 10m
volumes:
- /config/freshrss/data:/var/www/FreshRSS/data
- /config/freshrss/extensions:/var/www/FreshRSS/extensions
ports:
- "9999:80"
environment:
- CRON_MIN=3,33
- TRUSTED_PROXY=172.16.0.1/12 192.168.0.1/16
- FRESHRSS_INSTALL=--default-user ginnoir --api-enabled --base-url https://freshrss.ginnoir.com
- FRESHRSS_USER=--api-password ${FRESHRSS_API_PASSWORD} --email ${FRESHRSS_EMAIL} --password ${FRESHRSS_PASSWORD} --user ginnoir
vigilant:
image: ghcr.io/verifiedjoseph/vigilant:latest
restart: unless-stopped
environment:
- VIGILANT_NOTIFICATION_SERVICE=ntfy
- VIGILANT_NOTIFICATION_NTFY_URL=https://ntfy.ginnoir.com/
- VIGILANT_NOTIFICATION_NTFY_TOPIC=RSS
volumes:
- /config/vigilant/feeds.yaml:/app/feeds.yaml
- cache:/app/cache
security_opt:
- no-new-privileges:true
# ============================================================
# REMOTE ACCESS
# ============================================================
hbbr:
container_name: hbbr
image: rustdesk/rustdesk-server:latest
command: hbbr
restart: unless-stopped
ports:
- "21117:21117"
volumes:
- /config/rustdesk/hbbr/data:/root
hbbs:
container_name: hbbs
image: rustdesk/rustdesk-server:latest
command: hbbs
restart: unless-stopped
ports:
- "21115:21115"
- "21116:21116"
- "21116:21116/udp"
volumes:
- /config/rustdesk/hbbs/data:/root
depends_on:
- hbbr
# ============================================================
# UTILITIES
# ============================================================
5etools:
image: ghcr.io/5etools-mirror-3/5etools-src:latest
restart: unless-stopped
networks:
- foundry
ports:
- "9009:80"
watchtower:
image: containrrr/watchtower:latest
restart: unless-stopped
environment:
- WATCHTOWER_NOTIFICATION_SKIP_TITLE=true
- WATCHTOWER_NOTIFICATION_URL=ntfy://ntfy.ginnoir.com/watchtower?title=WatchtowerUpdates
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# ============================================================
# FAMAPP
# ============================================================
famapp:
image: ${FAMAPP_IMAGE:-ghcr.io/ginnoir/famapp:latest}
pull_policy: ${FAMAPP_PULL_POLICY:-always}
restart: unless-stopped
networks:
- famapp
environment:
NODE_ENV: production
AUTH_URL: ${AUTH_URL}
DATABASE_URL: postgres://${FAMAPP_DB_USER}:${FAMAPP_DB_PASSWORD}@famapp-db:5432/${FAMAPP_DB_NAME}
AUTH_SECRET: ${AUTH_SECRET}
AUTH_OIDC_ISSUER: ${AUTH_OIDC_ISSUER}
AUTH_OIDC_CLIENT_ID: ${AUTH_OIDC_CLIENT_ID}
AUTH_OIDC_CLIENT_SECRET: ${AUTH_OIDC_CLIENT_SECRET}
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY}
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY}
VAPID_SUBJECT: ${VAPID_SUBJECT}
NTFY_URL: ${NTFY_URL:-}
NTFY_TOPIC: ${NTFY_TOPIC:-}
LOG_LEVEL: ${LOG_LEVEL:-info}
RUN_MIGRATIONS: ${RUN_MIGRATIONS:-true}
MINIO_ENDPOINT: http://famapp-minio:9000
MINIO_ROOT_USER: ${famapp_MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${famapp_MINIO_ROOT_PASSWORD}
MINIO_BUCKET: ${famapp_MINIO_BUCKET}
OPENPLANTBOOK_CLIENT_ID: ${famapp_OPENPLANTBOOK_CLIENT_ID}
OPENPLANTBOOK_CLIENT_SECRET: ${famapp_OPENPLANTBOOK_CLIENT_SECRET}
ports:
- "3010:3000"
depends_on:
famapp-db:
condition: service_healthy
famapp-minio:
condition: service_healthy
famapp-db:
image: postgres:16-alpine
restart: unless-stopped
networks:
- famapp
environment:
POSTGRES_USER: ${FAMAPP_DB_USER}
POSTGRES_PASSWORD: ${FAMAPP_DB_PASSWORD}
POSTGRES_DB: ${FAMAPP_DB_NAME}
volumes:
- famapp_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${FAMAPP_DB_USER} -d ${FAMAPP_DB_NAME}"]
interval: 10s
timeout: 5s
retries: 5
famapp-minio:
image: minio/minio:latest
command: server /data --console-address ":9003"
restart: unless-stopped
environment:
MINIO_ROOT_USER: ${famapp_MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${famapp_MINIO_ROOT_PASSWORD}
volumes:
- garden_uploads:/data
ports:
- "9002:9000"
- "9003:9003"
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:9000/minio/health/live || exit 1"]
interval: 30s
timeout: 20s
retries: 3
start_period: 30s
networks:
- famapp
# ============================================================
# AUTHENTIK
# ============================================================
authentik-server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_IMAGE_TAG:-2024.12.3}
restart: unless-stopped
command: server
networks:
- authentik
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_DB_USER:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_DB_NAME:-authentik}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
ports:
- "9200:9000"
depends_on:
authentik-db:
condition: service_healthy
authentik-redis:
condition: service_healthy
authentik-worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_IMAGE_TAG:-2024.12.3}
restart: unless-stopped
command: worker
networks:
- authentik
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_DB_USER:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_DB_NAME:-authentik}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
depends_on:
authentik-db:
condition: service_healthy
authentik-redis:
condition: service_healthy
authentik-db:
image: postgres:16-alpine
restart: unless-stopped
networks:
- authentik
environment:
POSTGRES_USER: ${AUTHENTIK_DB_USER:-authentik}
POSTGRES_PASSWORD: ${AUTHENTIK_DB_PASSWORD}
POSTGRES_DB: ${AUTHENTIK_DB_NAME:-authentik}
volumes:
- authentik_db_data:/var/lib/postgresql/data
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${AUTHENTIK_DB_USER:-authentik} -d ${AUTHENTIK_DB_NAME:-authentik}",
]
interval: 10s
timeout: 5s
retries: 5
authentik-redis:
image: redis:7-alpine
restart: unless-stopped
networks:
- authentik
command: --save 60 1 --loglevel warning
volumes:
- authentik_redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
foundry:
owncloud:
resume:
media_external:
media:
famapp:
authentik:
volumes:
minio_data:
postgres_data:
cache:
famapp_db_data:
authentik_db_data:
authentik_redis_data:
garden_uploads:
+25
View File
@@ -0,0 +1,25 @@
# Pull the live production configs from valhalla into this repo, so local
# matches what's actually deployed. Run at the start of any session where
# you'll edit deployment config.
#
# Production is the source of truth. This OVERWRITES the local copies of
# docker-compose.yml, .env, and Caddyfile -- commit or stash first.
#
# Usage: powershell -File sync-prod.ps1
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
$server = "ginnoir@valhalla"
$repoDir = $PSScriptRoot
Write-Host "Syncing production configs from $server ..."
scp "${server}:~/htpc-download-box/docker-compose.yml" "$repoDir\docker-compose.yml"
scp "${server}:~/htpc-download-box/.env" "$repoDir\.env"
scp "${server}:/config/caddy/Caddyfile" "$repoDir\Caddyfile"
Write-Host "Done. Files synced:"
Get-ChildItem $repoDir -File |
Where-Object { $_.Name -in 'docker-compose.yml', '.env', 'Caddyfile' } |
Select-Object Name, Length, LastWriteTime