feat: full untested ansible setup

This commit is contained in:
Twirre Meulenbelt
2026-04-22 12:22:58 +02:00
parent b1d9b2a857
commit 0d967909e7
37 changed files with 1362 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
---
- name: Restart ssh after backupagent change
ansible.builtin.service:
name: ssh
state: restarted

View File

@@ -0,0 +1,44 @@
---
- name: Ensure backupagent user exists
ansible.builtin.user:
name: "{{ backupagent.name }}"
shell: "{{ backupagent.shell }}"
create_home: true
state: present
- name: Ensure backupagent SSH directory exists
ansible.builtin.file:
path: "/home/{{ backupagent.name }}/.ssh"
state: directory
owner: "{{ backupagent.name }}"
group: "{{ backupagent.name }}"
mode: "0700"
- name: Install backupagent authorized keys
ansible.builtin.copy:
dest: "/home/{{ backupagent.name }}/.ssh/authorized_keys"
content: |
{% for key in backupagent.authorized_keys %}
{{ key }}
{% endfor %}
owner: "{{ backupagent.name }}"
group: "{{ backupagent.name }}"
mode: "0600"
- name: Allow passwordless sudo for backup rsync
ansible.builtin.template:
src: backupagent-sudoers.j2
dest: /etc/sudoers.d/backupagent-rsync
owner: root
group: root
mode: "0440"
validate: /usr/sbin/visudo -cf %s
- name: Restrict SSH settings for backupagent
ansible.builtin.template:
src: backupagent-sshd-match.conf.j2
dest: /etc/ssh/sshd_config.d/60-backupagent.conf
owner: root
group: root
mode: "0644"
notify: Restart ssh after backupagent change

View File

@@ -0,0 +1,15 @@
Match User {{ backupagent.name }}
AuthenticationMethods publickey
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitTTY no
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
AllowStreamLocalForwarding no
PermitTunnel no
GatewayPorts no
PermitUserEnvironment no
PermitUserRC no
PermitOpen none

View File

@@ -0,0 +1 @@
{{ backupagent.name }} ALL=(root) NOPASSWD: {{ backupagent.sudo_commands | join(', ') }}

30
roles/base/tasks/main.yml Normal file
View File

@@ -0,0 +1,30 @@
---
- name: Set server hostname
ansible.builtin.hostname:
name: "{{ inventory_hostname_short }}"
- name: Ensure hostname resolves in /etc/hosts
ansible.builtin.lineinfile:
path: /etc/hosts
regexp: '^127\.0\.1\.1\s'
line: "127.0.1.1 {{ inventory_hostname }} {{ inventory_hostname_short }}"
state: present
- name: Set server timezone
ansible.builtin.command:
cmd: "timedatectl set-timezone {{ timezone }}"
changed_when: false
- name: Install base packages
ansible.builtin.apt:
name: "{{ base_packages_common }}"
state: present
update_cache: true
- name: Ensure srv root exists
ansible.builtin.file:
path: "{{ srv_root }}"
state: directory
owner: root
group: root
mode: "0755"

32
roles/bun/tasks/main.yml Normal file
View File

@@ -0,0 +1,32 @@
---
- name: Resolve Bun architecture
ansible.builtin.set_fact:
bun_arch: "{{ bun_arch_map[ansible_facts.architecture] }}"
- name: Create Bun installation root
ansible.builtin.file:
path: "{{ bun_install_root }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Download Bun release archive
ansible.builtin.get_url:
url: "https://github.com/oven-sh/bun/releases/download/bun-v{{ bun_version }}/bun-linux-{{ bun_arch }}.zip"
dest: "/tmp/bun-linux-{{ bun_arch }}.zip"
mode: "0644"
- name: Unpack Bun release
ansible.builtin.unarchive:
src: "/tmp/bun-linux-{{ bun_arch }}.zip"
dest: "{{ bun_install_root }}"
remote_src: true
creates: "{{ bun_install_root }}/bun-linux-{{ bun_arch }}/bun"
- name: Symlink Bun binary into PATH
ansible.builtin.file:
src: "{{ bun_install_root }}/bun-linux-{{ bun_arch }}/bun"
dest: "{{ bun_bin_path }}"
state: link
force: true

View File

