diff --git a/Makefile b/Makefile index 834f2ce..381374c 100755 --- a/Makefile +++ b/Makefile @@ -1,49 +1,50 @@ SERVICES=traefik whoami gitea nextcloud devbox docs-site +SERVICES_DIR=services .PHONY: up down restart backup init-env env-sync docs generate-docs serve-docs logs status up-% down-% up: for svc in $(SERVICES); do \ - if [ -f "$$svc/docker-compose.yml" ]; then \ + if [ -f "$(SERVICES_DIR)/$$svc/docker-compose.yml" ]; then \ echo "Starting $$svc..."; \ - (cd $$svc && docker compose --env-file ../.env `if [ -f .env ]; then echo --env-file .env; fi` up -d); \ + (cd $(SERVICES_DIR)/$$svc && docker compose --env-file ../../.env `if [ -f .env ]; then echo --env-file .env; fi` up -d); \ fi; \ done; \ up-%: @svc=$*; \ - if [ -f "$$svc/docker-compose.yml" ]; then \ + if [ -f "$(SERVICES_DIR)/$$svc/docker-compose.yml" ]; then \ echo "Starting $$svc..."; \ - (cd $$svc && docker compose --env-file ../.env `if [ -f .env ]; then echo --env-file .env; fi` up -d); \ + (cd $(SERVICES_DIR)/$$svc && docker compose --env-file ../../.env `if [ -f .env ]; then echo --env-file .env; fi` up -d); \ fi down-%: @svc=$*; \ - if [ -f "$$svc/docker-compose.yml" ]; then \ + if [ -f "$(SERVICES_DIR)/$$svc/docker-compose.yml" ]; then \ echo "Stopping $$svc..."; \ - (cd $$svc && docker compose down); \ + (cd $(SERVICES_DIR)/$$svc && docker compose down); \ fi down: @for svc in $(SERVICES); do \ - if [ -f "$$svc/docker-compose.yml" ]; then \ + if [ -f "$(SERVICES_DIR)/$$svc/docker-compose.yml" ]; then \ echo "Stopping $$svc..."; \ - (cd $$svc && docker compose down); \ + (cd $(SERVICES_DIR)/$$svc && docker compose down); \ fi; \ done restart: down up logs: - @echo "=== Traefik ===" && (cd traefik && docker compose logs --tail=10) - @echo "=== Gitea ===" && (cd gitea && docker compose logs --tail=10) - @echo "=== Nextcloud ===" && (cd nextcloud && docker compose logs --tail=10) + @echo "=== Traefik ===" && (cd $(SERVICES_DIR)/traefik && docker compose logs --tail=10) + @echo "=== Gitea ===" && (cd $(SERVICES_DIR)/gitea && docker compose logs --tail=10) + @echo "=== Nextcloud ===" && (cd $(SERVICES_DIR)/nextcloud && docker compose logs --tail=10) status: @for svc in $(SERVICES); do \ - if [ -f "$$svc/docker-compose.yml" ]; then \ + if [ -f "$(SERVICES_DIR)/$$svc/docker-compose.yml" ]; then \ echo "--- $$svc ---"; \ - (cd $$svc && docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"); \ + (cd $(SERVICES_DIR)/$$svc && docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"); \ fi; \ done @@ -69,6 +70,12 @@ generate-docs: python3 scripts/generate_docs.py docs: generate-docs + @command -v uvx >/dev/null 2>&1 || { \ + echo "Error: uvx is not installed or not in PATH."; \ + echo "Install with: curl -LsSf https://astral.sh/uv/install.sh | sh"; \ + echo "Then run: source $$HOME/.local/bin/env"; \ + exit 1; \ + } uvx --from mkdocs-material --with mkdocs-include-markdown-plugin mkdocs build serve-docs: generate-docs diff --git a/README.md b/README.md index 146f508..a5ca38d 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +> ⚠️ **Early development** +> +> This project is in early development and not all features are working yet. Use with caution. + # homelab-infra GitOps-managed infrastructure for homelab services using Docker Compose and Traefik for routing. @@ -39,8 +43,9 @@ homelab-infra/ ├── docs/ # MkDocs documentation source ├── mkdocs.yml # MkDocs configuration ├── scripts/ # Utility scripts -└── [service]/ # Per-service configurations - ├── docker-compose.yml - ├── .env.example - └── README.md +└── services/ # Per-service configurations + └── [service]/ + ├── docker-compose.yml + ├── .env.example + └── README.md ``` diff --git a/docs/dev/check_env_sync.md b/docs/dev/check_env_sync.md deleted file mode 100644 index 5f9e1c1..0000000 --- a/docs/dev/check_env_sync.md +++ /dev/null @@ -1,5 +0,0 @@ -# check_env_sync - -> Python script documentation - -::: scripts.check_env_sync diff --git a/docs/dev/generate_docs.md b/docs/dev/generate_docs.md deleted file mode 100644 index 959b338..0000000 --- a/docs/dev/generate_docs.md +++ /dev/null @@ -1,5 +0,0 @@ -# generate_docs - -> Python script documentation - -::: scripts.generate_docs diff --git a/docs/index.md b/docs/index.md index 58df80e..d7a8efa 100755 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,8 @@ # SJHL Documentation +!!! warning "Early development" + This documentation is in early development and not all features are working yet. Use with caution. + Welcome to Seirian & James' homelab documentation! This is a docs site that is built to easily show all the configs we use. MOst of the content is auto-generated from the actual config files, so it should always be up to date. This should be completely publically viewable as all private information is kept in `.env` files that are not committed to Git. It should provide good information on how to recover and rebuild the homelab if needed, and also just be a nice reference for how everything is configured. ## Data Classification @@ -7,7 +10,7 @@ Welcome to Seirian & James' homelab documentation! This is a docs site that is b | Type | Example | Git Repo? | Backup? | Location | |------|---------|-----------|---------|----------| | **Source Configs** | `docker-compose.yml`, `.env.example`, `Makefile` | Yes | No | `/srv/homelab-infra` | -| **Service Secrets** | `.env` (DB passwords, API keys) | No | Yes | `/srv/homelab-infra//.env` | +| **Service Secrets** | `.env` (DB passwords, API keys) | No | Yes | `/srv/homelab-infra/services//.env` | | **Runtime Configs** | `acme.json`, service configs | No | Yes | `/mnt/storage/docker-data/` | | **Persistent Data** | DB data, uploads, media | No | Yes | `/mnt/storage/docker-data/` | @@ -15,6 +18,7 @@ Welcome to Seirian & James' homelab documentation! This is a docs site that is b | Service | Image | Status | |---------|-------|--------| +| [cloudflare-ddns](services/cloudflare-ddns.md) | `favonia/cloudflare-ddns:1` | active | | [devbox](services/devbox.md) | `devbox-devcontainer` | active | | [docs-site](services/docs-site.md) | `nginx:alpine` | active | | [gitea](services/gitea.md) | `gitea/gitea:1.24.3` | active | @@ -29,21 +33,21 @@ Welcome to Seirian & James' homelab documentation! This is a docs site that is b git clone git@gitea.sjhl.nz:james/homelab-infra.git cd homelab-infra -# Set up environment -cp .env.example .env -# Edit .env with real values +# Create all missing .env files from templates +make init-env -# Set up service secrets -for svc in traefik gitea nextcloud qbittorrent jellyfin devbox obsidian; do - [ -f "$svc/.env.example" ] && cp "$svc/.env.example" "$svc/.env" - # Edit each .env with real secrets -done +# Edit .env and services/*/.env with real secrets # Create required Docker networks docker network create web -# Start everything +# Start and verify everything make up +make status + +# Optional: start/stop one service +# make up- +# make down- ``` ## Commands @@ -114,9 +118,13 @@ homelab-infra/ ├── backup.sh # Restic backup script ├── .pre-commit-config.yaml # Pre-commit hooks ├── docs/ # MkDocs documentation -├── mkdocs.yml # MkDocs config -├── scripts/ # Utility scripts -└── [service subdirs]/ # All services have their own subdir +├── mkdocs.yml # MkDocs config +├── scripts/ # Utility scripts +└── services/ # Service definitions + └── / # One folder per service + ├── docker-compose.yml + ├── .env.example + └── README.md ``` ## Data Locations diff --git a/docs/recovery.md b/docs/recovery.md deleted file mode 100755 index c953cd1..0000000 --- a/docs/recovery.md +++ /dev/null @@ -1,66 +0,0 @@ -# Recovery Reference - -This document was generated from `docker inspect` dumps taken after the accidental deletion of `/home/james`. It preserves the last known-good configuration state for rebuilding services. - -## Container Summary - -### traefik (Reverse Proxy) -- **Image:** `traefik:v3.6` -- **Config path (old):** `/home/james/apps/traefik/` -- **Volumes:** certs, dynamic config, docker.sock (ro) -- **Networks:** `web` (external) -- **Ports:** 80, 443, 8082->8080 -- **Let's Encrypt:** HTTP challenge, email `letsencrypt.debug772@passmail.net` - -### gitea -- **Image:** `gitea/gitea:1.24.3` -- **Data path (old):** `/mnt/storage/apps/gitea` -- **DB:** SQLite3 -- **SSH:** port 222 -- **URL:** `https://gitea.sjhl.nz/` - -### nextcloud (AIO) -- **Image:** `ghcr.io/nextcloud-releases/all-in-one:latest` -- **Data path (old):** `/mnt/storage/apps/nextcloud` -- **Components:** mastercontainer, apache, nextcloud, database (PostgreSQL 17), redis, collabora, imaginary, whiteboard, notify-push -- **URL:** `https://nextcloud.sjhl.nz` -- **AIO mgmt:** port 8081 -- **Apache port:** 11000 - -### qbittorrent + VPN -- **VPN:** ProtonVPN WireGuard, New Zealand, port forwarding -- **Data path (old):** `/home/james/apps/qbittorrent/` -- **Downloads:** `/mnt/storage/torrents` -- **Components:** gluetun, qbittorrent, jackett -- **Ports:** 8080 (qBittorrent), 9117 (Jackett) - -### jellyfin -- **Image:** `jellyfin/jellyfin:10.10.7` -- **Config path (old):** `/home/james/apps/jellyfin-config` -- **Media:** `/mnt/storage` -- **Port:** 8096 -- **Network:** default bridge (NOT on `web` network) -- **Note:** Was not behind Traefik in old setup - -### devbox -- **Image:** `devbox-devcontainer` (local build) -- **Data path (old):** `/mnt/storage/apps/devbox` -- **Port:** 46573->2222 (SSH) -- **Memory limit:** 10GB, swap 20GB - -### obsidian-livesync -- **Image:** `couchdb:latest` -- **Status:** Exited (127) at time of backup -- **Note:** No inspect data captured, container was stopped - -## Recovery Path Mapping - -| Old Path | New Path | -|----------|----------| -| `/home/james/apps/traefik/` | `/mnt/storage/docker-data/traefik/` | -| `/home/james/apps/qbittorrent/` | `/mnt/storage/docker-data/qbittorrent/` | -| `/home/james/apps/jellyfin-config` | `/mnt/storage/docker-data/jellyfin/config` | -| `/mnt/storage/apps/gitea` | `/mnt/storage/docker-data/gitea` | -| `/mnt/storage/apps/nextcloud` | `/mnt/storage/docker-data/nextcloud` | -| `/mnt/storage/apps/devbox` | `/mnt/storage/docker-data/devbox` | -| `/mnt/storage/torrents` | `/mnt/storage/torrents` (unchanged) | diff --git a/docs/services/cloudflare-ddns.md b/docs/services/cloudflare-ddns.md new file mode 100644 index 0000000..ac996be --- /dev/null +++ b/docs/services/cloudflare-ddns.md @@ -0,0 +1,15 @@ +# cloudflare-ddns + + +> Below are the configuration files for this service. For details on how to deploy or customize, refer to the README above or the official documentation for the service. +## Docker Compose Configuration + +```yaml +--8<-- "services/cloudflare-ddns/docker-compose.yml" +``` + +## Environment Variables (`.env.example`) + +```bash +--8<-- "services/cloudflare-ddns/.env.example" +``` diff --git a/docs/services/devbox.md b/docs/services/devbox.md index 5ca3732..872e4ef 100755 --- a/docs/services/devbox.md +++ b/docs/services/devbox.md @@ -5,11 +5,11 @@ ## Docker Compose Configuration ```yaml ---8<-- "devbox/docker-compose.yml" +--8<-- "services/devbox/docker-compose.yml" ``` ## Environment Variables (`.env.example`) ```bash ---8<-- "devbox/.env.example" +--8<-- "services/devbox/.env.example" ``` diff --git a/docs/services/docs-site.md b/docs/services/docs-site.md index 686ba3d..20de182 100644 --- a/docs/services/docs-site.md +++ b/docs/services/docs-site.md @@ -1,10 +1,10 @@ # docs-site ---8<-- "docs-site/README.md" +--8<-- "services/docs-site/README.md" > Below are the configuration files for this service. For details on how to deploy or customize, refer to the README above or the official documentation for the service. ## Docker Compose Configuration ```yaml ---8<-- "docs-site/docker-compose.yml" +--8<-- "services/docs-site/docker-compose.yml" ``` diff --git a/docs/services/gitea.md b/docs/services/gitea.md index 19ac060..802dd52 100755 --- a/docs/services/gitea.md +++ b/docs/services/gitea.md @@ -5,11 +5,11 @@ ## Docker Compose Configuration ```yaml ---8<-- "gitea/docker-compose.yml" +--8<-- "services/gitea/docker-compose.yml" ``` ## Environment Variables (`.env.example`) ```bash ---8<-- "gitea/.env.example" +--8<-- "services/gitea/.env.example" ``` diff --git a/docs/services/nextcloud.md b/docs/services/nextcloud.md index 1fecd77..d43532f 100755 --- a/docs/services/nextcloud.md +++ b/docs/services/nextcloud.md @@ -5,11 +5,11 @@ ## Docker Compose Configuration ```yaml ---8<-- "nextcloud/docker-compose.yml" +--8<-- "services/nextcloud/docker-compose.yml" ``` ## Environment Variables (`.env.example`) ```bash ---8<-- "nextcloud/.env.example" +--8<-- "services/nextcloud/.env.example" ``` diff --git a/docs/services/obsidian-livesync.md b/docs/services/obsidian-livesync.md index 967f364..7fd00b4 100755 --- a/docs/services/obsidian-livesync.md +++ b/docs/services/obsidian-livesync.md @@ -1,16 +1,16 @@ # obsidian-livesync ---8<-- "obsidian-livesync/README.md" +--8<-- "services/obsidian-livesync/README.md" > Below are the configuration files for this service. For details on how to deploy or customize, refer to the README above or the official documentation for the service. ## Docker Compose Configuration ```yaml ---8<-- "obsidian-livesync/docker-compose.yml" +--8<-- "services/obsidian-livesync/docker-compose.yml" ``` ## Environment Variables (`.env.example`) ```bash ---8<-- "obsidian-livesync/.env.example" +--8<-- "services/obsidian-livesync/.env.example" ``` diff --git a/docs/services/traefik.md b/docs/services/traefik.md index 1871fbf..3536d81 100755 --- a/docs/services/traefik.md +++ b/docs/services/traefik.md @@ -1,16 +1,16 @@ # traefik ---8<-- "traefik/README.md" +--8<-- "services/traefik/README.md" > Below are the configuration files for this service. For details on how to deploy or customize, refer to the README above or the official documentation for the service. ## Docker Compose Configuration ```yaml ---8<-- "traefik/docker-compose.yml" +--8<-- "services/traefik/docker-compose.yml" ``` ## Environment Variables (`.env.example`) ```bash ---8<-- "traefik/.env.example" +--8<-- "services/traefik/.env.example" ``` diff --git a/docs/services/whoami.md b/docs/services/whoami.md index 2c0c39c..402d779 100755 --- a/docs/services/whoami.md +++ b/docs/services/whoami.md @@ -5,5 +5,5 @@ ## Docker Compose Configuration ```yaml ---8<-- "whoami/docker-compose.yml" +--8<-- "services/whoami/docker-compose.yml" ``` diff --git a/mkdocs.yml b/mkdocs.yml index 2db5827..3fe960b 100755 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,8 +9,8 @@ markdown_extensions: - . nav: - Home: index.md -- Recovery Reference: recovery.md - Services: + - Cloudflare Ddns: services/cloudflare-ddns.md - Devbox: services/devbox.md - Docs Site: services/docs-site.md - Gitea: services/gitea.md diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index bb7f523..cdb0a37 100755 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -4,12 +4,11 @@ Auto-generate markdown docs from docker-compose.yml files. Usage: python3 scripts/generate_docs.py """ import yaml -import os import sys from pathlib import Path REPO_ROOT = Path(__file__).parent.parent -SERVICES_DIR = REPO_ROOT +SERVICES_DIR = REPO_ROOT / "services" DOCS_DIR = REPO_ROOT / "docs" / "services" DISPLAY_NAME_OVERRIDES = { @@ -175,6 +174,8 @@ def format_security(sec): def generate_service_md(service_name, service_dir): """Generate service doc that includes docker-compose.yml, Dockerfile, README, and .env.example.""" + include_base = f"services/{service_dir.name}" + lines = [] lines.append(f"# {service_name}") lines.append("") @@ -183,7 +184,7 @@ def generate_service_md(service_name, service_dir): # Include README.md first if exists readme_path = service_dir / "README.md" if readme_path.exists(): - lines.append(f'--8<-- "{service_dir.name}/README.md"') + lines.append(f'--8<-- "{include_base}/README.md"') lines.append("> Below are the configuration files for this service. For details on how to deploy or customize, refer to the README above or the official documentation for the service.") # Include docker-compose.yml @@ -192,7 +193,7 @@ def generate_service_md(service_name, service_dir): lines.append("## Docker Compose Configuration") lines.append("") lines.append("```yaml") - lines.append(f'--8<-- "{service_dir.name}/docker-compose.yml"') + lines.append(f'--8<-- "{include_base}/docker-compose.yml"') lines.append("```") lines.append("") @@ -202,7 +203,7 @@ def generate_service_md(service_name, service_dir): lines.append("## Dockerfile") lines.append("") lines.append("```dockerfile") - lines.append(f'--8<-- "{service_dir.name}/Dockerfile"') + lines.append(f'--8<-- "{include_base}/Dockerfile"') lines.append("```") lines.append("") @@ -212,7 +213,7 @@ def generate_service_md(service_name, service_dir): lines.append("## Environment Variables (`.env.example`)") lines.append("") lines.append("```bash") - lines.append(f'--8<-- "{service_dir.name}/.env.example"') + lines.append(f'--8<-- "{include_base}/.env.example"') lines.append("```") lines.append("") @@ -275,6 +276,10 @@ def cleanup_stale_service_docs(service_names): def main(): + if not SERVICES_DIR.exists(): + print(f"Services directory not found: {SERVICES_DIR}", file=sys.stderr) + return + all_services = {} # Find all service directories with docker-compose.yml @@ -284,9 +289,6 @@ def main(): compose_file = item / "docker-compose.yml" if not compose_file.exists(): continue - # Skip scripts/docs directories - if item.name in ("scripts", "docs", "__pycache__"): - continue service_name = item.name try: diff --git a/services/cloudflare-ddns/.env.example b/services/cloudflare-ddns/.env.example new file mode 100644 index 0000000..5a6221c --- /dev/null +++ b/services/cloudflare-ddns/.env.example @@ -0,0 +1,6 @@ +# cloudflare-ddns/.env +# Copy to .env and fill in real values. NEVER commit .env. + +CLOUDFLARE_API_TOKEN=__CHANGEME__ +DOMAINS=example.org,www.example.org +PROXIED=true diff --git a/services/cloudflare-ddns/docker-compose.yml b/services/cloudflare-ddns/docker-compose.yml new file mode 100644 index 0000000..0305cd4 --- /dev/null +++ b/services/cloudflare-ddns/docker-compose.yml @@ -0,0 +1,34 @@ +services: + cloudflare-ddns: + image: favonia/cloudflare-ddns:1 + # Prefer "1" or "1.x.y" in production. + # + # - "1" tracks the latest stable release whose major version is 1 + # - "1.x.y" pins one specific stable version + # - "latest" moves to each new stable release and may pick up breaking + # changes in a future major release, so it is not recommended in production + # - "edge" tracks the latest unreleased development build + network_mode: host + # Optional. This bypasses network isolation and makes IPv6 easier. + # See "Use IPv6 without sharing the host network". + restart: always + # Restart the updater after reboot + user: "1000:1000" + # Run the updater with specific user and group IDs (in that order). + # You can change the two numbers based on your need. + read_only: true + # Make the container filesystem read-only (optional but recommended) + cap_drop: [all] + # Drop all Linux capabilities (optional but recommended) + security_opt: [no-new-privileges:true] + # Another protection to restrict superuser privileges (optional but recommended) + env_file: + - .env + environment: + - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN} + # Your Cloudflare API token + - DOMAINS=${DOMAINS} + # Your domains (separated by commas) + - PROXIED=${PROXIED:-true} + # Leaning toward using Cloudflare's proxy for these domains (optional) + # Existing DNS records in Cloudflare keep their current proxy statuses \ No newline at end of file diff --git a/devbox/.env.example b/services/devbox/.env.example similarity index 100% rename from devbox/.env.example rename to services/devbox/.env.example diff --git a/devbox/docker-compose.yml b/services/devbox/docker-compose.yml similarity index 100% rename from devbox/docker-compose.yml rename to services/devbox/docker-compose.yml diff --git a/docs-site/README.md b/services/docs-site/README.md similarity index 100% rename from docs-site/README.md rename to services/docs-site/README.md diff --git a/docs-site/docker-compose.yml b/services/docs-site/docker-compose.yml similarity index 100% rename from docs-site/docker-compose.yml rename to services/docs-site/docker-compose.yml diff --git a/gitea/.env.example b/services/gitea/.env.example similarity index 100% rename from gitea/.env.example rename to services/gitea/.env.example diff --git a/gitea/docker-compose.yml b/services/gitea/docker-compose.yml similarity index 100% rename from gitea/docker-compose.yml rename to services/gitea/docker-compose.yml diff --git a/nextcloud/.env.example b/services/nextcloud/.env.example similarity index 100% rename from nextcloud/.env.example rename to services/nextcloud/.env.example diff --git a/nextcloud/docker-compose.yml b/services/nextcloud/docker-compose.yml similarity index 100% rename from nextcloud/docker-compose.yml rename to services/nextcloud/docker-compose.yml diff --git a/obsidian-livesync/.env.example b/services/obsidian-livesync/.env.example similarity index 100% rename from obsidian-livesync/.env.example rename to services/obsidian-livesync/.env.example diff --git a/obsidian-livesync/README.md b/services/obsidian-livesync/README.md similarity index 100% rename from obsidian-livesync/README.md rename to services/obsidian-livesync/README.md diff --git a/obsidian-livesync/docker-compose.yml b/services/obsidian-livesync/docker-compose.yml similarity index 100% rename from obsidian-livesync/docker-compose.yml rename to services/obsidian-livesync/docker-compose.yml diff --git a/traefik/.env.example b/services/traefik/.env.example similarity index 100% rename from traefik/.env.example rename to services/traefik/.env.example diff --git a/traefik/README.md b/services/traefik/README.md similarity index 100% rename from traefik/README.md rename to services/traefik/README.md diff --git a/traefik/docker-compose.yml b/services/traefik/docker-compose.yml similarity index 100% rename from traefik/docker-compose.yml rename to services/traefik/docker-compose.yml diff --git a/traefik/dynamic/nextcloud.yml b/services/traefik/dynamic/nextcloud.yml similarity index 100% rename from traefik/dynamic/nextcloud.yml rename to services/traefik/dynamic/nextcloud.yml diff --git a/traefik/dynamic/tls.yaml b/services/traefik/dynamic/tls.yaml similarity index 100% rename from traefik/dynamic/tls.yaml rename to services/traefik/dynamic/tls.yaml diff --git a/whoami/docker-compose.yml b/services/whoami/docker-compose.yml similarity index 100% rename from whoami/docker-compose.yml rename to services/whoami/docker-compose.yml