From ece5fd817ade5de56c20cec3f126b570e784bafa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Gra=CC=88fenstein?= Date: Sun, 22 Mar 2026 12:03:20 +0100 Subject: [PATCH] init --- README.md | 244 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a073e14 --- /dev/null +++ b/README.md @@ -0,0 +1,244 @@ +# Nextcloud Self-hosted VPS + +Docker Compose setup for a self-hosted VPS running Nextcloud, Gitea, and monitoring — managed as a GitOps-style repo. + +## Prerequisites + +- A VPS with SSH access +- Domain `t-gstone.de` with DNS control +- Git installed locally + +Check your VPS OS: + +```bash +cat /etc/os-release +``` + +## DNS Setup + +Create these A records pointing to your VPS IP: + +| Record | Value | +|--------|-------| +| `nextcloud.t-gstone.de` | `` | +| `git.t-gstone.de` | `` | + +## Quick Start + +```bash +# 1. Clone this repo on the VPS +git clone && cd nextcloud-selfhosted + +# 2. Create .env files from examples +cp .env.example .env +cp nextcloud/.env.example nextcloud/.env +cp gitea/.env.example gitea/.env +cp monitoring/.env.example monitoring/.env + +# 3. Edit each .env file with real values +# - Generate strong passwords for Postgres and Nextcloud admin +# - Add Grafana Cloud credentials to monitoring/.env + +# 4. Deploy +./deploy.sh +``` + +## Services + +| Service | Subdomain | Stack | +|---------|-----------|-------| +| Nextcloud | `nextcloud.t-gstone.de` | Nextcloud + PostgreSQL 16 + Redis 7 | +| Gitea | `git.t-gstone.de` | Gitea (SQLite) | +| Caddy | — | Reverse proxy, auto HTTPS | +| Monitoring | — | Grafana Alloy -> Grafana Cloud | + +## Data Layout + +All persistent data lives under `/opt/docker-data/`: + +``` +/opt/docker-data/ +├── caddy/data/ # TLS certificates +├── caddy/config/ +├── nextcloud/html/ # Nextcloud application +├── nextcloud/data/ # User files +├── nextcloud/db/ # PostgreSQL data +├── gitea/data/ # Repositories + Gitea DB +└── gitea/config/ # Gitea configuration +``` + +## Managing Services + +Each service has its own compose file and can be managed independently: + +```bash +# Restart just Nextcloud +docker compose -f nextcloud/docker-compose.yml --env-file .env up -d + +# View logs for Gitea +docker compose -f gitea/docker-compose.yml --env-file .env logs -f + +# Stop everything +for svc in monitoring gitea nextcloud caddy; do + docker compose -f $svc/docker-compose.yml --env-file .env down +done +``` + +## Adding a New Service + +1. Create a new directory: `mkdir myapp/` +2. Create `myapp/docker-compose.yml`: + - Join the `proxy` external network + - Bind mount data to `${DATA_ROOT}/myapp/` + - Add `myapp/.env.example` if the service needs secrets +3. Add a reverse proxy entry in `caddy/Caddyfile`: + ``` + myapp.t-gstone.de { + reverse_proxy myapp:8080 + } + ``` +4. Reload Caddy: `docker exec caddy caddy reload --config /etc/caddy/Caddyfile` +5. Add a DNS A record for `myapp.t-gstone.de` -> VPS IP +6. Add data directory creation to `deploy.sh` +7. Add backup steps to `backup.sh` if the service has persistent data + +## Backup & Restore + +### Creating Backups + +```bash +./backup.sh +``` + +This dumps the Nextcloud Postgres database, archives Nextcloud data/config and Gitea data, and stores them in `/opt/backups/` with date-stamped filenames. Backups older than 7 days are automatically removed. + +Schedule daily backups: + +```bash +crontab -e +# Add: +0 3 * * * /path/to/backup.sh >> /var/log/backup.log 2>&1 +``` + +### Restoring + +```bash +./restore.sh 2026-03-22 +``` + +This stops services, restores data from the specified date's backup files, restores the database, and restarts everything. + +### Backup Strategy Options + +The current setup stores backups locally on the same VPS. For production use, consider an off-site strategy: + +| Option | Pros | Cons | +|--------|------|------| +| **Local only** (`/opt/backups/`) | Simplest, no extra cost | Lost if VPS dies | +| **rsync to second VPS or home server** | Simple, full control | Need a second machine | +| **S3-compatible object storage** (Backblaze B2, Hetzner Object Storage, Wasabi) | Cheap, durable, off-site | Monthly cost (~$0.005/GB) | +| **Restic or BorgBackup** to any remote target | Encrypted, deduplicated, incremental | More setup complexity | + +Recommendation for a personal setup: **Backblaze B2 or Hetzner Object Storage with Restic**. Both offer free egress (B2) or low cost, and Restic handles encryption + deduplication automatically. A cron job running `restic backup` after `backup.sh` completes the pipeline. + +## Monitoring + +### Setup + +1. Sign up at [grafana.com](https://grafana.com) (free tier) +2. Go to **My Account** -> **Grafana Cloud** -> your stack +3. Find your Loki and Prometheus endpoints + credentials +4. Fill in `monitoring/.env` with those values +5. Start the monitoring stack: `docker compose -f monitoring/docker-compose.yml --env-file .env up -d` + +### Recommended Alerts + +Set these up in Grafana Cloud UI (**Alerting** -> **Alert rules**): + +| Alert | Condition | Severity | +|-------|-----------|----------| +| Disk usage high | `node_filesystem_avail_bytes` / `node_filesystem_size_bytes` < 0.2 | Critical | +| Container restarting | Container restart count > 3 in 10 min | Warning | +| High memory usage | `node_memory_MemAvailable_bytes` / `node_memory_MemTotal_bytes` < 0.1 | Warning | +| High CPU usage | `node_cpu_seconds_total` idle < 10% sustained 5 min | Warning | +| Nextcloud cron stale | No log line from `nextcloud-cron` in 15 min | Warning | + +### Recommended Dashboards + +Import these from [Grafana Dashboards](https://grafana.com/grafana/dashboards/): + +- **Node Exporter Full** (ID: 1860) — CPU, memory, disk, network +- **Docker Container Monitoring** (ID: 893) — per-container resource usage +- **Loki Docker Logs** — search and filter container logs + +## Security Hardening (Reference) + +These steps are **not automated** — apply them manually based on your needs. + +### Firewall (UFW) + +```bash +ufw default deny incoming +ufw default allow outgoing +ufw allow 22/tcp # SSH (change if using non-default port) +ufw allow 80/tcp # HTTP (Caddy redirect) +ufw allow 443/tcp # HTTPS +ufw allow 443/udp # HTTPS (HTTP/3 / QUIC) +ufw allow 2222/tcp # Gitea SSH +ufw enable +``` + +### SSH Hardening + +Edit `/etc/ssh/sshd_config`: + +``` +PasswordAuthentication no +PermitRootLogin prohibit-password +MaxAuthTries 3 +``` + +Then: `systemctl restart sshd` + +### fail2ban + +```bash +apt install fail2ban +``` + +Create `/etc/fail2ban/jail.local`: + +```ini +[sshd] +enabled = true +maxretry = 5 +bantime = 3600 + +[nextcloud] +enabled = true +port = 80,443 +filter = nextcloud +logpath = /opt/docker-data/nextcloud/data/nextcloud.log +maxretry = 5 +bantime = 3600 +``` + +Create `/etc/fail2ban/filter.d/nextcloud.conf`: + +```ini +[Definition] +failregex = ^.*Login failed.*Remote IP.*.*$ +``` + +### Unattended Security Upgrades + +```bash +apt install unattended-upgrades +dpkg-reconfigure -plow unattended-upgrades +``` + +### Docker Daemon + +- Keep Docker updated: `apt update && apt upgrade docker-ce` +- Don't run containers as root unless necessary (Gitea uses rootless image) +- The Docker socket is mounted read-only into the Alloy container — be aware this still grants significant access