feat: initial commit

This commit is contained in:
Iain Learmonth 2025-06-02 14:55:56 +01:00
commit 072a1ed764
36 changed files with 1089 additions and 0 deletions

View file

@ -0,0 +1,7 @@
---
podman_forgejo_mariadb_database: forgejo
# podman_forgejo_mariadb_password:
# podman_forgejo_mariadb_root_password:
podman_forgejo_mariadb_user: forgejo
podman_forgejo_podman_rootless_user: forge
podman_forgejo_web_hostname: "{{ inventory_hostname }}"

View file

@ -0,0 +1,24 @@
---
- name: restart forgejo
ansible.builtin.systemd_service:
name: forgejo
state: restarted
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_forgejo_podman_rootless_user }}"
- name: restart mariadb
ansible.builtin.systemd_service:
name: forgejo
state: restarted
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_forgejo_podman_rootless_user }}"
- name: restart sshd
service:
name: sshd
state: restarted
become: true

View file

@ -0,0 +1,88 @@
---
- name: setup alternate SSH port
lineinfile:
dest: "/etc/ssh/sshd_config"
regexp: "^Port"
line: "Port 2222"
notify: restart sshd
become: true
- name: create service configuration directories
ansible.builtin.file:
path: "/home/{{ podman_forgejo_podman_rootless_user }}/{{ item }}"
state: directory
owner: "{{ podman_forgejo_podman_rootless_user }}"
group: "{{ podman_forgejo_podman_rootless_user }}"
mode: "0755"
become: true
with_items:
- mysql
- forgejo
- name: install podman quadlet for rootless podman user
ansible.builtin.template:
src: "{{ item }}"
dest: "/home/{{ podman_forgejo_podman_rootless_user }}/.config/containers/systemd/{{ item }}"
owner: "{{ podman_forgejo_podman_rootless_user }}"
mode: "0400"
with_items:
- forgejo.container
- mariadb.container
notify:
- "restart {{ item | split('.') | first }}"
become: true
- name: install network quadlets for rootless podman user
ansible.builtin.template:
src: "{{ item }}"
dest: "/home/{{ podman_forgejo_podman_rootless_user }}/.config/containers/systemd/{{ item }}"
owner: "{{ podman_forgejo_podman_rootless_user }}"
mode: "0400"
with_items:
- frontend.network
- forgejo.network
become: true
- name: verify quadlets are correctly defined
ansible.builtin.command: /usr/libexec/podman/quadlet -dryrun -user
register: podman_forgejo_quadlet_result
ignore_errors: true
changed_when: false
become: true
become_user: "{{ podman_forgejo_podman_rootless_user }}"
- name: assert that the quadlet verification succeeded
ansible.builtin.assert:
that:
- podman_forgejo_quadlet_result.rc == 0
fail_msg: "'/usr/libexec/podman/quadlet -dryrun -user' failed! Output withheld to prevent leaking secrets."
- name: start forgejo and mariadb
ansible.builtin.systemd_service:
name: "{{ item }}"
state: started
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_forgejo_podman_rootless_user }}"
with_items:
- forgejo
- mariadb
- name: set up nginx
ansible.builtin.include_role:
name: podman_nginx
vars:
podman_nginx_frontend_network: frontend
podman_nginx_podman_rootless_user: "{{ podman_forgejo_podman_rootless_user }}"
podman_nginx_primary_hostname: "{{ podman_forgejo_web_hostname }}"
- name: create nginx configuration file
ansible.builtin.template:
src: nginx.conf
dest: "/home/{{ podman_forgejo_podman_rootless_user }}/nginx/nginx.conf"
owner: "{{ podman_forgejo_podman_rootless_user }}"
group: "{{ podman_forgejo_podman_rootless_user }}"
mode: "0644"
become: true
notify: restart nginx

View file

