From 9f2ebc2cce3af0e685677c23f2098c115e034adf Mon Sep 17 00:00:00 2001 From: Joe Carstairs Date: Thu, 13 Aug 2026 16:35:47 +0100 Subject: move ansible/ dir to root --- roles/actualbudget/tasks/main.yml | 52 +++++++ roles/actualbudget/templates/openrc/actualbudget | 26 ++++ roles/agate/tasks/main.yml | 8 ++ roles/comitium/tasks/main.yml | 22 +++ roles/dyndns/files/DIGITALOCEAN_TOKEN | 9 ++ roles/dyndns/tasks/main.yml | 72 ++++++++++ roles/etherpad/tasks/main.yml | 65 +++++++++ roles/etherpad/templates/openrc/etherpad | 28 ++++ roles/etherpad/templates/settings.json | 134 ++++++++++++++++++ roles/gemini/tasks/main.yml | 44 ++++++ roles/gemini/templates/openrc/gemini | 30 ++++ roles/git/files/.gitconfig | 3 + roles/git/files/cgitrc | 8 ++ roles/git/tasks/main.yml | 153 ++++++++++++++++++++ roles/git/templates/conf.d/fcgiwrap | 3 + roles/git/templates/conf.d/git-daemon | 3 + roles/git/templates/nginx/cgit.conf | 17 +++ roles/http/tasks/main.yml | 115 +++++++++++++++ roles/http/templates/config.ini | 11 ++ roles/http/templates/nginx/joeac.net.conf | 24 ++++ roles/http/templates/php-fpm/joeac.net.conf | 23 +++ roles/ln/files/public/milehouse.html | 1 + roles/ln/tasks/main.yml | 7 + roles/mox/files/adminpasswd | 8 ++ ...et.20260705T163220.rsa2048.privatekey.pkcs8.pem | 96 +++++++++++++ ...et.20260705T163220.rsa2048.privatekey.pkcs8.pem | 96 +++++++++++++ roles/mox/tasks/main.yml | 155 +++++++++++++++++++++ roles/mox/templates/dnssec.conf | 9 ++ roles/mox/templates/domains.conf | 98 +++++++++++++ roles/mox/templates/mox.conf | 62 +++++++++ roles/mox/templates/openrc/mox | 77 ++++++++++ roles/tls/files/certbot.fact | 35 +++++ roles/tls/files/ovh-dns-credentials.ini | 14 ++ roles/tls/tasks/main.yml | 76 ++++++++++ roles/vaultwarden/tasks/main.yml | 26 ++++ roles/vaultwarden/templates/openrc/vaultwarden | 46 ++++++ 36 files changed, 1656 insertions(+) create mode 100644 roles/actualbudget/tasks/main.yml create mode 100644 roles/actualbudget/templates/openrc/actualbudget create mode 100644 roles/agate/tasks/main.yml create mode 100644 roles/comitium/tasks/main.yml create mode 100644 roles/dyndns/files/DIGITALOCEAN_TOKEN create mode 100644 roles/dyndns/tasks/main.yml create mode 100644 roles/etherpad/tasks/main.yml create mode 100644 roles/etherpad/templates/openrc/etherpad create mode 100644 roles/etherpad/templates/settings.json create mode 100644 roles/gemini/tasks/main.yml create mode 100644 roles/gemini/templates/openrc/gemini create mode 100644 roles/git/files/.gitconfig create mode 100644 roles/git/files/cgitrc create mode 100644 roles/git/tasks/main.yml create mode 100644 roles/git/templates/conf.d/fcgiwrap create mode 100644 roles/git/templates/conf.d/git-daemon create mode 100644 roles/git/templates/nginx/cgit.conf create mode 100644 roles/http/tasks/main.yml create mode 100644 roles/http/templates/config.ini create mode 100644 roles/http/templates/nginx/joeac.net.conf create mode 100644 roles/http/templates/php-fpm/joeac.net.conf create mode 100644 roles/ln/files/public/milehouse.html create mode 100644 roles/ln/tasks/main.yml create mode 100644 roles/mox/files/adminpasswd create mode 100644 roles/mox/files/dkim/2026a._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem create mode 100644 roles/mox/files/dkim/2026b._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem create mode 100644 roles/mox/tasks/main.yml create mode 100644 roles/mox/templates/dnssec.conf create mode 100644 roles/mox/templates/domains.conf create mode 100644 roles/mox/templates/mox.conf create mode 100644 roles/mox/templates/openrc/mox create mode 100644 roles/tls/files/certbot.fact create mode 100644 roles/tls/files/ovh-dns-credentials.ini create mode 100644 roles/tls/tasks/main.yml create mode 100644 roles/vaultwarden/tasks/main.yml create mode 100755 roles/vaultwarden/templates/openrc/vaultwarden (limited to 'roles') diff --git a/roles/actualbudget/tasks/main.yml b/roles/actualbudget/tasks/main.yml new file mode 100644 index 0000000..81c89f2 --- /dev/null +++ b/roles/actualbudget/tasks/main.yml @@ -0,0 +1,52 @@ +- name: Install NPM + community.general.apk: + name: + - npm + +- name: Check current actual-server version + register: prev_actualbudget_version + changed_when: false + failed_when: false + ansible.builtin.command: + cmd: actual-server --version + +- name: Install actual-server with NPM + block: + - name: Install actual-server with NPM + ansible.builtin.command: npm install --global @actual-app/sync-server + changed_when: false + - name: Check new actual-server version + register: new_actualbudget_version + ansible.builtin.command: actual-server --version + changed_when: prev_actualbudget_version.stdout != new_actualbudget_version.stdout + +- name: Install actualbudget service + register: actualbudget_service + ansible.builtin.template: + src: openrc/actualbudget + dest: /etc/init.d/actualbudget + mode: "755" + +- name: Create actualbudget data directory + ansible.builtin.file: + path: "{{ actualbudget_data_dir }}" + state: directory + +- name: Restart actualbudget service and configure to start on boot + when: + new_actualbudget_version is changed + or actualbudget_service is changed + ansible.builtin.service: + name: actualbudget + enabled: true + state: restarted + +- name: Start actualbudget service and configure to start on boot + when: + not ( + new_actualbudget_version is changed + or actualbudget_service is changed ) + ansible.builtin.service: + name: actualbudget + enabled: true + state: started diff --git a/roles/actualbudget/templates/openrc/actualbudget b/roles/actualbudget/templates/openrc/actualbudget new file mode 100644 index 0000000..52fd8f0 --- /dev/null +++ b/roles/actualbudget/templates/openrc/actualbudget @@ -0,0 +1,26 @@ +#!/sbin/openrc-run + +description="Run actualbudget budgeting server" + +start() +{ + ebegin "Starting actualbudget" + start-stop-daemon --start \ + --exec /usr/local/bin/actual-server \ + --env ACTUAL_DATA_DIR={{ actualbudget_data_dir }} \ + --background \ + --stdout /var/log/actualbudget.log \ + --stderr /var/log/actualbudget.log \ + --make-pidfile \ + --pidfile /run/actualbudget.pid + eend $? +} + +stop() +{ + ebegin "Stopping actualbudget" + start-stop-daemon --stop \ + --exec /usr/bin/pnpx \ + --pidfile /run/actualbudget.pid + eend $? +} diff --git a/roles/agate/tasks/main.yml b/roles/agate/tasks/main.yml new file mode 100644 index 0000000..6fe7367 --- /dev/null +++ b/roles/agate/tasks/main.yml @@ -0,0 +1,8 @@ +- name: Install cargo + community.general.apk: + name: cargo + +- name: Install agate with cargo + ansible.builtin.command: + cmd: cargo install agate + creates: /root/.cargo/bin/agate diff --git a/roles/comitium/tasks/main.yml b/roles/comitium/tasks/main.yml new file mode 100644 index 0000000..da82c6d --- /dev/null +++ b/roles/comitium/tasks/main.yml @@ -0,0 +1,22 @@ +- name: Install go, make and scdoc + community.general.apk: + name: + - go + - make + - scdoc + +- name: Fetch comitium source code + ansible.builtin.git: + repo: https://git.sr.ht/~nytpu/comitium + dest: "{{ src_dir }}/comitium" + version: v{{ comitium_version }} + depth: 1 + +- name: Make + community.general.make: + chdir: "{{ src_dir }}/comitium" + +- name: Make install + community.general.make: + chdir: "{{ src_dir }}/comitium" + target: install diff --git a/roles/dyndns/files/DIGITALOCEAN_TOKEN b/roles/dyndns/files/DIGITALOCEAN_TOKEN new file mode 100644 index 0000000..7f85a33 --- /dev/null +++ b/roles/dyndns/files/DIGITALOCEAN_TOKEN @@ -0,0 +1,9 @@ +$ANSIBLE_VAULT;1.2;AES256;ansible +32323233343662343033613265383639303739386139363735646133633435393261346466666530 +3031303363663639343163633164393335663639353165300a373961376339336565353630303566 +32616562373263363365656531353633663830343633356462396464626164306337656665326463 +3336666530656539330a303238353335313130313130653138663266393430646662663066663036 +37626630396438336136303032633439323436323132616665353938373139353530613539366632 +39653161363765376262393933666163363230333037383033666138373439623838643833633463 +38393437353830356362393439366461623231653437613462666466643563323933366237313366 +33613166633665363238 diff --git a/roles/dyndns/tasks/main.yml b/roles/dyndns/tasks/main.yml new file mode 100644 index 0000000..198ef0d --- /dev/null +++ b/roles/dyndns/tasks/main.yml @@ -0,0 +1,72 @@ +- name: Fetch digitalocean_dyndns source + ansible.builtin.git: + repo: https://git.joeac.net/joeac/digitalocean_dyndns.git + dest: /usr/local/lib/digitalocean_dyndns + +- name: Mark get_ip_addr.sh executable + ansible.builtin.file: + path: /usr/local/lib/digitalocean_dyndns/get_ip_addr.sh + owner: joeac.net + group: joeac.net + mode: "775" + +- name: Mark dyndns.sh executable + ansible.builtin.file: + path: /usr/local/lib/digitalocean_dyndns/dyndns.sh + owner: joeac.net + group: joeac.net + mode: "775" + +- name: Symlink get_ip_addr.sh + ansible.builtin.file: + src: /usr/local/lib/digitalocean_dyndns/get_ip_addr.sh + dest: /usr/bin/get_ip_addr.sh + owner: joeac.net + group: joeac.net + state: link + +- name: Symlink dyndns.sh + ansible.builtin.file: + src: /usr/local/lib/digitalocean_dyndns/dyndns.sh + dest: /usr/bin/dyndns.sh + owner: joeac.net + group: joeac.net + state: link + +- name: Create config directory + ansible.builtin.file: + path: /etc/digitalocean_dyndns + state: directory + mode: "644" + +- name: Create cache directory + ansible.builtin.file: + path: /var/digitalocean_dyndns + state: directory + mode: "666" + +- name: Create log directory + ansible.builtin.file: + path: /var/log/digitalocean_dyndns + state: directory + mode: "666" + +- name: Copy DIGITALOCEAN_TOKEN + ansible.builtin.copy: + src: DIGITALOCEAN_TOKEN + dest: /etc/digitalocean_dyndns/DIGITALOCEAN_TOKEN + mode: "640" + +- name: Install daily crontabs for dyndns (IPv4) + loop: "{{ subdomains | map(attribute='name') }}" + ansible.builtin.cron: + special_time: daily + name: daily crontab for dyndns for {{ item }}.joeac.net (IPv4) + job: CACHE_DIR=/var/digitalocean_dyndns CONFIG_DIR=/etc/digitalocean_dyndns dyndns.sh 4 {{ item }}.joeac.net >> /var/log/digitalocean_dyndns/{{ item }}.joeac.net.ipv4.log + +- name: Install daily crontabs for dyndns (IPv6) + loop: "{{ subdomains | map(attribute='name') }}" + ansible.builtin.cron: + special_time: daily + name: daily crontab for dyndns for {{ item }}.joeac.net (IPv6) + job: CACHE_DIR=/var/digitalocean_dyndns CONFIG_DIR=/etc/digitalocean_dyndns CONN_DEVICE_NAME={{ ansible_facts.default_ipv4.alias }} dyndns.sh 6 {{ item }}.joeac.net >> /var/log/digitalocean_dyndns/{{ item }}.joeac.net.ipv6.log diff --git a/roles/etherpad/tasks/main.yml b/roles/etherpad/tasks/main.yml new file mode 100644 index 0000000..185fe51 --- /dev/null +++ b/roles/etherpad/tasks/main.yml @@ -0,0 +1,65 @@ +- name: Install PNPM and Git + community.general.apk: + name: + - pnpm + - git + +- name: Install tsx globally + ansible.builtin.shell: pnpm install --global tsx + register: pnpm_install_tsx + changed_when: "'Already up to date' not in pnpm_install_tsx.stdout" + environment: + PNPM_HOME: "{{ ansible_facts.env.HOME }}/.local/share/pnpm" + PATH: "{{ ansible_facts.env.PATH }}:{{ ansible_facts.env.HOME }}/.local/share/pnpm" + +- name: Checkout source code for latest etherpad release + register: etherpad_src + ansible.builtin.git: + repo: https://github.com/ether/etherpad.git + dest: "{{ src_dir }}/etherpad" + depth: 1 + version: master + +- name: Install etherpad settings + register: etherpad_settings + ansible.builtin.template: + src: settings.json + dest: "{{ src_dir }}/etherpad/settings.json" + mode: "640" + +- name: Install etherpad dependencies + register: etherpad_dependencies + changed_when: "'Already up to date' not in etherpad_dependencies.stdout" + ansible.builtin.command: + chdir: "{{ src_dir }}/etherpad" + cmd: pnpm install + +- name: Install etherpad service + register: etherpad_service + ansible.builtin.template: + src: openrc/etherpad + dest: /etc/init.d/etherpad + mode: "755" + +- name: Restart etherpad service and configure to start on boot + when: + etherpad_src is changed + or etherpad_service is changed + or etherpad_settings is changed + or etherpad_dependencies is changed + ansible.builtin.service: + name: etherpad + enabled: true + state: restarted + +- name: Start etherpad service and configure to start on boot + when: + not ( + etherpad_src is changed + or etherpad_settings is changed + or etherpad_service is changed + or etherpad_dependencies is changed ) + ansible.builtin.service: + name: etherpad + enabled: true + state: started diff --git a/roles/etherpad/templates/openrc/etherpad b/roles/etherpad/templates/openrc/etherpad new file mode 100644 index 0000000..f5f7260 --- /dev/null +++ b/roles/etherpad/templates/openrc/etherpad @@ -0,0 +1,28 @@ +#!/sbin/openrc-run + +description="Run etherpad document server" + +start() +{ + ebegin "Starting etherpad" + start-stop-daemon --start \ + --env NODE_ENV={{ etherpad_node_env }} \ + --exec /usr/bin/pnpx \ + --background \ + --chdir {{ src_dir }}/etherpad \ + --stdout /var/log/etherpad.log \ + --stderr /var/log/etherpad.log \ + --make-pidfile \ + --pidfile /run/etherpad.pid \ + -- tsx src/node/server.ts + eend $? +} + +stop() +{ + ebegin "Stopping etherpad" + start-stop-daemon --stop \ + --exec /usr/bin/pnpx \ + --pidfile /run/etherpad.pid + eend $? +} diff --git a/roles/etherpad/templates/settings.json b/roles/etherpad/templates/settings.json new file mode 100644 index 0000000..7818d93 --- /dev/null +++ b/roles/etherpad/templates/settings.json @@ -0,0 +1,134 @@ +{ + "title": "joeac's docs", + "showRecentPads": true, + "favicon": null, + "skinName": "colibris", + "skinVariants": "super-light-toolbar super-light-editor light-background", + "ip": "0.0.0.0", + "port": {{ services.etherpad.port }}, + "showSettingsInAdminPage": true, + "enableMetrics": "${ENABLE_METRICS:true}", + "cleanup": { + "enabled": false, + "keepRevisions": 5 + }, + "dbType": "sqlite", + "dbSettings": { + "filename": "var/sqlite.db" + }, + "defaultPadText" : "", + "padOptions": { + "noColors": false, + "showControls": true, + "showChat": true, + "showLineNumbers": true, + "useMonospaceFont": false, + "userName": null, + "userColor": null, + "rtl": false, + "alwaysShowChat": false, + "chatAndUsers": false, + "lang": null + }, + "padShortcutEnabled" : { + "altF9": true, + "altC": true, + "cmdShift2": true, + "delete": true, + "return": true, + "esc": true, + "cmdS": true, + "tab": true, + "cmdZ": true, + "cmdY": true, + "cmdI": true, + "cmdB": true, + "cmdU": true, + "cmd5": true, + "cmdShiftL": true, + "cmdShiftN": true, + "cmdShift1": true, + "cmdShiftC": true, + "cmdH": true, + "ctrlHome": true, + "pageUp": true, + "pageDown": true + }, + "updateServer": "https://etherpad.org/ep_infos", + "suppressErrorsInPadText": false, + "requireSession": false, + "editOnly": false, + "minify": true, + "maxAge": 21600, + "soffice": null, + "docxExport": true, + "allowUnknownFileEnds": true, + "requireAuthentication": false, + "requireAuthorization": false, + "trustProxy": false, + "cookie": { + "keyRotationInterval": 86400000, + "sameSite": "Lax", + "sessionLifetime": 864000000, + "sessionRefreshInterval": 86400000, + "sessionCleanup": true + }, + "disableIPlogging": false, + "automaticReconnectionTimeout": 0, + "scrollWhenFocusLineIsOutOfViewport": { + "percentage": { + "editionAboveViewport": 0, + "editionBelowViewport": 0 + }, + "duration": 0, + "scrollWhenCaretIsInTheLastLineOfViewport": false, + "percentageToScrollWhenUserPressesArrowUp": 0 + }, + "users": { + "admin": { + "password": "{{ etherpad_admin_password }}", + "is_admin": true + } + }, + "socketTransportProtocols" : ["websocket", "polling"], + "socketIo": { + "maxHttpBufferSize": 1000000 + }, + "loadTest": false, + "dumpOnUncleanExit": false, + "importExportRateLimiting": { + "windowMs": 90000, + "max": 10 + }, + "importMaxFileSize": 52428800, // 50 * 1024 * 1024 + "authenticationMethod": "${AUTHENTICATION_METHOD:sso}", + "enableDarkMode": "${ENABLE_DARK_MODE:true}", + "enablePadWideSettings": "${ENABLE_PAD_WIDE_SETTINGS:false}", + "commitRateLimiting": { + "duration": 1, + "points": 10 + }, + "logLayoutType": "colored", + "customLocaleStrings": {}, + "enableAdminUITests": false, + "lowerCasePadIds": false, + "sso": { + "issuer": "${SSO_ISSUER:http://localhost:{{ services.etherpad.port }}}", + "clients": [ + { + "client_id": "${ADMIN_CLIENT:admin_client}", + "client_secret": "${ADMIN_SECRET:admin}", + "grant_types": ["authorization_code"], + "response_types": ["code"], + "redirect_uris": ["${ADMIN_REDIRECT:http://localhost:{{ services.etherpad.port }}/admin/}"] + }, + { + "client_id": "${USER_CLIENT:user_client}", + "client_secret": "${USER_SECRET:user}", + "grant_types": ["authorization_code"], + "response_types": ["code"], + "redirect_uris": ["${USER_REDIRECT:http://localhost:{{ services.etherpad.port }}/}"] + } + ] + } +} diff --git a/roles/gemini/tasks/main.yml b/roles/gemini/tasks/main.yml new file mode 100644 index 0000000..73d730e --- /dev/null +++ b/roles/gemini/tasks/main.yml @@ -0,0 +1,44 @@ +- name: Checkout joeac.net source code + ansible.builtin.git: + repo: https://git.joeac.net/joeac/joeac.net.git + dest: "{{ src_dir }}/joeac.net" + depth: 1 + version: main + +- name: Run initial comitium refresh + register: comitium_refresh + ansible.builtin.command: + cmd: /usr/local/bin/comitium refresh --data {{ src_dir }}/joeac.net/gemini/comitium-data + creates: "{{ src_dir }}/joeac.net/gemini/comitium-data/feed.gmi" + +- name: Schedule daily comitium refresh + ansible.builtin.cron: + special_time: daily + job: /usr/local/bin/comitium refresh --data {{ src_dir }}/joeac.net/gemini/comitium-data + name: daily comitium refresh for joeac.net capsule + +- name: Build joeac.net capsule + register: build_capsule + community.general.make: + chdir: "{{ src_dir }}/joeac.net/gemini" + +- name: Install gemini service + register: install_service + ansible.builtin.template: + src: openrc/gemini + dest: /etc/init.d/gemini + mode: "775" + +- name: Start gemini service and configure to start on boot + when: not ( comitium_refresh is changed or build_capsule is changed or install_service is changed ) + ansible.builtin.service: + name: gemini + enabled: true + state: started + +- name: Restart gemini service and configure to start on boot + when: comitium_refresh is changed or build_capsule is changed or install_service is changed + ansible.builtin.service: + name: gemini + enabled: true + state: restarted diff --git a/roles/gemini/templates/openrc/gemini b/roles/gemini/templates/openrc/gemini new file mode 100644 index 0000000..fbc2cc8 --- /dev/null +++ b/roles/gemini/templates/openrc/gemini @@ -0,0 +1,30 @@ +#!/sbin/openrc-run + +description="agate gemini server (serving {{ src_dir }}/joeac.net/gemini/content)" + +start() +{ + ebegin "Starting agate (serving {{ src_dir }}/joeac.net/gemini/content)" + start-stop-daemon --start \ + --exec /root/.cargo/bin/agate \ + --background \ + --stdout /var/log/agate.log \ + --stderr /var/log/agate.log \ + --make-pidfile \ + --pidfile /run/agate.pid \ + -- \ + --content {{ src_dir }}/joeac.net/gemini/content/ \ + --addr 0.0.0.0:1965 \ + --addr [::]:1965 \ + --lang en-GB + eend $? +} + +stop() +{ + ebegin "Stopping agate" + start-stop-daemon --stop \ + --exec /usr/bin/pnpx \ + --pidfile /run/agate.pid + eend $? +} diff --git a/roles/git/files/.gitconfig b/roles/git/files/.gitconfig new file mode 100644 index 0000000..78fdfcf --- /dev/null +++ b/roles/git/files/.gitconfig @@ -0,0 +1,3 @@ +[safe] + directory = /srv/git/* + directory = /media/seagate/git/* diff --git a/roles/git/files/cgitrc b/roles/git/files/cgitrc new file mode 100644 index 0000000..9d9d99c --- /dev/null +++ b/roles/git/files/cgitrc @@ -0,0 +1,8 @@ +root-title=joeac's git repositories +root-desc= +strict-export=git-daemon-export-ok +scan-path=/srv/git +virtual-root=/ +about-filter=/usr/lib/cgit/filters/about-formatting.sh +readme=:README.md +snapshots=tar.gz zip diff --git a/roles/git/tasks/main.yml b/roles/git/tasks/main.yml new file mode 100644 index 0000000..a676d0b --- /dev/null +++ b/roles/git/tasks/main.yml @@ -0,0 +1,153 @@ +- name: Install cgit, fcgiwrap, git, nginx, and shadow + community.general.apk: + name: + - cgit + - fcgiwrap + - git + - nginx + - shadow + +- name: Add git group + ansible.builtin.group: + name: git + +- name: Add git user + ansible.builtin.user: + name: git + group: git + +- name: Find existing shells + register: existing_shells + changed_when: false + ansible.builtin.command: cat /etc/shells + +- name: Add git-shell to /etc/shells + when: "'/git-shell' not in existing_shells.stdout" + ansible.builtin.shell: which git-shell >> /etc/shells + +- name: Find current git login shell + register: current_git_login_shell + ansible.builtin.shell: "cat /etc/passwd | grep ^git: | grep -o [^:]*$" + +- name: Set git login shell to git-shell + when: "'git-shell' not in current_git_login_shell" + ansible.builtin.shell: chsh -s $(which git-shell) git + +- name: Create fcgiwrap user and append 'git' group + ansible.builtin.user: + name: "{{ fcgiwrap_user }}" + group: "{{ fcgiwrap_group }}" + groups: + - git + append: true + +- name: Create git directory in attached storage + ansible.builtin.file: + path: /media/seagate/git + state: directory + owner: git + group: git + mode: "750" + +- name: Symlink git directory to /srv/git + ansible.builtin.file: + src: /media/seagate/git + dest: /srv/git + owner: git + group: git + state: link + mode: "750" + +- name: Configure fcgiwrap daemon + register: fcgiwrap_conf + ansible.builtin.template: + src: conf.d/fcgiwrap + dest: /etc/conf.d/fcgiwrap + mode: "644" + +- name: Restart fcgiwrap daemon and configure to start on boot + when: fcgiwrap_conf is changed + ansible.builtin.service: + name: fcgiwrap + enabled: true + state: restarted + +- name: Start fcgiwrap daemon and configure to start on boot + when: not ( fcgiwrap_conf is changed ) + ansible.builtin.service: + name: fcgiwrap + enabled: true + state: started + +- name: Remove default nginx site config + register: default_nginx_site + ansible.builtin.file: + path: /etc/nginx/http.d/default.conf + state: absent + +- name: Install cgit nginx site config + register: cgit_nginx_site + ansible.builtin.template: + src: nginx/cgit.conf + dest: /etc/nginx/http.d/cgit.conf + owner: nginx + group: nginx + mode: "660" + +- name: Restart nginx daemon and configure to start on boot + when: default_nginx_site is changed or cgit_nginx_site is changed + ansible.builtin.service: + name: nginx + enabled: true + state: restarted + +- name: Start nginx daemon and configure to start on boot + when: not ( default_nginx_site is changed or cgit_nginx_site is changed ) + ansible.builtin.service: + name: nginx + enabled: true + state: started + +- name: Install cgit config + ansible.builtin.copy: + src: cgitrc + dest: /etc/cgitrc + mode: "+r" + +- name: Install git daemon + community.general.apk: + name: git-daemon-openrc + +- name: Configure git daemon + register: gitd_conf + ansible.builtin.template: + src: conf.d/git-daemon + dest: /etc/conf.d/git-daemon + +- name: Create gitd user + ansible.builtin.user: + name: gitd + group: git + +- name: Mark git directory safe for gitd git operations + register: gitd_gitconfig + ansible.builtin.copy: + src: .gitconfig + dest: ~gitd/.gitconfig + owner: gitd + group: git + mode: "644" + +- name: Restart git daemon and configure to start on boot + when: gitd_conf is changed or gitd_gitconfig is changed + ansible.builtin.service: + name: git-daemon + enabled: true + state: restarted + +- name: Start git daemon and configure to start on boot + when: not ( gitd_conf is changed or gitd_gitconfig is changed ) + ansible.builtin.service: + name: git-daemon + enabled: true + state: started diff --git a/roles/git/templates/conf.d/fcgiwrap b/roles/git/templates/conf.d/fcgiwrap new file mode 100644 index 0000000..65a40e8 --- /dev/null +++ b/roles/git/templates/conf.d/fcgiwrap @@ -0,0 +1,3 @@ +socket=unix:{{ fcgiwrap_socket }} +user={{ fcgiwrap_user }} +group={{ fcgiwrap_group }} diff --git a/roles/git/templates/conf.d/git-daemon b/roles/git/templates/conf.d/git-daemon new file mode 100644 index 0000000..c50b66b --- /dev/null +++ b/roles/git/templates/conf.d/git-daemon @@ -0,0 +1,3 @@ +GITDAEMON_OPTS="--syslog --base-path=/srv/git" +GIT_USER="gitd" +GIT_GROUP="git" diff --git a/roles/git/templates/nginx/cgit.conf b/roles/git/templates/nginx/cgit.conf new file mode 100644 index 0000000..456173b --- /dev/null +++ b/roles/git/templates/nginx/cgit.conf @@ -0,0 +1,17 @@ +server { + listen {{ services.git.port }}; + listen [::]:{{ services.git.port }}; + server_name {{ ( subdomains | selectattr('service', 'eq', 'git') | first ).full_domain }}; + + root /usr/share/webapps/cgit; + try_files $uri @cgit; + + location @cgit { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME /usr/share/webapps/cgit/cgit.cgi; + fastcgi_param PATH_INFO $uri; + fastcgi_param QUERY_STRING $args; + fastcgi_param HTTP_HOST $server_name; + fastcgi_pass unix:/run/fcgiwrap/fcgiwrap.sock; + } +} diff --git a/roles/http/tasks/main.yml b/roles/http/tasks/main.yml new file mode 100644 index 0000000..7676633 --- /dev/null +++ b/roles/http/tasks/main.yml @@ -0,0 +1,115 @@ +- name: Install composer, make, nginx, PHP, PHP-FPM, and PHP-PDO (SQLITE) + community.general.apk: + name: + - composer + - make + - nginx + - php85-fpm + - php85-pdo + - php85-pdo_sqlite + +- name: Start nginx service and configure to restart on boot + ansible.builtin.service: + name: nginx + enabled: true + state: started + +- name: Start PHP-FPM service and configure to restart on boot + ansible.builtin.service: + name: php-fpm85 + enabled: true + state: started + +- name: Remove default PHP-FPM pool + register: default_phpfpm_pool + ansible.builtin.file: + path: /etc/php85/php-fpm.d/www.conf + state: absent + +- name: Install PHP-FPM pool for joeac.net website + register: joeacnet_phpfpm_pool + ansible.builtin.template: + src: php-fpm/joeac.net.conf + dest: /etc/php85/php-fpm.d/joeac.net.conf + owner: nginx + group: nginx + mode: "600" + +- name: Restart PHP-FPM service if config changed + when: default_phpfpm_pool is changed or joeacnet_phpfpm_pool is changed + ansible.builtin.service: + name: php-fpm85 + state: restarted + +- name: Remove default nginx site config + register: default_nginx_site_config + ansible.builtin.file: + path: /etc/nginx/http.d/default.conf + state: absent + +- name: Install joeac.net nginx site config + register: joeacnet_nginx_site_config + ansible.builtin.template: + src: nginx/joeac.net.conf + dest: /etc/nginx/http.d/joeac.net.conf + +- name: Restart nginx service if config changed + when: default_nginx_site_config is changed or joeacnet_nginx_site_config is changed + ansible.builtin.service: + name: nginx + state: restarted + +- name: Checkout joeac.net source code + ansible.builtin.git: + repo: https://git.joeac.net/joeac/joeac.net.git + dest: "{{ src_dir }}/joeac.net" + depth: 1 + version: main + +- name: Build joeac.net website + community.general.make: + chdir: "{{ src_dir }}/joeac.net/http" + +- name: Create joeac.net public directory + ansible.builtin.file: + path: "{{ http_files_dir }}" + state: directory + owner: nginx + group: nginx + +- name: Configure website PHP with config.ini + ansible.builtin.template: + src: config.ini + dest: "{{ src_dir }}/joeac.net/http/config.ini" + +- name: Symlink config.ini + ansible.builtin.file: + src: "{{ src_dir }}/joeac.net/http/config.ini" + dest: "{{ http_files_dir }}/config.ini" + state: link + +- name: Symlink vendor to public directory + ansible.builtin.file: + src: "{{ src_dir }}/joeac.net/http/vendor" + dest: "{{ http_files_dir }}/vendor" + state: link + +- name: Symlink php to public directory + ansible.builtin.file: + src: "{{ src_dir }}/joeac.net/http/php" + dest: "{{ http_files_dir }}/php" + state: link + +- name: Symlink html to public directory + ansible.builtin.file: + src: "{{ src_dir }}/joeac.net/http/out" + dest: "{{ http_files_dir }}/html" + state: link + +- name: Touch database and grant rw permissions to nginx user + ansible.builtin.file: + path: "{{ http_files_dir }}/{{ http_database_filename }}" + state: touch + owner: nginx + group: nginx + mode: "600" diff --git a/roles/http/templates/config.ini b/roles/http/templates/config.ini new file mode 100644 index 0000000..4bbe7b1 --- /dev/null +++ b/roles/http/templates/config.ini @@ -0,0 +1,11 @@ +MAX_DAILY_EMAILS = 100 +CONTACT_MAILBOX = me@joeac.net +CONTACT_MAILBOX_NAME = Joe Carstairs +LOCAL_SMTP_FROM = jobot@mail.joeac.net +LOCAL_SMTP_FROM_NAME = Jobot +LOCAL_SMTP_HOST = mail.joeac.net +LOCAL_SMTP_PORT = 465 +LOCAL_SMTP_USER = jobot@mail.joeac.net +LOCAL_SMTP_PASSWORD = "{{ ( email_accounts | selectattr("name", "eq", "jobot") | first ).password | trim }}" +DB_SCHEME = sqlite +DB_PATH = {{ http_files_dir }}/{{ http_database_filename }} diff --git a/roles/http/templates/nginx/joeac.net.conf b/roles/http/templates/nginx/joeac.net.conf new file mode 100644 index 0000000..9f28fee --- /dev/null +++ b/roles/http/templates/nginx/joeac.net.conf @@ -0,0 +1,24 @@ +server { + listen {{ services.http.port }}; + listen [::]:{{ services.http.port }}; + server_name joeac.net; + + location / { + root {{ http_files_dir }}/html; + try_files $uri $uri.html $uri/index.html $uri.php $uri/index.php =404; + } + + error_page 404 /error.html; + + location ~ \.php$ { + root html; + fastcgi_pass unix:/run/php8.5-fpm-joeac.net-http.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME /var/joeac.net-http/html$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\.sqlite { + deny all; + } +} diff --git a/roles/http/templates/php-fpm/joeac.net.conf b/roles/http/templates/php-fpm/joeac.net.conf new file mode 100644 index 0000000..30c120d --- /dev/null +++ b/roles/http/templates/php-fpm/joeac.net.conf @@ -0,0 +1,23 @@ +[joeac.net] +user = nginx +group = nginx +listen = {{ http_phpfpm_socket }} +listen.owner = nginx +listen.group = nginx +listen.mode = 0660 + +access.log = /var/log/php85/$pool.access.log +access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{milli}d %{kilo}M %C%%" +catch_workers_output = yes +php_admin_value[error_log] = /var/log/php85/$pool.error.log +php_admin_flag[log_errors] = on + +php_admin_value[disable_functions] = exec,passthru,shell_exec,system +php_admin_flag[allow_url_fopen] = off + +pm = dynamic +pm.max_children = 20 +pm.start_servers = 4 +pm.min_spare_servers = 2 +pm.max_spare_servers = 8 +pm.max_requests = 500 diff --git a/roles/ln/files/public/milehouse.html b/roles/ln/files/public/milehouse.html new file mode 100644 index 0000000..34f796c --- /dev/null +++ b/roles/ln/files/public/milehouse.html @@ -0,0 +1 @@ + diff --git a/roles/ln/tasks/main.yml b/roles/ln/tasks/main.yml new file mode 100644 index 0000000..f600c21 --- /dev/null +++ b/roles/ln/tasks/main.yml @@ -0,0 +1,7 @@ +- name: Copy public files + ansible.builtin.copy: + src: public/ + dest: "{{ services.ln.root }}/" + owner: joeac.net + group: joeac.net + mode: "664" diff --git a/roles/mox/files/adminpasswd b/roles/mox/files/adminpasswd new file mode 100644 index 0000000..e8860ad --- /dev/null +++ b/roles/mox/files/adminpasswd @@ -0,0 +1,8 @@ +$ANSIBLE_VAULT;1.2;AES256;ansible +66376261663665306637396639623861613532343834303939396161326631633866383139393736 +3032383031653636396531666262623938613865303961610a323733383037303265663232616539 +63663563336261633766393039333732316330643062333134353435623033356339396532363666 +3037313035343864310a343830356235656131316661353238343233656536343332333934616439 +32313166363766623263303062343763613135643734616361363332646366383039353937303630 +36623339643662616561613064303939323164663962386435326435373835376636643632613664 +636235336261666438643162653036303233 diff --git a/roles/mox/files/dkim/2026a._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem b/roles/mox/files/dkim/2026a._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem new file mode 100644 index 0000000..41a9fc8 --- /dev/null +++ b/roles/mox/files/dkim/2026a._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem @@ -0,0 +1,96 @@ +$ANSIBLE_VAULT;1.2;AES256;ansible +35663562356239663038353735376163663261373336333064383332646136366532353736653763 +6562353834343931333635356532313631306266333432640a396434313839376161653837343930 +64623264343431373164386536666562373139333064656531653166396530363138343134643737 +3035313064333263360a376531386230346435383964313966636638313439323865393434393838 +65643130353631386261616432353766663239323836363936633030303831346433343738306534 +66613931323832636562333931333936643235333263663161346666336466316163613339393662 +62363966363434646534363865356162633438393466386330313233333537623837623636393239 +65306537353238326163393931383762313764656239326335623730356663386337396130356332 +61303864393637373861303835346661326334396561623939663966303632376665396161346462 +38653862303664316231643861623661333734343933646635623334616231313563636331396235 +33613037396130633939633062666433323438313861376365663234363461313339663061373562 +63303666633134623637383634663161326338383263613061313762663236653761333662326331 +33336132623336353933396637633233613666326239366536613934353136373634633965373633 +37356562633038303162363433313466356534343437303263633764336562666431303539383162 +31393237626130643066383266613735343861326564613933323433633631646630326232636661 +35363931613261326635353666646666643661623539303962326130303163636665383035356232 +65613665306663653962353761393962343236313530303365633132356562393938383034306563 +66363334363862643937323532323734616132376264333936636438333438643464653163323736 +65373639313631656562313362316366616563376236653364636630623735303034333961363466 +38643962366563656439663735613766333237346364336465306162303463316261656565346133 +32343431306534316662366537323430356635633832353163623936353361346561386137393038 +35646563336432396432376338626438303866393033663963663262663137396639626335386363 +62396139366163366539386134396236363237373831616632363930316266383835366230393538 +64336362646136663962393834333539656362343730366335333535363137366138636234383235 +35616537363033343430343663346163646134316265346462313162633334333866646137623139 +31346137636430646136383437666532646361356364333139623837366435363932613461373761 +35656131623932346461346431353364633735333365363133646232383736366134383930666538 +36373761316533666334396435373430396536346433316139386635353562666438316536326166 +39656166333830353338623732656532383936353464366130616365653138656239663461313937 +37663737313635353861363138326232663130353933653939656266393536656432316632623463 +34643630336264656231623966356238633336356632636437303133633338336663396663656634 +65313063663334356139353666373434353139356536336331376138613736346435303732376535 +34666639303965363364653165396530323766326135393665633230386566626364633166343132 +66316262326238666335653637363135363461623936333730643162633461343530323032613563 +38666665313633333362666163653334373337373634303632373932326234373464613335356661 +34373561633063356236316664316339366261613966386466323932343661363432636632393736 +32346534353835333436613134393065646334326461306333343439643662343433323062363634 +34326466366636336262633339646564613432373138643961663636313531323233373036393038 +64323164643664616561653931343765396263313931343063643430663735663936373637663933 +33613636336138363064313064346437643334616435326533343339303130653837376232656530 +31633266626264353061376235613061313430383238316537356637346465363730336438373061 +65373935636433643838373361333738663062333330336463343436616235376266666331383633 +62643262333331306633323432353531366137666336383261333563393436336163666363386539 +36653264663665313333326462653139626666613736666633333065653738633866323962303862 +37343662613736376333383964393366303762646430636263623161303261626465353563306561 +37386432393963643932306630643635333439366164653062616665663366613830333836383433 +37643436323439363938656462383639616363323135396435623439656230643961396561313034 +61353837376261326361373838633334356430383235343262313736376234633165393766383137 +31663065646135633133343939323532616435353061383462356137386335313763386661366636 +34633361623237326132386266616265353738303961336366396334356561633232343035326433 +66303431393932653635653131643534653031396338336662343730616235376338663335626433 +61316137396662636234653638663739633033643563363633363664356637613464303032363466 +38313732626637366564336634316534376364663439356431633663323738346234336632383436 +32616133363665613535663764373330623335653663636563356135346537363332383365383133 +65393234316166623364313463363035386231363238653434613665643961613836616436303163 +34323238313762323937313937386330353962353730366231383462353764613633643663626461 +64363437343333343462646664656636306536326361613063303333373035636239666361393964 +35386164353562396664613936633336323464336562303030356239303335353361326132646534 +30356532313938383632343436373262396561336533396435653236316661356234356664616135 +37353162623538373063656435396563333438376438653266653062626536383763643632643830 +64383634663366353433386332643932326636313765386434343039643931336264636264383162 +31316339333138623838663465653463363565326439366663653765346532396234326166326230 +38343033393664346564363565393637336165393331303964343030656538363630623432313231 +30363131653131613439346362653034383938633933613661333830346633376630623765313736 +66386537376461343030343161313735386233353030306466333136376432633437656432656534 +33613537613739636433613764633030326135353362333332306432646531663162373536633163 +36616135303766353430393933306630353338326163666632383939356334336536323637356465 +39363430333761343931663437383136643331663931623733623538636233633031653135343264 +64623336626334306137303530656264666234353333663230386564313330656663373966383735 +61303634336333656463643436626635363834633465383965306238366262626334643661333637 +63366234396230333432663264323738303062653066653830363630383236376531373235373532 +35383733316465353537653239623936613866653538363164613534646463366464333937323339 +31623036373736616530363138616338623836373939343231613164646337326132653935333636 +61326331333338333161653830356539613164646235333033376530623938616135393631653362 +39653564323139386139613566656538646265386463396232393063386265353931653366653038 +63346534363034333264393631356534303535613564343836623139356433326333646533373162 +63343064356164336333393863666636316639356132303964363834393163373730386539303432 +62323837383466396630353037346231353164333633323330643332313933623366396463646163 +31346131663734383363386437363430316339346363336466383265356663633162323434656534 +35326131333435383065623039303732636334313339316139383031663632323162663830613463 +36333931386339393230633236333661666639383131346165356535666236333831353837386634 +63396264353438326363653563333039636331633230656664666233336633653566333963353038 +61393463646631656236376432643762393030383836356466363563313034623563356162353331 +61633661616133623466666236646330633436663566646531616163393861653565663032306230 +39353333623831313434373136353336636331363861316133346366353633666461366536393236 +31653530313439383133353466666133356433653561363937376434363030356239633830303261 +38353965623337633134636538376235326435323032643739656631333130626335386436316531 +62366232326663623939623132666139333334306334383432366435643337373861373262656237 +31336661623362376162356161313731393733626334613966656466633264333530323961386135 +64306164363763663835363938326437346265613833373562383561626265353766633264623631 +38613034393830383533323236346363396334626332376135326265303364313137343936373233 +39383233343061396631613462323366313261393437313931313766616136333530396534343339 +30346563343839626633366261343931316237306161396135336235643062623638643862346132 +66376639393664613661656439366530353536366232376666333331356637326539616263306237 +313264333731366631643230306130616162 diff --git a/roles/mox/files/dkim/2026b._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem b/roles/mox/files/dkim/2026b._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem new file mode 100644 index 0000000..71b0b52 --- /dev/null +++ b/roles/mox/files/dkim/2026b._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem @@ -0,0 +1,96 @@ +$ANSIBLE_VAULT;1.2;AES256;ansible +31353563393530386631393261306630643364316236373263316265316638626632303637633331 +6139343730333665626138393431336632316234353236370a633130313632396261656264323165 +37393339353966633633643533343833653835386161613566353335626231633663306633356237 +3465376136376135650a623561383831383530353737336339653532633866613233356564333361 +62613166656435336364316530643165313362646639383964313361333936646162376166663431 +31346433343636316163323866666536326464376461316133653732323164626433343435303765 +66646633393138396366393034303866636137636230303663346633333638363563623364663736 +39653036613837616236333432353461376130393739333533613139323132313833376261613638 +31366437643065303563306539373134313262383639613439613363313962383837633135343032 +30636637323564356139653430366666353935343162383831633136633331383830363137393830 +32336432643831353962313736633932623631616365666633663435636639633931643736363736 +34356665636164633266383965366262323463306430363864656532303032376464396239636133 +35393632356136326632373039393432346362623365653861653233623336356132326531646237 +61326264313766356164333064623539333231356466376661613537653438313133323664396636 +30626138656634313138363931366232393832363661373564613266663362383837633331346334 +62346537663534646538323432626134643964316639393731346463626230386232656434653666 +39306632393961336434663466646264353066333063666163636164303935653336383635623138 +35643163666535613035323832353065386131323930356430636539383935343566333561316530 +63353338623461313130376337623239633133333039633032346165366134623837666433366364 +31623733343365393363313866316233663338663833613534346566383130643233663335633637 +30303433336561343133356230333962333834343131616665643066306232636266633437363064 +65346166313134336338316336643361316362386261616266636466633232663439306463383666 +61396438303664646336646132663531393866346130383131326639333462376364663563626437 +62313237623761623862643266366161633735356130623561326337626337386566613637626630 +38333861306536393932363734303465396138323366616262653462393331663463656138306535 +31313831326465356434343334366662633234343662376334653961653632643464623638303739 +32383539346439346632353265333038633836393131666564383836393565643166306262333733 +64323062393734633038666465633265393430303161623134626566646135326530323037326435 +63313832623064336564353030343533353064303237623161393437336339363063656533393631 +38356561343333663665303561323666343566303666323736663061613062356430313362366664 +61373837373861636634373239633931396266653637643162643534313236376463353634663833 +61373430613865373864633736356331633363646236626536666262343635396435646265306530 +39663435646265613865376364643336653139313833666632356664613837643932386235376637 +31316465363964303732663361343631663564336163306337663339323235376436623964366231 +38636132626438653664343733363635616331663833376239393337633164353365633166353765 +63346337653538666330636366646464393934376663656563336665306461363132343536366336 +66613238636665323266373138383335363961616637633961663730386133333933653437393161 +37336331343130656338366331663565653661653937353331386139396163303864646139323263 +64383564653935383364666563346563373831323764306231363034333935383061383535366131 +62393634663762373037356433643938373332333239646537343361653432623933663162353439 +30303037396535653238366439316532626235653230316362333266383738326136376239626564 +34373238353334616632343030323463363539383164353161633330636165366339333864616366 +61633966356230313265383363323532316663333665633934353535623630633961383438656163 +64373431663836663731653438653661666333363039626334343964356433323236386630316261 +35653266343436336235323065363933643438393065323865323639313633333563393763313938 +34383536613264633739343764633938623333646635623031613762626664656633636163383139 +65326138643635613336356639623538323334316564623761633630316531323061363237313937 +32323838646439653336613061643330386432323239653365623762616163623837646637663532 +62326337373534383238323937626339613464303736333566613035653861323239386564336264 +62616134363235653761643632336662323765383233313831663264323965313735396632366665 +31646632646335333336643161393263366433383463393635353834636534326534396363383635 +61633839333439323665623961643839326166393762363439656537363938623564643663646262 +39316435656337666636363531653166343934613666353634393831393034636335376135333462 +34346131323163643036353362346532316662323639316638323463666565373032633932366130 +66636438633634663261643738633437623939613766376230666361386266626261376538343234 +32646433653136326234363434613236326338336339376431353764323739613861366163613764 +39613365393363643864623661316536313539613230626137376435336338323439333537346431 +34373039663165323765353630656531396465343234616462326335326265343366396632316138 +62663237383162646661633461383763613534646131383738373036613964613061343362343839 +66336161366136383463313266376330376239323763663361323761633339333861346162326563 +32643133383736356533306565356634393730303032643466636438386137366265376537613734 +65633538383066383832306436333533373235313235353033366330643236363238343563333361 +62376430623264376564326535663464626235363437613436313564626662313236393037363966 +63653537323734383466303464346362343235356136323463386539636664636561633565323963 +35383237386636343665343432373336633963353936663930626239353562653065666639333434 +63666536303238373337383339616438366466353639346534393163666139376632373936656630 +38646261393936336539363831626539313564646266653635303066653333303134663038363938 +32643339616230323631323836366138643933326162333262663663386136336563383431643464 +34323936366261626336323064636136333430326639373263363333376361376331366133373161 +30613539316663643133346336306362376636663561353138336362333665643461666137306663 +33353664636133303436393730356636396662633632353861363065373539353635343238666437 +32616562616234326137336433343565663761653834316630343236376237333461306233396435 +34323933643630643761363166336237306431633734623466643538363462393163306331386437 +30633835336134373464333563616438663833326565363235643538326239323933366362636666 +61316264643264393731353261616233633265336530343834643835393532643964336430613461 +36366136636266376530626230376135653733333538666639373066353361313064646132656265 +38613530343838393839303132663031346461313235303665386335633332303232353363303136 +32323430653035336664656233643233656431333139633730333466663138363732346465633861 +33326234353062356161373930346430393837323130643066633863316362313339383932326561 +63653930356335313931383565313061313535323230653532623337376335646437396533373561 +38633134313936623935393133633933353032393232636462643665633461626562316637363031 +30336263313638626339366465646230663464616665623836353634323534363832386637643739 +63623262663038653464323733646362313532303532666234323162383764323836353931373334 +37663733306235353334663863393332626364323931323562396131613430633163356338643434 +38373732343937623938666266346230326666666565366465316462656664383361333631383665 +66656562306532633665313730323736313436626263336139386530663835396566653832643337 +30383132613334653264623032343666393530663835393330353430633036636462346666353633 +62373839336466336534363066633036393037306166613261353635303061323234353665623062 +36323834646330303665306532303038323565656265333537353830386337393536376562333930 +62366434666536353533663166663366306334333063623330653562653962346531326263363135 +38313235623539666433333737386233623136643938333766613139393964633032613664396534 +38653037373063383332626164323933346663633863353561316261333739326539626633633738 +61396235366134356461663366656162393935333261353935396635376537303738393130336339 +62636165383364653864303931643531613538303966653335653363343765383033386636303139 +396232353230313962383939636139373931 diff --git a/roles/mox/tasks/main.yml b/roles/mox/tasks/main.yml new file mode 100644 index 0000000..2e41a4c --- /dev/null +++ b/roles/mox/tasks/main.yml @@ -0,0 +1,155 @@ +- name: Add mox group + ansible.builtin.group: + name: mox + +- name: Add mox user + ansible.builtin.user: + name: mox + group: mox + groups: + - tls + append: false + +- name: Create mox configuration directory + register: mox_conf_dir + ansible.builtin.file: + path: ~mox/config/ + owner: mox + +- name: Create DKIM private keys directory + register: mox_dkim_dir + ansible.builtin.file: + path: ~mox/config/dkim + +- name: Install DKIM private key A + register: mox_dkim_a + ansible.builtin.copy: + src: dkim/2026a._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem + dest: ~mox/config/dkim/2026a._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem + mode: o-rwx + +- name: Install DKIM private key B + register: mox_dkim_b + ansible.builtin.copy: + src: dkim/2026b._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem + dest: ~mox/config/dkim/2026b._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem + mode: o-rwx + +- name: Install adminpasswd + register: mox_adminpasswd + ansible.builtin.copy: + src: adminpasswd + dest: ~mox/config/adminpasswd + mode: o-rwx + +- name: Install mox configuration + register: mox_conf + ansible.builtin.template: + src: mox.conf + dest: ~mox/config/mox.conf + mode: o-rwx + +- name: Install mox domain configuration + register: mox_domain_conf + ansible.builtin.template: + src: domains.conf + dest: ~mox/config/domains.conf + mode: o-rwx + +- name: Create mox data directory + ansible.builtin.file: + path: ~mox/data + state: directory + mode: o-rwx + +- name: Create unbound configuration directory + ansible.builtin.file: + path: /etc/unbound/unbound.conf.d + state: directory + mode: "444" + +- name: Install DNSSEC configuration + register: dnssec_conf + ansible.builtin.template: + src: dnssec.conf + dest: /etc/unbound/unbound.conf.d/dnssec.conf + +- name: Install unbound binary + community.general.apk: + name: unbound + state: present + +- name: Restart unbound service and configure to start on boot + when: dnssec_conf is changed + ansible.builtin.service: + name: unbound + enabled: true + state: restarted + +- name: Start unbound service and configure to start on boot + when: not ( dnssec_conf is changed ) + ansible.builtin.service: + name: unbound + enabled: true + state: started + +- name: Configure networking to point at unbound DNS resolver + ansible.builtin.copy: + content: "nameserver: 127.0.0.1" + dest: /etc/resolv.conf + backup: true + mode: "444" + +- name: Install mox binary + register: mox_binary + ansible.builtin.get_url: + dest: /usr/bin/mox + mode: "755" + url: "{{ mox_url }}" + +- name: Install mox service + register: mox_service + ansible.builtin.template: + src: openrc/mox + dest: /etc/init.d/mox + mode: "755" + +- name: Restart mox service and configure to start on boot + when: + mox_conf_dir is changed + or mox_dkim_dir is change + or mox_dkim_a is change + or mox_dkim_b is change + or mox_adminpasswd is change + or mox_conf is change + or mox_domain_conf is changed + or mox_binary is changed + or mox_service is changed + ansible.builtin.service: + name: mox + enabled: true + state: restarted + +- name: Start mox service and configure to start on boot + when: not ( + mox_conf_dir is changed + or mox_dkim_dir is change + or mox_dkim_a is change + or mox_dkim_b is change + or mox_adminpasswd is change + or mox_conf is change + or mox_domain_conf is changed + or mox_binary is changed + or mox_service is changed ) + ansible.builtin.service: + name: mox + enabled: true + state: started + +- name: Set account passwords + loop: "{{ email_accounts }}" + no_log: true + ansible.builtin.command: + cmd: mox setaccountpassword {{ item.name }} + stdin: "{{ item.password }}" + chdir: ~mox diff --git a/roles/mox/templates/dnssec.conf b/roles/mox/templates/dnssec.conf new file mode 100644 index 0000000..de6bf8c --- /dev/null +++ b/roles/mox/templates/dnssec.conf @@ -0,0 +1,9 @@ +server: + qname-minimisation: yes + interface: 0.0.0.0 + interface: ::0 + access-control: {{ router.lan.ipv4 }} allow + access-control: {{ router.lan.ipv6 }} allow + +remote-control: + control-enable: yes diff --git a/roles/mox/templates/domains.conf b/roles/mox/templates/domains.conf new file mode 100644 index 0000000..bccfab3 --- /dev/null +++ b/roles/mox/templates/domains.conf @@ -0,0 +1,98 @@ +Domains: + mail.joeac.net: + ClientSettingsDomain: mail.joeac.net + LocalpartCatchallSeparator: + + DKIM: + Selectors: + 2026a: + Expiration: 72h + PrivateKeyFile: dkim/2026a._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem + 2026b: + Expiration: 72h + PrivateKeyFile: dkim/2026b._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem + Sign: + - 2026a + DMARC: + Localpart: dmarcreports + Account: me + Mailbox: DMARC + MTASTS: + PolicyID: 20260705T153220 + Mode: enforce + MaxAge: 24h0m0s + MX: + - mail.joeac.net + TLSRPT: + Localpart: tlsreports + Account: me + Mailbox: TLSRPT + +Accounts: +{% for account in email_accounts %} + {{ account.name }}: + Domain: mail.joeac.net + Destinations: + {{ account.name }}@mail.joeac.net: nil + SubjectPass: + Period: 12h0m0s + RejectsMailbox: Rejects + AutomaticJunkFlags: + Enabled: true + JunkMailboxRegexp: ^(junk|spam) + NeutralMailboxRegexp: ^(inbox|neutral|postmaster|dmarc|tlsrpt|rejects) + JunkFilter: + Threshold: 0.950000 + Params: + Onegrams: true + MaxPower: 0.010000 + TopWords: 10 + IgnoreWords: 0.100000 + RareWords: 2 + NoCustomPassword: true +{% endfor %} + +MonitorDNSBLs: + - zen.spamhaus.org + - bl.spamcop.net + +WebHandlers: + +{% for subdomain in ( subdomains | selectattr("mox_handler", "eq", "static") ) %} + - + LogName: {{ subdomain.name }}.joeac.net + PathRegexp: ^/ + Compress: true + Domain: {{ subdomain.full_domain }} + WebStatic: + Root: {{ services[subdomain.service].root }} + ListFiles: {{ "directory_listing" in services[subdomain.service] and services[subdomain.service].directory_listing | lower }} + ContinueNotFound: true + + - + LogName: {{ subdomain.name }}.joeac.net + PathRegexp: ^.*[^(.html)]$ + Compress: true + Domain: {{ subdomain.full_domain }} + WebRedirect: + OrigPathRegexp: ^(.*[^(.html)])$ + ReplacePath: $1/index.html + + - + LogName: {{ subdomain.name }}.joeac.net + PathRegexp: ^/.*/index.html$ + Compress: true + Domain: {{ subdomain.full_domain }} + WebRedirect: + OrigPathRegexp: ^(.*)/index.html$ + ReplacePath: $1.html +{% endfor %} + +{% for subdomain in ( subdomains | selectattr("mox_handler", "eq", "forward") ) %} + - + LogName: {{ subdomain.name }}.joeac.net + PathRegexp: ^/ + Compress: true + Domain: {{ subdomain.full_domain }} + WebForward: + URL: http://{{ hostvars[services[subdomain.service].host].ansible_host }}:{{ services[subdomain.service].port }} +{% endfor %} diff --git a/roles/mox/templates/mox.conf b/roles/mox/templates/mox.conf new file mode 100644 index 0000000..8cafc25 --- /dev/null +++ b/roles/mox/templates/mox.conf @@ -0,0 +1,62 @@ +DataDir: ../data +LogLevel: debug +User: mox +Hostname: mail.joeac.net +AdminPasswordFile: adminpasswd +CheckUpdates: true +Listeners: + internal: + IPs: + - 127.0.0.1 + - ::1 + Hostname: localhost + MetricsHTTP: + Enabled: true + public: + IPs: +{% for interface in ansible_facts.interfaces | reject("eq", "lo") %} + - {{ ansible_facts[interface].ipv4.address }} +{% for ipv6 in ansible_facts[interface].ipv6 | selectattr("scope", "eq", "global") %} + - {{ ipv6.address }} +{% endfor %} +{% endfor %} + NATIPs: + - {{ router.wan.ipv4 }} + - {{ ansible_facts.default_ipv6.address }} + AccountHTTPS: + Enabled: true + AdminHTTPS: + Enabled: true + WebmailHTTPS: + Enabled: true + WebAPIHTTPS: + Enabled: true + AutoconfigHTTPS: + Enabled: true + MTASTSHTTPS: + Enabled: true + WebserverHTTP: + Enabled: true + WebserverHTTPS: + Enabled: true + TLS: + KeyCerts: +{% for subdomain in ( subdomains | map(attribute='name') ) %} + - + CertFile: /etc/letsencrypt/live/{{ subdomain }}.joeac.net/fullchain.pem + KeyFile: /etc/letsencrypt/live/{{ subdomain }}.joeac.net/privkey.pem +{% endfor %} + SMTP: + Enabled: true + RequireSTARTTLS: true + Submissions: + Enabled: true + IMAPS: + Enabled: true +Postmaster: + Account: me + Mailbox: Postmaster +HostTLSRPT: + Account: me + Mailbox: TLSRPT + Localpart: tlsreports diff --git a/roles/mox/templates/openrc/mox b/roles/mox/templates/openrc/mox new file mode 100644 index 0000000..e04d96a --- /dev/null +++ b/roles/mox/templates/openrc/mox @@ -0,0 +1,77 @@ +#!/sbin/openrc-run + +description="Run mox email server" +extra_commands="configtest" +description_configtest="Check configuration via \"mox config test\"" + +configtest() +{ + if ! [ -f ~mox/config/mox.conf ]; then + eerror "The config file ~mox/config/mox.conf does not exist!" + return 1 + fi + cd ~mox + if mox config test >&/dev/null; then + einfo "config: ok" + else + eerror "config: error" + return 1 + fi +} + +depend() +{ + need net unbound +} + +start_pre() { + ebegin "Waiting for global IPv6 address {{ ansible_facts.default_ipv6.address }} to be ready" + + local i=0 + while [ "$i" -lt 30 ]; do + if ip -6 addr show scope global | grep -q "{{ ansible_facts.default_ipv6.address }}" && \ + ! ip -6 addr show scope global | grep -q "{{ ansible_facts.default_ipv6.address }}.*\btentative\b" + then + eend 0 + return 0 + fi + sleep 2 + i=$((i + 2)) + done + + eend 1 "Global IPv6 address {{ ansible_facts.default_ipv6.address }} not ready after 30 seconds" + return 1 +} + +start() +{ + configtest || return 1 + ebegin "Starting mox" + start-stop-daemon --start \ + --exec /usr/bin/mox \ + --background \ + --chdir ~mox \ + --stdout /var/log/mox.log \ + --stderr /var/log/mox.log \ + --make-pidfile \ + --pidfile /run/mox.pid \ + -- serve + eend $? +} + +stop() +{ + ebegin "Stopping mox" + start-stop-daemon --stop \ + --exec /usr/bin/mox \ + --pidfile /run/mox.pid + eend $? +} + +restart() +{ + ebegin "Restarting mox" + svc_stop + svc_start + eend $? +} diff --git a/roles/tls/files/certbot.fact b/roles/tls/files/certbot.fact new file mode 100644 index 0000000..baa43bb --- /dev/null +++ b/roles/tls/files/certbot.fact @@ -0,0 +1,35 @@ +#!/bin/sh + +certbot_certificates_output="$(certbot certificates 2>/dev/null)" +certificates="$(echo "$certbot_certificates_output" | grep "Certificate Name:" | sed "s/.*Certificate Name: *//g")" +all_certificates="" +valid_certificates="" +invalid_certificates="" +for certificate in $certificates +do + all_certificates="$all_certificates $certificate" + if [ -n "$(echo "$certbot_certificates_output" | grep "Expiry Date:" | grep -E "VALID:")" ] + then + valid_certificates="$valid_certificates $certificate" + else + invalid_certificates="$invalid_certificates $certificate" + fi +done + +jsonify() +{ + if [ -z "$1" ] + then + echo "[]" + else + echo "[\"$1\"]" | sed "s/ /\", \"/g" + fi +} + +cat < 0 + ansible.builtin.shell: + certbot renew --non-interactive + +- name: Install OVH DNS credentials + ansible.builtin.copy: + src: ovh-dns-credentials.ini + dest: /etc/ovh-dns-credentials.ini + mode: "400" + +- name: Install missing TLS certificates + loop: "{{ subdomains }}" + when: not ( ( item.name ~ ".joeac.net" ) in ansible_local.certbot.certificates ) + ansible.builtin.shell: + certbot certonly \ + --dns-ovh \ + --dns-ovh-credentials /etc/ovh-dns-credentials.ini \ + --cert-name {{ item.name }}.joeac.net \ + --domain {{ item.full_domain }} \ + --non-interactive + +- name: Add joeac.net group + ansible.builtin.group: + name: joeac.net + +- name: Add tls group + ansible.builtin.group: + name: tls + +- name: Add joeac.net user + ansible.builtin.user: + name: joeac.net + group: joeac.net + groups: + - tls + append: false + +- name: Give ownership of /etc/letsencrypt to joeac.net:tls + ansible.builtin.file: + path: /etc/letsencrypt + owner: joeac.net + group: tls + mode: g+rwx + recurse: true + +- name: Install daily crontab to renew TLS certificates + ansible.builtin.cron: + special_time: daily + name: daily crontab to renew all TLS certificates near to expiry + job: + certbot renew --non-interactive; chown -R joeac.net:tls /etc/letsencrypt diff --git a/roles/vaultwarden/tasks/main.yml b/roles/vaultwarden/tasks/main.yml new file mode 100644 index 0000000..6533642 --- /dev/null +++ b/roles/vaultwarden/tasks/main.yml @@ -0,0 +1,26 @@ +- name: Install podman and jq + community.general.apk: + name: + - podman + - jq + +- name: Install vaultwarden service + register: vaultwarden_service + ansible.builtin.template: + src: openrc/vaultwarden + dest: /etc/init.d/vaultwarden + mode: "755" + +- name: Restart vaultwarden service and configure to start on boot + when: vaultwarden_service is changed + ansible.builtin.service: + name: vaultwarden + enabled: true + state: restarted + +- name: Start vaultwarden service and configure to start on boot + when: not ( vaultwarden_service is changed ) + ansible.builtin.service: + name: vaultwarden + enabled: true + state: started diff --git a/roles/vaultwarden/templates/openrc/vaultwarden b/roles/vaultwarden/templates/openrc/vaultwarden new file mode 100755 index 0000000..cf718a5 --- /dev/null +++ b/roles/vaultwarden/templates/openrc/vaultwarden @@ -0,0 +1,46 @@ +#!/sbin/openrc-run + +description="Run vaultwarden container image" + +start() { + ebegin "Starting vaultwarden" + podman create \ + --name=joeac.net-vaultwarden \ + --env DOMAIN=https://pwd.joeac.net \ + --volume /var/vaultwarden/data:/data/ \ + --publish 9000:80 \ + --restart unless-stopped \ + docker.io/vaultwarden/server:latest + podman start joeac.net-vaultwarden + eend $? +} + +stop() { + ebegin "Stopping vaultwarden" + podman rm --force joeac.net-vaultwarden + eend $? +} + +status() { + if ! $(podman container exists joeac.net-vaultwarden); then + einfo "status: no container found called joeac.net-vaultwarden" + exit 3 + fi + state="$(podman inspect joeac.net-vaultwarden | jq .[0].State)" + if [ "$(echo "$state" | jq .Paused)" == "true" ]; then + einfo "status: paused" + exit 0 + elif [ "$(echo "$state" | jq .Restarting)" == "true" ]; then + einfo "status: restarting" + exit 0 + elif [ "$(echo "$state" | jq .Running)" == "true" ]; then + einfo "status: running" + exit 0 + elif [ "$(echo "$state" | jq .Error)" == "0" ]; then + einfo "status: stopped" + exit 3 + else + einfo "status: crashed" + exit 3 + fi +} -- cgit v1.2.3