commit 7699566068c045072d59d867c5e9a71efbcd33b8 Author: Chris Roberts <chris.roberts@learningunix.net> Date: Tue, 19 May 2026 04:51:07 -0500 initial commit Diffstat:
| A | .gitignore | | | 2 | ++ |
| A | README.md | | | 37 | +++++++++++++++++++++++++++++++++++++ |
| A | inventory.ini.example | | | 2 | ++ |
| A | roles/docker/tasks/main.yml | | | 41 | +++++++++++++++++++++++++++++++++++++++++ |
| A | roles/fail2ban/handlers/main.yml | | | 5 | +++++ |
| A | roles/fail2ban/tasks/main.yml | | | 23 | +++++++++++++++++++++++ |
| A | roles/gitea/files/docker-compose.yml | | | 39 | +++++++++++++++++++++++++++++++++++++++ |
| A | roles/gitea/tasks/main.yml | | | 29 | +++++++++++++++++++++++++++++ |
| A | roles/gitea/templates/gitea.env.j2 | | | 4 | ++++ |
| A | roles/hugo/tasks/main.yml | | | 11 | +++++++++++ |
| A | roles/nginx/files/gitea-ssl.conf | | | 25 | +++++++++++++++++++++++++ |
| A | roles/nginx/files/gitea.conf | | | 13 | +++++++++++++ |
| A | roles/nginx/files/learningunix.net-ssl.conf | | | 24 | ++++++++++++++++++++++++ |
| A | roles/nginx/files/learningunix.net.conf | | | 12 | ++++++++++++ |
| A | roles/nginx/handlers/main.yml | | | 5 | +++++ |
| A | roles/nginx/tasks/main.yml | | | 89 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | roles/patching/tasks/main.yml | | | 24 | ++++++++++++++++++++++++ |
| A | roles/ufw/tasks/main.yml | | | 33 | +++++++++++++++++++++++++++++++++ |
| A | roles/user/handlers/main.yml | | | 5 | +++++ |
| A | roles/user/tasks/main.yml | | | 39 | +++++++++++++++++++++++++++++++++++++++ |
| A | site.yml | | | 22 | ++++++++++++++++++++++ |
21 files changed, 484 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +inventory.ini +group_vars/all/vault.yml diff --git a/README.md b/README.md @@ -0,0 +1,37 @@ +# learningunix.net Ansible + +Ansible roles for configuring the learningunix.net server. + +## Requirements + +- Ansible +- Ansible Vault password + +## Usage + +Copy `inventory.ini.example` to `inventory.ini` and fill in your values. + +Run the full playbook: + +``` +ansible-playbook -i inventory.ini site.yml --ask-vault-pass +``` + +Run a single role: + +``` +ansible-playbook -i inventory.ini site.yml --tags gitea --ask-vault-pass +``` + +## Roles + +| Role | Description | +|---|---| +| user | Creates a non-root user | +| ufw | Configures firewall rules | +| fail2ban | Installs and configures fail2ban | +| patching | Configures automatic security updates | +| docker | Installs Docker and Docker Compose | +| gitea | Deploys Gitea via Docker Compose | +| nginx | Configures nginx reverse proxy with SSL | +| hugo | Installs Hugo | diff --git a/inventory.ini.example b/inventory.ini.example @@ -0,0 +1,2 @@ +[server] +your-server ansible_host=your.server.ip ansible_user=your-user ansible_ssh_private_key_file=/home/your-user/.ssh/your-key diff --git a/roles/docker/tasks/main.yml b/roles/docker/tasks/main.yml @@ -0,0 +1,41 @@ +--- +- name: Install dependencies + apt: + name: + - ca-certificates + - curl + state: present + update_cache: true + +- name: Create keyrings directory + file: + path: /etc/apt/keyrings + state: directory + mode: '0755' + +- name: Add Docker GPG key + get_url: + url: https://download.docker.com/linux/debian/gpg + dest: /etc/apt/keyrings/docker.asc + mode: '0644' + +- name: Add Docker repository + apt_repository: + repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" + state: present + +- name: Install Docker + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-compose-plugin + state: present + update_cache: true + +- name: Enable and start Docker + systemd: + name: docker + enabled: true + state: started diff --git a/roles/fail2ban/handlers/main.yml b/roles/fail2ban/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart fail2ban + systemd: + name: fail2ban + state: restarted diff --git a/roles/fail2ban/tasks/main.yml b/roles/fail2ban/tasks/main.yml @@ -0,0 +1,23 @@ +--- +- name: Install fail2ban + apt: + name: fail2ban + state: present + +- name: Configure fail2ban for ssh + copy: + dest: /etc/fail2ban/jail.d/sshd.conf + content: | + [sshd] + enabled = true + maxretry = 5 + bantime = 1h + findtime = 10m + notify: restart fail2ban + +- name: Enable and start fail2ban + systemd: + name: fail2ban + enabled: true + state: started + diff --git a/roles/gitea/files/docker-compose.yml b/roles/gitea/files/docker-compose.yml @@ -0,0 +1,39 @@ +services: + gitea: + image: gitea/gitea:latest + restart: always + environment: + - USER_UID=1000 + - USER_GID=1000 + - GITEA__database__DB_TYPE=postgres + - GITEA__database__HOST=db:5432 + - GITEA__database__NAME=gitea + - GITEA__database__USER=gitea + - GITEA__database__PASSWD=${GITEA_DB_PASSWD} + - GITEA__mailer__ENABLED=true + - GITEA__mailer__FROM=chris.roberts@learningunix.net + - GITEA__mailer__SMTP_ADDR=smtp.gmail.com + - GITEA__mailer__SMTP_PORT=587 + - GITEA__mailer__USER=chris.roberts@learningunix.net + - GITEA__mailer__PASSWD=${GITEA_SMTP_PASSWD} + + volumes: + - gitea_data:/data + ports: + - "127.0.0.1:3000:3000" + depends_on: + - db + + db: + image: postgres:16 + restart: always + environment: + - POSTGRES_USER=gitea + - POSTGRES_PASSWORD=${GITEA_DB_PASSWD} + - POSTGRES_DB=gitea + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + gitea_data: + postgres_data: diff --git a/roles/gitea/tasks/main.yml b/roles/gitea/tasks/main.yml @@ -0,0 +1,29 @@ +--- +- name: Create gitea directory + file: + path: /opt/gitea + state: directory + owner: cjr + group: cjr + mode: '0755' + +- name: Deploy docker-compose file + copy: + src: docker-compose.yml + dest: /opt/gitea/docker-compose.yml + owner: cjr + group: cjr + mode: '0644' + +- name: Deploy gitea env file + template: + src: gitea.env.j2 + dest: /opt/gitea/.env + owner: cjr + group: cjr + mode: '0600' + +- name: Start gitea + community.docker.docker_compose_v2: + project_src: /opt/gitea + state: present diff --git a/roles/gitea/templates/gitea.env.j2 b/roles/gitea/templates/gitea.env.j2 @@ -0,0 +1,4 @@ +GITEA_DB_PASSWD={{ gitea_db_passwd }} +GITEA_SMTP_PASSWD={{ gitea_smtp_passwd }} + + diff --git a/roles/hugo/tasks/main.yml b/roles/hugo/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- name: Download Hugo extended deb + get_url: + url: https://github.com/gohugoio/hugo/releases/download/v0.161.1/hugo_extended_0.161.1_linux-amd64.deb + dest: /tmp/hugo.deb + mode: '0644' + +- name: Install Hugo + apt: + deb: /tmp/hugo.deb + state: present diff --git a/roles/nginx/files/gitea-ssl.conf b/roles/nginx/files/gitea-ssl.conf @@ -0,0 +1,25 @@ +server { + listen 80; + listen [::]:80; + server_name gitea.learningunix.net; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + server_name gitea.learningunix.net; + + ssl_certificate /etc/letsencrypt/live/learningunix.net/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/learningunix.net/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + + location / { + proxy_pass http://127.0.0.1:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/roles/nginx/files/gitea.conf b/roles/nginx/files/gitea.conf @@ -0,0 +1,13 @@ +server { + listen 80; + listen [::]:80; + server_name gitea.learningunix.net; + + location / { + proxy_pass http://127.0.0.1:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/roles/nginx/files/learningunix.net-ssl.conf b/roles/nginx/files/learningunix.net-ssl.conf @@ -0,0 +1,24 @@ +server { + listen 80; + listen [::]:80; + server_name learningunix.net; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + server_name learningunix.net; + + ssl_certificate /etc/letsencrypt/live/learningunix.net/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/learningunix.net/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + + root /var/www/learningunix.net/html; + index index.html; + + location / { + try_files $uri $uri/ =404; + } +} diff --git a/roles/nginx/files/learningunix.net.conf b/roles/nginx/files/learningunix.net.conf @@ -0,0 +1,12 @@ +server { + listen 80; + listen [::]:80; + server_name learningunix.net; + + root /var/www/learningunix.net/html; + index index.html; + + location / { + try_files $uri $uri/ =404; + } +} diff --git a/roles/nginx/handlers/main.yml b/roles/nginx/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: reload nginx + service: + name: nginx + state: reloaded diff --git a/roles/nginx/tasks/main.yml b/roles/nginx/tasks/main.yml @@ -0,0 +1,89 @@ +--- +- name: Install nginx and certbot + apt: + name: + - nginx + - certbot + - python3-certbot-nginx + state: present + update_cache: true + +- name: Remove default nginx site + file: + path: /etc/nginx/sites-enabled/default + state: absent + notify: reload nginx + +- name: Create blog webroot + file: + path: /var/www/learningunix.net/html + state: directory + owner: cjr + group: www-data + mode: '0755' + +- name: Check if cert exists + stat: + path: /etc/letsencrypt/live/learningunix.net/fullchain.pem + register: cert + +- name: Deploy HTTP-only learningunix.net vhost + copy: + src: learningunix.net.conf + dest: /etc/nginx/sites-available/learningunix.net.conf + when: not cert.stat.exists + notify: reload nginx + +- name: Deploy HTTP-only gitea vhost + copy: + src: gitea.conf + dest: /etc/nginx/sites-available/gitea.conf + when: not cert.stat.exists + notify: reload nginx + +- name: Enable learningunix.net site + file: + src: /etc/nginx/sites-available/learningunix.net.conf + dest: /etc/nginx/sites-enabled/learningunix.net.conf + state: link + notify: reload nginx + +- name: Enable gitea site + file: + src: /etc/nginx/sites-available/gitea.conf + dest: /etc/nginx/sites-enabled/gitea.conf + state: link + notify: reload nginx + +- name: Start and enable nginx + service: + name: nginx + state: started + enabled: true + +- name: Flush handlers to reload nginx before certbot runs + meta: flush_handlers + +- name: Obtain SSL certificates + command: > + certbot --nginx + -d learningunix.net + -d gitea.learningunix.net + --non-interactive + --agree-tos + --email chris.roberts@learningunix.net + --redirect + args: + creates: /etc/letsencrypt/live/learningunix.net/fullchain.pem + +- name: Deploy SSL learningunix.net vhost + copy: + src: learningunix.net-ssl.conf + dest: /etc/nginx/sites-available/learningunix.net.conf + notify: reload nginx + +- name: Deploy SSL gitea vhost + copy: + src: gitea-ssl.conf + dest: /etc/nginx/sites-available/gitea.conf + notify: reload nginx diff --git a/roles/patching/tasks/main.yml b/roles/patching/tasks/main.yml @@ -0,0 +1,24 @@ +--- +- name: Install unattended upgrades + apt: + name: + - unattended-upgrades + - apt-listchanges + state: present + +- name: Enable automatic security updates + copy: + dest: /etc/apt/apt.conf.d/20auto-upgrades + content: | + APT::Periodic::Update-Package-Lists "1"; + APT::Periodic::Unattended-Upgrade "1"; + +- name: Configure unattended-upgrades + copy: + dest: /etc/apt/apt.conf.d/50unattended-upgrades + content: | + Unattended-Upgrade::Allowed-Origins { + "${distro_id}:${distro_codename}-security"; + }; + Unattended-Upgrade::Automatic-Reboot "true"; + Unattended-Upgrade::Automatic-Reboot-Time "03:00"; diff --git a/roles/ufw/tasks/main.yml b/roles/ufw/tasks/main.yml @@ -0,0 +1,33 @@ +--- +- name: Install ufw + apt: + name: ufw + state: present + update_cache: true + +- name: Deny all incoming traffic by default + ufw: + default: deny + direction: incoming +- name: Allow ssh + ufw: + rule: allow + port: '22' + proto: tcp +- name: Allow http + ufw: + rule: allow + port: '80' + proto: tcp + +- name: Allow https + ufw: + rule: allow + port: '443' + proto: tcp + +- name: enable ufw + ufw: + state: enabled + +- diff --git a/roles/user/handlers/main.yml b/roles/user/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart sshd + systemd: + name: sshd + state: restarted diff --git a/roles/user/tasks/main.yml b/roles/user/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: Create user cjr + user: + name: cjr + shell: /bin/bash + groups: + - sudo + - docker + append: true + password: "{{ cjr_password }}" + state: present + +- name: Add SSH key for cjr + authorized_key: + user: cjr + key: "{{ lookup('file', '~/.ssh/id_ed25519_learningunix_t15.pub') }}" + state: present + + +- name: Allow sudo with password + lineinfile: + path: /etc/sudoers.d/cjr + line: "cjr ALL=(ALL) ALL" + create: true + validate: 'visudo -cf %s' + +- name: Disable password authentication + lineinfile: + path: /etc/ssh/sshd_config + regexp: '^#?PasswordAuthentication' + line: 'PasswordAuthentication no' + notify: restart sshd + +- name: Disable root SSH login + lineinfile: + path: /etc/ssh/sshd_config + regexp: '^#?PermitRootLogin' + line: 'PermitRootLogin no' + notify: restart sshd diff --git a/site.yml b/site.yml @@ -0,0 +1,22 @@ +--- +- hosts: server + become: true + roles: + - role: user + tags: user + - role: ufw + tags: ufw + - role: fail2ban + tags: fail2ban + - role: patching + tags: patching + - role: docker + tags: docker + - role: gitea + tags: gitea + - role: nginx + tags: nginx + - role: hugo + tags: hugo + +