@ -0,0 +1,28 @@
[Unit]
Requires=mariadb.service
After=mariadb.service
[Container]
ContainerName=forgejo
Environment=USER_UID=1000
Environment=USER_GID=1000
Environment=FORGEJO__database__DB_TYPE=mysql
Environment=FORGEJO__database__HOST=mariadb:3306
Environment=FORGEJO__database__NAME={{ podman_forgejo_mariadb_database }}
Environment=FORGEJO__database__USER={{ podman_forgejo_mariadb_user }}
Environment=FORGEJO__database__PASSWD={{ podman_forgejo_mariadb_password }}
Environment=FORGEJO__oauth2_client__ENABLE_AUTO_REGISTRATION=true
Environment=FORGEJO__server__LANDING_PAGE=/explore/repos
Image=codeberg.org/forgejo/forgejo:11
Network=frontend.network
Network=forgejo.network
PublishPort=22:22
Volume=/home/forge/forgejo:/data
Volume=/etc/timezone:/etc/timezone:ro
Volume=/etc/localtime:/etc/localtime:ro
[Service]
Restart=always
[Install]
WantedBy=default.target

View file

@ -0,0 +1,2 @@
[Network]
NetworkName=forgejo

View file

@ -0,0 +1,2 @@
[Network]
NetworkName=frontend

View file

@ -0,0 +1,15 @@
[Container]
ContainerName=mariadb
Environment=MARIADB_ROOT_PASSWORD={{ podman_forgejo_mariadb_root_password }}
Environment=MARIADB_USER={{ podman_forgejo_mariadb_user }}
Environment=MARIADB_PASSWORD={{ podman_forgejo_mariadb_password }}
Environment=MARIADB_DATABASE={{ podman_forgejo_mariadb_database }}
Image=docker.io/mariadb:11
Network=forgejo
Volume=/home/{{ podman_forgejo_podman_rootless_user }}/mysql:/var/lib/mysql
[Service]
Restart=always
[Install]
WantedBy=default.target

View file

@ -0,0 +1,42 @@
# {{ ansible_managed }}
server {
listen 80;
listen [::]:80;
server_name {{ podman_forgejo_web_hostname }};
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://{{ podman_forgejo_web_hostname }}$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name {{ podman_forgejo_web_hostname }};
server_tokens off;
ssl_certificate /etc/letsencrypt/live/{{ podman_forgejo_web_hostname }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ podman_forgejo_web_hostname }}/privkey.pem;
location / {
proxy_pass http://forgejo:3000;
proxy_set_header Connection $http_connection;
proxy_set_header Upgrade $http_upgrade;
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;
client_max_body_size 512M;
}
}

View file

@ -0,0 +1,3 @@
---
podman_host_minimum_unpriv_port: "22"
podman_host_rootless_users: ["podman"]

View file

@ -0,0 +1,69 @@
---
- name: set unprivileged port minimum
ansible.posix.sysctl:
name: net.ipv4.ip_unprivileged_port_start
value: "{{ podman_host_minimum_unpriv_port }}"
sysctl_set: true
sysctl_file: /etc/sysctl.d/zzz-podman-unpriv-port.conf
reload: true
become: true
- name: create users for rootless podman
ansible.builtin.user:
name: "{{ item }}"
become: true
with_items: "{{ podman_host_rootless_users }}"
- name: set XDG_RUNTIME_DIR in .profile for rootless users
ansible.builtin.lineinfile:
path: "/home/{{ item }}/.bashrc"
line: "export XDG_RUNTIME_DIR=/run/user/$(id -u)"
create: false
become: true
become_user: "{{ item }}"
with_items: "{{ podman_host_rootless_users }}"
- name: enable linger for rootless users
ansible.builtin.command:
argv:
- /usr/bin/loginctl
- enable-linger
- "{{ item }}"
creates: "/var/lib/systemd/linger/{{ item }}"
become: true
with_items: "{{ podman_host_rootless_users }}"
- name: create /etc/subuid and /etc/subgid
ansible.builtin.template:
dest: "/etc/{{ item }}"
src: subXid.j2
with_items:
- subuid
- subgid
become: true
- name: install podman
ansible.builtin.apt:
pkg: podman
state: latest
become: true
- name: create quadlets directory
ansible.builtin.file:
path: "/home/{{ item }}/.config/containers/systemd"
state: directory
owner: "{{ item }}"
group: "{{ item }}"
mode: "0700"
with_items: "{{ podman_host_rootless_users }}"
become: true
- name: enable podman auto update timer for rootless users
ansible.builtin.systemd_service:
name: podman-auto-update.timer
scope: user
state: started
enabled: true
become: true
become_user: "{{ item }}"
with_items: "{{ podman_host_rootless_users }}"