@@ -0,0 +1,110 @@
---
- name: Ensure Bun app group exists
ansible.builtin.group:
name: "{{ bun_app.deploy_group }}"
state: present
- name: Ensure Bun app user exists
ansible.builtin.user:
name: "{{ bun_app.deploy_user }}"
group: "{{ bun_app.deploy_group }}"
system: true
shell: /usr/sbin/nologin
create_home: true
- name: Ensure Bun app directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ bun_app.deploy_user }}"
group: "{{ bun_app.deploy_group }}"
mode: "0755"
loop:
- "{{ bun_app.path }}"
- "/var/lib/{{ bun_app.name }}"
- "/etc/{{ bun_app.name }}"
- name: Ensure Bun app extra directories exist
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
owner: "{{ item.owner | default(bun_app.deploy_user) }}"
group: "{{ item.group | default(bun_app.deploy_group) }}"
mode: "{{ item.mode | default('0755') }}"
loop: "{{ bun_app.extra_directories | default([]) }}"
- name: Install Bun app deploy key when provided
ansible.builtin.copy:
dest: "/etc/{{ bun_app.name }}/deploy_key"
content: "{{ bun_app.git_ssh_key }}"
owner: "{{ bun_app.deploy_user }}"
group: "{{ bun_app.deploy_group }}"
mode: "0600"
when:
- bun_app.git_ssh_key is defined
- bun_app.git_ssh_key | length > 0
- name: Deploy Bun app checkout
ansible.builtin.git:
repo: "{{ bun_app.repo }}"
version: "{{ bun_app.version }}"
dest: "{{ bun_app.path }}"
accept_hostkey: true
key_file: "{{ '/etc/' ~ bun_app.name ~ '/deploy_key' if (bun_app.git_ssh_key is defined and bun_app.git_ssh_key | length > 0) else omit }}"
update: true
become_user: "{{ bun_app.deploy_user }}"
register: bun_app_checkout
- name: Check whether Bun app has package metadata
ansible.builtin.stat:
path: "{{ bun_app.path }}/package.json"
register: bun_app_package_json
- name: Check whether Bun app dependencies are installed
ansible.builtin.stat:
path: "{{ bun_app.path }}/node_modules"
register: bun_app_node_modules
- name: Install Bun app dependencies
ansible.builtin.command:
cmd: "{{ bun_bin_path }} install"
chdir: "{{ bun_app.path }}"
become_user: "{{ bun_app.deploy_user }}"
when:
- bun_app_package_json.stat.exists
- bun_app_checkout.changed or not bun_app_node_modules.stat.exists
register: bun_app_install
- name: Render Bun app environment file
ansible.builtin.template:
src: bun-app.env.j2
dest: "/etc/{{ bun_app.name }}/app.env"
owner: root
group: "{{ bun_app.deploy_group }}"
mode: "0640"
register: bun_app_env
- name: Install Bun app systemd unit
ansible.builtin.template:
src: bun-app.service.j2
dest: "/etc/systemd/system/{{ bun_app.service_name }}.service"
owner: root
group: root
mode: "0644"
register: bun_app_unit
- name: Reload systemd for Bun app changes
ansible.builtin.systemd_service:
daemon_reload: true
when: bun_app_unit.changed
- name: Ensure Bun app service is enabled and running
ansible.builtin.service:
name: "{{ bun_app.service_name }}"
state: >-
{{
'restarted'
if (bun_app_checkout.changed or bun_app_env.changed or bun_app_unit.changed or (bun_app_install is defined and bun_app_install.changed))
else 'started'
}}
enabled: true

View File

@@ -0,0 +1,6 @@
{% set app_vault_env = vars['vault_' + (bun_app.name | replace('-', '_')) + '_env'] | default({}) %}
{% set app_non_vault_env_keys = bun_app.non_vault_env_keys | default([]) %}
{% set app_filtered_vault_env = app_vault_env | dict2items | rejectattr('key', 'in', app_non_vault_env_keys) | items2dict %}
{% for key, value in (bun_app.env | combine(app_filtered_vault_env)) | dictsort %}
{{ key }}={{ value }}
{% endfor %}

View File

