- Jinja 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| files/keys | ||
| inventory | ||
| playbooks | ||
| roles | ||
| .gitignore | ||
| ansible.cfg | ||
| README.md | ||
| requirements.yml | ||
devserver
Ansible setup for development, business and home servers.
Layout
inventory/
hosts.yml Groups: devservers, businessservers, homeservers
group_vars/all/ Users, sudo
group_vars/devservers/ Docker users, Forgejo; vault.yml.example for secrets
host_vars/ Per-host overrides (e.g. personal users)
playbooks/
site.yml Everything, in order
common.yml Every host: users, security, firewall, docker
devservers.yml Forgejo (+ Caddy)
businessservers.yml Empty
homeservers.yml Empty
roles/
users/ admin, developer and per-host personal accounts, SSH keys, sudo
security/ SSH hardening: key-only login, no root login
firewall/ ufw, default-deny incoming; allow_ports.yml for other roles
docker/ Docker Engine + compose (Debian/Ubuntu, Arch); compose_up.yml for other roles
caddy/ Reverse proxy + automatic Let's Encrypt (docker compose)
forgejo/ Forgejo + Postgres (docker compose), served at git.<host> through Caddy
files/keys/ Public SSH keys: <name>.pub
Prerequisites
Control user on each host
Ansible connects as a dedicated ansible user with passwordless sudo, created
once by hand per host (this account is not managed by roles/users). As root on
the target:
useradd --create-home --shell /bin/bash ansible
passwd --lock ansible
echo 'ansible ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/ansible
chmod 440 /etc/sudoers.d/ansible
install -d -m 700 -o ansible -g ansible /home/ansible/.ssh
Install your key (ssh-keygen -t ed25519 -f ~/.ssh/ansible if you don't have one):
cat ~/.ssh/ansible.pub | ssh root@<host> \
"cat >> /home/ansible/.ssh/authorized_keys && chmod 600 /home/ansible/.ssh/authorized_keys && chown ansible:ansible /home/ansible/.ssh/authorized_keys"
ssh -i ~/.ssh/ansible ansible@<host> sudo whoami # root, no prompt
sudo must be installed on the target. The key path is set in inventory/hosts.yml.
Collections
ansible-galaxy collection install -r requirements.yml
DNS
Every proxied app is served at <subdomain>.<inventory_hostname> (for Forgejo
git.<host>). Point a DNS record (A/AAAA or CNAME) for each at the server
before the first run. The run checks this itself, see "Reverse proxy and apps".
Secrets
echo -n 'your-vault-password' > .vault_pass && chmod 600 .vault_pass
cd inventory/group_vars/devservers
cp vault.yml.example vault.yml # set vault_forgejo_db_password
ansible-vault encrypt vault.yml
ansible.cfg points at .vault_pass; Ansible won't start without that file.
Running
ansible-playbook playbooks/site.yml # everything
ansible-playbook playbooks/common.yml # users, security, firewall, docker only
ansible-playbook playbooks/site.yml --limit <host>
ansible-playbook playbooks/site.yml --check --diff
Users
-
admin(groupadmin, sudo) anddeveloper(groupdeveloper, no sudo) exist on every host. -
Personal accounts come from
users_personal. The default is set ininventory/group_vars/all/vars.yml; override per host inhost_vars/<host>.yml:users_personal: - name: first_user - name: second_user groups: [developer] # default: admin + developer -
Keys are read from
files/keys/<name>.pub(or setssh_keys:on the entry).authorized_keysis managed exclusively: keys not declared are removed. -
No passwords are set, so sudo is passwordless (
users_sudo_nopasswd). -
On devservers all of these accounts are in the
dockergroup (root-equivalent), seedocker_users.
Security
roles/security writes /etc/ssh/sshd_config.d/00-hardening.conf:
- Public-key authentication only (
AuthenticationMethods publickey); password and keyboard-interactive login are off. - Root login off (
security_ssh_permit_root_login), X11 forwarding off,MaxAuthTries 3. - Optional
security_ssh_allow_users/security_ssh_allow_groups. The default is no restriction. If you setsecurity_ssh_allow_users, include theansibleuser.
The role runs after users, so the keys are in place before passwords are disabled.
It checks the effective configuration (sshd -T) before reloading sshd, so a
config that doesn't take effect fails the run instead of silently doing nothing.
The ansible user must have its key installed (see Prerequisites), or you lock yourself out.
The SSH port is not changed here; the firewall follows the port Ansible connects on.
Firewall and Docker
roles/firewall uses ufw: incoming denied, SSH (the port Ansible connects on)
allowed. Roles open their own ports through roles/firewall/tasks/allow_ports.yml
(Caddy: 80/443, Forgejo: git-ssh port).
Docker-published ports bypass ufw, so the rules document intent but don't
enforce it. Only publish ports a service needs. Everything behind Caddy
publishes nothing and is reached over the shared proxy network.
Reverse proxy and apps
roles/caddy runs Caddy in /opt/caddy and creates the shared proxy docker network.
An app joins that network and drops <name>.caddy into /opt/caddy/etc/sites/
(see roles/forgejo); the app role declares caddy in its meta/main.yml.
App roles also include two checks from roles/caddy, so a missing DNS record
or failed certificate issuance fails the run instead of going unnoticed:
check_dns.yml(before the site file is deployed): the domain must resolve, and only to addresses of the target (its interfaces, or the address Ansible connects to). Disable withcaddy_check_dns: false, e.g. behind a CDN proxy.verify_https.yml(after the reload):https://<domain>/must present a valid certificate when requested from the control node, retried for up tocaddy_verify_retries * caddy_verify_delayseconds (3 minutes by default). Any HTTP status counts, only connection and TLS errors fail. Disable withcaddy_verify_https: false, e.g. if the control node can't reach the server. Skipped in--checkmode.
Forgejo
- Web:
https://<forgejo_subdomain>.<host>/(subdomain defaultgit). - Git over SSH:
ssh://git@<forgejo_subdomain>.<host>:<forgejo_ssh_port>/<user>/<repo>.git(set ingroup_vars/devservers; the system sshd keeps its own port). - Open registration is off (
forgejo_disable_registration). Complete the install wizard on first visit to create the admin account. - Files live in
/opt/forgejo. Every run reconciles the stack withdocker compose up -d.
Not included yet
SSH hardening (root login, password auth), base packages and backups.