View file

@ -0,0 +1,4 @@
# {{ ansible_managed }}
{% for username in podman_host_rootless_users %}
{{ username }}:{{ 100000 + ((loop.index - 1) * 65536) }}:65536
{% endfor %}

View file

@ -0,0 +1,17 @@
---
podman_identity_certbot_testing: false
# podman_identity_keycloak_admin_password:
podman_identity_keycloak_admin_username: admin
podman_identity_keycloak_hostname: "{{ inventory_hostname }}"
podman_identity_keycloak_providers:
- url: https://github.com/jacekkow/keycloak-protocol-cas/releases/download/26.1.2/keycloak-protocol-cas-26.1.2.jar
sha256: de106e8b6b0018a5f121dfa04a14859743e35c5c7ef0abdb01e4a6018d1e2d84
- url: https://github.com/sventorben/keycloak-restrict-client-auth/releases/download/v26.0.0/keycloak-restrict-client-auth.jar
sha256: 69274e7864f1356f6e14c668787be0c4b8f4d1f4ed28b4e5fa540fa71c8df472
# podman_identity_ldap_administrator_password:
# podman_identity_ldap_directory_manager_password:
# podman_identity_ldap_database_suffix_dn:
podman_identity_podman_rootless_user: identity
podman_identity_postgres_keycloak_database: keycloak
# podman_identity_postgres_keycloak_password:
podman_identity_postgres_keycloak_username: keycloak

View file

@ -0,0 +1,27 @@
---
- name: restart ldap
ansible.builtin.systemd_service:
name: ldap
state: restarted
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_identity_podman_rootless_user }}"
- name: restart postgres
ansible.builtin.systemd_service:
name: postgres
state: restarted
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_identity_podman_rootless_user }}"
- name: restart keycloak
ansible.builtin.systemd_service:
name: keycloak
state: restarted
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_identity_podman_rootless_user }}"

View file

