Prerequisites
Install these tools before doing anything else:- Ansible — configuration management
- just — task runner (
justto list all commands) - Terraform 1.8+ — infrastructure provisioning
- Vagrant + vagrant-libvirt — local dev VMs
just with no arguments at any time to see every available command.
Initial Setup
Follow these steps when setting up the project for the first time.-
Clone the repository and install Ansible dependencies:
-
Install pre-commit hooks (runs lint on each commit):
-
Provision the staging VPS with Terraform (production is bare-metal and not
managed by Terraform):
-
Configure the server with Ansible:
Environments
| Target | Host | Description |
|---|---|---|
dev | 127.0.0.1:2223 | Local Vagrant VM |
staging | staging.atl.sh | Hetzner Cloud VPS |
prod | atl.sh | Physical Hetzner dedicated server |
ansible/inventory/. Each environment maps to an
Ansible host group with its own host_vars for per-environment overrides.
Command Reference
All commands run viajust. Run just to list them.
Deployment
Infrastructure (staging only)
Terraform manages only the staging VPS and Cloudflare DNS. Production is bare-metal and not provisioned by Terraform.User Management
Inventory & Diagnostics
Development
Other
Selective Deployment
Deploy only specific roles using tags. This is useful for iterating on a single role without running the full playbook.Ansible Roles
The playbook uses five roles, each handling a distinct layer of the system.| Role | Purpose |
|---|---|
base | Apt cache, base packages, NTP, shells, languages, editors, CLI tools |
infra | SSH hardening, firewall, fail2ban, auditd, AIDE, monitoring, backups |
users | Skel files, MOTD, PAM limits |
environment | Cgroup limits, disk quotas, tmpfs isolation, XDG dirs, PATH |
services | Nginx, Gemini, Gopher, finger, FTP, games, webring |
infra role is further split internally into three task files
(security/, monitoring.yml, backup.yml) to keep concerns navigable
without creating extra top-level roles.
User Management
Creating a user
ansible/playbooks/create-user.yml, which:
- Creates a system account in the
pubnixgroup - Installs the SSH public key to
~/.ssh/authorized_keys - Copies
/etc/skel/to their home directory, which includes:public_html/— static web hosting rootpublic_gemini/— Gemini capsule rootpublic_gopher/— Gopher hole root.plan,.project— finger profile files.tmux.conf— pre-configured tmux withCtrl-aprefix
Removing a user
ansible/playbooks/remove-user.yml, which removes the account and
home directory.
Secrets Management
All secrets live inansible/inventory/group_vars/all/vault.yml, encrypted
with Ansible Vault. Never commit this file unencrypted.
Logging and Auditing
The server uses a hybrid logging approach.| Log source | Mechanism | Location |
|---|---|---|
| System logs | systemd-journald (1 GB cap) | journalctl |
| Nginx | logrotate | /var/log/nginx/ |
| Fail2ban | logrotate | /var/log/fail2ban.log |
| Gemini | molly-brown | /var/log/molly-brown/ |
| Audit log | auditd | /var/log/audit/audit.log |
| Backups | borgmatic timer | journalctl -u borgmatic |
auditd runs 40+ rules covering identity files, privilege escalation, network
configuration changes, suspicious tools (wget, curl, nc, socat),
scripting interpreters run by users, and system calls (ptrace,
memfd_create, execve, chmod, chown).
Linting
Run all linters before opening a pull request:pre-commit against all files (trailing whitespace, YAML checks,
terraform fmt/validate) followed by ansible-lint separately.
To check only playbook syntax without running all hooks: