removed stoat service user

This commit is contained in:
2026-09-13 14:00:45 -06:00
parent f70e88f6d5
commit 0ee978142a
8 changed files with 55 additions and 79 deletions

View File

@@ -1,6 +1,6 @@
# Stoat — Fedora 44 self-hosted chat deployment
Ansible automation that **hardens a Fedora 44 server**, installs **rootless Podman**, and deploys a self-hosted **[Stoat](https://stoat.chat)** instance (open-source community chat, formerly Revolt) using the official [`stoatchat/self-hosted`](https://github.com/stoatchat/self-hosted) stack.
Ansible automation that **hardens a Fedora 44 server**, installs **Podman**, and deploys a self-hosted **[Stoat](https://stoat.chat)** instance (open-source community chat, formerly Revolt) using the official [`stoatchat/self-hosted`](https://github.com/stoatchat/self-hosted) stack.
**Target host:** `stoat-demo`
@@ -17,7 +17,7 @@ Ansible automation that **hardens a Fedora 44 server**, installs **rootless Podm
│ ├── main.yml # imports, in order
│ ├── system.yml # dnf full update + base packages
│ ├── harden.yml # firewalld, sshd, fail2ban, dnf5-automatic, logrotate
│ ├── podman.yml # rootless podman, compose, service user, linger
│ ├── podman.yml # podman, compose, SELinux container policy
│ └── stoat.yml # clone repo → generate config → systemd unit → start
├── handlers/main.yml
└── templates/
@@ -60,8 +60,8 @@ Ansible automation that **hardens a Fedora 44 server**, installs **rootless Podm
| **Fail2Ban** | `sshd` jail (3 retries → 1 h ban, incremental for repeat offenders) |
| **Auto-updates** | `dnf5-plugin-automatic`, security-only, `apply_updates = yes`, started timer |
| **Logs** | logrotate rule for fail2ban logs (daily, 7 rotations, compressed) |
| **Podman** | rootless engine + `podman-compose` + `podman-docker`; `net.ipv4.ip_unprivileged_port_start=80` so Caddy can bind 80/443; dedicated `stoat` service user with lingering enabled |
| **SELinux** | keeps mode enforcing; installs `container-selinux` (container policy) + `policycoreutils-python-utils` / `setools-console` (`semanage`, `audit2why`) |
| **Podman** | engine + `podman-compose` + `podman-docker`; runs as root (no unprivileged service user, no linger) |
| **SELinux** | keeps mode enforcing; installs `container-selinux` (container policy) + `policycoreutils-python-utils` / `setools-console` (`semanage`, `audit2why`); registers the deployment tree as `container_file_t` so rootful containers can use the bind mounts |
| **Stoat** | clone `stoatchat/self-hosted` to `/opt/stoat/self-hosted`; run `generate_config.sh <domain>` (answers: built-in Caddy, video enabled); start stack under `podman-compose-stoat.service` |
---
@@ -86,8 +86,6 @@ Ansible automation that **hardens a Fedora 44 server**, installs **rootless Podm
| `stoat_version` | `main` | git ref of `self-hosted` repo |
| `stoat_reverse_proxy` | `false` | `true` = move Caddy to `:8880`, bring your own front proxy/TLS |
| `stoat_video` | `true` | enable camera/screen sharing (LiveKit) |
| `stoat_service_user` | `stoat` | unprivileged user running rootless podman |
| `podman_unprivileged_port_start` | `80` | lowest port rootless containers may bind |
| `selinux_mode` | `enforcing` | SELinux mode the role enforces (config + setenforce) |
| `fail2ban_port` / `fail2ban_maxretry` / `fail2ban_bantime` | `22` / `3` / `3600` | sshd jail tuning |
| `firewall_services` / `firewall_ports` | ssh,http,https / 7881, UDP range | firewalld allow rules |
@@ -114,7 +112,7 @@ ansible-playbook -i inventory.ini site.yml -k -e "stoat_domain=chat.example.org
## Day-to-day operations
All commands run on the server, mostly as the `stoat` user (`sudo -u stoat` when needed):
All commands run on the server as root:
```bash
# Stack status
@@ -122,14 +120,14 @@ systemctl status podman-compose-stoat
podman ps
# Logs (per service: api / events / web / livekit / database …)
sudo -u stoat podman logs -f api
podman logs -f api
# Restart the whole stack
sudo systemctl restart podman-compose-stoat
systemctl restart podman-compose-stoat
# Update Stoat to new images (run in /opt/stoat/self-hosted as stoat)
sudo -u stoat podman-compose pull
sudo -u stoat podman-compose up -d
# Update Stoat to new images (run in /opt/stoat/self-hosted)
podman-compose pull
podman-compose up -d
# Fail2Ban status
sudo fail2ban-client status sshd
@@ -142,7 +140,7 @@ sudo firewall-cmd --list-all
## SELinux notes
Fedora 44 ships SELinux **enforcing**; the role pins that mode. The stack runs in **rootless** podman, where containers run in the user's own SELinux domain and can read/write the `stoat` user's files — bind mounts under `/opt/stoat/self-hosted/data` generally need no relabeling or `:Z` flags.
Fedora 44 ships SELinux **enforcing**; the role pins that mode. Containers run as root under the `container_t` domain, so the role registers `/opt/stoat/self-hosted` as **`container_file_t`** (via `semanage fcontext` + `restorecon`) — that's what lets the rootful containers read/write the bind-mounted `data/` volumes and config files without per-volume `:Z` flags, and the registration is persistent (survives `restorecon`).
If a service misbehaves, check for denials before anything else:
@@ -159,9 +157,10 @@ Common knobs if a denial shows up (only set what the denial actually requires):
sudo setsebool -P container_connect_any 1
# container needs to manage cgroups (systemd inside a container)
sudo setsebool -P container_manage_cgroup 1
# give a specific bind-mounted volume its container context (last resort,
# only if audit2why points at file labeling):
sudo chcon -R -t container_file_t /opt/stoat/self-hosted/data
# re-apply file contexts if you add bind mounts outside the
# deployment tree (the role only labels /opt/stoat/self-hosted):
sudo semanage fcontext -a -t container_file_t '/opt/stoat/moredata(/.*)?'
sudo restorecon -RF /opt/stoat/moredata
```
---
@@ -169,8 +168,8 @@ sudo chcon -R -t container_file_t /opt/stoat/self-hosted/data
## Caveats & notes
- **First start pulls ~2 GB of images** and initializes MongoDB — allow a few minutes; the systemd unit tolerates up to 15 min (`TimeoutStartSec=900`).
- **Podman, not Docker**: the upstream stack is docker-compose based; this deployment runs it with `podman-compose` (rootless). It supports the compose features this stack needs (`depends_on: condition: service_healthy`, bind mounts, port ranges).
- **`generate_config.sh` is guarded** by `creates:` — it never overwrites existing `secrets.env`/`Revolt.toml` unintentionally. To regenerate config deliberately, run it manually with `--overwrite` as the `stoat` user (after backing up secrets).
- **Security posture**: rootless containers, fail2ban, firewalld default-deny, security-only auto-updates. SSH configuration is left exactly as you set it. (Aligned with the upstream guide's recommendations.)
- **Rootless bind mounts**: data persists in `/opt/stoat/self-hosted/data/` (mongodb, rabbitmq, minio, caddy).
- **Podman, not Docker**: the upstream stack is docker-compose based; this deployment runs it with `podman-compose` as root. It supports the compose features this stack needs (`depends_on: condition: service_healthy`, bind mounts, port ranges).
- **`generate_config.sh` is guarded** by `creates:` — it never overwrites existing `secrets.env`/`Revolt.toml` unintentionally. To regenerate config deliberately, run it manually with `--overwrite` (after backing up secrets).
- **Security posture**: fail2ban, firewalld default-deny, security-only auto-updates, SELinux enforcing with `container_file_t` labels. SSH configuration is left exactly as you set it. (Aligned with the upstream guide's recommendations.)
- **Data persistence**: everything persists in `/opt/stoat/self-hosted/data/` (mongodb, rabbitmq, minio, caddy) — keep it out of the git clone/reset.
- System hardening + Stoat are one role run; to re-apply only config changes, re-running `site.yml` is idempotent.