@ -0,0 +1,218 @@
---
# TODO: configure ufw
- name: create service configuration directories
ansible.builtin.file:
path: "/home/{{ podman_identity_podman_rootless_user }}/{{ item }}"
state: directory
owner: "{{ podman_identity_podman_rootless_user }}"
group: "{{ podman_identity_podman_rootless_user }}"
mode: "0755"
become: true
with_items:
- keycloak
- ldap
- postgres
- name: download keycloak providers
ansible.builtin.get_url:
url: "{{ item.url }}"
dest: "/home/{{ podman_identity_podman_rootless_user }}/keycloak/{{ item.url | basename }}"
checksum: "sha256:{{ item.sha256 }}"
with_items: "{{ podman_identity_keycloak_providers }}"
become: true
become_user: "{{ podman_identity_podman_rootless_user }}"
notify: restart keycloak
- name: install systemd units for rootless podman user
ansible.builtin.template:
src: "{{ item }}"
dest: "/home/{{ podman_identity_podman_rootless_user }}/.config/containers/systemd/{{ item }}"
owner: "{{ podman_identity_podman_rootless_user }}"
mode: "0400"
with_items:
- ldap.container
- keycloak.container
- postgres.container
notify:
- "restart {{ item | split('.') | first }}"
become: true
- name: install network quadlets for rootless podman user
ansible.builtin.template:
src: "{{ item }}"
dest: "/home/{{ podman_identity_podman_rootless_user }}/.config/containers/systemd/{{ item }}"
owner: "{{ podman_identity_podman_rootless_user }}"
mode: "0400"
with_items:
- frontend.network
- ldap.network
- keycloak.network
become: true
- name: verify quadlets are correctly defined
ansible.builtin.command: /usr/libexec/podman/quadlet -dryrun -user
register: podman_identity_quadlet_result
ignore_errors: true
changed_when: false
become: true
become_user: "{{ podman_identity_podman_rootless_user }}"
- name: assert that the quadlet verification succeeded
ansible.builtin.assert:
that:
- podman_identity_quadlet_result.rc == 0
fail_msg: "'/usr/libexec/podman/quadlet -dryrun -user' failed! Output withheld to prevent leaking secrets."
- name: start postgres and keycloak
ansible.builtin.systemd_service:
name: "{{ item }}"
state: started
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_identity_podman_rootless_user }}"
with_items:
- postgres
- keycloak
- name: set up nginx
ansible.builtin.import_role:
name: podman_nginx
vars:
podman_nginx_podman_rootless_user: "{{ podman_identity_podman_rootless_user }}"
podman_nginx_primary_hostname: "{{ podman_identity_keycloak_hostname }}"
podman_nginx_frontend_network: frontend
- name: start ldap
ansible.builtin.systemd_service:
name: ldap
state: started
scope: user
become: true
become_user: "{{ podman_identity_podman_rootless_user }}"
- name: create nginx configuration file
ansible.builtin.template:
src: nginx.conf
dest: "/home/{{ podman_identity_podman_rootless_user }}/nginx/nginx.conf"
owner: "{{ podman_identity_podman_rootless_user }}"
group: "{{ podman_identity_podman_rootless_user }}"
mode: "0644"
become: true
notify: restart nginx
- name: wait 30 seconds for ldap server to start
ansible.builtin.pause:
seconds: 30
- name: create ldap suffix
containers.podman.podman_container_exec:
name: ldap
argv:
- dsconf
- -v
- localhost
- backend
- create
- --suffix
- "{{ podman_identity_ldap_database_suffix_dn }}"
- --be-name
- "{{ podman_identity_ldap_database_backend_name }}"
- --create-suffix
become: true
become_user: "{{ podman_identity_podman_rootless_user }}"
register: podman_identity_create_suffix
ignore_errors: true
changed_when: false
tags:
- ldap
- name: create suffix result (only when changed)
debug:
msg: "Suffix was created"
when: not podman_identity_create_suffix.failed
changed_when: not podman_identity_create_suffix.failed
- name: ldap organisational units
community.general.ldap_entry:
dn: "ou={{ item }},{{ podman_identity_ldap_database_suffix_dn }}"
objectClass:
- top
- organizationalUnit
server_uri: ldaps://{{ inventory_hostname }}/
bind_dn: "cn=Directory Manager"
bind_pw: "{{ podman_identity_ldap_directory_manager_password }}"
delegate_to: localhost
with_items:
- Administrators
- People
- Groups
environment:
- LDAPTLS_REQCERT: "{% if podman_identity_certbot_testing %}never{% else %}always{% endif %}"
tags: ldap
- name: enable memberOf plugin
containers.podman.podman_container_exec:
name: ldap
argv:
- dsconf
- -v
- localhost
- -D "cn=Directory Manager"
- plugin
- memberof
- enable
become: true
become_user: "{{ podman_identity_podman_rootless_user }}"
tags:
- ldap
- name: disable anonymous bind
containers.podman.podman_container_exec:
name: ldap
argv:
- dsconf
- -v
- localhost
- -D "cn=Directory Manager"
- config
- replace
- nsslapd-allow-anonymous-access=off
become: true
become_user: "{{ podman_identity_podman_rootless_user }}"
tags:
- ldap
- name: ldap read-only administrator
community.general.ldap_entry:
dn: "uid=admin,ou=Administrators,{{ podman_identity_ldap_database_suffix_dn }}"
objectClass:
- top
- person
- organizationalPerson
- inetOrgPerson
attributes:
cn: admin
sn: admin
userPassword: "{{ podman_identity_ldap_administrator_password }}"
server_uri: ldaps://{{ inventory_hostname }}/
bind_dn: "cn=Directory Manager"
bind_pw: "{{ podman_identity_ldap_directory_manager_password }}"
delegate_to: localhost
environment:
- LDAPTLS_REQCERT: "{% if podman_identity_certbot_testing %}never{% else %}always{% endif %}"
tags: ldap
- name: ldap access control information
community.general.ldap_attrs:
dn: "{{ podman_identity_ldap_database_suffix_dn }}"
attributes:
aci: '(target="ldap:///{{ podman_identity_ldap_database_suffix_dn }}")(targetattr="*") (version 3.0; acl "readonly"; allow (search,read,compare) userdn="ldap:///uid=admin,ou=Administrators,{{ podman_identity_ldap_database_suffix_dn }}";)'
server_uri: ldaps://{{ inventory_hostname }}/
bind_dn: "cn=Directory Manager"
bind_pw: "{{ podman_identity_ldap_directory_manager_password }}"
delegate_to: localhost
environment:
- LDAPTLS_REQCERT: "{% if podman_identity_certbot_testing %}never{% else %}always{% endif %}"
tags: ldap

