This commit is contained in:
bee
2026-08-15 19:18:59 +02:00
parent 59aa8a7bcf
commit c63db540e8
32 changed files with 767 additions and 64 deletions
+18 -1
View File
@@ -12,7 +12,9 @@
tasks:
- name: Install rsync
ansible.builtin.apt:
name: rsync
name:
- rsync
- sqlite3
state: present
- name: Create backup directory
@@ -33,6 +35,12 @@
dest: /opt/backup/backupWeekly.sh
mode: '0700'
- name: Copy restore test
ansible.builtin.copy:
src: restoreTest.sh
dest: /opt/backup/restoreTest.sh
mode: '0700'
- name: Copy passphrase
ansible.builtin.template:
src: passphrase.txt.j2
@@ -61,3 +69,12 @@
hour: '5'
weekday: '0'
job: /opt/backup/backupWeekly.sh >> /var/log/beepi-backup.log 2>&1
- name: Schedule weekly restore test
ansible.builtin.cron:
name: beepi restore test
user: root
minute: '0'
hour: '6'
weekday: '0'
job: /opt/backup/restoreTest.sh >> /var/log/beepi-backup.log 2>&1
+15 -1
View File
@@ -28,6 +28,7 @@ restore() {
systemctl --user --machine gitea@.host start gitea-db.service gitea-server.service || true
systemctl --user --machine immich@.host start immich-db.service immich-redis.service immich-server.service || true
systemctl --user --machine vaultwarden@.host start vaultwarden.service || true
rm -f /home/immich/systemd-immich-*.tar
}
@@ -69,4 +70,17 @@ systemctl --user --machine immich@.host start immich-db.service immich-redis.ser
mkdir -p /opt/backup/beeserver/immich/
mv /home/immich/systemd-immich-*.tar /opt/backup/beeserver/immich/
tar -czf - /opt/backup/beeserver | gpg --batch --passphrase-file /opt/backup/passphrase.txt -c -o /home/beeshare/raid/serverBackup/beeserver_daily_${now}.tar.gz.gpg
systemctl --user --machine vaultwarden@.host stop vaultwarden.service
rsync -aH /home/vaultwarden/data /opt/backup/beeserver/vaultwarden
systemctl --user --machine vaultwarden@.host start vaultwarden.service
archive=/home/beeshare/raid/serverBackup/beeserver_daily_${now}.tar.gz.gpg
tar -czf - /opt/backup/beeserver | gpg --batch --passphrase-file /opt/backup/passphrase.txt -c -o ${archive}
if ! gpg --batch --passphrase-file /opt/backup/passphrase.txt -d ${archive} | tar -tzf - >/dev/null
then
echo "[${now}] verify failed, discarding ${archive}"
rm -f ${archive}
exit 1
fi
+9 -3
View File
@@ -4,12 +4,18 @@ set -euo pipefail
now=$(date +"%Y%m%d%H%M")
tar -czf - /opt/backup/beeserver | gpg --batch --passphrase-file /opt/backup/passphrase.txt -c -o /home/beeshare/raid/serverBackup/beeserver_weekly_${now}.tar.gz.gpg
archive=/home/beeshare/raid/serverBackup/beeserver_weekly_${now}.tar.gz.gpg
if [ -s /home/beeshare/raid/serverBackup/beeserver_weekly_${now}.tar.gz.gpg ]
tar -czf - /opt/backup/beeserver | gpg --batch --passphrase-file /opt/backup/passphrase.txt -c -o ${archive}
if ! gpg --batch --passphrase-file /opt/backup/passphrase.txt -d ${archive} | tar -tzf - >/dev/null
then
rm -rf /opt/backup/beeserver
echo "[${now}] verify failed, discarding ${archive} and keeping staging"
rm -f ${archive}
exit 1
fi
rm -rf /opt/backup/beeserver
find /home/beeshare/raid/serverBackup -maxdepth 1 -name 'beeserver_weekly_*.tar.gz.gpg' -mtime +30 -delete
find /home/beeshare/raid/serverBackup -maxdepth 1 -name 'beeserver_daily_*.tar.gz.gpg' -mtime +7 -delete
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
set -euo pipefail
now=$(date +"%Y%m%d%H%M")
backup_dir=/home/beeshare/raid/serverBackup
image=docker.io/vaultwarden/server:1.37.1
container=vaultwarden-restoretest
port=18222
work=$(mktemp -d)
cleanup() {
podman rm -f ${container} >/dev/null 2>&1 || true
rm -rf "${work}"
}
trap cleanup EXIT
latest=$(ls -1t ${backup_dir}/beeserver_*.tar.gz.gpg | head -1)
echo "[${now}] restore test using ${latest}"
gpg --batch --passphrase-file /opt/backup/passphrase.txt -d "${latest}" \
| tar -xzf - -C "${work}" opt/backup/beeserver/vaultwarden
data="${work}/opt/backup/beeserver/vaultwarden/data"
[[ -s "${data}/db.sqlite3" ]]
[[ -s "${data}/rsa_key.pem" ]]
echo " db and rsa_key present"
integrity=$(sqlite3 "${data}/db.sqlite3" "PRAGMA integrity_check;")
[[ "${integrity}" == "ok" ]]
echo " integrity_check ok"
users=$(sqlite3 "${data}/db.sqlite3" "select count(*) from users;")
[[ "${users}" -ge 1 ]]
echo " users ${users}"
email=$(sqlite3 "${data}/db.sqlite3" "select email from users limit 1;")
podman run -d --rm --name ${container} -p 127.0.0.1:${port}:80 -v "${data}":/data ${image} >/dev/null
for _ in $(seq 60)
do
if curl -sf http://127.0.0.1:${port}/api/config >/dev/null 2>&1
then
break
fi
sleep 1
done
config=$(curl -sf http://127.0.0.1:${port}/api/config)
[[ "${config}" == *'"version"'* ]]
echo " api/config ok"
prelogin=$(curl -sf -X POST http://127.0.0.1:${port}/identity/accounts/prelogin \
-H 'Content-Type: application/json' \
-d "{\"email\":\"${email}\"}")
[[ "${prelogin}" == *[Kk]df* ]]
echo " prelogin ok for ${email}"
echo "[${now}] restore test PASSED"
+3 -3
View File
@@ -97,14 +97,14 @@
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ user_info.uid }}/bus"
ansible.builtin.systemd:
name: gitea-server.service
state: started
state: restarted
scope: user
- name: Debug
ansible.builtin.debug:
msg:
- "Service status: systemctl --user --machine {{ user_info.name }}@.host status gitea-server"
- "Service status: sudo systemctl --user --machine {{ user_info.name }}@.host status gitea-server"
- "Read logs: sudo journalctl _UID={{ user_info.uid }} -n 200 --no-pager"
- "Shell: machinectl shell {{ user_info.name }}@.host /bin/bash"
- "Shell: sudo machinectl shell {{ user_info.name }}@.host /bin/bash"
tags:
- always
+97
View File
@@ -0,0 +1,97 @@
- name: Hetzner
hosts: beepi
become: true
vars:
user: hetzner
user_home: /home/hetzner
hetzner_raid_mount: /home/beeshare/raid
hetzner_port: 23
hetzner_paths:
- src: /home/beeshare/raid/serverBackup
dest: ./beepi
- src: /home/beeshare/raid/foundry/backup
dest: ./foundry
# Fill in after creating the storage box sub-account, then re-run
hetzner_host: "u651287.your-storagebox.de"
hetzner_user: "u651287"
tasks:
- name: Install rsync
ansible.builtin.apt:
name: rsync
state: present
- name: Create user
ansible.builtin.user:
name: "{{ user }}"
home: "{{ user_home }}"
shell: /usr/sbin/nologin
create_home: true
- name: Create ssh directory
ansible.builtin.file:
path: "{{ user_home }}/.ssh"
state: directory
owner: "{{ user }}"
group: "{{ user }}"
mode: '0700'
- name: Generate ssh key
ansible.builtin.command:
cmd: "ssh-keygen -t ed25519 -N '' -C beepi-hetzner -f {{ user_home }}/.ssh/id_ed25519"
creates: "{{ user_home }}/.ssh/id_ed25519"
- name: Set key ownership
ansible.builtin.file:
path: "{{ user_home }}/.ssh/{{ item.name }}"
owner: "{{ user }}"
group: "{{ user }}"
mode: "{{ item.mode }}"
loop:
- name: id_ed25519
mode: '0600'
- name: id_ed25519.pub
mode: '0644'
- name: Scan storage box host key
ansible.builtin.command:
cmd: "ssh-keyscan -p {{ hetzner_port }} {{ hetzner_host }}"
register: hetzner_hostkey
changed_when: false
when: hetzner_host | length > 0
- name: Pin known hosts
ansible.builtin.copy:
dest: "{{ user_home }}/.ssh/known_hosts"
content: "{{ hetzner_hostkey.stdout }}\n"
owner: "{{ user }}"
group: "{{ user }}"
mode: '0600'
when: hetzner_host | length > 0
- name: Copy sync script
ansible.builtin.template:
src: hetznerSync.sh.j2
dest: /opt/backup/hetznerSync.sh
mode: '0700'
when: hetzner_host | length > 0
- name: Schedule daily sync
ansible.builtin.cron:
name: beepi hetzner sync
user: root
minute: '0'
hour: '7'
job: /opt/backup/hetznerSync.sh >> /var/log/beepi-backup.log 2>&1
when: hetzner_host | length > 0
- name: Debug
ansible.builtin.debug:
msg:
- "Test manually: sudo /opt/backup/hetznerSync.sh"
- "Read logs: sudo tail -50 /var/log/beepi-backup.log"
tags:
- always
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
set -euo pipefail
now=$(date +"%Y%m%d%H%M")
if ! mountpoint -q {{ hetzner_raid_mount }}
then
echo "[${now}] {{ hetzner_raid_mount }} not mounted, refusing to sync"
exit 1
fi
sync_path() {
local src=$1
local dest=$2
if [ -z "$(ls -A ${src} 2>/dev/null)" ]
then
echo "[${now}] ${src} empty, refusing to sync"
exit 1
fi
echo "[${now}] syncing ${src} to ${dest}"
sudo -u {{ user }} rsync -a --delete --partial \
-e "ssh -p {{ hetzner_port }} -i {{ user_home }}/.ssh/id_ed25519 -o UserKnownHostsFile={{ user_home }}/.ssh/known_hosts -o StrictHostKeyChecking=yes" \
${src}/ \
{{ hetzner_user }}@{{ hetzner_host }}:${dest}/
}
echo "[${now}] offsite sync start"
{% for path in hetzner_paths %}
sync_path {{ path.src }} {{ path.dest }}
{% endfor %}
echo "[${now}] offsite sync done"
+1 -1
View File
@@ -5,7 +5,7 @@ After=immich-db.service immich-redis.service
[Container]
ContainerName=immich-server
Image=ghcr.io/immich-app/immich-server:release
Image=ghcr.io/immich-app/immich-server:v3.1.0
Network=immich.network
PublishPort=127.0.0.1:2283:2283
Volume=immich-upload.volume:/usr/src/app/upload
+3 -3
View File
@@ -107,12 +107,12 @@
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ immich_user_info.uid }}/bus"
ansible.builtin.systemd:
name: immich-server.service
state: started
state: restarted
scope: user
- name: Debug
ansible.builtin.debug:
msg:
- "Service status: systemctl --user --machine {{ immich_user_info.name }}@.host status immich-server"
- "Service status: sudo systemctl --user --machine {{ immich_user_info.name }}@.host status immich-server"
- "Read logs: sudo journalctl _UID={{ immich_user_info.uid }} -n 200 --no-pager"
- "Shell: machinectl shell {{ immich_user_info.name }}@.host /bin/bash"
- "Shell: sudo machinectl shell {{ immich_user_info.name }}@.host /bin/bash"
@@ -0,0 +1,96 @@
- name: Ingress-hardening
hosts: animeistrash
become: true
tasks:
- name: Check for an authorized key
become: false
ansible.builtin.stat:
path: "~/.ssh/authorized_keys"
register: authorized_keys
- name: Refuse to lock out
ansible.builtin.assert:
that:
- authorized_keys.stat.exists
- authorized_keys.stat.size > 0
fail_msg: "No authorized_keys for the connecting user, refusing to disable password auth"
- name: Install packages
ansible.builtin.apt:
name:
- unattended-upgrades
- needrestart
state: present
- name: Harden sshd
ansible.builtin.copy:
dest: /etc/ssh/sshd_config.d/00-hardening.conf
content: |
PermitRootLogin prohibit-password
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
AllowAgentForwarding no
owner: root
group: root
mode: '0644'
validate: /usr/sbin/sshd -t -f %s
notify: Reload sshd
- name: Enable unattended upgrades
ansible.builtin.copy:
dest: /etc/apt/apt.conf.d/20auto-upgrades
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
owner: root
group: root
mode: '0644'
- name: Configure unattended upgrades
ansible.builtin.copy:
dest: /etc/apt/apt.conf.d/52unattended-upgrades-local
content: |
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
owner: root
group: root
mode: '0644'
- name: Restart services after library upgrades
ansible.builtin.copy:
dest: /etc/needrestart/conf.d/99-auto.conf
content: "$nrconf{restart} = 'a';\n"
owner: root
group: root
mode: '0644'
- name: Enable apt timers
ansible.builtin.service:
name: "{{ item }}"
state: started
enabled: true
loop:
- apt-daily.timer
- apt-daily-upgrade.timer
- name: Debug
ansible.builtin.debug:
msg:
- "Effective sshd config: sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication|maxauthtries'"
- "Dry-run upgrades: sudo unattended-upgrade --dry-run --debug"
- "Read logs: sudo tail -50 /var/log/unattended-upgrades/unattended-upgrades.log"
tags:
- always
handlers:
- name: Reload sshd
ansible.builtin.service:
name: ssh
state: reloaded
+35 -1
View File
@@ -1,12 +1,30 @@
- name: Letsencrypt
hosts: beepi
become: true
vars:
njalla_api_token: !vault |
$ANSIBLE_VAULT;1.1;AES256
65666463616462633533663835383164353566326437333336616266333864333465613732346633
6534633837373937393563633966343738316562653930610a346461613236653266303237306663
66663234313832373939346539386633316465373861663862646537303662313564363038303164
3865666132613130380a623737663837343438333837626338346565653735613435313164373663
33316231366334623461303263386136656234613133373361353262346366636133396134663838
3733353465643235653337306434396366623935303862633737
tasks:
- name: Install Certbot
ansible.builtin.apt:
name: python3-certbot-nginx
name:
- python3-certbot-nginx
- python3-pip
state: present
- name: Install Njalla DNS plugin
ansible.builtin.pip:
name: certbot-dns-njalla
state: present
extra_args: --break-system-packages
- name: Request root certificates
changed_when: true
ansible.builtin.command: "certbot certonly --nginx -m secretbumblebee@proton.me --agree-tos -n --domains {{ item }}"
@@ -19,6 +37,22 @@
- gallery.secretbee.buzz
- ott.secretbee.buzz
- name: Copy njalla credentials
ansible.builtin.copy:
dest: /etc/letsencrypt/njalla.ini
content: "dns_njalla_token={{ njalla_api_token }}\n"
owner: root
group: root
mode: '0600'
- name: Request vault certificate
changed_when: true
ansible.builtin.command: >-
certbot certonly -a dns-njalla
--dns-njalla-credentials /etc/letsencrypt/njalla.ini
-m secretbumblebee@proton.me --agree-tos -n
--domains vault.secretbee.buzz
- name: Change permission on live
ansible.builtin.file:
path: /etc/letsencrypt/live/
+1 -1
View File
@@ -71,7 +71,7 @@ services:
restart: always
podman-exporter:
image: quay.io/navidys/prometheus-podman-exporter:1.21.2
image: quay.io/navidys/prometheus-podman-exporter:v1.21.2
container_name: podman-exporter
pull_policy: always
restart: always
+20 -2
View File
@@ -28,6 +28,14 @@
group: root
mode: '0644'
- name: Copy ssl.conf
ansible.builtin.copy:
src: ssl.conf
dest: /etc/nginx/ssl.conf
owner: root
group: root
mode: '0644'
- name: Copy html content
ansible.builtin.copy:
src: www/
@@ -36,7 +44,17 @@
group: www-data
mode: '0644'
- name: Restart nginx
- name: Validate config
ansible.builtin.command: nginx -t
changed_when: false
- name: Start nginx
ansible.builtin.service:
name: nginx
state: restarted
state: started
enabled: true
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
+6
View File
@@ -0,0 +1,6 @@
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
include /etc/nginx/ssl.conf;
ssl_reject_handshake on;
}
@@ -15,8 +15,7 @@ server {
ssl_certificate /etc/letsencrypt/live/gallery.secretbee.buzz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/gallery.secretbee.buzz/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
include /etc/nginx/ssl.conf;
# allow large file uploads
client_max_body_size 50000M;
@@ -17,8 +17,7 @@ server {
ssl_certificate /etc/letsencrypt/live/git.secretbee.buzz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/git.secretbee.buzz/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
include /etc/nginx/ssl.conf;
location / {
client_max_body_size 512M;
@@ -26,8 +26,7 @@ server {
ssl_certificate /etc/letsencrypt/live/grafana.secretbee.buzz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/grafana.secretbee.buzz/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
include /etc/nginx/ssl.conf;
location / {
proxy_set_header Host $host;
@@ -17,8 +17,7 @@ server {
ssl_certificate /etc/letsencrypt/live/lounge.secretbee.buzz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/lounge.secretbee.buzz/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
include /etc/nginx/ssl.conf;
location / {
proxy_pass http://127.0.0.1:9000/;
@@ -7,6 +7,7 @@ server {
ssl_certificate /etc/letsencrypt/live/matrix.secretbee.buzz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.secretbee.buzz/privkey.pem;
include /etc/nginx/ssl.conf;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
@@ -9,6 +9,7 @@ server {
ssl_certificate /etc/letsencrypt/live/ott.secretbee.buzz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ott.secretbee.buzz/privkey.pem;
include /etc/nginx/ssl.conf;
#deny all;
location / {
+1 -2
View File
@@ -28,8 +28,7 @@ server {
ssl_certificate /etc/letsencrypt/live/secretbee.buzz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/secretbee.buzz/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
include /etc/nginx/ssl.conf;
location /.well-known/matrix/server {
default_type application/json;
@@ -0,0 +1,29 @@
server {
listen 443 ssl http2;
server_name vault.secretbee.buzz;
ssl_certificate /etc/letsencrypt/live/vault.secretbee.buzz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vault.secretbee.buzz/privkey.pem;
include /etc/nginx/ssl.conf;
allow 10.10.1.0/24;
allow 192.168.178.0/24;
deny all;
client_max_body_size 128M;
location / {
proxy_pass http://localhost:8222;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "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;
}
location /admin {
return 404;
}
}
+3
View File
@@ -0,0 +1,3 @@
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
+3 -3
View File
@@ -144,14 +144,14 @@
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ user_info.uid }}/bus"
ansible.builtin.systemd:
name: ott-server.service
state: started
state: restarted
scope: user
- name: Debug
ansible.builtin.debug:
msg:
- "Service status: systemctl --user --machine {{ user_info.name }}@.host status ott-server"
- "Service status: sudo systemctl --user --machine {{ user_info.name }}@.host status ott-server"
- "Read logs: sudo journalctl _UID={{ user_info.uid }} -n 200 --no-pager"
- "Shell: machinectl shell {{ user_info.name }}@.host /bin/bash"
- "Shell: sudo machinectl shell {{ user_info.name }}@.host /bin/bash"
tags:
- always
@@ -0,0 +1,18 @@
[Unit]
Description=Vaultwarden
[Container]
ContainerName=vaultwarden
Image=docker.io/vaultwarden/server:1.37.1
PublishPort=127.0.0.1:8222:80
Volume={{ user_home }}/data:/data
Environment=DOMAIN=https://{{ vaultwarden_domain }}
Environment=SIGNUPS_ALLOWED={{ vaultwarden_signups_allowed | string | lower }}
Environment=INVITATIONS_ALLOWED=false
Environment=ROCKET_PORT=80
[Service]
Restart=on-failure
[Install]
WantedBy=default.target
+101
View File
@@ -0,0 +1,101 @@
- name: Vaultwarden
hosts: beepi
become: true
vars:
recovery_2fa: !vault |
$ANSIBLE_VAULT;1.1;AES256
33656334663834356232623236316238396630656530363332353566363433356133313932613230
3063613030363566376635363830336466333237633063310a666236333335363566343237646135
65656466363833373231653565306563333036373664316535366339393531363434386336303563
6636396665316634320a643966313338633564363830663263666461303635623164376265633631
39633162323130663533343734303538636433323632316131373531376438633432323433303234
3162626630373264356338356638323364656563666635393636
user: vaultwarden
user_home: /home/vaultwarden
vaultwarden_domain: vault.secretbee.buzz
# Bootstrap = -e vaultwarden_signups_allowed=true
vaultwarden_signups_allowed: false
tasks:
- name: Install dependencies
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- podman
- systemd-container
- passt
- name: Create user
ansible.builtin.user:
name: "{{ user }}"
home: "{{ user_home }}"
shell: /usr/sbin/nologin
create_home: true
register: user_info
- name: Enable linger
ansible.builtin.command:
cmd: "loginctl enable-linger {{ user }}"
creates: "/var/lib/systemd/linger/{{ user }}"
- name: Create quadlet directory
ansible.builtin.file:
path: "{{ user_home }}/.config/containers/systemd"
state: directory
owner: "{{ user }}"
group: "{{ user }}"
mode: '0755'
- name: Create data directory
ansible.builtin.file:
path: "{{ user_home }}/data"
state: directory
owner: "{{ user }}"
group: "{{ user }}"
mode: '0700'
- name: Deploy server quadlet
ansible.builtin.template:
src: vaultwarden.container.j2
dest: "{{ user_home }}/.config/containers/systemd/vaultwarden.container"
owner: "{{ user }}"
group: "{{ user }}"
mode: '0600'
- name: Reload user systemd
become: true
become_user: "{{ user }}"
environment:
XDG_RUNTIME_DIR: "/run/user/{{ user_info.uid }}"
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ user_info.uid }}/bus"
ansible.builtin.systemd:
daemon_reload: true
scope: user
- name: Start user manager
ansible.builtin.systemd:
name: "user@{{ user_info.uid }}.service"
state: started
- name: Start vaultwarden
become: true
become_user: "{{ user }}"
environment:
XDG_RUNTIME_DIR: "/run/user/{{ user_info.uid }}"
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ user_info.uid }}/bus"
ansible.builtin.systemd:
name: vaultwarden.service
state: restarted
scope: user
- name: Debug
ansible.builtin.debug:
msg:
- "Service status: sudo systemctl --user --machine {{ user_info.name }}@.host status vaultwarden"
- "Read logs: sudo journalctl _UID={{ user_info.uid }} -n 200 --no-pager"
- "Shell: sudo machinectl shell {{ user_info.name }}@.host /bin/bash"
tags:
- always
@@ -0,0 +1,10 @@
[Interface]
PrivateKey = {{ lookup('file', wireguard_client_dir ~ '/' ~ item.item.name ~ '.private') }}
Address = {{ item.item.ip }}/32
[Peer]
PublicKey = {{ wireguard_server_public_key }}
PresharedKey = {{ lookup('file', wireguard_client_dir ~ '/' ~ item.item.name ~ '.psk.private') }}
Endpoint = {{ wireguard_ingress_endpoint }}:{{ wireguard_ingress_port }}
AllowedIPs = {{ item.item.allowed_ips | default(wireguard_client_allowed_ips) }}
PersistentKeepalive = 25
+18 -28
View File
@@ -1,36 +1,26 @@
[Interface]
PrivateKey = {{ wireguard_server_private_key }}
Address = 10.10.1.1/24
ListenPort = 41194
Address = {{ wireguard_ingress_address }}
ListenPort = {{ wireguard_ingress_port }}
PostUp = sysctl -w net.ipv4.ip_forward=1
PreUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 25 -j DNAT --to-destination 10.10.1.2:25
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 25 -j DNAT --to-destination 10.10.1.2:25
PreUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 993 -j DNAT --to-destination 10.10.1.2:993
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 993 -j DNAT --to-destination 10.10.1.2:993
PreUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 587 -j DNAT --to-destination 10.10.1.2:587
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 587 -j DNAT --to-destination 10.10.1.2:587
PreUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 465 -j DNAT --to-destination 10.10.1.2:465
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 465 -j DNAT --to-destination 10.10.1.2:465
PreUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 10.10.1.2:80
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 10.10.1.2:80
PreUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 10.10.1.2:443
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 10.10.1.2:443
PreUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8448 -j DNAT --to-destination 10.10.1.2:8448
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 8448 -j DNAT --to-destination 10.10.1.2:8448
PreUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 4444 -j DNAT --to-destination 10.10.1.2:4444
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 4444 -j DNAT --to-destination 10.10.1.2:4444
{% for port in wireguard_dnat_ports %}
PreUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport {{ port }} -j DNAT --to-destination {{ wireguard_beepi_ip }}:{{ port }}
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport {{ port }} -j DNAT --to-destination {{ wireguard_beepi_ip }}:{{ port }}
{% endfor %}
#PreUp = iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
#PostDown = iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE
# ----- site-to-site: beepi -----
[Peer]
PublicKey = oI69uWkbkwtpuIYxDNGmzg4nRjY1BIZFVFjZwguIvwE=
AllowedIPs = 10.10.1.2/32
PublicKey = {{ wireguard_beepi_public_key }}
AllowedIPs = {{ wireguard_beepi_ip }}/32
{% for peer in client_public_keys.results %}
# ----- road-warrior: {{ peer.item.name }} -----
[Peer]
PublicKey = {{ peer.stdout }}
PresharedKey = {{ lookup('file', wireguard_client_dir ~ '/' ~ peer.item.name ~ '.psk.private') }}
AllowedIPs = {{ peer.item.ip }}/32
{% endfor %}
@@ -10,23 +10,163 @@
3239666637326337650a636433616264386162326535306461326233323961643664373134363634
35633238393933386231373535353635646462383763613362363761363935363965383836366431
3766343166643632306162343761613030653631353361373936
wireguard_ingress_address: 10.10.1.1/24
wireguard_ingress_port: 41194
wireguard_ingress_endpoint: 192.3.44.243
wireguard_server_public_key: r0XaxIE3AVud9Z42UPZF3mwoh2R0B0M1EUDY9cvuyDA=
wireguard_beepi_ip: 10.10.1.2
wireguard_beepi_public_key: oI69uWkbkwtpuIYxDNGmzg4nRjY1BIZFVFjZwguIvwE=
wireguard_dnat_ports:
- 25 # mail
- 80
- 443
- 465 # mail
- 587 # mail
- 993 # mail
- 4444 # backup ssh
- 8448 # matrix federation
wireguard_clients:
- name: phone
ip: 10.10.1.3
wireguard_client_allowed_ips: 10.10.1.2/32
wireguard_client_dir: "{{ playbook_dir }}/clients"
# Changes to Address, ListenPort or wireguard_dnat_ports = -e wireguard_full_restart=true
wireguard_full_restart: false
tasks:
- name: Install wireguard
ansible.builtin.apt:
name: wireguard
state: present
- name: Down wg0
changed_when: true
failed_when: false
ansible.builtin.command: wg-quick down wg0
- name: Create local client key directory
ansible.builtin.file:
path: "{{ wireguard_client_dir }}"
state: directory
mode: '0700'
delegate_to: localhost
become: false
- name: Generate client private keys
ansible.builtin.shell:
cmd: "umask 077 && wg genkey > {{ wireguard_client_dir }}/{{ item.name }}.private"
creates: "{{ wireguard_client_dir }}/{{ item.name }}.private"
loop: "{{ wireguard_clients }}"
loop_control:
label: "{{ item.name }}"
delegate_to: localhost
become: false
- name: Generate client preshared keys
ansible.builtin.shell:
cmd: "umask 077 && wg genpsk > {{ wireguard_client_dir }}/{{ item.name }}.psk.private"
creates: "{{ wireguard_client_dir }}/{{ item.name }}.psk.private"
loop: "{{ wireguard_clients }}"
loop_control:
label: "{{ item.name }}"
delegate_to: localhost
become: false
- name: Derive client public keys
ansible.builtin.command:
cmd: "wg pubkey"
stdin: "{{ lookup('file', wireguard_client_dir ~ '/' ~ item.name ~ '.private') }}"
loop: "{{ wireguard_clients }}"
loop_control:
label: "{{ item.name }}"
register: client_public_keys
changed_when: false
delegate_to: localhost
become: false
- name: Copy wg0.conf
ansible.builtin.template:
src: wg0.conf.j2
dest: /etc/wireguard/wg0.conf
mode: '0600'
register: wg0_config
- name: Up wg0
- name: Check whether wg0 is up
ansible.builtin.command:
cmd: wg show wg0
register: wg0_state
failed_when: false
changed_when: false
- name: Sync peers without dropping the tunnel
ansible.builtin.shell:
cmd: "wg syncconf wg0 <(wg-quick strip wg0)"
executable: /bin/bash
changed_when: true
ansible.builtin.command: wg-quick up wg0
when:
- wg0_config.changed
- wg0_state.rc == 0
- not wireguard_full_restart | bool
- name: Bounce wg0
ansible.builtin.shell:
cmd: "wg-quick down wg0 || true; wg-quick up wg0"
changed_when: true
when: wg0_state.rc != 0 or wireguard_full_restart | bool
- name: Enable wg-quick@wg0 on boot
ansible.builtin.service:
name: wg-quick@wg0
enabled: true
- name: Render client configs
ansible.builtin.template:
src: client.conf.j2
dest: "{{ wireguard_client_dir }}/{{ item.item.name }}.conf"
mode: '0600'
loop: "{{ client_public_keys.results }}"
loop_control:
label: "{{ item.item.name }}"
delegate_to: localhost
become: false
- name: Check for qrencode
ansible.builtin.command:
cmd: "which qrencode"
register: qrencode_check
failed_when: false
changed_when: false
delegate_to: localhost
become: false
- name: Render QR codes for mobile enrolment
ansible.builtin.command:
cmd: "qrencode -t ansiutf8 -r {{ wireguard_client_dir }}/{{ item.item.name }}.conf"
loop: "{{ client_public_keys.results }}"
loop_control:
label: "{{ item.item.name }}"
register: client_qr
changed_when: false
when: qrencode_check.rc == 0
delegate_to: localhost
become: false
- name: Show QR codes
ansible.builtin.debug:
msg: "{{ (item.stdout | default('')).split('\n') }}"
loop: "{{ client_qr.results | default([]) }}"
loop_control:
label: "{{ item.item.item.name | default('') }}"
when: qrencode_check.rc == 0
- name: Show peer status
ansible.builtin.command:
cmd: wg show wg0
register: wg0_final
changed_when: false
- name: Report
ansible.builtin.debug:
msg: "{{ wg0_final.stdout_lines }}"