From 4dbb0b9180128285b663278e812f6f6610741a75 Mon Sep 17 00:00:00 2001 From: knight Date: Wed, 31 Dec 2025 13:29:43 -0500 Subject: [PATCH] Initial commit: 23 docker stacks for GitOps deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stacks included: - Infrastructure: traefik, authentik, gitea, registry, watchtower, dockge - Monitoring: smokeping, changedetection - Apps: ghost, gollum, wallabag, radicale, invidious, xbackbone, filebrowser, syncthing, zerotier - Custom: obsidian-tools, memento, perilous, ramz, bookclub, brain 🤖 Generated with Claude Code --- .gitea/workflows/deploy.yml | 114 +++++++++++++++++ .gitignore | 26 ++++ README.md | 141 ++++++++++++++++++++++ scripts/deploy.sh | 48 ++++++++ stacks/authentik/.env.template | 3 + stacks/authentik/docker-compose.yml | 75 ++++++++++++ stacks/bookclub/.env.template | 14 +++ stacks/bookclub/Dockerfile | 7 ++ stacks/bookclub/docker-compose.yml | 20 +++ stacks/bookclub/package.json | 13 ++ stacks/bookclub/server.js | 51 ++++++++ stacks/brain/.env.template | 3 + stacks/brain/docker-compose.yml | 32 +++++ stacks/changedetection/.env.template | 1 + stacks/changedetection/docker-compose.yml | 40 ++++++ stacks/dockge/.env.template | 1 + stacks/dockge/docker-compose.yml | 25 ++++ stacks/filebrowser/.env.template | 2 + stacks/filebrowser/docker-compose.yml | 22 ++++ stacks/ghost/.env.template | 2 + stacks/ghost/docker-compose.yml | 37 ++++++ stacks/gitea/.env.template | 4 + stacks/gitea/docker-compose.yml | 58 +++++++++ stacks/gollum/.env.template | 1 + stacks/gollum/docker-compose.yml | 24 ++++ stacks/invidious/.env.template | 5 + stacks/invidious/docker-compose.yml | 67 ++++++++++ stacks/memento/.env.template | 6 + stacks/memento/docker-compose.yml | 22 ++++ stacks/obsidian-tools/.env.template | 1 + stacks/obsidian-tools/docker-compose.yml | 87 +++++++++++++ stacks/perilous/.env.template | 1 + stacks/perilous/Dockerfile | 7 ++ stacks/perilous/docker-compose.yml | 43 +++++++ stacks/perilous/package.json | 12 ++ stacks/perilous/server.js | 24 ++++ stacks/radicale/.env.template | 1 + stacks/radicale/docker-compose.yml | 35 ++++++ stacks/ramz/Dockerfile | 13 ++ stacks/ramz/docker-compose.yml | 24 ++++ stacks/registry/.env.template | 2 + stacks/registry/docker-compose.yml | 27 +++++ stacks/smokeping/.env.template | 1 + stacks/smokeping/docker-compose.yml | 26 ++++ stacks/syncthing/.env.template | 1 + stacks/syncthing/docker-compose.yml | 30 +++++ stacks/traefik/.env.template | 1 + stacks/traefik/docker-compose.yml | 25 ++++ stacks/traefik/traefik.yml | 40 ++++++ stacks/wallabag/.env.template | 3 + stacks/wallabag/docker-compose.yml | 55 +++++++++ stacks/watchtower/docker-compose.yml | 14 +++ stacks/xbackbone/.env.template | 1 + stacks/xbackbone/docker-compose.yml | 26 ++++ stacks/zerotier/.env.template | 2 + stacks/zerotier/docker-compose.yml | 24 ++++ 56 files changed, 1390 insertions(+) create mode 100644 .gitea/workflows/deploy.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100755 scripts/deploy.sh create mode 100644 stacks/authentik/.env.template create mode 100644 stacks/authentik/docker-compose.yml create mode 100644 stacks/bookclub/.env.template create mode 100644 stacks/bookclub/Dockerfile create mode 100644 stacks/bookclub/docker-compose.yml create mode 100644 stacks/bookclub/package.json create mode 100644 stacks/bookclub/server.js create mode 100644 stacks/brain/.env.template create mode 100644 stacks/brain/docker-compose.yml create mode 100644 stacks/changedetection/.env.template create mode 100644 stacks/changedetection/docker-compose.yml create mode 100644 stacks/dockge/.env.template create mode 100644 stacks/dockge/docker-compose.yml create mode 100644 stacks/filebrowser/.env.template create mode 100644 stacks/filebrowser/docker-compose.yml create mode 100644 stacks/ghost/.env.template create mode 100644 stacks/ghost/docker-compose.yml create mode 100644 stacks/gitea/.env.template create mode 100644 stacks/gitea/docker-compose.yml create mode 100644 stacks/gollum/.env.template create mode 100644 stacks/gollum/docker-compose.yml create mode 100644 stacks/invidious/.env.template create mode 100644 stacks/invidious/docker-compose.yml create mode 100644 stacks/memento/.env.template create mode 100644 stacks/memento/docker-compose.yml create mode 100644 stacks/obsidian-tools/.env.template create mode 100644 stacks/obsidian-tools/docker-compose.yml create mode 100644 stacks/perilous/.env.template create mode 100644 stacks/perilous/Dockerfile create mode 100644 stacks/perilous/docker-compose.yml create mode 100644 stacks/perilous/package.json create mode 100644 stacks/perilous/server.js create mode 100644 stacks/radicale/.env.template create mode 100644 stacks/radicale/docker-compose.yml create mode 100644 stacks/ramz/Dockerfile create mode 100644 stacks/ramz/docker-compose.yml create mode 100644 stacks/registry/.env.template create mode 100644 stacks/registry/docker-compose.yml create mode 100644 stacks/smokeping/.env.template create mode 100644 stacks/smokeping/docker-compose.yml create mode 100644 stacks/syncthing/.env.template create mode 100644 stacks/syncthing/docker-compose.yml create mode 100644 stacks/traefik/.env.template create mode 100644 stacks/traefik/docker-compose.yml create mode 100644 stacks/traefik/traefik.yml create mode 100644 stacks/wallabag/.env.template create mode 100644 stacks/wallabag/docker-compose.yml create mode 100644 stacks/watchtower/docker-compose.yml create mode 100644 stacks/xbackbone/.env.template create mode 100644 stacks/xbackbone/docker-compose.yml create mode 100644 stacks/zerotier/.env.template create mode 100644 stacks/zerotier/docker-compose.yml diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..7a68692 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,114 @@ +name: Deploy Stacks + +on: + push: + branches: [main] + paths: + - 'stacks/**' + workflow_dispatch: + inputs: + stack: + description: 'Stack to deploy (or "all")' + required: true + default: 'all' + +env: + STACKS_DIR: /var/core + +jobs: + detect-changes: + runs-on: ubuntu-prod + outputs: + stacks: ${{ steps.changes.outputs.stacks }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Find changed stacks + id: changes + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + if [ "${{ github.event.inputs.stack }}" = "all" ]; then + STACKS=$(ls stacks/ | tr '\n' ' ') + else + STACKS="${{ github.event.inputs.stack }}" + fi + else + STACKS=$(git diff --name-only HEAD~1 HEAD | grep '^stacks/' | cut -d'/' -f2 | sort -u | tr '\n' ' ') + fi + echo "stacks=$STACKS" >> $GITHUB_OUTPUT + echo "Detected stacks to deploy: $STACKS" + + deploy: + needs: detect-changes + if: needs.detect-changes.outputs.stacks != '' + runs-on: ubuntu-prod + strategy: + matrix: + stack: ${{ fromJson(format('["{0}"]', join(fromJson(format('["{0}"]', replace(needs.detect-changes.outputs.stacks, ' ', '","'))), '","'))) }} + fail-fast: false + steps: + - uses: actions/checkout@v4 + + - name: Create .env file + run: | + cd stacks/${{ matrix.stack }} + if [ -f ".env.template" ]; then + envsubst < .env.template > .env + fi + env: + # Global + DOMAIN: ${{ secrets.DOMAIN }} + VOLUMES_ROOT: ${{ secrets.VOLUMES_ROOT }} + # Traefik + ACME_EMAIL: ${{ secrets.ACME_EMAIL }} + # Authentik + AUTHENTIK_SECRET_KEY: ${{ secrets.AUTHENTIK_SECRET_KEY }} + AUTHENTIK_PG_PASS: ${{ secrets.AUTHENTIK_PG_PASS }} + # Immich + IMMICH_DB_PASSWORD: ${{ secrets.IMMICH_DB_PASSWORD }} + # Planka + PLANKA_SECRET_KEY: ${{ secrets.PLANKA_SECRET_KEY }} + PLANKA_OIDC_CLIENT_ID: ${{ secrets.PLANKA_OIDC_CLIENT_ID }} + PLANKA_OIDC_CLIENT_SECRET: ${{ secrets.PLANKA_OIDC_CLIENT_SECRET }} + # Registry + REGISTRY_HTTP_SECRET: ${{ secrets.REGISTRY_HTTP_SECRET }} + REGISTRY_HTPASSWD: ${{ secrets.REGISTRY_HTPASSWD }} + # Memento + MEMENTO_AUTH_SECRET: ${{ secrets.MEMENTO_AUTH_SECRET }} + MEMENTO_AUTHENTIK_CLIENT_ID: ${{ secrets.MEMENTO_AUTHENTIK_CLIENT_ID }} + MEMENTO_AUTHENTIK_CLIENT_SECRET: ${{ secrets.MEMENTO_AUTHENTIK_CLIENT_SECRET }} + # Bookclub + BOOKCLUB_SMTP_HOST: ${{ secrets.BOOKCLUB_SMTP_HOST }} + BOOKCLUB_SMTP_USER: ${{ secrets.BOOKCLUB_SMTP_USER }} + BOOKCLUB_SMTP_PASS: ${{ secrets.BOOKCLUB_SMTP_PASS }} + BOOKCLUB_MAIL_FROM: ${{ secrets.BOOKCLUB_MAIL_FROM }} + BOOKCLUB_MAIL_TO: ${{ secrets.BOOKCLUB_MAIL_TO }} + BOOKCLUB_SECRET_PHRASE: ${{ secrets.BOOKCLUB_SECRET_PHRASE }} + # Perilous + PERILOUS_CODE_SERVER_PASSWORD: ${{ secrets.PERILOUS_CODE_SERVER_PASSWORD }} + + - name: Deploy ${{ matrix.stack }} + run: | + STACK_DIR="${{ env.STACKS_DIR }}/${{ matrix.stack }}" + + # Create stack directory if needed + mkdir -p "$STACK_DIR" + + # Copy files to stack directory + cp -r stacks/${{ matrix.stack }}/* "$STACK_DIR/" + + cd "$STACK_DIR" + + # Pull and deploy + docker compose pull --ignore-pull-failures || true + docker compose up -d --remove-orphans + + echo "✅ Deployed ${{ matrix.stack }}" + + - name: Verify deployment + run: | + sleep 5 + cd ${{ env.STACKS_DIR }}/${{ matrix.stack }} + docker compose ps diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a198851 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Environment files (secrets are in Gitea) +.env +*.env +!.env.template +!.env.example + +# Data directories +*/data/ +*/postgres/ +*/redis/ +*/config/ +*/upload/ +*/media/ +*/certs/ +acme.json + +# OS files +.DS_Store +Thumbs.db + +# Editor files +*.swp +*.swo +*~ +.idea/ +.vscode/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..65cdb79 --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +# Docker Stacks + +GitOps-managed Docker Compose stacks. Push changes to `main` branch and Gitea Actions will automatically deploy. + +## Structure + +``` +stacks/ +├── traefik/ # Reverse proxy + SSL +├── authentik/ # SSO/Identity provider +├── registry/ # Docker registry +├── immich/ # Photo management +├── planka/ # Kanban boards +├── syncthing/ # File sync +├── filebrowser/ # Web file manager +├── memento/ # Custom app +├── obsidian-tools/ # Obsidian vault tools +├── perilous/ # Blog/website +├── ramz/ # Go web app +├── bookclub/ # Form mailer +├── watchtower/ # Auto container updates +├── dockge/ # Container management UI +└── smokeping/ # Network monitoring +``` + +## How It Works + +1. Edit compose files in `stacks//` +2. Commit and push to `main` +3. Gitea Actions detects changed stacks +4. Deploys only the changed stacks to `/var/core//` + +## Manual Deploy + +```bash +# Deploy single stack +./scripts/deploy.sh traefik + +# Deploy all stacks +./scripts/deploy.sh all +``` + +## Required Gitea Secrets + +Set these in Gitea → Repository → Settings → Actions → Secrets: + +### Global +| Secret | Description | +|--------|-------------| +| `DOMAIN` | Base domain (e.g., `ghost.tel`) | +| `VOLUMES_ROOT` | Data root path (e.g., `/var/core`) | +| `ACME_EMAIL` | Email for Let's Encrypt | + +### Authentik +| Secret | Description | +|--------|-------------| +| `AUTHENTIK_SECRET_KEY` | Generate: `openssl rand -hex 50` | +| `AUTHENTIK_PG_PASS` | PostgreSQL password | + +### Immich +| Secret | Description | +|--------|-------------| +| `IMMICH_DB_PASSWORD` | PostgreSQL password | + +### Planka +| Secret | Description | +|--------|-------------| +| `PLANKA_SECRET_KEY` | Generate: `openssl rand -hex 64` | +| `PLANKA_OIDC_CLIENT_ID` | Authentik client ID | +| `PLANKA_OIDC_CLIENT_SECRET` | Authentik client secret | + +### Registry +| Secret | Description | +|--------|-------------| +| `REGISTRY_HTTP_SECRET` | Generate: `openssl rand -hex 32` | + +### Memento +| Secret | Description | +|--------|-------------| +| `MEMENTO_AUTH_SECRET` | Auth.js secret | +| `MEMENTO_AUTHENTIK_CLIENT_ID` | Authentik client ID | +| `MEMENTO_AUTHENTIK_CLIENT_SECRET` | Authentik client secret | + +### Bookclub +| Secret | Description | +|--------|-------------| +| `BOOKCLUB_SMTP_HOST` | SMTP server | +| `BOOKCLUB_SMTP_USER` | SMTP username | +| `BOOKCLUB_SMTP_PASS` | SMTP password | +| `BOOKCLUB_MAIL_FROM` | From email | +| `BOOKCLUB_MAIL_TO` | Recipient email | +| `BOOKCLUB_SECRET_PHRASE` | Form submission secret | + +### Perilous +| Secret | Description | +|--------|-------------| +| `PERILOUS_CODE_SERVER_PASSWORD` | Code-server password | + +## Runner Setup + +The workflow requires a self-hosted runner on the prod server: + +```bash +# On ubuntu-prod, register a Gitea runner +# See: https://docs.gitea.com/usage/actions/act-runner + +# Install act_runner +wget https://gitea.com/gitea/act_runner/releases/download/v0.2.6/act_runner-0.2.6-linux-amd64 +chmod +x act_runner-* +sudo mv act_runner-* /usr/local/bin/act_runner + +# Register with Gitea +act_runner register --no-interactive \ + --instance https://gitea.ghost.tel \ + --token \ + --name ubuntu-prod \ + --labels ubuntu-prod + +# Run as service +act_runner daemon +``` + +## First-Time Setup + +1. Create the `web` Docker network: + ```bash + docker network create web + ``` + +2. Create `acme.json` for Traefik: + ```bash + touch /var/core/traefik/acme.json + chmod 600 /var/core/traefik/acme.json + ``` + +3. Deploy traefik first: + ```bash + ./scripts/deploy.sh traefik + ``` + +4. Then deploy other stacks as needed. diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..28d6cd9 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Manual deploy script for docker-stacks +# Usage: ./deploy.sh [stack-name|all] + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" +STACKS_DIR="/var/core" + +deploy_stack() { + local stack=$1 + echo "🚀 Deploying $stack..." + + local src="$REPO_ROOT/stacks/$stack" + local dest="$STACKS_DIR/$stack" + + if [ ! -d "$src" ]; then + echo "❌ Stack $stack not found in $src" + return 1 + fi + + # Create destination directory + mkdir -p "$dest" + + # Copy files + cp -r "$src"/* "$dest/" + + # Deploy + cd "$dest" + docker compose pull --ignore-pull-failures 2>/dev/null || true + docker compose up -d --remove-orphans + + echo "✅ Deployed $stack" +} + +if [ -z "$1" ] || [ "$1" = "all" ]; then + echo "Deploying all stacks..." + for stack_dir in "$REPO_ROOT/stacks"/*/; do + stack=$(basename "$stack_dir") + deploy_stack "$stack" + done +else + deploy_stack "$1" +fi + +echo "" +echo "🎉 Deployment complete!" diff --git a/stacks/authentik/.env.template b/stacks/authentik/.env.template new file mode 100644 index 0000000..dee1777 --- /dev/null +++ b/stacks/authentik/.env.template @@ -0,0 +1,3 @@ +DOMAIN=${DOMAIN} +AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY} +AUTHENTIK_PG_PASS=${AUTHENTIK_PG_PASS} diff --git a/stacks/authentik/docker-compose.yml b/stacks/authentik/docker-compose.yml new file mode 100644 index 0000000..2cae678 --- /dev/null +++ b/stacks/authentik/docker-compose.yml @@ -0,0 +1,75 @@ +services: + server: + image: ghcr.io/goauthentik/server:latest + container_name: authentik-server + restart: unless-stopped + command: server + environment: + AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY} + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: authentik + AUTHENTIK_POSTGRESQL__NAME: authentik + AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_PG_PASS} + volumes: + - ./media:/media + - ./custom-templates:/templates + env_file: + - .env + depends_on: + - postgresql + - redis + networks: + - web + - default + labels: + - "traefik.enable=true" + - "traefik.http.routers.authentik.entrypoints=https" + - "traefik.http.routers.authentik.rule=Host(`authentik.${DOMAIN}`)" + - "traefik.http.routers.authentik.tls.certresolver=http" + - "traefik.http.services.authentik.loadbalancer.server.port=9000" + + worker: + image: ghcr.io/goauthentik/server:latest + container_name: authentik-worker + restart: unless-stopped + command: worker + environment: + AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY} + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: authentik + AUTHENTIK_POSTGRESQL__NAME: authentik + AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_PG_PASS} + volumes: + - ./media:/media + - ./certs:/certs + - ./custom-templates:/templates + env_file: + - .env + depends_on: + - postgresql + - redis + + postgresql: + image: postgres:15-alpine + container_name: authentik-postgres + restart: unless-stopped + environment: + POSTGRES_PASSWORD: ${AUTHENTIK_PG_PASS} + POSTGRES_USER: authentik + POSTGRES_DB: authentik + volumes: + - ./postgres:/var/lib/postgresql/data + + redis: + image: redis:alpine + container_name: authentik-redis + restart: unless-stopped + command: --save 60 1 --loglevel warning + volumes: + - ./redis:/data + +networks: + web: + external: true diff --git a/stacks/bookclub/.env.template b/stacks/bookclub/.env.template new file mode 100644 index 0000000..466c17d --- /dev/null +++ b/stacks/bookclub/.env.template @@ -0,0 +1,14 @@ +DOMAIN=${DOMAIN} +SMTP_HOST=${BOOKCLUB_SMTP_HOST} +SMTP_PORT=465 +SMTP_SECURE=true +SMTP_USER=${BOOKCLUB_SMTP_USER} +SMTP_PASS=${BOOKCLUB_SMTP_PASS} +MAIL_FROM=${BOOKCLUB_MAIL_FROM} +MAIL_TO=${BOOKCLUB_MAIL_TO} +SECRET_PHRASE=${BOOKCLUB_SECRET_PHRASE} +THANK_YOU_URL=/thanks.html +BIND_HOST=0.0.0.0 +BIND_PORT=3000 +TRUST_PROXY=true +TZ=America/New_York diff --git a/stacks/bookclub/Dockerfile b/stacks/bookclub/Dockerfile new file mode 100644 index 0000000..4ab08e8 --- /dev/null +++ b/stacks/bookclub/Dockerfile @@ -0,0 +1,7 @@ +FROM node:20-alpine +WORKDIR /app +COPY package.json ./ +RUN npm install +COPY server.js ./ +EXPOSE 3000 +CMD ["node", "server.js"] diff --git a/stacks/bookclub/docker-compose.yml b/stacks/bookclub/docker-compose.yml new file mode 100644 index 0000000..c3594ce --- /dev/null +++ b/stacks/bookclub/docker-compose.yml @@ -0,0 +1,20 @@ +services: + form-mailer: + build: . + container_name: bookclub-form-mailer + restart: unless-stopped + env_file: + - .env + labels: + - "traefik.enable=true" + - "traefik.http.routers.form-mailer.rule=Host(`bookclub.${DOMAIN}`)" + - "traefik.http.routers.form-mailer.entrypoints=https" + - "traefik.http.routers.form-mailer.tls=true" + - "traefik.http.routers.form-mailer.tls.certresolver=http" + - "traefik.http.services.form-mailer.loadbalancer.server.port=3000" + networks: + - web + +networks: + web: + external: true diff --git a/stacks/bookclub/package.json b/stacks/bookclub/package.json new file mode 100644 index 0000000..ce38a93 --- /dev/null +++ b/stacks/bookclub/package.json @@ -0,0 +1,13 @@ +{ + "name": "bookclub-form-mailer", + "version": "1.0.0", + "description": "Simple form mailer service", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2", + "nodemailer": "^6.9.7" + } +} diff --git a/stacks/bookclub/server.js b/stacks/bookclub/server.js new file mode 100644 index 0000000..5c0354c --- /dev/null +++ b/stacks/bookclub/server.js @@ -0,0 +1,51 @@ +const express = require('express'); +const nodemailer = require('nodemailer'); + +const app = express(); +app.use(express.urlencoded({ extended: true })); +app.use(express.json()); + +if (process.env.TRUST_PROXY === 'true') { + app.set('trust proxy', true); +} + +const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: parseInt(process.env.SMTP_PORT || '465'), + secure: process.env.SMTP_SECURE === 'true', + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASS + } +}); + +app.post('/submit', async (req, res) => { + const { secret, ...formData } = req.body; + + if (secret !== process.env.SECRET_PHRASE) { + return res.status(403).send('Invalid secret phrase'); + } + + try { + await transporter.sendMail({ + from: process.env.MAIL_FROM, + to: process.env.MAIL_TO, + subject: 'New Form Submission', + text: Object.entries(formData) + .map(([key, value]) => `${key}: ${value}`) + .join('\n') + }); + + res.redirect(process.env.THANK_YOU_URL || '/thanks.html'); + } catch (error) { + console.error('Mail error:', error); + res.status(500).send('Failed to send email'); + } +}); + +const port = parseInt(process.env.BIND_PORT || '3000'); +const host = process.env.BIND_HOST || '0.0.0.0'; + +app.listen(port, host, () => { + console.log(`Form mailer listening on ${host}:${port}`); +}); diff --git a/stacks/brain/.env.template b/stacks/brain/.env.template new file mode 100644 index 0000000..409064d --- /dev/null +++ b/stacks/brain/.env.template @@ -0,0 +1,3 @@ +DOMAIN=${DOMAIN} +BRAIN_SFTP_USER=${BRAIN_SFTP_USER} +BRAIN_SFTP_PASSWORD=${BRAIN_SFTP_PASSWORD} diff --git a/stacks/brain/docker-compose.yml b/stacks/brain/docker-compose.yml new file mode 100644 index 0000000..5b03c60 --- /dev/null +++ b/stacks/brain/docker-compose.yml @@ -0,0 +1,32 @@ +services: + web: + image: nginx:latest + container_name: brain + restart: unless-stopped + expose: + - 80 + volumes: + - ./public:/usr/share/nginx/html + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.brain.entrypoints=https" + - "traefik.http.routers.brain.rule=Host(`brain.${DOMAIN}`)" + - "traefik.http.routers.brain.tls.certresolver=http" + - "traefik.http.routers.brain.middlewares=auth@file" + + sftp: + image: atmoz/sftp + container_name: brain-sftp + restart: unless-stopped + volumes: + - ./public:/home/commander/upload + ports: + - "2232:22" + command: ${BRAIN_SFTP_USER}:${BRAIN_SFTP_PASSWORD}:1001 + +networks: + web: + external: true diff --git a/stacks/changedetection/.env.template b/stacks/changedetection/.env.template new file mode 100644 index 0000000..8d4fab9 --- /dev/null +++ b/stacks/changedetection/.env.template @@ -0,0 +1 @@ +DOMAIN=${DOMAIN} diff --git a/stacks/changedetection/docker-compose.yml b/stacks/changedetection/docker-compose.yml new file mode 100644 index 0000000..8990307 --- /dev/null +++ b/stacks/changedetection/docker-compose.yml @@ -0,0 +1,40 @@ +services: + changedetection: + image: ghcr.io/dgtlmoon/changedetection.io + container_name: changedetection + hostname: changedetection + restart: unless-stopped + volumes: + - ./data:/datastore + environment: + - PLAYWRIGHT_DRIVER_URL=ws://playwright-chrome:3000/?stealth=1&--disable-web-security=true + ports: + - 5000:5000 + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.changedetection.entrypoints=https" + - "traefik.http.routers.changedetection.rule=Host(`change.${DOMAIN}`)" + - "traefik.http.routers.changedetection.tls.certresolver=http" + - "traefik.http.routers.changedetection.middlewares=auth@file" + depends_on: + - playwright-chrome + + playwright-chrome: + image: dgtlmoon/sockpuppetbrowser:latest + hostname: playwright-chrome + restart: unless-stopped + cap_add: + - SYS_ADMIN + environment: + - SCREEN_WIDTH=1920 + - SCREEN_HEIGHT=1024 + - SCREEN_DEPTH=16 + - MAX_CONCURRENT_CHROME_PROCESSES=10 + networks: + - web + +networks: + web: + external: true diff --git a/stacks/dockge/.env.template b/stacks/dockge/.env.template new file mode 100644 index 0000000..8d4fab9 --- /dev/null +++ b/stacks/dockge/.env.template @@ -0,0 +1 @@ +DOMAIN=${DOMAIN} diff --git a/stacks/dockge/docker-compose.yml b/stacks/dockge/docker-compose.yml new file mode 100644 index 0000000..2ad4ab0 --- /dev/null +++ b/stacks/dockge/docker-compose.yml @@ -0,0 +1,25 @@ +services: + dockge: + image: louislam/dockge:1 + container_name: dockge + restart: unless-stopped + ports: + - 5001:5001 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./data:/app/data + - /var/core:/var/core + environment: + - DOCKGE_STACKS_DIR=/var/core + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.dockge.rule=Host(`dockge.${DOMAIN}`)" + - "traefik.http.routers.dockge.entrypoints=https" + - "traefik.http.routers.dockge.tls.certresolver=http" + - "traefik.http.services.dockge.loadbalancer.server.port=5001" + +networks: + web: + external: true diff --git a/stacks/filebrowser/.env.template b/stacks/filebrowser/.env.template new file mode 100644 index 0000000..382bee5 --- /dev/null +++ b/stacks/filebrowser/.env.template @@ -0,0 +1,2 @@ +DOMAIN=${DOMAIN} +FILE_ROOT=/srv diff --git a/stacks/filebrowser/docker-compose.yml b/stacks/filebrowser/docker-compose.yml new file mode 100644 index 0000000..19e4539 --- /dev/null +++ b/stacks/filebrowser/docker-compose.yml @@ -0,0 +1,22 @@ +services: + filebrowser: + image: filebrowser/filebrowser:latest + container_name: filebrowser + restart: unless-stopped + volumes: + - ./config/filebrowser.db:/database.db + - ${FILE_ROOT:-/srv}:/srv + expose: + - 80 + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.filebrowser.entrypoints=https" + - "traefik.http.routers.filebrowser.rule=Host(`files.${DOMAIN}`)" + - "traefik.http.routers.filebrowser.tls.certresolver=http" + - "traefik.http.services.filebrowser.loadbalancer.server.port=80" + +networks: + web: + external: true diff --git a/stacks/ghost/.env.template b/stacks/ghost/.env.template new file mode 100644 index 0000000..55a6401 --- /dev/null +++ b/stacks/ghost/.env.template @@ -0,0 +1,2 @@ +DOMAIN=${DOMAIN} +GHOST_DB_PASSWORD=${GHOST_DB_PASSWORD} diff --git a/stacks/ghost/docker-compose.yml b/stacks/ghost/docker-compose.yml new file mode 100644 index 0000000..9186417 --- /dev/null +++ b/stacks/ghost/docker-compose.yml @@ -0,0 +1,37 @@ +services: + ghost: + image: ghost:4-alpine + container_name: ghost + restart: unless-stopped + expose: + - 2368 + networks: + - web + - default + environment: + database__client: mysql + database__connection__host: db + database__connection__user: root + database__connection__password: ${GHOST_DB_PASSWORD} + database__connection__database: ghost + url: https://ghost.${DOMAIN} + volumes: + - ./content:/var/lib/ghost/content + labels: + - "traefik.enable=true" + - "traefik.http.routers.ghost.entrypoints=https" + - "traefik.http.routers.ghost.rule=Host(`ghost.${DOMAIN}`)" + - "traefik.http.routers.ghost.tls.certresolver=http" + + db: + image: mysql:5.7 + container_name: ghost-db + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: ${GHOST_DB_PASSWORD} + volumes: + - ./mysql:/var/lib/mysql + +networks: + web: + external: true diff --git a/stacks/gitea/.env.template b/stacks/gitea/.env.template new file mode 100644 index 0000000..d246a22 --- /dev/null +++ b/stacks/gitea/.env.template @@ -0,0 +1,4 @@ +DOMAIN=${DOMAIN} +GITEA_DB_ROOT_PASSWORD=${GITEA_DB_ROOT_PASSWORD} +GITEA_DB_PASSWORD=${GITEA_DB_PASSWORD} +GITEA_RUNNER_TOKEN=${GITEA_RUNNER_TOKEN} diff --git a/stacks/gitea/docker-compose.yml b/stacks/gitea/docker-compose.yml new file mode 100644 index 0000000..992c536 --- /dev/null +++ b/stacks/gitea/docker-compose.yml @@ -0,0 +1,58 @@ +services: + web: + image: gitea/gitea:latest + container_name: gitea + restart: unless-stopped + volumes: + - ./data:/data + expose: + - "3000" + - "22" + ports: + - "2245:22" + depends_on: + - db + environment: + - DOMAIN=gitea.${DOMAIN} + - SSH_DOMAIN=gitea.${DOMAIN} + - SSH_PORT=2245 + - SSH_LISTEN_PORT=22 + networks: + - default + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.gitea.entrypoints=https" + - "traefik.http.routers.gitea.rule=Host(`gitea.${DOMAIN}`)" + - "traefik.http.routers.gitea.tls.certresolver=http" + - "traefik.http.services.gitea.loadbalancer.server.port=3000" + + db: + image: mariadb:10 + container_name: gitea-db + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=${GITEA_DB_ROOT_PASSWORD} + - MYSQL_DATABASE=gitea + - MYSQL_USER=gitea + - MYSQL_PASSWORD=${GITEA_DB_PASSWORD} + - MARIADB_AUTO_UPGRADE=1 + volumes: + - ./db:/var/lib/mysql + + runner: + image: gitea/act_runner + container_name: gitea-runner + restart: unless-stopped + depends_on: + - web + volumes: + - ./runner-data:/data + - /var/run/docker.sock:/var/run/docker.sock + environment: + - GITEA_INSTANCE_URL=https://gitea.${DOMAIN} + - GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_TOKEN} + +networks: + web: + external: true diff --git a/stacks/gollum/.env.template b/stacks/gollum/.env.template new file mode 100644 index 0000000..8d4fab9 --- /dev/null +++ b/stacks/gollum/.env.template @@ -0,0 +1 @@ +DOMAIN=${DOMAIN} diff --git a/stacks/gollum/docker-compose.yml b/stacks/gollum/docker-compose.yml new file mode 100644 index 0000000..8b8fe84 --- /dev/null +++ b/stacks/gollum/docker-compose.yml @@ -0,0 +1,24 @@ +services: + gollum: + image: gollumwiki/gollum:master + container_name: gollum + restart: unless-stopped + volumes: + - ./config.rb:/etc/gollum/config.rb + - ./wikidata:/wiki + command: + - "--config=/etc/gollum/config.rb" + expose: + - 4567 + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.gollum.entrypoints=https" + - "traefik.http.routers.gollum.rule=Host(`gollum.${DOMAIN}`)" + - "traefik.http.routers.gollum.tls.certresolver=http" + - "traefik.http.routers.gollum.middlewares=auth@file" + +networks: + web: + external: true diff --git a/stacks/invidious/.env.template b/stacks/invidious/.env.template new file mode 100644 index 0000000..86a1f3d --- /dev/null +++ b/stacks/invidious/.env.template @@ -0,0 +1,5 @@ +DOMAIN=${DOMAIN} +INVIDIOUS_DB_PASSWORD=${INVIDIOUS_DB_PASSWORD} +INVIDIOUS_VISITOR_DATA=${INVIDIOUS_VISITOR_DATA} +INVIDIOUS_PO_TOKEN=${INVIDIOUS_PO_TOKEN} +INVIDIOUS_HMAC_KEY=${INVIDIOUS_HMAC_KEY} diff --git a/stacks/invidious/docker-compose.yml b/stacks/invidious/docker-compose.yml new file mode 100644 index 0000000..d70c175 --- /dev/null +++ b/stacks/invidious/docker-compose.yml @@ -0,0 +1,67 @@ +services: + invidious: + image: quay.io/invidious/invidious:latest + container_name: invidious + restart: unless-stopped + ports: + - "3001:3000" + environment: + INVIDIOUS_CONFIG: | + db: + dbname: invidious + user: kemal + password: ${INVIDIOUS_DB_PASSWORD} + host: invidious-db + port: 5432 + check_tables: true + signature_server: inv_sig_helper:12999 + visitor_data: ${INVIDIOUS_VISITOR_DATA} + po_token: ${INVIDIOUS_PO_TOKEN} + hmac_key: "${INVIDIOUS_HMAC_KEY}" + logging: + options: + max-size: "1G" + max-file: "4" + depends_on: + - invidious-db + labels: + - "traefik.enable=true" + - "traefik.http.services.invidious.loadbalancer.server.port=3000" + - "traefik.http.routers.invidious.entrypoints=https" + - "traefik.http.routers.invidious.rule=Host(`invid.${DOMAIN}`, `i.${DOMAIN}`)" + - "traefik.http.routers.invidious.tls.certresolver=http" + networks: + - web + - default + + inv_sig_helper: + image: quay.io/invidious/inv-sig-helper:latest + container_name: inv-sig-helper + command: ["--tcp", "0.0.0.0:12999"] + environment: + - RUST_LOG=info + restart: unless-stopped + cap_drop: + - ALL + read_only: true + security_opt: + - no-new-privileges:true + + invidious-db: + image: postgres:14 + container_name: invidious-db + restart: unless-stopped + volumes: + - ./postgres:/var/lib/postgresql/data + - ./config/sql:/config/sql + - ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh + environment: + POSTGRES_DB: invidious + POSTGRES_USER: kemal + POSTGRES_PASSWORD: ${INVIDIOUS_DB_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + +networks: + web: + external: true diff --git a/stacks/memento/.env.template b/stacks/memento/.env.template new file mode 100644 index 0000000..e4553a7 --- /dev/null +++ b/stacks/memento/.env.template @@ -0,0 +1,6 @@ +DOMAIN=${DOMAIN} +AUTH_SECRET=${MEMENTO_AUTH_SECRET} +AUTH_AUTHENTIK_CLIENT_ID=${MEMENTO_AUTHENTIK_CLIENT_ID} +AUTH_AUTHENTIK_CLIENT_SECRET=${MEMENTO_AUTHENTIK_CLIENT_SECRET} +AUTH_AUTHENTIK_ISSUER=https://authentik.${DOMAIN}/application/o/memento +AUTH_AUTHENTIK_REDIRECT_URI=https://memento.${DOMAIN} diff --git a/stacks/memento/docker-compose.yml b/stacks/memento/docker-compose.yml new file mode 100644 index 0000000..d8cc243 --- /dev/null +++ b/stacks/memento/docker-compose.yml @@ -0,0 +1,22 @@ +services: + app: + image: gitea.${DOMAIN}/knight/memento:latest + container_name: memento + restart: unless-stopped + expose: + - "3002" + env_file: + - .env + networks: + - web + - default + labels: + - "traefik.enable=true" + - "traefik.http.routers.memento.entrypoints=https" + - "traefik.http.routers.memento.rule=Host(`memento.${DOMAIN}`)" + - "traefik.http.routers.memento.tls.certresolver=http" + - "traefik.http.services.memento.loadbalancer.server.port=3002" + +networks: + web: + external: true diff --git a/stacks/obsidian-tools/.env.template b/stacks/obsidian-tools/.env.template new file mode 100644 index 0000000..8d4fab9 --- /dev/null +++ b/stacks/obsidian-tools/.env.template @@ -0,0 +1 @@ +DOMAIN=${DOMAIN} diff --git a/stacks/obsidian-tools/docker-compose.yml b/stacks/obsidian-tools/docker-compose.yml new file mode 100644 index 0000000..ad8a3a1 --- /dev/null +++ b/stacks/obsidian-tools/docker-compose.yml @@ -0,0 +1,87 @@ +services: + obbytodo: + image: gitea.${DOMAIN}/knight/obbytodo:latest + container_name: obbytodo + restart: unless-stopped + expose: + - "3000" + environment: + - PORT=3000 + - VAULT_PATH=/vault + - DEFAULT_TASK_FILE=Mobile Tasks.md + - DAILY_DIR=log + - DAILY_PRIMARY_DIR=Control/log + volumes: + - ./vault:/vault + networks: + - web + depends_on: + - syncthing + labels: + - "traefik.enable=true" + - "traefik.docker.network=web" + - "traefik.http.routers.todo-obbytodo.entrypoints=https" + - "traefik.http.routers.todo-obbytodo.rule=Host(`shell.${DOMAIN}`) && PathPrefix(`/todo`)" + - "traefik.http.routers.todo-obbytodo.tls.certresolver=http" + - "traefik.http.routers.todo-obbytodo.middlewares=todo-obbytodo-stripprefix@docker,dashboard-auth@file" + - "traefik.http.routers.todo-obbytodo.priority=100" + - "traefik.http.middlewares.todo-obbytodo-stripprefix.stripPrefix.prefixes=/todo" + - "traefik.http.services.todo-obbytodo.loadbalancer.server.port=3000" + - "traefik.http.routers.todo-obbytodo.service=todo-obbytodo" + - "traefik.http.routers.events-obbytodo.entrypoints=https" + - "traefik.http.routers.events-obbytodo.rule=Host(`shell.${DOMAIN}`) && PathPrefix(`/events`)" + - "traefik.http.routers.events-obbytodo.tls.certresolver=http" + - "traefik.http.routers.events-obbytodo.middlewares=dashboard-auth@file" + - "traefik.http.routers.events-obbytodo.priority=100" + - "traefik.http.routers.events-obbytodo.service=todo-obbytodo" + + search-service: + image: gitea.${DOMAIN}/knight/obsidiansearch:latest + container_name: obsidian-search-service + restart: unless-stopped + expose: + - "3033" + environment: + - NODE_ENV=production + - OBSIDIAN_VAULT_PATH=/data + - GRAPH_DATA_PATH=/usr/src/app/graph-data/graph.json + volumes: + - ./vault:/data:ro + - ./graph-data:/usr/src/app/graph-data + depends_on: + - syncthing + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.docker.network=web" + - "traefik.http.routers.shell-secure.entrypoints=https" + - "traefik.http.routers.shell-secure.rule=Host(`shell.${DOMAIN}`)" + - "traefik.http.routers.shell-secure.tls.certresolver=http" + - "traefik.http.routers.shell-secure.middlewares=dashboard-auth@file" + - "traefik.http.services.shell-secure.loadbalancer.server.port=3033" + - "traefik.http.routers.shell-secure.service=shell-secure" + + syncthing: + image: lscr.io/linuxserver/syncthing:latest + container_name: obsidian-syncthing + hostname: obsidian-syncthing + restart: unless-stopped + environment: + - PUID=1000 + - PGID=1000 + - TZ=America/New_York + volumes: + - ./syncthing-config:/config + - ./vault:/data1 + ports: + - "8385:8384" + - "22001:22000/tcp" + - "22001:22000/udp" + - "21028:21027/udp" + networks: + - web + +networks: + web: + external: true diff --git a/stacks/perilous/.env.template b/stacks/perilous/.env.template new file mode 100644 index 0000000..16a8689 --- /dev/null +++ b/stacks/perilous/.env.template @@ -0,0 +1 @@ +PERILOUS_CODE_SERVER_PASSWORD=${PERILOUS_CODE_SERVER_PASSWORD} diff --git a/stacks/perilous/Dockerfile b/stacks/perilous/Dockerfile new file mode 100644 index 0000000..27489f6 --- /dev/null +++ b/stacks/perilous/Dockerfile @@ -0,0 +1,7 @@ +FROM node:18-alpine +WORKDIR /usr/src/app +COPY package.json ./ +RUN npm install +COPY server.js ./ +EXPOSE 3000 +CMD ["node", "server.js"] diff --git a/stacks/perilous/docker-compose.yml b/stacks/perilous/docker-compose.yml new file mode 100644 index 0000000..132432d --- /dev/null +++ b/stacks/perilous/docker-compose.yml @@ -0,0 +1,43 @@ +services: + web: + build: . + container_name: perilous-web + restart: unless-stopped + ports: + - "3003:3000" + volumes: + - ./content:/usr/src/app/content + labels: + - "traefik.enable=true" + - "traefik.http.routers.perilous-secure.entrypoints=https,http" + - "traefik.http.routers.perilous-secure.rule=Host(`chapel.perilous.dev`,`forest.perilous.dev`,`castle.perilous.dev`,`siege.perilous.dev`,`pass.perilous.dev`,`perilous.dev`,`www.perilous.dev`,`word.perilous.dev`,`mirror.perilous.dev`,`request.perilous.dev`,`the.chapel.perilous.dev`,`the.forest.perilous.dev`,`the.castle.perilous.dev`,`the.siege.perilous.dev`,`the.pass.perilous.dev`,`the.word.perilous.dev`,`the.mirror.perilous.dev`,`the.request.perilous.dev`,`the.adventure.perilous.dev`,`the.cs.perilous.dev`,`the.gallery.perilous.dev`,`gallery.perilous.dev`,`the.perilous.dev`,`ring.perilous.dev`,`the.ring.perilous.dev`,`autumn.perilous.dev`,`the.autumn.perilous.dev`)" + - "traefik.http.routers.perilous-secure.tls.certresolver=http" + networks: + - web + + code-server: + image: lscr.io/linuxserver/code-server:latest + container_name: perilous-code-server + restart: unless-stopped + volumes: + - ./content:/home/project + - ./config:/config + ports: + - "8180:8443" + environment: + - PASSWORD=${PERILOUS_CODE_SERVER_PASSWORD} + - PUID=1000 + - PGID=1000 + - PROXY_DOMAIN=cs.perilous.dev + - DEFAULT_WORKSPACE=/home/project + labels: + - "traefik.enable=true" + - "traefik.http.routers.pcs-secure.entrypoints=https,http" + - "traefik.http.routers.pcs-secure.rule=Host(`cs.perilous.dev`)" + - "traefik.http.routers.pcs-secure.tls.certresolver=http" + networks: + - web + +networks: + web: + external: true diff --git a/stacks/perilous/package.json b/stacks/perilous/package.json new file mode 100644 index 0000000..60e1f96 --- /dev/null +++ b/stacks/perilous/package.json @@ -0,0 +1,12 @@ +{ + "name": "perilous", + "version": "1.0.0", + "description": "Perilous static site server", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2" + } +} diff --git a/stacks/perilous/server.js b/stacks/perilous/server.js new file mode 100644 index 0000000..568815e --- /dev/null +++ b/stacks/perilous/server.js @@ -0,0 +1,24 @@ +const express = require('express'); +const path = require('path'); +const fs = require('fs'); + +const app = express(); +const PORT = process.env.PORT || 3000; +const CONTENT_DIR = '/usr/src/app/content'; + +app.use(express.static(CONTENT_DIR)); + +app.get('*', (req, res) => { + const filePath = path.join(CONTENT_DIR, req.path); + if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) { + const indexPath = path.join(filePath, 'index.html'); + if (fs.existsSync(indexPath)) { + return res.sendFile(indexPath); + } + } + res.sendFile(path.join(CONTENT_DIR, 'index.html')); +}); + +app.listen(PORT, () => { + console.log(`Perilous server running on port ${PORT}`); +}); diff --git a/stacks/radicale/.env.template b/stacks/radicale/.env.template new file mode 100644 index 0000000..8d4fab9 --- /dev/null +++ b/stacks/radicale/.env.template @@ -0,0 +1 @@ +DOMAIN=${DOMAIN} diff --git a/stacks/radicale/docker-compose.yml b/stacks/radicale/docker-compose.yml new file mode 100644 index 0000000..3625254 --- /dev/null +++ b/stacks/radicale/docker-compose.yml @@ -0,0 +1,35 @@ +services: + radicale: + image: tomsquest/docker-radicale + container_name: radicale + restart: unless-stopped + init: true + read_only: true + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + cap_add: + - SETUID + - SETGID + - CHOWN + - KILL + healthcheck: + test: curl -f http://127.0.0.1:5232 || exit 1 + interval: 30s + retries: 3 + expose: + - 5232 + networks: + - web + volumes: + - ./data:/data + labels: + - "traefik.enable=true" + - "traefik.http.routers.radicale.entrypoints=https" + - "traefik.http.routers.radicale.rule=Host(`radicale.${DOMAIN}`)" + - "traefik.http.routers.radicale.tls.certresolver=http" + +networks: + web: + external: true diff --git a/stacks/ramz/Dockerfile b/stacks/ramz/Dockerfile new file mode 100644 index 0000000..ffab9ba --- /dev/null +++ b/stacks/ramz/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.21-alpine AS builder +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download +COPY *.go ./ +RUN CGO_ENABLED=0 GOOS=linux go build -o ramz . + +FROM alpine:latest +WORKDIR /root/ +COPY --from=builder /app/ramz . +COPY static/ ./static/ +EXPOSE 3333 +CMD ["./ramz"] diff --git a/stacks/ramz/docker-compose.yml b/stacks/ramz/docker-compose.yml new file mode 100644 index 0000000..7479d9f --- /dev/null +++ b/stacks/ramz/docker-compose.yml @@ -0,0 +1,24 @@ +services: + app: + build: . + container_name: ramz + restart: unless-stopped + ports: + - "3333:3333" + environment: + - GIN_MODE=release + volumes: + - ./templates:/root/templates + - ./data:/root/data + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.ramz-secure.entrypoints=https,http" + - "traefik.http.routers.ramz-secure.rule=Host(`parker.ramz.cc`)" + - "traefik.http.routers.ramz-secure.tls.certresolver=http" + - "traefik.http.services.ramz.loadbalancer.server.port=3333" + +networks: + web: + external: true diff --git a/stacks/registry/.env.template b/stacks/registry/.env.template new file mode 100644 index 0000000..14d5f3a --- /dev/null +++ b/stacks/registry/.env.template @@ -0,0 +1,2 @@ +DOMAIN=${DOMAIN} +REGISTRY_HTTP_SECRET=${REGISTRY_HTTP_SECRET} diff --git a/stacks/registry/docker-compose.yml b/stacks/registry/docker-compose.yml new file mode 100644 index 0000000..af5b617 --- /dev/null +++ b/stacks/registry/docker-compose.yml @@ -0,0 +1,27 @@ +services: + registry: + image: registry:2 + container_name: registry + restart: unless-stopped + volumes: + - ./data:/var/lib/registry + - ./auth:/auth + environment: + REGISTRY_AUTH: htpasswd + REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm + REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd + REGISTRY_HTTP_SECRET: ${REGISTRY_HTTP_SECRET} + expose: + - 5000 + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.registry.entrypoints=https" + - "traefik.http.routers.registry.rule=Host(`registry.${DOMAIN}`)" + - "traefik.http.routers.registry.tls.certresolver=http" + - "traefik.http.services.registry.loadbalancer.server.port=5000" + +networks: + web: + external: true diff --git a/stacks/smokeping/.env.template b/stacks/smokeping/.env.template new file mode 100644 index 0000000..8d4fab9 --- /dev/null +++ b/stacks/smokeping/.env.template @@ -0,0 +1 @@ +DOMAIN=${DOMAIN} diff --git a/stacks/smokeping/docker-compose.yml b/stacks/smokeping/docker-compose.yml new file mode 100644 index 0000000..4463bdc --- /dev/null +++ b/stacks/smokeping/docker-compose.yml @@ -0,0 +1,26 @@ +services: + smokeping: + image: lscr.io/linuxserver/smokeping:latest + container_name: smokeping + restart: unless-stopped + environment: + - PUID=1000 + - PGID=1000 + - TZ=America/New_York + volumes: + - ./config:/config + - ./data:/data + expose: + - 80 + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.smokeping.entrypoints=https" + - "traefik.http.routers.smokeping.rule=Host(`smokeping.${DOMAIN}`)" + - "traefik.http.routers.smokeping.tls.certresolver=http" + - "traefik.http.services.smokeping.loadbalancer.server.port=80" + +networks: + web: + external: true diff --git a/stacks/syncthing/.env.template b/stacks/syncthing/.env.template new file mode 100644 index 0000000..8d4fab9 --- /dev/null +++ b/stacks/syncthing/.env.template @@ -0,0 +1 @@ +DOMAIN=${DOMAIN} diff --git a/stacks/syncthing/docker-compose.yml b/stacks/syncthing/docker-compose.yml new file mode 100644 index 0000000..1ea3b82 --- /dev/null +++ b/stacks/syncthing/docker-compose.yml @@ -0,0 +1,30 @@ +services: + syncthing: + image: lscr.io/linuxserver/syncthing:latest + container_name: syncthing + hostname: syncthing + restart: unless-stopped + environment: + - PUID=1000 + - PGID=1000 + - TZ=America/New_York + volumes: + - ./config:/config + - ./data:/data + ports: + - "8384:8384" + - "22000:22000/tcp" + - "22000:22000/udp" + - "21027:21027/udp" + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.syncthing.entrypoints=https" + - "traefik.http.routers.syncthing.rule=Host(`syncthing.${DOMAIN}`)" + - "traefik.http.routers.syncthing.tls.certresolver=http" + - "traefik.http.services.syncthing.loadbalancer.server.port=8384" + +networks: + web: + external: true diff --git a/stacks/traefik/.env.template b/stacks/traefik/.env.template new file mode 100644 index 0000000..2b5acc8 --- /dev/null +++ b/stacks/traefik/.env.template @@ -0,0 +1 @@ +ACME_EMAIL=${ACME_EMAIL} diff --git a/stacks/traefik/docker-compose.yml b/stacks/traefik/docker-compose.yml new file mode 100644 index 0000000..9f4800e --- /dev/null +++ b/stacks/traefik/docker-compose.yml @@ -0,0 +1,25 @@ +services: + traefik: + image: traefik:latest + container_name: traefik + restart: unless-stopped + security_opt: + - no-new-privileges:true + networks: + - web + ports: + - 80:80 + - 443:443 + - 8080:8080 + environment: + - TZ=America/New_York + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./traefik.yml:/traefik.yml:ro + - ./acme.json:/acme.json + - ./conf.d/:/conf.d/ + - /var/log:/var/log + +networks: + web: + external: true diff --git a/stacks/traefik/traefik.yml b/stacks/traefik/traefik.yml new file mode 100644 index 0000000..06312a4 --- /dev/null +++ b/stacks/traefik/traefik.yml @@ -0,0 +1,40 @@ +global: + checkNewVersion: true + +api: + dashboard: true + insecure: true + +entryPoints: + http: + address: ":80" + https: + address: ":443" + +providers: + providersThrottleDuration: 2s + file: + directory: "/conf.d" + watch: true + docker: + watch: true + endpoint: "unix:///var/run/docker.sock" + exposedByDefault: false + network: web + +certificatesResolvers: + http: + acme: + email: ${ACME_EMAIL} + storage: acme.json + httpChallenge: + entryPoint: http + +log: + level: INFO + filePath: /var/log/traefik.log + format: json + +accessLog: + filePath: /var/log/traefik_access.log + format: json diff --git a/stacks/wallabag/.env.template b/stacks/wallabag/.env.template new file mode 100644 index 0000000..13e132e --- /dev/null +++ b/stacks/wallabag/.env.template @@ -0,0 +1,3 @@ +DOMAIN=${DOMAIN} +WALLABAG_DB_PASSWORD=${WALLABAG_DB_PASSWORD} +WALLABAG_DB_ROOT_PASSWORD=${WALLABAG_DB_ROOT_PASSWORD} diff --git a/stacks/wallabag/docker-compose.yml b/stacks/wallabag/docker-compose.yml new file mode 100644 index 0000000..529a1c3 --- /dev/null +++ b/stacks/wallabag/docker-compose.yml @@ -0,0 +1,55 @@ +services: + wallabag: + image: wallabag/wallabag:latest + container_name: wallabag + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=${WALLABAG_DB_ROOT_PASSWORD} + - SYMFONY__ENV__DATABASE_DRIVER=pdo_mysql + - SYMFONY__ENV__DATABASE_HOST=db + - SYMFONY__ENV__DATABASE_PORT=3306 + - SYMFONY__ENV__DATABASE_NAME=wallabag + - SYMFONY__ENV__DATABASE_USER=wallabag + - SYMFONY__ENV__DATABASE_PASSWORD=${WALLABAG_DB_PASSWORD} + - SYMFONY__ENV__DATABASE_CHARSET=utf8mb4 + - SYMFONY__ENV__MAILER_HOST=127.0.0.1 + - SYMFONY__ENV__MAILER_USER=~ + - SYMFONY__ENV__MAILER_PASSWORD=~ + - SYMFONY__ENV__FROM_EMAIL=wallabag@${DOMAIN} + - SYMFONY__ENV__DOMAIN_NAME=https://wallabag.${DOMAIN} + expose: + - 80 + networks: + - web + - default + volumes: + - ./images:/var/www/wallabag/web/assets/images + labels: + - "traefik.enable=true" + - "traefik.http.routers.wallabag.entrypoints=https" + - "traefik.http.routers.wallabag.rule=Host(`wallabag.${DOMAIN}`)" + - "traefik.http.routers.wallabag.tls.certresolver=http" + - "traefik.http.middlewares.wallabag-header.headers.customRequestHeaders.X-Forwarded-Proto=https" + - "traefik.http.routers.wallabag.middlewares=wallabag-header" + depends_on: + - db + - redis + + db: + image: mariadb + container_name: wallabag-db + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=${WALLABAG_DB_ROOT_PASSWORD} + - MARIADB_AUTO_UPGRADE=1 + volumes: + - ./data:/var/lib/mysql + + redis: + image: redis:alpine + container_name: wallabag-redis + restart: unless-stopped + +networks: + web: + external: true diff --git a/stacks/watchtower/docker-compose.yml b/stacks/watchtower/docker-compose.yml new file mode 100644 index 0000000..f0c5186 --- /dev/null +++ b/stacks/watchtower/docker-compose.yml @@ -0,0 +1,14 @@ +services: + watchtower: + image: containrrr/watchtower:latest + container_name: watchtower + restart: unless-stopped + environment: + - TZ=America/New_York + - DOCKER_API_VERSION=1.44 + - WATCHTOWER_SCHEDULE=0 0 4 * * * + - WATCHTOWER_CLEANUP=true + - WATCHTOWER_INCLUDE_STOPPED=false + - WATCHTOWER_REVIVE_STOPPED=false + volumes: + - /var/run/docker.sock:/var/run/docker.sock diff --git a/stacks/xbackbone/.env.template b/stacks/xbackbone/.env.template new file mode 100644 index 0000000..8d4fab9 --- /dev/null +++ b/stacks/xbackbone/.env.template @@ -0,0 +1 @@ +DOMAIN=${DOMAIN} diff --git a/stacks/xbackbone/docker-compose.yml b/stacks/xbackbone/docker-compose.yml new file mode 100644 index 0000000..df188c4 --- /dev/null +++ b/stacks/xbackbone/docker-compose.yml @@ -0,0 +1,26 @@ +services: + xbackbone: + image: lscr.io/linuxserver/xbackbone:latest + container_name: xbackbone + restart: unless-stopped + environment: + - PUID=1000 + - PGID=1000 + - TZ=America/New_York + volumes: + - ./config:/config + expose: + - 443 + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.xbackbone.entrypoints=https" + - "traefik.http.routers.xbackbone.rule=Host(`xb.${DOMAIN}`)" + - "traefik.http.routers.xbackbone.tls.certresolver=http" + - "traefik.http.middlewares.xbackbone-header.headers.customRequestHeaders.X-Forwarded-Proto=https" + - "traefik.http.routers.xbackbone.middlewares=xbackbone-header" + +networks: + web: + external: true diff --git a/stacks/zerotier/.env.template b/stacks/zerotier/.env.template new file mode 100644 index 0000000..dbfa060 --- /dev/null +++ b/stacks/zerotier/.env.template @@ -0,0 +1,2 @@ +DOMAIN=${DOMAIN} +# Copy additional env vars from /var/core/zerotier/denv on old prod diff --git a/stacks/zerotier/docker-compose.yml b/stacks/zerotier/docker-compose.yml new file mode 100644 index 0000000..9deedd0 --- /dev/null +++ b/stacks/zerotier/docker-compose.yml @@ -0,0 +1,24 @@ +services: + ztncui: + image: keynetworks/ztncui + container_name: zerotier-ui + restart: always + expose: + - 3180 + volumes: + - ./ztncui:/opt/key-networks/ztncui/etc + - ./zt1:/var/lib/zerotier-one + env_file: + - .env + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.zerotier.entrypoints=https" + - "traefik.http.routers.zerotier.rule=Host(`zerotierui.${DOMAIN}`)" + - "traefik.http.routers.zerotier.tls.certresolver=http" + - "traefik.http.routers.zerotier.middlewares=dashboard-auth@file" + +networks: + web: + external: true