View file

@ -0,0 +1,5 @@
[Network]
Driver=bridge
[Install]
WantedBy=default.target

View file

@ -0,0 +1,33 @@
[Unit]
Requires=postgres.service
After=postgres.service
[Container]
AutoUpdate=registry
ContainerName=keycloak
Environment=\
KC_DB=postgres \
KC_DB_PASSWORD={{ podman_identity_postgres_keycloak_password }} \
KC_DB_URL=jdbc:postgresql://postgres/{{ podman_identity_postgres_keycloak_database }} \
KC_DB_USERNAME={{ podman_identity_postgres_keycloak_username }} \
KC_HOSTNAME={{ podman_identity_keycloak_hostname }} \
KC_HTTP_ENABLED=true \
KC_HTTP_PORT=8080 \
KC_PROXY_HEADERS=xforwarded \
KC_BOOTSTRAP_ADMIN_USERNAME={{ podman_identity_keycloak_admin_username }} \
KC_BOOTSTRAP_ADMIN_PASSWORD={{ podman_identity_keycloak_admin_password }} \
PROXY_ADDRESS_FORWARDING=true
Exec=start
Image=quay.io/keycloak/keycloak:26.1
Network=keycloak.network
Network=ldap.network
Network=frontend.network
{% for provider in podman_identity_keycloak_providers %}
Volume=/home/{{ podman_identity_podman_rootless_user }}/keycloak/{{ provider.url | basename }}:/opt/keycloak/providers/{{ provider.url | basename }}:ro
{% endfor %}
[Service]
Restart=always
[Install]
WantedBy=default.target

View file

@ -0,0 +1,5 @@
[Network]
Driver=bridge
[Install]
WantedBy=default.target

View file

@ -0,0 +1,17 @@
[Container]
ContainerName=ldap
Environment=DS_DM_PASSWORD={{ podman_identity_ldap_directory_manager_password }}
Image=quay.io/389ds/dirsrv:latest
Network=ldap.network
PublishPort=636:3636/tcp
Volume=/home/{{ podman_identity_podman_rootless_user }}/ldap:/data:rw
Volume=/home/{{ podman_identity_podman_rootless_user }}/certbot/conf/live/{{ podman_identity_keycloak_hostname }}/privkey.pem:/data/tls/server.key:ro
Volume=/home/{{ podman_identity_podman_rootless_user }}/certbot/conf/live/{{ podman_identity_keycloak_hostname }}/cert.pem:/data/tls/server.crt:ro
Volume=/home/{{ podman_identity_podman_rootless_user }}/certbot/conf/live/{{ podman_identity_keycloak_hostname }}/chain.pem:/data/tls/ca/chain.crt:ro
[Service]
RuntimeMaxSec=604800
Restart=always
[Install]
WantedBy=default.target

View file

@ -0,0 +1,5 @@
[Network]
Driver=bridge
[Install]
WantedBy=default.target

View file