@@ -0,0 +1,17 @@
[Unit]
Description={{ bun_app.name }} Bun service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User={{ bun_app.deploy_user }}
Group={{ bun_app.deploy_group }}
WorkingDirectory={{ bun_app.path }}
EnvironmentFile=/etc/{{ bun_app.name }}/app.env
ExecStart={{ bun_bin_path }} {{ bun_app.entrypoint }}
Restart=always
RestartSec=1
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,35 @@
---
- name: Install certbot packages
ansible.builtin.apt:
name: "{{ certbot_packages }}"
state: present
update_cache: true
- name: Ensure ACME webroot exists
ansible.builtin.file:
path: /var/www/letsencrypt
state: directory
owner: www-data
group: www-data
mode: "0755"
- name: Enable certbot timer
ansible.builtin.service:
name: certbot.timer
enabled: true
state: started
- name: Request managed certificates
ansible.builtin.command:
cmd: >-
certbot certonly --non-interactive --agree-tos
--email {{ certbot_email }}
--webroot -w /var/www/letsencrypt
--cert-name {{ item.name }}
{% for domain in item.domains %}-d {{ domain }} {% endfor %}
args:
creates: "/etc/letsencrypt/live/{{ item.name }}/fullchain.pem"
when: certbot_manage_certificates | bool
loop: "{{ certbot_certificates }}"
loop_control:
label: "{{ item.name }}"

View File

@@ -0,0 +1,17 @@
---
- name: Install Docker packages
ansible.builtin.apt:
name: "{{ docker_packages }}"
state: present
update_cache: true
- name: Ensure docker group exists
ansible.builtin.group:
name: docker
state: present
- name: Enable Docker service
ansible.builtin.service:
name: docker
enabled: true
state: started

View File

@@ -0,0 +1,5 @@
---
- name: Restart fail2ban
ansible.builtin.service:
name: fail2ban
state: restarted

View File

@@ -0,0 +1,21 @@
---
- name: Install fail2ban package
ansible.builtin.apt:
name: fail2ban
state: present
update_cache: true
- name: Configure fail2ban jail.local
ansible.builtin.template:
src: jail.local.j2
dest: /etc/fail2ban/jail.local
owner: root
group: root
mode: "0644"
notify: Restart fail2ban
- name: Ensure fail2ban service is enabled
ansible.builtin.service:
name: fail2ban
state: started
enabled: true

View File

@@ -0,0 +1,9 @@
[DEFAULT]
bantime = {{ fail2ban_bantime }}
findtime = {{ fail2ban_findtime }}
maxretry = {{ fail2ban_maxretry }}
bantime.increment = true
ignoreip = {{ fail2ban_ignoreip | join(' ') }}
[sshd]
enabled = true

View File

@@ -0,0 +1,65 @@
---
- name: Ensure Gitea service user exists
ansible.builtin.user:
name: "{{ gitea.service_user }}"
groups:
- "{{ gitea.service_group }}"
append: true
system: true
shell: /usr/sbin/nologin
create_home: false
- name: Look up Gitea service user account details
ansible.builtin.getent:
database: passwd
key: "{{ gitea.service_user }}"
- name: Look up Gitea service group details
ansible.builtin.getent:
database: group
key: "{{ gitea.service_group }}"
- name: Set Gitea runtime UID and GID from host account
ansible.builtin.set_fact:
gitea_runtime_uid: "{{ getent_passwd[gitea.service_user][1] }}"
gitea_runtime_gid: "{{ getent_group[gitea.service_group][1] }}"
- name: Ensure Gitea directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ gitea.service_user }}"
group: "{{ gitea.service_group }}"
mode: "0755"
loop:
- "{{ gitea.path }}"
- "{{ gitea.data_dir }}"
- name: Render Gitea compose file
ansible.builtin.template:
src: compose.yaml.j2
dest: "{{ gitea.path }}/compose.yaml"
owner: "{{ gitea.service_user }}"
group: "{{ gitea.service_group }}"
mode: "0640"
register: gitea_compose
- name: Install Gitea compose systemd unit
ansible.builtin.template:
src: gitea-compose.service.j2
dest: /etc/systemd/system/gitea-compose.service
owner: root
group: root
mode: "0644"
register: gitea_unit
- name: Reload systemd for Gitea unit changes
ansible.builtin.systemd_service:
daemon_reload: true
when: gitea_unit.changed
- name: Enable Gitea compose stack
ansible.builtin.service:
name: gitea-compose
state: "{{ 'restarted' if (gitea_compose.changed or gitea_unit.changed) else 'started' }}"
enabled: true

View File

