Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.allthingslinux.org/llms.txt

Use this file to discover all available pages before exploring further.

This guide covers everything you need to build, configure, and manage atl.sh: from first-time infrastructure provisioning through day-to-day operations.

Prerequisites

Install these tools before doing anything else: Run 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.
  1. Clone the repository and install Ansible dependencies:
    just install
    
  2. Install pre-commit hooks (runs lint on each commit):
    pre-commit install
    
  3. Provision the staging VPS with Terraform (production is bare-metal and not managed by Terraform):
    cp terraform/terraform.tfvars.example terraform/terraform.tfvars
    # Edit terraform.tfvars with Hetzner API token, Cloudflare credentials, etc.
    just tf-init
    just tf-apply
    
  4. Configure the server with Ansible:
    just deploy staging   # staging VPS first
    just deploy prod      # physical server when ready
    

Environments

TargetHostDescription
dev127.0.0.1:2223Local Vagrant VM
stagingstaging.atl.shHetzner Cloud VPS
prodatl.shPhysical Hetzner dedicated server
The inventory lives in ansible/inventory/. Each environment maps to an Ansible host group with its own host_vars for per-environment overrides.

Command Reference

All commands run via just. Run just to list them.

Deployment

just deploy <target>           # full playbook run
just deploy-check <target>     # dry run (--check --diff, no changes)
just deploy-tag <target> <tag> # selective run by role tag
just deploy-list-tags          # list all available tags
just deploy-list-tasks <target># list tasks that would run

Infrastructure (staging only)

Terraform manages only the staging VPS and Cloudflare DNS. Production is bare-metal and not provisioned by Terraform.
just tf-init    # initialize Terraform backend and providers
just tf-plan    # preview infrastructure changes
just tf-apply   # apply infrastructure changes

User Management

just create-user <username> '<ssh-key>' <target>
just remove-user <username> <target>

Inventory & Diagnostics

just ping <target>             # test SSH connectivity (Ansible ping)
just inventory-list <target>   # list inventory hosts as JSON
just inventory-graph           # show inventory as tree
just syntax-check              # validate playbook syntax
just config-dump               # show effective Ansible configuration
just ansible-doc <module>      # look up module documentation

Development

just dev-up     # start Vagrant dev VM (requires .ssh/dev_key)
just dev-down   # halt Vagrant dev VM
just dev-check  # verify dev prerequisites

Other

just install    # install Ansible roles and collections
just lint       # run all linters (pre-commit, ansible-lint)
just vault-edit # edit encrypted secrets

Selective Deployment

Deploy only specific roles using tags. This is useful for iterating on a single role without running the full playbook.
just deploy-tag prod base          # apt, packages, NTP, shells, languages
just deploy-tag prod infra         # SSH, firewall, fail2ban, AIDE, auditd, monitoring, backups
just deploy-tag prod users         # skel files, MOTD, PAM limits
just deploy-tag prod environment   # cgroup limits, quotas, tmpfs, PATH
just deploy-tag prod services      # nginx, Gemini, Gopher, finger, FTP, games, webring
You can combine tags:
just deploy-tag prod "infra,services"

Ansible Roles

The playbook uses five roles, each handling a distinct layer of the system.
RolePurpose
baseApt cache, base packages, NTP, shells, languages, editors, CLI tools
infraSSH hardening, firewall, fail2ban, auditd, AIDE, monitoring, backups
usersSkel files, MOTD, PAM limits
environmentCgroup limits, disk quotas, tmpfs isolation, XDG dirs, PATH
servicesNginx, Gemini, Gopher, finger, FTP, games, webring
The 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

just create-user johndoe 'ssh-ed25519 AAAA...' prod
This runs ansible/playbooks/create-user.yml, which:
  1. Creates a system account in the pubnix group
  2. Installs the SSH public key to ~/.ssh/authorized_keys
  3. Copies /etc/skel/ to their home directory, which includes:
    • public_html/ — static web hosting root
    • public_gemini/ — Gemini capsule root
    • public_gopher/ — Gopher hole root
    • .plan, .project — finger profile files
    • .tmux.conf — pre-configured tmux with Ctrl-a prefix

Removing a user

just remove-user johndoe prod
This runs ansible/playbooks/remove-user.yml, which removes the account and home directory.

Secrets Management

All secrets live in ansible/inventory/group_vars/all/vault.yml, encrypted with Ansible Vault. Never commit this file unencrypted.
# Edit secrets
just vault-edit

# Run a playbook with vault decryption (prompts for password)
cd ansible && ansible-playbook site.yml --ask-vault-pass

# Or store the vault password in a file (do not commit this file)
echo "your-vault-password" > .vault-pass
cd ansible && ansible-playbook site.yml --vault-password-file ../.vault-pass
Variables stored in the vault include Borgmatic repository credentials, Cloudflare API tokens, and any other credentials the roles consume.

Logging and Auditing

The server uses a hybrid logging approach.
Log sourceMechanismLocation
System logssystemd-journald (1 GB cap)journalctl
Nginxlogrotate/var/log/nginx/
Fail2banlogrotate/var/log/fail2ban.log
Geminimolly-brown/var/log/molly-brown/
Audit logauditd/var/log/audit/audit.log
Backupsborgmatic timerjournalctl -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).
# Follow nginx access log in real time
tail -f /var/log/nginx/access.log

# View all logs since last boot
journalctl -b

# Stream logs for a specific service
journalctl -u sshd -f

# Recent audit events
ausearch -ts recent

# Privilege escalation events
ausearch -k priv_esc

# Generate audit summary report
aureport --summary

Linting

Run all linters before opening a pull request:
just lint
This runs 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:
just syntax-check