@ -0,0 +1,39 @@
# {{ ansible_managed }}
server {
listen 80;
listen [::]:80;
server_name {{ podman_identity_keycloak_hostname }};
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://{{ podman_identity_keycloak_hostname }}$request_uri;
}
}
server {
listen 443 default_server ssl;
listen [::]:443 ssl;
http2 on;
server_name {{ podman_identity_keycloak_hostname }};
server_tokens off;
ssl_certificate /etc/letsencrypt/live/{{ podman_identity_keycloak_hostname }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ podman_identity_keycloak_hostname }}/privkey.pem;
location / {
proxy_pass http://keycloak:8080/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port 443;
}
}

View file

@ -0,0 +1,15 @@
[Container]
AutoUpdate=registry
ContainerName=postgres
Environment=\
POSTGRES_DB={{ podman_identity_postgres_keycloak_database }} \
POSTGRES_PASSWORD={{ podman_identity_postgres_keycloak_password }} \
POSTGRES_USER={{ podman_identity_postgres_keycloak_username }} \
POSTGRES_HOST_AUTH_METHOD=scram-sha-256 \
POSTGRES_INITDB_ARGS=--auth-host=scram-sha-256
Image=docker.io/postgres:17.3
Network=keycloak.network
Volume=/home/{{ podman_identity_podman_rootless_user }}/postgres:/var/lib/postgresql/data:rw
[Install]
WantedBy=default.target

View file

@ -0,0 +1,6 @@
---
podman_nginx_additional_hostnames: []
podman_nginx_certbot_testing: false
# podman_nginx_frontend_network:
podman_nginx_podman_rootless_user: nginx
# podman_nginx_primary_hostname:

View file

@ -0,0 +1,18 @@
---
- name: restart certbot-renew
ansible.builtin.systemd_service:
name: certbot-renew
state: started
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_nginx_podman_rootless_user }}"
- name: restart nginx
ansible.builtin.systemd_service:
name: nginx
state: restarted
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_nginx_podman_rootless_user }}"

View file

@ -0,0 +1,110 @@
---
- name: create service configuration directories
ansible.builtin.file:
path: "/home/{{ podman_nginx_podman_rootless_user }}/{{ item }}"
state: directory
owner: "{{ podman_nginx_podman_rootless_user }}"
group: "{{ podman_nginx_podman_rootless_user }}"
mode: "0755"
become: true
with_items:
- .config/systemd/user
- certbot/conf
- certbot/www
- nginx
- name: install podman quadlet for rootless podman user
ansible.builtin.template:
src: "{{ item }}"
dest: "/home/{{ podman_nginx_podman_rootless_user }}/.config/containers/systemd/{{ item }}"
owner: "{{ podman_nginx_podman_rootless_user }}"
mode: "0400"
with_items:
- certbot-renew.container
- nginx.container
notify:
- "restart {{ item | split('.') | first }}"
become: true
- name: install certbot renewal timer for rootless podman user
ansible.builtin.template:
src: "certbot-renew.timer"
dest: "/home/{{ podman_nginx_podman_rootless_user }}/.config/systemd/user/certbot-renew.timer"
owner: "{{ podman_nginx_podman_rootless_user }}"
mode: "0400"
become: true
- name: verify quadlets are correctly defined
ansible.builtin.command: /usr/libexec/podman/quadlet -dryrun -user
register: podman_nginx_quadlet_result
ignore_errors: true
changed_when: false
become: true
become_user: "{{ podman_nginx_podman_rootless_user }}"
- name: check if certificate exists
stat:
path: "/home/{{ podman_nginx_podman_rootless_user }}/certbot/conf/live/{{ podman_nginx_primary_hostname }}/fullchain.pem"
register: podman_nginx_cert_stat
become: yes
become_user: "{{ podman_nginx_podman_rootless_user }}"
- name: create temporary nginx configuration (no https)
ansible.builtin.template:
src: nginx.conf
dest: "/home/{{ podman_nginx_podman_rootless_user }}/nginx/nginx.conf"
owner: "{{ podman_nginx_podman_rootless_user }}"
group: "{{ podman_nginx_podman_rootless_user }}"
mode: "0644"
become: true
when: podman_nginx_cert_stat.stat.exists == false
- name: start nginx
ansible.builtin.systemd_service:
name: nginx
state: started
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_nginx_podman_rootless_user }}"
- name: run certbot container to create certificate
ansible.builtin.command:
cmd: >
podman run --name certbot-generate
--rm
--volume /home/{{ podman_nginx_podman_rootless_user }}/certbot/www:/var/www/certbot:rw
--volume /home/{{ podman_nginx_podman_rootless_user }}/certbot/conf:/etc/letsencrypt:rw
docker.io/certbot/certbot:latest
certonly
--register-unsafely-without-email
--agree-tos
--webroot
--webroot-path /var/www/certbot/
-d "{{ podman_nginx_primary_hostname }}"
{% for hostname in podman_nginx_additional_hostnames %} -d "{{ hostname }}"{% endfor %}
{% if podman_nginx_certbot_testing %} --test-cert{% endif %}
when: podman_nginx_cert_stat.stat.exists == false
become: yes
become_user: "{{ podman_nginx_podman_rootless_user }}"
- name: check if certificate exists
stat:
path: "/home/{{ podman_nginx_podman_rootless_user }}/certbot/conf/live/{{ podman_nginx_primary_hostname }}/fullchain.pem"
register: podman_nginx_cert_stat
become: yes
become_user: "{{ podman_nginx_podman_rootless_user }}"
- name: ensure certificate exists now
ansible.builtin.assert:
that:
- podman_nginx_cert_stat.stat.exists
fail_msg: "Failed to get a Lets Encrypt certificate."
- name: start certbot renewal timer
ansible.builtin.systemd_service:
name: "certbot-renew.timer"
state: started
scope: user
become: true
become_user: "{{ podman_nginx_podman_rootless_user }}"

