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:
2025-12-31 13:29:43 -05:00
commit 4dbb0b9180
56 changed files with 1390 additions and 0 deletions

View 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

View 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"]

View 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

View 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
View 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}`);
});