@@ -0,0 +1,28 @@
services:
server:
image: {{ gitea.image }}
container_name: gitea
restart: unless-stopped
environment:
USER_UID: "{{ gitea_runtime_uid }}"
USER_GID: "{{ gitea_runtime_gid }}"
GITEA__security__SECRET_KEY: "{{ vault_gitea_secret_key | default('change-me') }}"
GITEA__security__INTERNAL_TOKEN: "{{ vault_gitea_internal_token | default('change-me') }}"
GITEA__security__LFS_JWT_SECRET: "{{ vault_gitea_lfs_jwt_secret | default('change-me') }}"
GITEA__server__DOMAIN: "{{ gitea.domain }}"
GITEA__server__ROOT_URL: "https://{{ gitea.domain }}/"
GITEA__server__PROTOCOL: "http"
GITEA__server__SSH_DOMAIN: "{{ gitea.domain }}"
GITEA__server__SSH_PORT: "{{ gitea.ssh_port }}"
GITEA__server__HTTP_PORT: "{{ gitea.http_port }}"
ports:
- "{{ gitea.http_bind_address }}:{{ gitea.http_port }}:3000"
- "{{ gitea.ssh_port }}:22"
volumes:
- "{{ gitea.data_dir }}:/data"
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
default:
name: {{ gitea.compose_project_name }}

View File

@@ -0,0 +1,16 @@
[Unit]
Description=Gitea Docker Compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory={{ gitea.path }}
ExecStart=/usr/bin/docker compose -f {{ gitea.path }}/compose.yaml up -d
ExecStop=/usr/bin/docker compose -f {{ gitea.path }}/compose.yaml down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,77 @@
---
- name: Ensure mail service user exists
ansible.builtin.user:
name: "{{ mailserver.service_user }}"
groups:
- "{{ mailserver.service_group }}"
append: true
system: true
shell: /usr/sbin/nologin
create_home: false
- name: Ensure mailserver directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ mailserver.service_user }}"
group: "{{ mailserver.service_group }}"
mode: "0755"
loop:
- "{{ mailserver.path }}"
- "{{ mailserver.path }}/docker-data"
- "{{ mailserver.path }}/docker-data/dms/mail-data"
- "{{ mailserver.path }}/docker-data/dms/mail-state"
- "{{ mailserver.path }}/docker-data/dms/mail-logs"
- "{{ mailserver.path }}/docker-data/dms/config"
- name: Render mailserver environment file
ansible.builtin.template:
src: mailserver.env.j2
dest: "{{ mailserver.path }}/mailserver.env"
owner: "{{ mailserver.service_user }}"
group: "{{ mailserver.service_group }}"
mode: "0640"
register: mailserver_env
- name: Render mailserver accounts file
ansible.builtin.copy:
dest: "{{ mailserver.path }}/docker-data/dms/config/postfix-accounts.cf"
content: "{{ vault_mailserver_accounts | default('# add mail accounts here\n') }}"
owner: "{{ mailserver.service_user }}"
group: "{{ mailserver.service_group }}"
mode: "0600"
register: mailserver_accounts
- name: Render mailserver compose file
ansible.builtin.template:
src: compose.yaml.j2
dest: "{{ mailserver.path }}/compose.yaml"
owner: "{{ mailserver.service_user }}"
group: "{{ mailserver.service_group }}"
mode: "0640"
register: mailserver_compose
- name: Install mailserver compose systemd unit
ansible.builtin.template:
src: mailserver-compose.service.j2
dest: /etc/systemd/system/mailserver-compose.service
owner: root
group: root
mode: "0644"
register: mailserver_unit
- name: Reload systemd for mailserver unit changes
ansible.builtin.systemd_service:
daemon_reload: true
when: mailserver_unit.changed
- name: Enable mailserver compose stack
ansible.builtin.service:
name: mailserver-compose
state: >-
{{
'restarted'
if (mailserver_env.changed or mailserver_accounts.changed or mailserver_compose.changed or mailserver_unit.changed)
else 'started'
}}
enabled: true

View File

@@ -0,0 +1,28 @@
services:
mailserver:
image: {{ mailserver.image }}
container_name: mailserver
hostname: {{ mailserver.hostname }}
env_file: {{ mailserver.path }}/mailserver.env
restart: unless-stopped
stop_grace_period: 1m
ports:
- "25:25"
- "143:143"
- "465:465"
- "587:587"
- "993:993"
volumes:
- {{ mailserver.path }}/docker-data/dms/mail-data:/var/mail
- {{ mailserver.path }}/docker-data/dms/mail-state:/var/mail-state
- {{ mailserver.path }}/docker-data/dms/mail-logs:/var/log/mail
- {{ mailserver.path }}/docker-data/dms/config:/tmp/docker-mailserver
- {{ mailserver.tls_root_path }}:/etc/letsencrypt:ro
- /etc/localtime:/etc/localtime:ro
cap_add:
- NET_ADMIN
- SYS_PTRACE
networks:
default:
name: {{ mailserver.compose_project_name }}

