tightening up the docs

This commit is contained in:
Matthias Johnson 2026-02-28 21:34:02 -07:00
parent bad5d480d1
commit 9ecc7a54fc

105
README.md
View file

@ -4,38 +4,25 @@
**[codeberg.org/opennomad/linderhof](https://codeberg.org/opennomad/linderhof)**
a self-hosting stack based on ansible and docker compose that comes with
a self-hosting stack based on ansible and docker compose that comes with email, web server, git hosting, matrix, monitoring, web analytics, calendar & contacts, backups, overlay networking, and intrusion prevention — no databases, no external services.
- email
- [docker-mailserver](https://github.com/docker-mailserver/docker-mailserver)
- [rainloop](https://www.rainloop.net/)
- web server
- [caddy](https://caddyserver.com/)
- git server
- [forgejo](https://forgejo.org/)
- matrix homeserver
- [tuwunel](https://github.com/matrix-construct/tuwunel)
- monitoring
- [alloy](https://github.com/grafana/alloy)
- [grafana](https://grafana.com/)
- [prometheus](https://prometheus.io/)
- [loki](https://github.com/grafana/loki)
- web analytics
- [goaccess](https://goaccess.io/)
- calendar & contacts
- [radicale](https://radicale.org/)
- backups
- [restic](https://github.com/restic/restic)
- overlay network
- [nebula](https://github.com/slackhq/nebula)
- docker image update notifications
- [diun](https://github.com/crazy-max/diun)
- intrusion prevention
- [fail2ban](https://github.com/fail2ban/fail2ban)
set `enable_<service>: false` in `config.yml` to disable any service — DNS records, Docker networks, and deployment tasks are all skipped automatically.
other features include:
- runs on opensource
- no databases / no external services
| service | toggle | default | powered by |
|---|---|---|---|
| web server | `enable_caddy` | on | [caddy](https://caddyserver.com/) |
| email | `enable_mail` | on | [docker-mailserver](https://github.com/docker-mailserver/docker-mailserver), [rainloop](https://www.rainloop.net/) |
| git hosting | `enable_forgejo` | on | [forgejo](https://forgejo.org/) |
| matrix homeserver | `enable_tuwunel` | on | [tuwunel](https://github.com/matrix-construct/tuwunel) |
| monitoring | `enable_monitoring` | on | [prometheus](https://prometheus.io/), [grafana](https://grafana.com/), [loki](https://github.com/grafana/loki), [alloy](https://github.com/grafana/alloy) |
| web analytics | `enable_goaccess` | on | [goaccess](https://goaccess.io/) |
| calendar & contacts | `enable_radicale` | on | [radicale](https://radicale.org/) |
| backups | `enable_restic` | **off** | [restic](https://github.com/restic/restic) |
| overlay network | `enable_nebula` | on | [nebula](https://github.com/slackhq/nebula) |
| image update alerts | `enable_diun` | on | [diun](https://github.com/crazy-max/diun) |
| intrusion prevention | `enable_fail2ban` | on | [fail2ban](https://github.com/fail2ban/fail2ban) |
> **restic** is off by default — it requires a [Hetzner Storage Box](https://www.hetzner.com/storage/storage-box/) for its backup target. enable it and configure `restic_repository` in `config.yml` once you have one.
## what you need
@ -78,41 +65,27 @@ ansible-galaxy collection install -r requirements.yml
## deploy
### provision a server (Hetzner)
full deployment order for a fresh server:
```bash
ansible-playbook playbooks/provision.yml
ansible-playbook playbooks/provision.yml # create server, writes IP to stack config
ansible-playbook playbooks/dns.yml # create DNS zones and records
ansible-playbook playbooks/site.yml --tags bootstrap # users, SSH hardening, packages, Docker
ansible-playbook playbooks/site.yml # deploy all services
ansible-playbook playbooks/dkim_sync.yml # generate DKIM keys and publish to DNS
```
creates the server, registers your SSH key, and writes the IP to your stack config automatically. default type is `cx23` (2 vCPU, 4 GB); override with `-e hcloud_server_type=cx33`.
**provision** creates the server on Hetzner, registers your SSH key, and writes the IP to your stack config automatically. default type is `cx23` (2 vCPU, 4 GB); override with `-e hcloud_server_type=cx33`.
### update DNS
**dns** creates all zones and records conditional on your `enable_*` settings — disabled services get no DNS entries.
```bash
ansible-playbook playbooks/dns.yml
```
**bootstrap** connects as `root` (the only user on a fresh server), creates your admin user with passwordless sudo, hardens SSH, and installs base packages including Docker.
creates all DNS zones and records for your domain. records are conditional on your `enable_*` settings — disabled services won't get DNS entries.
**site.yml** deploys all enabled services. subsequent runs are idempotent — safe to re-run to apply config changes.
### bootstrap the server
> **note:** on first deployment, the mail role briefly stops Caddy to acquire a Let's Encrypt certificate for the mail hostname via certbot standalone. Caddy is restarted immediately after. this only happens once.
first-time setup of the server (users, SSH hardening, packages, Docker):
```bash
ansible-playbook playbooks/bootstrap.yml
```
this connects as `root` (the only user on a fresh server), creates your admin user with passwordless sudo, sets passwords for `root` and the admin user, hardens SSH, and installs base packages.
### deploy services
```bash
ansible-playbook playbooks/site.yml
```
deploys all enabled services. subsequent runs are idempotent — safe to re-run to apply config changes.
> **note:** on first deployment, the mail role briefly stops Caddy to acquire a Let's Encrypt certificate for the mail hostname via certbot standalone. Caddy is restarted immediately after. this only happens once — subsequent runs detect the existing certificate and skip it.
**dkim_sync** generates DKIM keys for all mail domains, writes them to your stack config, and publishes the `mail._domainkey` DNS records. safe to re-run.
## bring your own server
@ -120,7 +93,7 @@ deploys all enabled services. subsequent runs are idempotent — safe to re-run
if you already have an Ubuntu server with SSH access:
1. run `./setup.sh` — enter the server's existing hostname and IP when prompted
2. ensure your SSH key is authorized for the admin user and they have passwordless sudo — or run `bootstrap.yml` first if starting from root access
2. ensure your SSH key is authorized for the admin user and they have passwordless sudo — or run `ansible-playbook playbooks/site.yml --tags bootstrap` first if starting from root access
3. skip `provision.yml` and `dns.yml` if you're managing DNS elsewhere
4. run `ansible-playbook playbooks/site.yml`
@ -157,24 +130,6 @@ stack config lives at `$XDG_CONFIG_HOME/linderhof/<stack>/`:
```
## service toggles
set `enable_<service>: false` in `config.yml` to disable a service. DNS records, Docker networks, and deployment tasks for that service will all be skipped automatically.
| variable | service |
|---|---|
| `enable_mail` | email (docker-mailserver + rainloop) |
| `enable_forgejo` | git hosting |
| `enable_tuwunel` | Matrix homeserver |
| `enable_monitoring` | Prometheus, Grafana, Loki, Alloy |
| `enable_goaccess` | web analytics |
| `enable_goaccess_sync` | rsync analytics reports to a remote host (off by default) |
| `enable_radicale` | CalDAV/CardDAV |
| `enable_restic` | encrypted backups (requires a Hetzner Storage Box — off by default) |
| `enable_nebula` | overlay network |
| `enable_diun` | Docker image update notifications |
| `enable_fail2ban` | intrusion prevention |
## overriding variables