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)** **[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 set `enable_<service>: false` in `config.yml` to disable any service — DNS records, Docker networks, and deployment tasks are all skipped automatically.
- [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)
other features include: | service | toggle | default | powered by |
- runs on opensource |---|---|---|---|
- no databases / no external services | 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 ## what you need
@ -78,41 +65,27 @@ ansible-galaxy collection install -r requirements.yml
## deploy ## deploy
### provision a server (Hetzner) full deployment order for a fresh server:
```bash ```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 **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.
ansible-playbook playbooks/dns.yml
```
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): **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.
```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.
## bring your own server ## 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: if you already have an Ubuntu server with SSH access:
1. run `./setup.sh` — enter the server's existing hostname and IP when prompted 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 3. skip `provision.yml` and `dns.yml` if you're managing DNS elsewhere
4. run `ansible-playbook playbooks/site.yml` 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 ## overriding variables