From d0540f7eb80590d0bf182c1f277f89c772884b3f Mon Sep 17 00:00:00 2001 From: Oskar Kapala Date: Thu, 7 May 2026 21:16:03 +0200 Subject: [PATCH] Add infrastructure standards and deployment conventions --- .gitignore | 29 +++++++ .idea/.gitignore | 10 --- .idea/codeStyles/codeStyleConfig.xml | 5 -- .idea/homelab-codex-ws.iml | 9 -- .idea/misc.xml | 7 -- .idea/modules.xml | 8 -- .idea/vcs.xml | 6 -- README.md | 95 ++++++---------------- __pycache__/ollama_client.cpython-313.pyc | Bin 2460 -> 0 bytes docs/deployment.md | 40 +++++++++ docs/standards.md | 68 ++++++++++++++++ hosts/piha/README.md | 14 ++++ hosts/saturn/README.md | 13 +++ hosts/solaria/README.md | 12 +++ hosts/vps/README.md | 13 +++ hosts/vps/services.txt | 1 + scripts/deploy/deploy-node.sh | 66 +++++++++++++++ scripts/deploy/orchestrate-deploy.sh | 15 ++++ services/.gitkeep | 0 services/npm/docker-compose.yml | 12 +++ 20 files changed, 307 insertions(+), 116 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/homelab-codex-ws.iml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 __pycache__/ollama_client.cpython-313.pyc create mode 100644 docs/deployment.md create mode 100644 docs/standards.md create mode 100644 hosts/piha/README.md create mode 100644 hosts/saturn/README.md create mode 100644 hosts/solaria/README.md create mode 100644 hosts/vps/README.md create mode 100644 hosts/vps/services.txt create mode 100755 scripts/deploy/deploy-node.sh create mode 100755 scripts/deploy/orchestrate-deploy.sh create mode 100644 services/.gitkeep create mode 100644 services/npm/docker-compose.yml diff --git a/.gitignore b/.gitignore index de6a291..0de12c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,31 @@ +# Environment variables .env +*.env +!*.env.example + +# IDE artifacts +.idea/ +.vscode/ +*.swp +*.swo + +# Python +__pycache__/ +*.py[cod] +*$py.class +venv/ +.venv/ + +# Tools .aider* +.codex + +# OS files +.DS_Store +Thumbs.db + +# Runtime data (should live outside repo, but adding just in case) +data/ +logs/ +temp/ +tmp/ diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 30cf57e..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Ignored default folder with query files -/queries/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a1..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/homelab-codex-ws.iml b/.idea/homelab-codex-ws.iml deleted file mode 100644 index d6ebd48..0000000 --- a/.idea/homelab-codex-ws.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 51da044..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 8247b25..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index c5fa3c9..b32bc90 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,38 @@ -# Homelab Current State +# Homelab Codex -## Description +GitOps-lite orchestration for a distributed homelab environment. -This repository documents the current known state of the homelab. +## Architecture -The documentation is based only on stated facts. Missing details are recorded as unknowns and need clarification. +The homelab consists of several nodes connected via a Tailscale internal mesh. -## Shared context sync lock +| Host | Role | Description | +|------|------|-------------| +| **SATURN** | Primary Node | Development, orchestration, and git source of truth (commit node). | +| **SOLARIA** | Compute Node | GPU, inference, and heavy compute workloads. | +| **PIHA** | Infra Node | Core infrastructure services, automation, and monitoring. | +| **VPS** | Edge Node | Public ingress, reverse proxy, and edge services. | -`sync-context.sh` uses a git-tracked `.context.lock` file to serialize updates to `codex_context.yaml`. +## Repository Structure -If `codex_context.yaml` has changes, the script: +- `docs/`: [Infrastructure Standards](docs/standards.md) and [Deployment Conventions](docs/deployment.md). +- `hosts/`: Host-specific configurations and service assignments. +- `services/`: Reusable Docker Compose service definitions. +- `scripts/`: Deployment and management scripts. -1. pulls with rebase -2. aborts if `.context.lock` already exists and prints its contents -3. creates `.context.lock` with `hostname`, `username`, and UTC `timestamp` -4. commits and pushes the lock with message `lock shared context` -5. validates `codex_context.yaml`, commits it, and pushes -6. removes `.context.lock`, commits `unlock shared context`, and pushes +## Getting Started -If any step fails after lock creation, the script prints `Lock may need manual cleanup` and leaves the lock in place. +1. **Standardization**: Follow the [Infrastructure Standards](docs/standards.md). +2. **Deployment**: See [Deployment Conventions](docs/deployment.md) for how to roll out changes. +3. **SATURN**: Remember that SATURN is the only node where commits should be made. -Manual cleanup: - -1. inspect `.context.lock` -2. confirm the owning host/user is no longer updating context -3. remove the file -4. commit `unlock shared context` -5. push - -## Current configuration - -- Main server hardware: Raspberry Pi 5 -- Core stack: - - Docker - - Portainer - - Nginx Proxy Manager -- Network position: behind NAT -- Public access path: Nginx Proxy Manager with HTTPS using Let's Encrypt -- Private access path: Tailscale -- Known port forwarding: - - External ports `80-81` forward to internal ports `4480-4481` - - External port `443` forwards to internal port `4443` - -## Documentation index +## Documentation Index +- [Infrastructure Standards](docs/standards.md) +- [Deployment Conventions](docs/deployment.md) - [Hardware](docs/hardware.md) -- [Core stack](docs/core-stack.md) - [Networking](docs/networking.md) -- [Access](docs/access.md) - [Services](docs/services.md) -- [Hetzner VPS](docs/hetzner-vps.md) -- [Joplin Server](docs/joplin-server.md) -- [Unknowns and clarification questions](docs/questions.md) -## Known facts - -- The homelab has one known main server: Raspberry Pi 5. -- Docker is part of the current stack. -- Portainer is part of the current stack. -- Nginx Proxy Manager is part of the current stack. -- The homelab is behind NAT. -- Public services are exposed through Nginx Proxy Manager with HTTPS certificates from Let's Encrypt. -- Private access is provided through Tailscale. -- A Hetzner VPS handoff has been received from another Codex session. -- The Hetzner VPS hostname is `ubuntu-4gb-hel1-1`. -- The Hetzner VPS Tailscale IP is `100.95.58.48`. -- `100.108.208.3` is explicitly not the Hetzner VPS Tailscale IP. -- Nginx Proxy Manager is running on the Hetzner VPS as container `npm`. -- Joplin Server files exist on the Hetzner VPS, but Joplin is not running yet. - -## Unknown / needs clarification - -- Operating system and version on the Raspberry Pi 5. -- Storage layout and attached disks. -- Network interface configuration. -- LAN IP addresses. -- Public domain names for the Raspberry Pi 5 services. -- List of all running containers. -- Exact Nginx Proxy Manager proxy host configuration. -- Tailscale tailnet, device name, and subnet/exit-node configuration if any. -- Backup configuration. -- Monitoring and alerting configuration. -- Whether the Hetzner VPS is part of the homelab, a separate public edge, or both. +--- +*Note: This repository documents the state of the homelab. Runtime state lives outside the repository in `/opt/homelab`.* diff --git a/__pycache__/ollama_client.cpython-313.pyc b/__pycache__/ollama_client.cpython-313.pyc deleted file mode 100644 index d1935064b2e13cb93a9aeeb74c2fbdc46cf5d030..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2460 zcmah~OKcNI7@qa6ch`@#v7LkvXuNp7O}-ey5w-a94?yAA$HDDM(>XB2rly!Z>>uVV#vD>==`9bk)QwYb#P* z3ft?d>=M$yxpb#+y-Woqg5n+%6@EUE+vm%&UlfpChCO5gPNgqA?F>^wcD3 ztBfX@NhaofMHLO%-r-ZHj(m1xd?axy-!NraMqfOh(UWRsN;j>(J$rh(du%3d^9Iqg zhGp{vTREa7t8O^X2r#qoTj_@xf@c)h2vXQiSDaBe#oftj%&2paPKj!Gs{qAn>01Mi z^0iF>DSWqURIKecPTdGo3vk242{o=FDxNMz@hZLpoaSmq|Kk~?^4&4wQv8Z^z~w?3 zJHjSH@A47qtwM>&dY-cCiN--Wz*oC)8Lf%2ZoOnHI=g!PntQ~p1ZdinAf4M$3)+o7 ztO}*zb|zdhiXs`lq1}hrD4Jke*2dbfUVox-JtiP^2SOUJY#QMcP3!q^HzD9`+lbT6 znXV|rr}0Pl_UbyZwh}h-p?SR~@L`MNL-@UxTpL>Yr()y{wZh#(WRVd*fVwx<&{~rl zZLd*1F7T%!S@$23khRKKJq_N!&&~v@+)%83C?7~+Y?v5d?nvq>Ol&S^ViI%N9MLn_ z7LvMVVa+-gV{La94rFXGi%nCV#HP)g7Qt#Z?}@8MI-ZU8(z7_Oo0nA*hemq| zCoR*#GU`RA4z71xGvj(Dqh{6dWF`$2&}o1o)x1o)K#>Q2<^;U@o9JbzrQF;;-%zUG zU#vS&4sZTb;DY?kPhNYGFErOV+qvYGOJ4bfuYP9t?Q~I;Ur3?3^S90ygat`nl6ID) zo%ez4Du<%QmhR%Fo^r5$*@Jv7%gF8HZw@T`1McVxDKd9q_QI0XR+8H84a|p&ll-s%IT*QpeD+3Rx)h8R z#n>AG4lGJNZ(obZAD-)lzc5Kh;i>NhdwAI?@ryBDxAM}<$mO+rUy;;{l#GK zqS*WP1;pf>mPZm}}QCM)#+*lNw-!8L&{AGRx`F_~gJRqTmd*zS&*+&iA zfqvBQ9q(ChQuDE`?BiWqkNVjsJOlI-e+!*E0)q_u/services.txt` (or similar mapping). +- Commit and push changes to the Forgejo instance. + +### 2. Deployment (on Execution Node) + +Execution nodes run a deployment script (e.g., via cron or manual trigger) that: + +1. Performs a `git pull` from the source of truth. +2. Identifies services assigned to this host. +3. Symlinks or copies `services//docker-compose.yml` to `/opt/homelab/services/`. +4. Runs `docker compose up -d --remove-orphans`. + +## Host-Local Overrides + +If a service requires host-specific configuration (e.g., unique device paths for GPUs on SOLARIA): + +1. Create a `docker-compose.override.yml` in `/opt/homelab/config//`. +2. The deployment script should include this override if it exists. + +## Secrets Management + +- **Do NOT commit secrets to Git.** +- Secrets should be placed in `/opt/homelab/config//.env` on the target host. +- The deployment script should ensure these are sourced by Docker Compose. diff --git a/docs/standards.md b/docs/standards.md new file mode 100644 index 0000000..fd10174 --- /dev/null +++ b/docs/standards.md @@ -0,0 +1,68 @@ +# Infrastructure Standards + +This document defines the standards and conventions for the homelab GitOps-lite environment. + +## Host Architecture + +| Host | Role | Description | +|------|------|-------------| +| **SATURN** | Primary Node | Development, orchestration, and git source of truth (commit node). | +| **SOLARIA** | Compute Node | GPU, inference, and heavy compute workloads. | +| **PIHA** | Infra Node | Core infrastructure services, automation, and monitoring. | +| **VPS** | Edge Node | Public ingress, reverse proxy, and edge services. | + +## Directory Layout + +### Repository Layout + +```text +/ +├── docs/ # Infrastructure documentation +├── hosts/ # Host-specific configurations +│ ├── saturn/ +│ ├── solaria/ +│ ├── piha/ +│ └── vps/ +├── services/ # Reusable service definitions (Docker Compose) +├── scripts/ # Management and deployment scripts +└── README.md +``` + +### Runtime Layout (on Execution Nodes) + +Runtime state must live outside the repository to keep it immutable and clean. + +```text +/opt/homelab/ +├── services/ # Active docker-compose files (deployed from git) +├── data/ # Persistent volume data (backed up) +├── config/ # Host-local overrides and secrets (not in git) +└── logs/ # Service logs +``` + +## Docker Compose Standards + +1. **File Naming**: Use `docker-compose.yml`. +2. **Container Naming**: `service-name`. +3. **Restarts**: Always use `restart: unless-stopped`. +4. **Networking**: + - Use `tailscale` internal mesh for inter-host communication. + - Expose ports only when necessary. +5. **Volumes**: Use named volumes or absolute paths to `/opt/homelab/data/service-name`. + +## Environment Variables + +- `.env`: Default environment variables (checked into git if safe). +- `.env.local`: Host-specific overrides (not in git). + +## Naming Conventions + +- Hosts: All caps (SATURN, SOLARIA, PIHA, VPS). +- Services: Kebab-case (e.g., `ollama-server`). +- Containers: Match service name. + +## Deployment Flow + +1. Changes are committed and pushed to **SATURN**. +2. Execution nodes (SOLARIA, PIHA, VPS) pull changes. +3. Deployment scripts trigger `docker compose up -d`. diff --git a/hosts/piha/README.md b/hosts/piha/README.md new file mode 100644 index 0000000..a68512c --- /dev/null +++ b/hosts/piha/README.md @@ -0,0 +1,14 @@ +# PIHA - Infrastructure + Automation Node + +## Role +- Core network services. +- Home automation (Home Assistant). +- Monitoring and logging. + +## Configured Services +- Home Assistant +- Mosquitto (MQTT) +- Zigbee2MQTT + +## Runtime Data +- `/opt/homelab/data/homeassistant` diff --git a/hosts/saturn/README.md b/hosts/saturn/README.md new file mode 100644 index 0000000..b5f0e9e --- /dev/null +++ b/hosts/saturn/README.md @@ -0,0 +1,13 @@ +# SATURN - Primary Development & Orchestration Node + +## Role +- Source of truth for all infrastructure Git repositories. +- Primary workstation for development and configuration management. +- The ONLY node allowed to commit changes to the homelab repositories. + +## Configured Services +(List services deployed on this host) +- Forgejo (Git source of truth) + +## Runtime Data +- `/opt/homelab/data/forgejo` diff --git a/hosts/solaria/README.md b/hosts/solaria/README.md new file mode 100644 index 0000000..72048b0 --- /dev/null +++ b/hosts/solaria/README.md @@ -0,0 +1,12 @@ +# SOLARIA - Compute / GPU / Inference Node + +## Role +- High-performance compute tasks. +- GPU-accelerated workloads (LLM inference, transcoding). + +## Configured Services +- Ollama +- Open WebUI + +## Runtime Data +- `/opt/homelab/data/ollama` diff --git a/hosts/vps/README.md b/hosts/vps/README.md new file mode 100644 index 0000000..ba2949c --- /dev/null +++ b/hosts/vps/README.md @@ -0,0 +1,13 @@ +# VPS - Public Edge + Ingress Node + +## Role +- Public-facing reverse proxy. +- HTTPS termination (Let's Encrypt). +- Edge security and routing. + +## Configured Services +- Nginx Proxy Manager (NPM) +- Authelia / Authentik (Auth) + +## Runtime Data +- `/opt/homelab/data/npm` diff --git a/hosts/vps/services.txt b/hosts/vps/services.txt new file mode 100644 index 0000000..b235581 --- /dev/null +++ b/hosts/vps/services.txt @@ -0,0 +1 @@ +npm diff --git a/scripts/deploy/deploy-node.sh b/scripts/deploy/deploy-node.sh new file mode 100755 index 0000000..05e3ec1 --- /dev/null +++ b/scripts/deploy/deploy-node.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# deployment-node.sh - To be run on the execution node (SOLARIA, PIHA, VPS) +# This script pulls the latest changes and ensures services are running. + +set -e + +# Configuration +REPO_PATH="${HOME}/homelab-codex-ws" +RUNTIME_PATH="/opt/homelab" +HOSTNAME=$(hostname | tr '[:lower:]' '[:upper:]') + +echo "--- Starting Deployment on ${HOSTNAME} ---" + +# 1. Update Repository +if [ ! -d "$REPO_PATH" ]; then + echo "Error: Repository not found at $REPO_PATH" + exit 1 +fi + +cd "$REPO_PATH" +echo "Pulling latest changes..." +git pull + +# 2. Identify Services +# Based on our convention, we look for services assigned to this host +# For now, we'll check if a 'services.txt' exists in the host folder +SERVICE_LIST="${REPO_PATH}/hosts/$(hostname | tr '[:upper:]' '[:lower:]')/services.txt" + +if [ ! -f "$SERVICE_LIST" ]; then + echo "No services.txt found for ${HOSTNAME}. Skipping service deployment." + exit 0 +fi + +# 3. Deploy Services +while IFS= read -r service || [ -n "$service" ]; do + [[ "$service" =~ ^#.*$ ]] && continue # Skip comments + [[ -z "$service" ]] && continue # Skip empty lines + + echo "Deploying service: ${service}..." + + COMPOSE_FILE="${REPO_PATH}/services/${service}/docker-compose.yml" + + if [ ! -f "$COMPOSE_FILE" ]; then + echo "Warning: Compose file not found for ${service} at ${COMPOSE_FILE}" + continue + fi + + # Target directory in runtime + TARGET_DIR="${RUNTIME_PATH}/services/${service}" + mkdir -p "$TARGET_DIR" + + # We use the compose file from the repo directly + # but we can also handle overrides here + OVERRIDE_FILE="${RUNTIME_PATH}/config/${service}/docker-compose.override.yml" + + COMPOSE_CMD="docker compose -f ${COMPOSE_FILE}" + if [ -f "$OVERRIDE_FILE" ]; then + echo "Using override file for ${service}" + COMPOSE_CMD="${COMPOSE_CMD} -f ${OVERRIDE_FILE}" + fi + + $COMPOSE_CMD up -d --remove-orphans + +done < "$SERVICE_LIST" + +echo "--- Deployment Complete ---" diff --git a/scripts/deploy/orchestrate-deploy.sh b/scripts/deploy/orchestrate-deploy.sh new file mode 100755 index 0000000..3045438 --- /dev/null +++ b/scripts/deploy/orchestrate-deploy.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# orchestrate-deploy.sh - To be run on SATURN +# Triggers deployment on remote execution nodes. + +set -e + +HOSTS=("solaria" "piha" "vps") +USER="oskar" # Default user + +for HOST in "${HOSTS[@]}"; do + echo ">>> Triggering deployment on ${HOST}..." + ssh "${USER}@${HOST}" "bash ~/homelab-codex-ws/scripts/deploy/deploy-node.sh" +done + +echo ">>> All deployments triggered." diff --git a/services/.gitkeep b/services/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/services/npm/docker-compose.yml b/services/npm/docker-compose.yml new file mode 100644 index 0000000..07ce9f6 --- /dev/null +++ b/services/npm/docker-compose.yml @@ -0,0 +1,12 @@ +services: + npm: + image: 'jc21/nginx-proxy-manager:latest' + container_name: npm + restart: unless-stopped + ports: + - '80:80' + - '81:81' + - '443:443' + volumes: + - /opt/homelab/data/npm/data:/data + - /opt/homelab/data/npm/letsencrypt:/etc/letsencrypt