No description
  • Jinja 52.2%
  • Python 47.8%
Find a file
Jose Manuel Martinez Gallardo aeb2d16b93 Add weeWX as Phase 5 in deploy pipeline
- Import install_weewx from tasks/weewx.py
- Add as Phase 5 (after Apache, before Docker)
- Renumber subsequent phases (6→8)
2026-06-18 14:08:26 +02:00
files Change weeWX domain to weather.aralnoth.cc 2026-06-17 18:31:05 +02:00
group_data Make Cloudflare-only configurable (CLOUDFLARE_ONLY flag) 2026-06-17 18:37:11 +02:00
tasks Install weewx-interceptor plugin via wee_extension and open port 8000 in UFW 2026-06-18 12:49:41 +02:00
.gitignore Consistency: weewx.py __name__ guard + gitignore pyinfra-debug.log 2026-06-17 17:35:19 +02:00
deploy.py Add weeWX as Phase 5 in deploy pipeline 2026-06-18 14:08:26 +02:00
inventory.py Address review findings 2026-06-17 17:27:24 +02:00
pyproject.toml Move inline file writes to files/ templates 2026-06-17 11:52:42 +02:00
README.md Change weeWX domain to weather.aralnoth.cc 2026-06-17 18:31:05 +02:00

Infrastructure

Personal server infrastructure managed with pyinfra.

Prerequisites

  • Python 3.11+
  • SSH access to the target server (as root for initial bootstrap)
  • OrbStack (optional, for local testing)

Setup

# Create a virtual environment
python3 -m venv .venv
source .venv/bin/activate

# Install dependencies (pyinfra + jinja2 + paramiko)
pip install -e .

Usage

Full provision (all 7 phases)

pyinfra inventory.py deploy.py --sudo -y

Target a specific host or group

# Development VM (OrbStack)
pyinfra inventory.py deploy.py --limit "192.168.139.47" --sudo -y

# Production
pyinfra inventory.py deploy.py --limit "my-server.local" --sudo -y

Run individual task files

# Bootstrap base system
pyinfra inventory.py tasks/bootstrap.py --sudo

# Security hardening
pyinfra inventory.py tasks/security.py --sudo

# Mail (requires secrets.py)
pyinfra inventory.py tasks/mail.py --sudo

# Web server (Apache + ModSecurity + Certbot)
pyinfra inventory.py tasks/web.py --sudo

# Docker
pyinfra inventory.py tasks/docker.py --sudo

# Docker services (FreshRSS, Forgejo)
pyinfra inventory.py tasks/services.py --sudo

# GoBackup (requires secrets.py)
pyinfra inventory.py tasks/backup.py --sudo

# weeWX (standalone)
pyinfra inventory.py tasks/weewx.py --sudo

Secrets

# Create your secrets file (one time only)
cp group_data/secrets.example.py group_data/secrets.py
# Edit group_data/secrets.py with your real credentials

Cloudflare setup

This server requires Cloudflare for HTTP/HTTPS. UFW blocks all traffic to ports 80/443 except from Cloudflare IP ranges.

DNS configuration

Record Type Target Proxy
git.aralnoth.cc A <server-ip> Proxied (orange cloud)
rss.aralnoth.cc A <server-ip> Proxied (orange cloud)
weather.aralnoth.cc A <server-ip> Proxied (orange cloud)

All three must be Proxied (orange cloud). If any is set to DNS-only (gray cloud), traffic will bypass Cloudflare and be blocked by UFW.

SSL/TLS

Set SSL/TLS encryption mode to Full (not Flexible, not Full Strict) in Cloudflare dashboard → SSL/TLS. This lets Cloudflare proxy HTTPS to your server using the Let's Encrypt certificates you obtain via certbot.

Alternatively, set Flexible if you don't want to run certbot on the server (Cloudflare handles SSL entirely). In that case, Apache only needs port 80. Choose whichever you prefer.

  • SSL/TLS → Edge Certificates: Always Use HTTPS → ON
  • Security → Settings: Challenge Passage → 30 minutes
  • Security → WAF: Custom rules for rate-limiting SSH/HTTP if desired
  • Speed → Optimization: Auto Minify → CSS/JS/HTML as you prefer