View File

@@ -0,0 +1,16 @@
[Unit]
Description=Docker Mailserver Compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory={{ mailserver.path }}
ExecStart=/usr/bin/docker compose -f {{ mailserver.path }}/compose.yaml up -d
ExecStop=/usr/bin/docker compose -f {{ mailserver.path }}/compose.yaml down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,5 @@
OVERRIDE_HOSTNAME={{ mailserver.hostname }}
POSTMASTER_ADDRESS={{ certbot_email }}
{% for key, value in mailserver.env.items() %}
{{ key }}={{ value }}
{% endfor %}

View File

@@ -0,0 +1,5 @@
---
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded

View File

@@ -0,0 +1,94 @@
---
- name: Install nginx package
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Ensure ACME webroot exists for nginx
ansible.builtin.file:
path: /var/www/letsencrypt
state: directory
owner: www-data
group: www-data
mode: "0755"
- name: Ensure static site directories exist
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
owner: "{{ item.owner }}"
group: "{{ item.group }}"
mode: "0755"
loop: "{{ static_sites | default([]) }}"
loop_control:
label: "{{ item.path }}"
- name: Publish static placeholder files
ansible.builtin.copy:
dest: "{{ item.0.path }}/{{ item.1.path }}"
content: "{{ item.1.content }}"
owner: "{{ item.0.owner }}"
group: "{{ item.0.group }}"
mode: "0644"
loop: "{{ (static_sites | default([])) | subelements('files', skip_missing=True) }}"
loop_control:
label: "{{ item.0.name }}/{{ item.1.path }}"
- name: Remove default nginx site
ansible.builtin.file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Reload nginx
- name: Check which ACME certificates already exist
ansible.builtin.stat:
path: "/etc/letsencrypt/live/{{ item.certificate_name | default(item.server_names[0]) }}/fullchain.pem"
loop: "{{ nginx_sites | selectattr('acme_managed', 'defined') | selectattr('acme_managed') | list }}"
loop_control:
label: "{{ item.name }}"
register: nginx_site_cert_stats
- name: Build ACME certificate availability map
ansible.builtin.set_fact:
nginx_acme_certificates_available: >-
{{
dict(
nginx_site_cert_stats.results
| map(attribute='item.name')
| zip(nginx_site_cert_stats.results | map(attribute='stat.exists'))
)
}}
- name: Render nginx site configurations
ansible.builtin.template:
src: site.conf.j2
dest: "/etc/nginx/sites-available/{{ item.name }}.conf"
owner: root
group: root
mode: "0644"
loop: "{{ nginx_sites }}"
loop_control:
label: "{{ item.name }}"
notify: Reload nginx
- name: Enable nginx sites
ansible.builtin.file:
src: "/etc/nginx/sites-available/{{ item.name }}.conf"
dest: "/etc/nginx/sites-enabled/{{ item.name }}.conf"
state: link
force: true
loop: "{{ nginx_sites }}"
loop_control:
label: "{{ item.name }}"
notify: Reload nginx
- name: Validate nginx configuration
ansible.builtin.command: nginx -t
changed_when: false
- name: Ensure nginx service is enabled
ansible.builtin.service:
name: nginx
state: started
enabled: true

View File

@@ -0,0 +1,76 @@
server {
listen 80{% if item.default_server | default(false) %} default_server{% endif %};
listen [::]:80{% if item.default_server | default(false) %} default_server{% endif %};
server_name {{ item.server_names | join(' ') }};
location /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
}
{% if item.acme_only | default(false) %}
location / {
return 404;
}
{% else %}
location / {
return 301 https://$host$request_uri;
}
{% endif %}
}
{% if not (item.acme_only | default(false)) %}
server {
listen 443 ssl http2{% if item.default_server | default(false) %} default_server{% endif %};
listen [::]:443 ssl http2{% if item.default_server | default(false) %} default_server{% endif %};
server_name {{ item.server_names | join(' ') }};
{% if item.acme_managed | default(true) %}
{% set certificate_name = item.certificate_name | default(item.server_names[0]) %}
{% set nginx_site_has_live_cert = nginx_acme_certificates_available[item.name] | default(false) %}
{% if nginx_site_has_live_cert %}
ssl_certificate /etc/letsencrypt/live/{{ certificate_name }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ certificate_name }}/privkey.pem;
{% else %}
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
{% endif %}
{% endif %}
client_max_body_size 50m;
{% if item.static_root is defined %}
root {{ item.static_root }};
index index.html;
location / {
try_files $uri $uri/ =404;
}
{% else %}
{% for location in item.static_locations | default([]) %}
{% if location.path.endswith('/') %}
location = {{ location.path[:-1] }} {
return 301 {{ location.path }};
}
{% endif %}
location ^~ {{ location.path }} {
alias {{ location.alias }};
{% if location.autoindex | default(false) %}
autoindex on;
{% endif %}
}
{% endfor %}
location / {
proxy_pass http://{{ item.upstream_host }}:{{ item.upstream_port }};
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;
{% if item.websocket | default(false) %}
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
{% endif %}
}
{% endif %}
}
{% endif %}

