Initial commit: 23 docker stacks for GitOps deployment
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
This commit is contained in:
commit
4dbb0b9180
114
.gitea/workflows/deploy.yml
Normal file
114
.gitea/workflows/deploy.yml
Normal file
@ -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
|
||||
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@ -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/
|
||||
141
README.md
Normal file
141
README.md
Normal file
@ -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/<service>/`
|
||||
2. Commit and push to `main`
|
||||
3. Gitea Actions detects changed stacks
|
||||
4. Deploys only the changed stacks to `/var/core/<service>/`
|
||||
|
||||
## 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 <runner-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.
|
||||
48
scripts/deploy.sh
Executable file
48
scripts/deploy.sh
Executable file
@ -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!"
|
||||
3
stacks/authentik/.env.template
Normal file
3
stacks/authentik/.env.template
Normal file
@ -0,0 +1,3 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}
|
||||
AUTHENTIK_PG_PASS=${AUTHENTIK_PG_PASS}
|
||||
75
stacks/authentik/docker-compose.yml
Normal file
75
stacks/authentik/docker-compose.yml
Normal file
@ -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
|
||||
14
stacks/bookclub/.env.template
Normal file
14
stacks/bookclub/.env.template
Normal file
@ -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
|
||||
7
stacks/bookclub/Dockerfile
Normal file
7
stacks/bookclub/Dockerfile
Normal file
@ -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"]
|
||||
20
stacks/bookclub/docker-compose.yml
Normal file
20
stacks/bookclub/docker-compose.yml
Normal file
@ -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
|
||||
13
stacks/bookclub/package.json
Normal file
13
stacks/bookclub/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
51
stacks/bookclub/server.js
Normal file
51
stacks/bookclub/server.js
Normal file
@ -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}`);
|
||||
});
|
||||
3
stacks/brain/.env.template
Normal file
3
stacks/brain/.env.template
Normal file
@ -0,0 +1,3 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
BRAIN_SFTP_USER=${BRAIN_SFTP_USER}
|
||||
BRAIN_SFTP_PASSWORD=${BRAIN_SFTP_PASSWORD}
|
||||
32
stacks/brain/docker-compose.yml
Normal file
32
stacks/brain/docker-compose.yml
Normal file
@ -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
|
||||
1
stacks/changedetection/.env.template
Normal file
1
stacks/changedetection/.env.template
Normal file
@ -0,0 +1 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
40
stacks/changedetection/docker-compose.yml
Normal file
40
stacks/changedetection/docker-compose.yml
Normal file
@ -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
|
||||
1
stacks/dockge/.env.template
Normal file
1
stacks/dockge/.env.template
Normal file
@ -0,0 +1 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
25
stacks/dockge/docker-compose.yml
Normal file
25
stacks/dockge/docker-compose.yml
Normal file
@ -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
|
||||
2
stacks/filebrowser/.env.template
Normal file
2
stacks/filebrowser/.env.template
Normal file
@ -0,0 +1,2 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
FILE_ROOT=/srv
|
||||
22
stacks/filebrowser/docker-compose.yml
Normal file
22
stacks/filebrowser/docker-compose.yml
Normal file
@ -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
|
||||
2
stacks/ghost/.env.template
Normal file
2
stacks/ghost/.env.template
Normal file
@ -0,0 +1,2 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
GHOST_DB_PASSWORD=${GHOST_DB_PASSWORD}
|
||||
37
stacks/ghost/docker-compose.yml
Normal file
37
stacks/ghost/docker-compose.yml
Normal file
@ -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
|
||||
4
stacks/gitea/.env.template
Normal file
4
stacks/gitea/.env.template
Normal file
@ -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}
|
||||
58
stacks/gitea/docker-compose.yml
Normal file
58
stacks/gitea/docker-compose.yml
Normal file
@ -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
|
||||
1
stacks/gollum/.env.template
Normal file
1
stacks/gollum/.env.template
Normal file
@ -0,0 +1 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
24
stacks/gollum/docker-compose.yml
Normal file
24
stacks/gollum/docker-compose.yml
Normal file
@ -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
|
||||
5
stacks/invidious/.env.template
Normal file
5
stacks/invidious/.env.template
Normal file
@ -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}
|
||||
67
stacks/invidious/docker-compose.yml
Normal file
67
stacks/invidious/docker-compose.yml
Normal file
@ -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
|
||||
6
stacks/memento/.env.template
Normal file
6
stacks/memento/.env.template
Normal file
@ -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}
|
||||
22
stacks/memento/docker-compose.yml
Normal file
22
stacks/memento/docker-compose.yml
Normal file
@ -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
|
||||
1
stacks/obsidian-tools/.env.template
Normal file
1
stacks/obsidian-tools/.env.template
Normal file
@ -0,0 +1 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
87
stacks/obsidian-tools/docker-compose.yml
Normal file
87
stacks/obsidian-tools/docker-compose.yml
Normal file
@ -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
|
||||
1
stacks/perilous/.env.template
Normal file
1
stacks/perilous/.env.template
Normal file
@ -0,0 +1 @@
|
||||
PERILOUS_CODE_SERVER_PASSWORD=${PERILOUS_CODE_SERVER_PASSWORD}
|
||||
7
stacks/perilous/Dockerfile
Normal file
7
stacks/perilous/Dockerfile
Normal file
@ -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"]
|
||||
43
stacks/perilous/docker-compose.yml
Normal file
43
stacks/perilous/docker-compose.yml
Normal file
@ -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
|
||||
12
stacks/perilous/package.json
Normal file
12
stacks/perilous/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
24
stacks/perilous/server.js
Normal file
24
stacks/perilous/server.js
Normal file
@ -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}`);
|
||||
});
|
||||
1
stacks/radicale/.env.template
Normal file
1
stacks/radicale/.env.template
Normal file
@ -0,0 +1 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
35
stacks/radicale/docker-compose.yml
Normal file
35
stacks/radicale/docker-compose.yml
Normal file
@ -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
|
||||
13
stacks/ramz/Dockerfile
Normal file
13
stacks/ramz/Dockerfile
Normal file
@ -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"]
|
||||
24
stacks/ramz/docker-compose.yml
Normal file
24
stacks/ramz/docker-compose.yml
Normal file
@ -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
|
||||
2
stacks/registry/.env.template
Normal file
2
stacks/registry/.env.template
Normal file
@ -0,0 +1,2 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
REGISTRY_HTTP_SECRET=${REGISTRY_HTTP_SECRET}
|
||||
27
stacks/registry/docker-compose.yml
Normal file
27
stacks/registry/docker-compose.yml
Normal file
@ -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
|
||||
1
stacks/smokeping/.env.template
Normal file
1
stacks/smokeping/.env.template
Normal file
@ -0,0 +1 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
26
stacks/smokeping/docker-compose.yml
Normal file
26
stacks/smokeping/docker-compose.yml
Normal file
@ -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
|
||||
1
stacks/syncthing/.env.template
Normal file
1
stacks/syncthing/.env.template
Normal file
@ -0,0 +1 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
30
stacks/syncthing/docker-compose.yml
Normal file
30
stacks/syncthing/docker-compose.yml
Normal file
@ -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
|
||||
1
stacks/traefik/.env.template
Normal file
1
stacks/traefik/.env.template
Normal file
@ -0,0 +1 @@
|
||||
ACME_EMAIL=${ACME_EMAIL}
|
||||
25
stacks/traefik/docker-compose.yml
Normal file
25
stacks/traefik/docker-compose.yml
Normal file
@ -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
|
||||
40
stacks/traefik/traefik.yml
Normal file
40
stacks/traefik/traefik.yml
Normal file
@ -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
|
||||
3
stacks/wallabag/.env.template
Normal file
3
stacks/wallabag/.env.template
Normal file
@ -0,0 +1,3 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
WALLABAG_DB_PASSWORD=${WALLABAG_DB_PASSWORD}
|
||||
WALLABAG_DB_ROOT_PASSWORD=${WALLABAG_DB_ROOT_PASSWORD}
|
||||
55
stacks/wallabag/docker-compose.yml
Normal file
55
stacks/wallabag/docker-compose.yml
Normal file
@ -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
|
||||
14
stacks/watchtower/docker-compose.yml
Normal file
14
stacks/watchtower/docker-compose.yml
Normal file
@ -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
|
||||
1
stacks/xbackbone/.env.template
Normal file
1
stacks/xbackbone/.env.template
Normal file
@ -0,0 +1 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
26
stacks/xbackbone/docker-compose.yml
Normal file
26
stacks/xbackbone/docker-compose.yml
Normal file
@ -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
|
||||
2
stacks/zerotier/.env.template
Normal file
2
stacks/zerotier/.env.template
Normal file
@ -0,0 +1,2 @@
|
||||
DOMAIN=${DOMAIN}
|
||||
# Copy additional env vars from /var/core/zerotier/denv on old prod
|
||||
24
stacks/zerotier/docker-compose.yml
Normal file
24
stacks/zerotier/docker-compose.yml
Normal file
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user