Ports not proxied (direct access)

These ports bypass Cloudflare and connect directly to your server:

  • SSH (22): Direct server access
  • Forgejo SSH (2222): Git operations over SSH

Operational commands

# Check today's warnings and errors
journalctl -p warning..alert -S today

# Check specific services
journalctl -u ssh -u fail2ban -u apache2 -u docker -S today

# Fail2ban status
fail2ban-client status
fail2ban-client status sshd
fail2ban-client status forgejo-ssh
fail2ban-client status recidive

# Verify everything is working
pyinfra inventory.py tasks/verify.py --sudo

# Logwatch (daily summary)
logwatch --detail Med --mailto root

Deploy phases

Fase Tarea Qué hace
1 bootstrap Hostname, admin user, SSH hardening, paquetes base
2 security UFW, fail2ban (8 jails), unattended-upgrades, sysctl, AppArmor, rkhunter, auditd, logrotate
3 mail msmtp MTA para correos de logwatch / rkhunter
4 web Apache, ModSecurity + OWASP CRS, Certbot, 3 virtual hosts
5 docker docker.io + docker-compose (repos Debian)
6 services FreshRSS + Forgejo con Docker Compose
7 backup GoBackup con respaldos a S3 + R2 + local

Cloudflare (mandatory)

HTTP/HTTPS ports (80/443) solo aceptan tráfico desde los rangos de IP de Cloudflare. Si quitas Cloudflare, el servidor web dejará de ser accesible. SSH y Forgejo SSH (2222) no pasan por Cloudflare — se acceden directamente.

Post-deploy manual steps

# 1. Obtain SSL certificates (required for HTTPS sites)
certbot --apache -d git.aralnoth.cc -d rss.aralnoth.cc -d weather.aralnoth.cc

# 2. Enable SSL sites after certbot
a2ensite forgejo-le-ssl.conf freshrss-le-ssl.conf weewx-le-ssl.conf
systemctl reload apache2

# 3. Install and configure weeWX (if needed)
pyinfra inventory.py tasks/weewx.py --sudo

Project structure

├── pyproject.toml          # Dependencies and tool config
├── inventory.py            # Hosts and group definitions
├── deploy.py               # Main entry point (all 7 phases)
├── group_data/
│   ├── all.py              # Public variables
│   ├── secrets.py          # 🔒 Sensitive credentials (gitignored)
│   └── secrets.example.py  # Template for secrets
├── tasks/
│   ├── bootstrap.py        # Hostname, user, SSH, base packages
│   ├── security.py         # UFW, fail2ban, sysctl, AppArmor, auditd, ...
│   ├── mail.py             # msmtp MTA
│   ├── web.py              # Apache, ModSecurity + OWASP CRS, Certbot
│   ├── docker.py           # Docker Engine + Compose
│   ├── services.py         # FreshRSS + Forgejo Compose
│   ├── backup.py           # GoBackup installation and config
│   └── weewx.py            # weeWX weather station (standalone)
└── files/
    ├── backup/             # GoBackup templates
    ├── docker/             # Docker Compose files
    ├── ssh/                # SSH config templates
    ├── fail2ban/           # Fail2ban jail configs
    ├── apt/                # APT unattended-upgrades configs
    ├── sysctl/             # Kernel hardening
    ├── mail/               # msmtp config templates
    ├── apache/             # Apache virtual hosts
    ├── weewx/              # weeWX config template
    └── logrotate/          # Log rotation configs

Configuration

Edit group_data/all.py to set:

Variable Description
HOSTNAME Server hostname
ADMIN_USER Unprivileged admin username
ADMIN_SSH_KEY Public SSH key for the admin user
SSH_PORT SSH port (22 by default)
TIMEZONE Server timezone
BASE_PACKAGES APT packages to install

Edit group_data/secrets.py (copy from secrets.example.py):

Variable Description
SMTP_* Mail credentials for msmtp
AWS_* AWS S3 backup keys
R2_* Cloudflare R2 backup keys
TELEGRAM_* Telegram notification bot
BACKUP_ENCRYPTION_PASSWORD GoBackup encryption password
WEEWX_* weeWX API credentials