View file

@ -0,0 +1,13 @@
[Unit]
Description=Run certbot renew
[Container]
AutoUpdate=registry
ContainerName=certbot-renew
Exec=renew
Image=docker.io/certbot/certbot:latest
Volume=/home/{{ podman_nginx_podman_rootless_user }}/certbot/www:/var/www/certbot
Volume=/home/{{ podman_nginx_podman_rootless_user }}/certbot/conf:/etc/letsencrypt
[Service]
Restart=no

View file

@ -0,0 +1,9 @@
[Unit]
Description=Timer for certbot renewals
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,17 @@
# {{ ansible_managed }}
server {
listen 80;
listen [::]:80;
server_name {{ podman_nginx_primary_hostname }};
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://{{ podman_nginx_primary_hostname }}$request_uri;
}
}

View file

@ -0,0 +1,16 @@
[Container]
ContainerName=nginx
Image=docker.io/nginx:latest
{% if podman_nginx_frontend_network is defined %}Network={{ podman_nginx_frontend_network }}.network{% endif +%}
PublishPort=80:80
PublishPort=443:443
Volume=/home/{{ podman_nginx_podman_rootless_user }}/certbot/www:/var/www/certbot/:ro
Volume=/home/{{ podman_nginx_podman_rootless_user }}/certbot/conf/:/etc/letsencrypt/:ro
Volume=/home/{{ podman_nginx_podman_rootless_user }}/nginx:/etc/nginx/conf.d/:ro
[Service]
RuntimeMaxSec=604800
Restart=always
[Install]
WantedBy=default.target

View file

@ -0,0 +1,12 @@
---
system_baseline_admin_users:
- user: irl
comment: irl
ssh_public_key: "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIJpoCJEax0XTNK6qfYfZV60euSwoc0RQ0bwFDQGMWYQnAAAABHNzaDo="
system_baseline_admin_user_groups_debian:
- adm
- staff
- sudo
- systemd-journal
system_baseline_retired_admin_users: []
system_baseline_service_users: []

View file

@ -0,0 +1,6 @@
---
- name: reload sshd
service:
name: sshd
state: reloaded
become: true

View file

@ -0,0 +1,29 @@
---
- name: upgrade debian packages (apt)
ansible.builtin.apt:
upgrade: safe
cache_valid_time: 3600
become: true
when: ansible_distribution == 'Debian'
- name: install system packages (dnf)
dnf:
name: "*"
state: latest
update_cache: true
become: true
when: ansible_distribution == 'AlmaLinux'
- name: setup users
ansible.builtin.include_tasks:
file: users.yml
- name: setup OpenSSH server
ansible.builtin.include_tasks:
file: sshd.yml
- name: remove root authorised keys
ansible.builtin.file:
path: /root/.ssh/authorized_keys
state: absent
become: true