View File

@@ -0,0 +1,5 @@
---
- name: Restart ssh
ansible.builtin.service:
name: ssh
state: restarted

58
roles/ssh/tasks/main.yml Normal file
View File

@@ -0,0 +1,58 @@
---
- name: Install SSH packages
ansible.builtin.apt:
name: "{{ ssh_packages }}"
state: present
update_cache: true
- name: Ensure admin groups exist
ansible.builtin.group:
name: "{{ item }}"
state: present
loop: "{{ ssh_admin_groups }}"
- name: Ensure admin users exist
ansible.builtin.user:
name: "{{ item.name }}"
shell: "{{ item.shell | default('/bin/bash') }}"
groups: "{{ item.groups | default([]) }}"
append: true
create_home: true
state: present
loop: "{{ ssh_admin_users }}"
loop_control:
label: "{{ item.name }}"
- name: Ensure .ssh directories exist
ansible.builtin.file:
path: "/home/{{ item.name }}/.ssh"
state: directory
owner: "{{ item.name }}"
group: "{{ item.name }}"
mode: "0700"
loop: "{{ ssh_admin_users }}"
loop_control:
label: "{{ item.name }}"
- name: Install SSH authorized key files
ansible.builtin.copy:
dest: "/home/{{ item.name }}/.ssh/authorized_keys"
content: |
{% for key in item.authorized_keys | default([]) %}
{{ key }}
{% endfor %}
owner: "{{ item.name }}"
group: "{{ item.name }}"
mode: "0600"
loop: "{{ ssh_admin_users }}"
loop_control:
label: "{{ item.name }}"
- name: Harden sshd configuration
ansible.builtin.template:
src: sshd_config.j2
dest: /etc/ssh/sshd_config.d/99-twirre.conf
owner: root
group: root
mode: "0644"
notify: Restart ssh

View File

@@ -0,0 +1,5 @@
PasswordAuthentication no
PermitRootLogin prohibit-password
PubkeyAuthentication yes
KbdInteractiveAuthentication no
UsePAM yes

View File

@@ -0,0 +1,5 @@
---
- name: Restart WireGuard
ansible.builtin.service:
name: "wg-quick@{{ wireguard_interface.name }}"
state: restarted

View File

@@ -0,0 +1,31 @@
---
- name: Install WireGuard packages
ansible.builtin.apt:
name:
- wireguard
- wireguard-tools
state: present
update_cache: true
- name: Ensure WireGuard configuration directory exists
ansible.builtin.file:
path: /etc/wireguard
state: directory
owner: root
group: root
mode: "0700"
- name: Render WireGuard interface configuration
ansible.builtin.template:
src: wg0.conf.j2
dest: "/etc/wireguard/{{ wireguard_interface.name }}.conf"
owner: root
group: root
mode: "0600"
notify: Restart WireGuard
- name: Enable WireGuard interface
ansible.builtin.service:
name: "wg-quick@{{ wireguard_interface.name }}"
state: started
enabled: true

View File

@@ -0,0 +1,21 @@
[Interface]
Address = {{ wireguard_interface.address | join(', ') }}
ListenPort = {{ wireguard_interface.listen_port }}
PrivateKey = {{ wireguard_interface.private_key }}
{% for peer in wireguard_interface.peers %}
# {{ peer.name }}
[Peer]
PublicKey = {{ peer.public_key }}
{% if peer.preshared_key is defined and peer.preshared_key | length > 0 %}
PresharedKey = {{ peer.preshared_key }}
{% endif %}
AllowedIPs = {{ peer.allowed_ips | join(', ') }}
{% if peer.endpoint is defined %}
Endpoint = {{ peer.endpoint }}
{% endif %}
{% if peer.persistent_keepalive is defined %}
PersistentKeepalive = {{ peer.persistent_keepalive }}
{% endif %}
{% endfor %}