View file

@ -0,0 +1,24 @@
---
- name: sshd PermitRootLogin=no
lineinfile:
dest: "/etc/ssh/sshd_config"
regexp: "^#?\\w*PermitRootLogin"
line: "PermitRootLogin no"
state: present
become: true
notify: "reload sshd"
- name: sshd PasswordAuthentication=no
lineinfile:
dest: "/etc/ssh/sshd_config"
regexp: "^#?\\w*PasswordAuthentication"
line: "PasswordAuthentication no"
state: present
become: true
notify: "reload sshd"
- name: retrieve ssh host key
fetch:
src: "/etc/ssh/ssh_host_ed25519_key.pub"
dest: "files/ssh_host_keys/{{ inventory_hostname }}_ed25519.pub"
flat: yes

View file

@ -0,0 +1,74 @@
---
- name: create a group for admin users
ansible.builtin.group:
name: ops
state: present
become: true
- name: create admin users
ansible.builtin.user:
name: "{{ item.user }}"
comment: "{{ item.comment | default(item.user) }}"
group: ops
with_items: "{{ system_baseline_admin_users }}"
become: true
- name: remove retired admin users
ansible.builtin.user:
name: "{{ item }}"
state: absent
with_items: "{{ system_baseline_retired_admin_users }}"
become: true
- name: additional groups for admin users (Debian only)
ansible.builtin.user:
name: "{{ item.user }}"
groups: "{{ system_baseline_admin_user_groups_debian }}"
append: true
with_items: "{{ system_baseline_admin_users }}"
become: true
when: ansible_distribution == 'Debian'
- name: install SSH keys for admin users
ansible.posix.authorized_key:
user: "{{ item.user }}"
state: present
key: "{{ item.ssh_public_key }}"
exclusive: true
with_items: "{{ system_baseline_admin_users }}"
become: true
- name: allow passwordless sudo for sudo group (Debian only)
ansible.builtin.lineinfile:
path: /etc/sudoers
state: present
regexp: "^#?\\w*%sudo "
line: "%sudo ALL=(ALL) NOPASSWD: ALL"
validate: "/usr/sbin/visudo -cf %s"
become: true
when: ansible_distribution == 'Debian'
- name: create a group for service users
ansible.builtin.group:
name: services
state: present
become: true
- name: create service users
ansible.builtin.user:
name: "{{ item.user }}"
comment: "{{ item.comment | default(item.user) }}"
group: services
with_items: "{{ system_baseline_service_users }}"
become: true
- name: enable linger for service users
ansible.builtin.command:
argv:
- /usr/bin/loginctl
- enable-linger
- "{{ item.user }}"
creates: "/var/lib/systemd/linger/{{ item.user }}"
when: "ansible_distribution == 'Debian' and (item.linger is not defined or item.linger)"
become: true
with_items: "{{ system_baseline_service_users }}"

35
roles/vps/tasks/main.yml Normal file
View file

@ -0,0 +1,35 @@
---
# https://support.solusvm.com/hc/en-us/articles/21334950006807-How-to-install-Guest-Tools-manually-inside-a-VM-in-SolusVM-2
- name: install required packages
apt:
pkg:
- qemu-guest-agent
- cloud-init
- tuned
state: latest
cache_valid_time: 3600
become: true
when: ansible_distribution == 'Debian'
- name: install required packages
dnf:
name:
- qemu-guest-agent
- cloud-init
- tuned
state: latest
update_cache: true
become: true
when: ansible_distribution == 'AlmaLinux'
- name: check tuned profile
command: tuned-adm active
register: vps_tuned_profile
become: true
changed_when: false
- name: start tuned profile
shell: tuned-adm profile virtual-guest
become: true
when: "'virtual-guest' not in vps_tuned_profile.stdout"