Compare commits
9 Commits
c9e2ff930c
...
2f3eebd422
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f3eebd422 | |||
| d976a9d701 | |||
| e74fffd5fc | |||
| 30dfb9ee8b | |||
| b0324cf3fe | |||
| a6ca97ca0e | |||
| b2a66099aa | |||
| 314fa715fd | |||
| 80026fac0b |
+4
-11
@@ -1,11 +1,4 @@
|
|||||||
inventory/*
|
/inventory
|
||||||
!inventory/hosts.example
|
/inventory_data
|
||||||
!inventory/host_vars/
|
/playbooks
|
||||||
inventory/host_vars/*
|
/roadmap
|
||||||
!inventory/host_vars/example.yml
|
|
||||||
inventory_data/
|
|
||||||
playbook.yml
|
|
||||||
playbooks/*
|
|
||||||
!playbooks/example.yml
|
|
||||||
!playbooks/bootstrap.yml
|
|
||||||
TODO.md
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
collections:
|
collections:
|
||||||
- name: ansible.netcommon
|
- name: ansible.netcommon
|
||||||
- name: ansible.posix
|
- name: ansible.posix
|
||||||
|
version: ">=2.2.0"
|
||||||
- name: community.general
|
- name: community.general
|
||||||
- name: community.postgresql
|
- name: community.postgresql
|
||||||
- name: containers.podman
|
- name: containers.podman
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
server_name {{ gitea_nginx_hostname }};
|
server_name {{ gitea_nginx_hostname }};
|
||||||
|
|
||||||
# Certbot webroot for ACME challenges
|
# Certbot webroot for ACME challenges
|
||||||
@@ -18,6 +19,7 @@ server {
|
|||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
server_name {{ gitea_nginx_hostname }};
|
server_name {{ gitea_nginx_hostname }};
|
||||||
|
|
||||||
# Let's Encrypt certificates (managed by Certbot)
|
# Let's Encrypt certificates (managed by Certbot)
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ Both passwords must be set in your inventory (min 12 characters):
|
|||||||
- `immich_postgres_password` - PostgreSQL database password
|
- `immich_postgres_password` - PostgreSQL database password
|
||||||
- `immich_valkey_password` - Valkey/Redis password
|
- `immich_valkey_password` - Valkey/Redis password
|
||||||
|
|
||||||
|
## External Libraries
|
||||||
|
|
||||||
|
Mount host paths read-only into the server container via `immich_external_libraries`,
|
||||||
|
then add the in-container `mount_path` in the Immich UI
|
||||||
|
(Administration → External Libraries). The `{{ ansible_user }}` running the rootless
|
||||||
|
pod must have read access on the host path.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Valkey ACL Issues
|
### Valkey ACL Issues
|
||||||
|
|||||||
@@ -5,6 +5,15 @@ immich_version: release
|
|||||||
# Storage location (@see https://docs.immich.app/install/environment-variables/)
|
# Storage location (@see https://docs.immich.app/install/environment-variables/)
|
||||||
immich_upload_location: "{{ podman_projects_dir }}/immich/data/upload"
|
immich_upload_location: "{{ podman_projects_dir }}/immich/data/upload"
|
||||||
|
|
||||||
|
# External libraries (read-only host paths exposed to the server container)
|
||||||
|
# Use the in-container `mount_path` when registering the library in the Immich UI.
|
||||||
|
# Example:
|
||||||
|
# immich_external_libraries:
|
||||||
|
# - name: clement-photos
|
||||||
|
# host_path: /mnt/andromeda/clement-photos
|
||||||
|
# mount_path: /mnt/external/clement-photos
|
||||||
|
immich_external_libraries: []
|
||||||
|
|
||||||
# PostgreSQL configuration (REQUIRED password - must be set explicitly)
|
# PostgreSQL configuration (REQUIRED password - must be set explicitly)
|
||||||
immich_postgres_db_name: immich
|
immich_postgres_db_name: immich
|
||||||
immich_postgres_user: immich
|
immich_postgres_user: immich
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ spec:
|
|||||||
readOnly: true
|
readOnly: true
|
||||||
- name: immich-data
|
- name: immich-data
|
||||||
mountPath: /data
|
mountPath: /data
|
||||||
|
{% for lib in immich_external_libraries %}
|
||||||
|
- name: ext-{{ lib.name }}
|
||||||
|
mountPath: {{ lib.mount_path }}
|
||||||
|
readOnly: true
|
||||||
|
{% endfor %}
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /api/server/ping
|
path: /api/server/ping
|
||||||
@@ -83,6 +88,12 @@ spec:
|
|||||||
hostPath:
|
hostPath:
|
||||||
path: {{ immich_upload_location }}
|
path: {{ immich_upload_location }}
|
||||||
type: Directory
|
type: Directory
|
||||||
|
{% for lib in immich_external_libraries %}
|
||||||
|
- name: ext-{{ lib.name }}
|
||||||
|
hostPath:
|
||||||
|
path: {{ lib.host_path }}
|
||||||
|
type: Directory
|
||||||
|
{% endfor %}
|
||||||
- name: model-cache
|
- name: model-cache
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: immich-model-cache
|
claimName: immich-model-cache
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
server_name {{ immich_nginx_hostname }};
|
server_name {{ immich_nginx_hostname }};
|
||||||
|
|
||||||
# Certbot webroot for ACME challenges
|
# Certbot webroot for ACME challenges
|
||||||
@@ -18,6 +19,8 @@ server {
|
|||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
|
http2 on;
|
||||||
server_name {{ immich_nginx_hostname }};
|
server_name {{ immich_nginx_hostname }};
|
||||||
|
|
||||||
# Let's Encrypt certificates (managed by Certbot)
|
# Let's Encrypt certificates (managed by Certbot)
|
||||||
@@ -38,6 +41,12 @@ server {
|
|||||||
|
|
||||||
client_max_body_size 50000M;
|
client_max_body_size 50000M;
|
||||||
|
|
||||||
|
# Timeouts for slow mobile uploads (client <-> nginx leg)
|
||||||
|
client_body_timeout 600s;
|
||||||
|
client_header_timeout 600s;
|
||||||
|
send_timeout 600s;
|
||||||
|
keepalive_timeout 600s;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://127.0.0.1:{{ immich_port }};
|
proxy_pass http://127.0.0.1:{{ immich_port }};
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
@@ -50,7 +59,12 @@ server {
|
|||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
# Timeouts for large file uploads
|
# Stream uploads directly to backend instead of buffering full body on disk
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_buffering off;
|
||||||
|
|
||||||
|
# Timeouts for large file uploads (nginx <-> immich leg)
|
||||||
|
proxy_connect_timeout 600s;
|
||||||
proxy_read_timeout 600s;
|
proxy_read_timeout 600s;
|
||||||
proxy_send_timeout 600s;
|
proxy_send_timeout 600s;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Metabase
|
||||||
|
|
||||||
|
Business intelligence and analytics. Defaults: [`defaults/main.yml`](defaults/main.yml).
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- `podman` role
|
||||||
|
- `postgres` role
|
||||||
|
- `nginx` role (optional, for public access)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Set in inventory:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
metabase_postgres_password: "strongpassword"
|
||||||
|
metabase_postgres_host: "{{ podman_gw_gateway }}"
|
||||||
|
metabase_nginx_enabled: true
|
||||||
|
metabase_nginx_hostname: metabase.example.com
|
||||||
|
```
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
metabase_version: latest
|
||||||
|
metabase_image: metabase/metabase
|
||||||
|
|
||||||
|
metabase_port: 3000
|
||||||
|
|
||||||
|
metabase_postgres_db_name: metabase
|
||||||
|
metabase_postgres_user: metabase
|
||||||
|
# metabase_postgres_password: "" # Intentionally undefined - role will fail if not set
|
||||||
|
# metabase_postgres_host: "" # Must be set in inventory (e.g. "{{ podman_gw_gateway }}")
|
||||||
|
metabase_postgres_port: 5432
|
||||||
|
|
||||||
|
metabase_timezone: UTC
|
||||||
|
|
||||||
|
metabase_nginx_enabled: false
|
||||||
|
metabase_nginx_hostname: metabase.nas.local
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
- name: Reload systemd user
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
daemon_reload: true
|
||||||
|
scope: user
|
||||||
|
become: false
|
||||||
|
become_user: "{{ ansible_user }}"
|
||||||
|
|
||||||
|
- name: Restart Metabase
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: metabase.service
|
||||||
|
state: restarted
|
||||||
|
scope: user
|
||||||
|
become: false
|
||||||
|
become_user: "{{ ansible_user }}"
|
||||||
|
|
||||||
|
- name: Reload nginx
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: nginx
|
||||||
|
state: reloaded
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
dependencies:
|
||||||
|
- role: podman
|
||||||
|
- role: postgres
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
---
|
||||||
|
- name: Validate required passwords are set
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- metabase_postgres_password is defined
|
||||||
|
- metabase_postgres_password | length >= 12
|
||||||
|
fail_msg: |
|
||||||
|
metabase_postgres_password is required (min 12 chars).
|
||||||
|
See roles/metabase/defaults/main.yml for configuration instructions.
|
||||||
|
success_msg: "Password validation passed"
|
||||||
|
|
||||||
|
- name: Create PostgreSQL database for Metabase
|
||||||
|
community.postgresql.postgresql_db:
|
||||||
|
name: "{{ metabase_postgres_db_name }}"
|
||||||
|
owner: "{{ metabase_postgres_user }}"
|
||||||
|
state: present
|
||||||
|
become: false
|
||||||
|
become_user: "{{ postgres_admin_user | default('postgres') }}"
|
||||||
|
|
||||||
|
- name: Create PostgreSQL user for Metabase
|
||||||
|
community.postgresql.postgresql_user:
|
||||||
|
name: "{{ metabase_postgres_user }}"
|
||||||
|
password: "{{ metabase_postgres_password }}"
|
||||||
|
state: present
|
||||||
|
become: false
|
||||||
|
become_user: "{{ postgres_admin_user | default('postgres') }}"
|
||||||
|
|
||||||
|
- name: Grant all privileges on database to Metabase user
|
||||||
|
community.postgresql.postgresql_privs:
|
||||||
|
login_db: "{{ metabase_postgres_db_name }}"
|
||||||
|
roles: "{{ metabase_postgres_user }}"
|
||||||
|
type: database
|
||||||
|
privs: ALL
|
||||||
|
state: present
|
||||||
|
become: false
|
||||||
|
become_user: "{{ postgres_admin_user | default('postgres') }}"
|
||||||
|
|
||||||
|
- name: Ensure Metabase user has no superuser privileges
|
||||||
|
community.postgresql.postgresql_user:
|
||||||
|
name: "{{ metabase_postgres_user }}"
|
||||||
|
role_attr_flags: NOSUPERUSER,NOCREATEDB,NOCREATEROLE
|
||||||
|
state: present
|
||||||
|
become: false
|
||||||
|
become_user: "{{ postgres_admin_user | default('postgres') }}"
|
||||||
|
|
||||||
|
- name: Create Metabase project directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ podman_projects_dir }}/metabase"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ ansible_user }}"
|
||||||
|
group: "{{ ansible_user }}"
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Pull Metabase container image
|
||||||
|
ansible.builtin.command: "podman pull {{ metabase_image }}:{{ metabase_version }}"
|
||||||
|
register: pull_result
|
||||||
|
changed_when: pull_result.stdout is search('Writing manifest')
|
||||||
|
become: false
|
||||||
|
become_user: "{{ ansible_user }}"
|
||||||
|
|
||||||
|
- name: Deploy Kubernetes YAML for Metabase
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: metabase.yaml.j2
|
||||||
|
dest: "{{ podman_projects_dir }}/metabase/metabase.yaml"
|
||||||
|
owner: "{{ ansible_user }}"
|
||||||
|
group: "{{ ansible_user }}"
|
||||||
|
mode: "0644"
|
||||||
|
notify: Restart Metabase
|
||||||
|
|
||||||
|
- name: Get home directory for {{ ansible_user }}
|
||||||
|
ansible.builtin.getent:
|
||||||
|
database: passwd
|
||||||
|
key: "{{ ansible_user }}"
|
||||||
|
|
||||||
|
- name: Set user home directory fact
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
user_home_dir: "{{ ansible_facts['getent_passwd'][ansible_user][4] }}"
|
||||||
|
|
||||||
|
- name: Create systemd user directory for Metabase
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ user_home_dir }}/.config/systemd/user"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ ansible_user }}"
|
||||||
|
group: "{{ ansible_user }}"
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Deploy systemd service for Metabase (user scope)
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: metabase.service.j2
|
||||||
|
dest: "{{ user_home_dir }}/.config/systemd/user/metabase.service"
|
||||||
|
owner: "{{ ansible_user }}"
|
||||||
|
group: "{{ ansible_user }}"
|
||||||
|
mode: "0644"
|
||||||
|
notify: Reload systemd user
|
||||||
|
|
||||||
|
- name: Enable lingering for user {{ ansible_user }}
|
||||||
|
ansible.builtin.command: "loginctl enable-linger {{ ansible_user }}"
|
||||||
|
when: ansible_user != 'root'
|
||||||
|
|
||||||
|
- name: Enable and start Metabase service (user scope)
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: metabase.service
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
scope: user
|
||||||
|
become: false
|
||||||
|
become_user: "{{ ansible_user }}"
|
||||||
|
|
||||||
|
- name: Provision TLS certificate for Metabase
|
||||||
|
ansible.builtin.include_tasks: "{{ role_path }}/../nginx/tasks/certbot.yml"
|
||||||
|
vars:
|
||||||
|
certbot_hostname: "{{ metabase_nginx_hostname }}"
|
||||||
|
when: metabase_nginx_enabled
|
||||||
|
|
||||||
|
- name: Deploy nginx vhost configuration for Metabase
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: nginx-vhost.conf.j2
|
||||||
|
dest: "{{ nginx_conf_dir | default('/etc/nginx/conf.d') }}/metabase.conf"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
when: metabase_nginx_enabled
|
||||||
|
notify: Reload nginx
|
||||||
|
|
||||||
|
- name: Remove nginx vhost configuration for Metabase
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ nginx_conf_dir | default('/etc/nginx/conf.d') }}/metabase.conf"
|
||||||
|
state: absent
|
||||||
|
when: not metabase_nginx_enabled
|
||||||
|
notify: Reload nginx
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Metabase BI Server
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=notify
|
||||||
|
NotifyAccess=all
|
||||||
|
WorkingDirectory={{ podman_projects_dir }}/metabase
|
||||||
|
ExecStart=/usr/bin/podman kube play --replace --service-container=true --network=pasta:--map-host-loopback={{ podman_gw_gateway }} metabase.yaml
|
||||||
|
ExecStop=/usr/bin/podman kube down metabase.yaml
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
TimeoutStartSec=180
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: metabase
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: server
|
||||||
|
image: {{ metabase_image }}:{{ metabase_version }}
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
hostPort: {{ metabase_port }}
|
||||||
|
env:
|
||||||
|
- name: MB_DB_TYPE
|
||||||
|
value: postgres
|
||||||
|
- name: MB_DB_DBNAME
|
||||||
|
value: "{{ metabase_postgres_db_name }}"
|
||||||
|
- name: MB_DB_PORT
|
||||||
|
value: "{{ metabase_postgres_port }}"
|
||||||
|
- name: MB_DB_USER
|
||||||
|
value: "{{ metabase_postgres_user }}"
|
||||||
|
- name: MB_DB_PASS
|
||||||
|
value: "{{ metabase_postgres_password }}"
|
||||||
|
- name: MB_DB_HOST
|
||||||
|
value: "{{ metabase_postgres_host }}"
|
||||||
|
- name: JAVA_TIMEZONE
|
||||||
|
value: "{{ metabase_timezone }}"
|
||||||
|
volumeMounts:
|
||||||
|
- name: localtime
|
||||||
|
mountPath: /etc/localtime
|
||||||
|
readOnly: true
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/health
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 90
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
restartPolicy: Never
|
||||||
|
volumes:
|
||||||
|
- name: localtime
|
||||||
|
hostPath: { path: /etc/localtime, type: File }
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Metabase vhost
|
||||||
|
# Managed by Ansible - DO NOT EDIT MANUALLY
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name {{ metabase_nginx_hostname }};
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
location / {
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
|
http2 on;
|
||||||
|
server_name {{ metabase_nginx_hostname }};
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/{{ metabase_nginx_hostname }}/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/{{ metabase_nginx_hostname }}/privkey.pem;
|
||||||
|
ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.3') }};
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
{% if nginx_log_backend | default('journald') == 'journald' %}
|
||||||
|
access_log syslog:server=unix:/dev/log,nohostname,tag=nginx_metabase;
|
||||||
|
error_log syslog:server=unix:/dev/log,nohostname,tag=nginx_metabase;
|
||||||
|
{% else %}
|
||||||
|
access_log /var/log/nginx/{{ metabase_nginx_hostname }}_access.log main;
|
||||||
|
error_log /var/log/nginx/{{ metabase_nginx_hostname }}_error.log;
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:{{ metabase_port }};
|
||||||
|
proxy_set_header Host $http_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;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_read_timeout 600s;
|
||||||
|
proxy_send_timeout 600s;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,3 +16,7 @@ nfs_port: 2049
|
|||||||
|
|
||||||
nfs_server_firewall_allowed_sources:
|
nfs_server_firewall_allowed_sources:
|
||||||
- 127.0.0.0/8
|
- 127.0.0.0/8
|
||||||
|
|
||||||
|
# OS-dependent package name
|
||||||
|
nfs_package_name: >-
|
||||||
|
{{ (ansible_facts['os_family'] == 'Archlinux') | ternary('nfs-utils', 'nfs-kernel-server') }}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
- name: Install nfs-server
|
- name: Install nfs-server
|
||||||
ansible.builtin.package:
|
ansible.builtin.package:
|
||||||
name: "{{ (ansible_facts['os_family'] == 'Archlinux') | ternary('nfs-utils', 'nfs-kernel-server') }}"
|
name: "{{ nfs_package_name }}"
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
- name: Configure nfs configuration
|
- name: Configure nfs configuration
|
||||||
|
|||||||
@@ -11,6 +11,22 @@ Installs and configures Nginx as a reverse proxy for web applications with modul
|
|||||||
- SSL/TLS configuration
|
- SSL/TLS configuration
|
||||||
- **Native ACME/Let's Encrypt support** (Nginx 1.25.0+)
|
- **Native ACME/Let's Encrypt support** (Nginx 1.25.0+)
|
||||||
- **Transparent proxy forwarding** (HTTP/HTTPS to other hosts)
|
- **Transparent proxy forwarding** (HTTP/HTTPS to other hosts)
|
||||||
|
- **Catch-all `default_server`** that rejects unknown SNI/Host with `444`
|
||||||
|
|
||||||
|
## Catch-all default_server
|
||||||
|
|
||||||
|
A `00-default.conf` vhost is deployed and marked `default_server` on both
|
||||||
|
ports 80 and 443. It uses a self-signed cert (`/etc/nginx/ssl/default.crt`)
|
||||||
|
and returns `444` (close connection) for any request whose SNI/Host does
|
||||||
|
not match an explicit vhost. ACME HTTP-01 challenges (`/.well-known/acme-challenge/`)
|
||||||
|
are still answered on port 80 so Certbot keeps working for new hostnames.
|
||||||
|
|
||||||
|
Without this, clients hitting the server IP directly (or doing HTTP/2
|
||||||
|
connection coalescing across vhosts sharing the same IP) would receive the
|
||||||
|
certificate of the first vhost loaded alphabetically, leaking that
|
||||||
|
hostname and breaking TLS verification on other vhosts.
|
||||||
|
|
||||||
|
Disable with `nginx_default_server_enabled: false`.
|
||||||
|
|
||||||
## Service Integration Pattern
|
## Service Integration Pattern
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ nginx_client_max_body_size: 100M
|
|||||||
# SSL configuration (volontarily omit TLSv1.2 here)
|
# SSL configuration (volontarily omit TLSv1.2 here)
|
||||||
nginx_ssl_protocols: TLSv1.3
|
nginx_ssl_protocols: TLSv1.3
|
||||||
|
|
||||||
|
# Catch-all default_server (rejects unknown SNI / Host with 444).
|
||||||
|
# Prevents leaking the first-loaded vhost's cert to unrelated requests.
|
||||||
|
nginx_default_server_enabled: true
|
||||||
|
nginx_default_ssl_cert: /etc/nginx/ssl/default.crt
|
||||||
|
nginx_default_ssl_key: /etc/nginx/ssl/default.key
|
||||||
|
|
||||||
# Logging configuration
|
# Logging configuration
|
||||||
# Backend: 'file' (traditional /var/log/nginx/*.log) or 'journald' (systemd journal)
|
# Backend: 'file' (traditional /var/log/nginx/*.log) or 'journald' (systemd journal)
|
||||||
nginx_log_backend: journald
|
nginx_log_backend: journald
|
||||||
|
|||||||
@@ -29,6 +29,16 @@
|
|||||||
path: "/etc/letsencrypt/live/{{ certbot_hostname }}/fullchain.pem"
|
path: "/etc/letsencrypt/live/{{ certbot_hostname }}/fullchain.pem"
|
||||||
register: certbot_cert_file
|
register: certbot_cert_file
|
||||||
|
|
||||||
|
- name: Ensure letsencrypt directories are traversable by nginx
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
loop:
|
||||||
|
- /etc/letsencrypt/live
|
||||||
|
- /etc/letsencrypt/archive
|
||||||
|
when: certbot_cert_file.stat.exists
|
||||||
|
|
||||||
- name: Provision certificate for {{ certbot_hostname }}
|
- name: Provision certificate for {{ certbot_hostname }}
|
||||||
when: not certbot_cert_file.stat.exists
|
when: not certbot_cert_file.stat.exists
|
||||||
block:
|
block:
|
||||||
|
|||||||
@@ -78,6 +78,49 @@
|
|||||||
group: root
|
group: root
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Configure catch-all default_server
|
||||||
|
when: nginx_default_server_enabled
|
||||||
|
block:
|
||||||
|
- name: Ensure nginx ssl directory exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ nginx_default_ssl_cert | dirname }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Generate self-signed cert for default_server
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: >-
|
||||||
|
openssl req -x509 -nodes -newkey rsa:2048
|
||||||
|
-keyout {{ nginx_default_ssl_key }}
|
||||||
|
-out {{ nginx_default_ssl_cert }}
|
||||||
|
-days 3650 -subj "/CN=default"
|
||||||
|
creates: "{{ nginx_default_ssl_cert }}"
|
||||||
|
|
||||||
|
- name: Restrict permissions on default_server key
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ nginx_default_ssl_key }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0600"
|
||||||
|
|
||||||
|
- name: Deploy default_server vhost
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: default-server.conf.j2
|
||||||
|
dest: "{{ nginx_conf_dir }}/00-default.conf"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
notify: Reload nginx
|
||||||
|
|
||||||
|
- name: Remove default_server vhost when disabled
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ nginx_conf_dir }}/00-default.conf"
|
||||||
|
state: absent
|
||||||
|
when: not nginx_default_server_enabled
|
||||||
|
notify: Reload nginx
|
||||||
|
|
||||||
- name: Ensure Certbot webroot directory exists
|
- name: Ensure Certbot webroot directory exists
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: /var/www/certbot
|
path: /var/www/certbot
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Catch-all default_server vhosts
|
||||||
|
# Managed by Ansible - DO NOT EDIT MANUALLY
|
||||||
|
#
|
||||||
|
# Purpose: reject any request whose Host/SNI does not match an explicit
|
||||||
|
# server_name. Without this, the first vhost loaded alphabetically would
|
||||||
|
# leak its certificate to unrelated SNI requests (e.g. clients doing
|
||||||
|
# HTTP/2 connection coalescing or hitting the IP directly).
|
||||||
|
#
|
||||||
|
# `return 444` closes the connection without sending an HTTP response.
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
listen [::]:80 default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
# Keep ACME HTTP-01 challenges working for any hostname
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 444;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl default_server;
|
||||||
|
listen [::]:443 ssl default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
ssl_certificate {{ nginx_default_ssl_cert }};
|
||||||
|
ssl_certificate_key {{ nginx_default_ssl_key }};
|
||||||
|
|
||||||
|
return 444;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
server_name {{ certbot_hostname }};
|
server_name {{ certbot_hostname }};
|
||||||
|
|
||||||
location /.well-known/acme-challenge/ {
|
location /.well-known/acme-challenge/ {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
server_name {{ ntfy_nginx_hostname }};
|
server_name {{ ntfy_nginx_hostname }};
|
||||||
|
|
||||||
# Certbot webroot for ACME challenges
|
# Certbot webroot for ACME challenges
|
||||||
@@ -18,6 +19,7 @@ server {
|
|||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
server_name {{ ntfy_nginx_hostname }};
|
server_name {{ ntfy_nginx_hostname }};
|
||||||
|
|
||||||
# Let's Encrypt certificates (managed by Certbot)
|
# Let's Encrypt certificates (managed by Certbot)
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Samba Server
|
||||||
|
|
||||||
|
Minimal SMB/CIFS file sharing, mirroring the design of the `nfs_server` role.
|
||||||
|
|
||||||
|
Security is assumed to come from the network (firewall + VPN). No Active
|
||||||
|
Directory, no Kerberos, no winbind. Standalone server, `tdbsam` backend.
|
||||||
|
|
||||||
|
## In a nutshell
|
||||||
|
|
||||||
|
**Supports:**
|
||||||
|
|
||||||
|
- SMB2/SMB3 over TCP (port 445) and legacy NetBIOS (port 139)
|
||||||
|
- Per-share access control (`valid_users`, `write_list`, `force_user/group`)
|
||||||
|
- Optional guest fallback (`map to guest = Bad User`)
|
||||||
|
- UFW firewall configuration
|
||||||
|
- `testparm`-validated config before reload
|
||||||
|
- Idempotent user creation via `smbpasswd`
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
|
||||||
|
- No Active Directory / Kerberos integration
|
||||||
|
- Samba user accounts are only **created**, never updated. To rotate a
|
||||||
|
password, run `pdbedit -x <username>` first, then rerun the playbook.
|
||||||
|
- The matching system user (`/etc/passwd`) must already exist; this role
|
||||||
|
does not create UNIX accounts.
|
||||||
|
|
||||||
|
## Inventory
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Bind only to private interfaces
|
||||||
|
samba_bind_interfaces_only: true
|
||||||
|
samba_interfaces:
|
||||||
|
- lo
|
||||||
|
- lan0
|
||||||
|
- 192.168.1.161
|
||||||
|
|
||||||
|
# UNIX users must exist beforehand (e.g. via the `users` role
|
||||||
|
# or manual `useradd`). This role only manages the SMB password.
|
||||||
|
samba_users:
|
||||||
|
- username: alice
|
||||||
|
password: "{{ vault_alice_smb_password }}"
|
||||||
|
- username: bob
|
||||||
|
password: "{{ vault_bob_smb_password }}"
|
||||||
|
|
||||||
|
samba_shares:
|
||||||
|
- name: photos
|
||||||
|
path: /mnt/andromeda/family-photos
|
||||||
|
comment: "Family photos"
|
||||||
|
read_only: false
|
||||||
|
valid_users: ["alice", "bob"]
|
||||||
|
write_list: ["alice"]
|
||||||
|
force_user: alice
|
||||||
|
force_group: users
|
||||||
|
|
||||||
|
- name: public
|
||||||
|
path: /mnt/andromeda/public
|
||||||
|
comment: "Read-only public share"
|
||||||
|
guest_ok: true
|
||||||
|
read_only: true
|
||||||
|
|
||||||
|
samba_server_firewall_allowed_sources:
|
||||||
|
- 192.168.1.0/24
|
||||||
|
- 192.168.27.0/27
|
||||||
|
```
|
||||||
|
|
||||||
|
See [`defaults/main.yml`](./defaults/main.yml) for all variables and defaults.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- https://wiki.archlinux.org/title/Samba
|
||||||
|
- https://wiki.samba.org/index.php/Setting_up_Samba_as_a_Standalone_Server
|
||||||
|
- `man smb.conf`, `man smbpasswd`, `man pdbedit`
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
# Global server identity
|
||||||
|
samba_workgroup: "WORKGROUP"
|
||||||
|
samba_server_string: "Samba Server"
|
||||||
|
samba_netbios_name: "{{ inventory_hostname | upper }}"
|
||||||
|
|
||||||
|
# Map unknown users to guest (similar to NFS all_squash).
|
||||||
|
# "Never" disables guest fallback, "Bad User" maps unknown users to guest.
|
||||||
|
samba_map_to_guest: "Bad User"
|
||||||
|
samba_guest_account: "nobody"
|
||||||
|
|
||||||
|
# Interfaces to bind samba listeners to.
|
||||||
|
# `bind interfaces only` is always enabled. If samba_interfaces is empty,
|
||||||
|
# samba binds to no interface and is effectively isolated.
|
||||||
|
samba_interfaces: []
|
||||||
|
# Example:
|
||||||
|
# samba_interfaces:
|
||||||
|
# - lo
|
||||||
|
# - lan0
|
||||||
|
# - 192.168.1.161
|
||||||
|
|
||||||
|
# Samba user accounts. The matching system user MUST already exist
|
||||||
|
# (created by another role or manually). The role only manages the
|
||||||
|
# samba password (smbpasswd) and is idempotent: existing users are
|
||||||
|
# not touched. To rotate a password, delete it first with
|
||||||
|
# `pdbedit -x <username>` then rerun the playbook.
|
||||||
|
samba_users: []
|
||||||
|
# Example:
|
||||||
|
# samba_users:
|
||||||
|
# - username: alice
|
||||||
|
# password: "secret"
|
||||||
|
|
||||||
|
# Shares
|
||||||
|
samba_shares: []
|
||||||
|
# Example:
|
||||||
|
# samba_shares:
|
||||||
|
# - name: photos
|
||||||
|
# path: /mnt/andromeda/family-photos
|
||||||
|
# comment: "Family photos"
|
||||||
|
# browseable: true # default: true
|
||||||
|
# read_only: false # default: true
|
||||||
|
# guest_ok: false # default: false
|
||||||
|
# valid_users: ["alice"] # optional
|
||||||
|
# write_list: ["alice"] # optional
|
||||||
|
# force_user: alice # optional
|
||||||
|
# force_group: users # optional
|
||||||
|
# create_mask: "0664" # default: 0664
|
||||||
|
# directory_mask: "0775" # default: 0775
|
||||||
|
# manage_directory: false # default: false (do not create/chown the dir)
|
||||||
|
# extra_options: # optional, raw smb.conf key/values
|
||||||
|
# "veto files": "/.DS_Store/"
|
||||||
|
|
||||||
|
samba_config_file: "/etc/samba/smb.conf"
|
||||||
|
|
||||||
|
# smbd defaults to 445 (SMB) and 139 (NetBIOS Session)
|
||||||
|
samba_port_smb: 445
|
||||||
|
samba_port_netbios: 139
|
||||||
|
|
||||||
|
samba_server_firewall_allowed_sources:
|
||||||
|
- 127.0.0.0/8
|
||||||
|
|
||||||
|
# OS-dependent service name
|
||||||
|
samba_service_name: >-
|
||||||
|
{{ (ansible_facts['os_family'] == 'Archlinux') | ternary('smb', 'smbd') }}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
- name: Restart samba
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "{{ samba_service_name }}"
|
||||||
|
state: restarted
|
||||||
|
daemon_reload: true
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
- name: Validate samba users have a password set
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- item.username is defined and item.username | length > 0
|
||||||
|
- item.password is defined and item.password | length >= 8
|
||||||
|
fail_msg: |
|
||||||
|
Each samba_users entry must define `username` and `password` (>=8 chars).
|
||||||
|
See roles/samba_server/defaults/main.yml for the expected schema.
|
||||||
|
loop: "{{ samba_users }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.username | default('<unnamed>') }}"
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Install samba
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: samba
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Configure samba
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: smb.conf.j2
|
||||||
|
dest: "{{ samba_config_file }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
validate: "testparm -s %s"
|
||||||
|
notify: Restart samba
|
||||||
|
|
||||||
|
- name: Ensure share directories exist
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item.path }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ item.force_user | default('root') }}"
|
||||||
|
group: "{{ item.force_group | default('root') }}"
|
||||||
|
mode: "{{ item.directory_mask | default('0775') }}"
|
||||||
|
loop: "{{ samba_shares }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
when: item.manage_directory | default(false)
|
||||||
|
|
||||||
|
- name: Verify system users exist for samba accounts
|
||||||
|
ansible.builtin.getent:
|
||||||
|
database: passwd
|
||||||
|
key: "{{ item.username }}"
|
||||||
|
loop: "{{ samba_users }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.username }}"
|
||||||
|
|
||||||
|
- name: Check existing samba users
|
||||||
|
ansible.builtin.command: pdbedit -L
|
||||||
|
register: samba_existing_users
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Add samba users
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
set -o pipefail
|
||||||
|
(echo "{{ item.password }}"; echo "{{ item.password }}") | smbpasswd -s -a "{{ item.username }}"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
loop: "{{ samba_users }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.username }}"
|
||||||
|
when: item.username not in (samba_existing_users.stdout | default(''))
|
||||||
|
changed_when: true
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Systemd service for samba is started and enabled
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "{{ samba_service_name }}"
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
- name: Setup firewall rules for samba
|
||||||
|
community.general.ufw:
|
||||||
|
rule: allow
|
||||||
|
src: "{{ item.0 }}"
|
||||||
|
port: "{{ item.1 }}"
|
||||||
|
proto: tcp
|
||||||
|
direction: in
|
||||||
|
comment: "Samba (SMB)"
|
||||||
|
loop: "{{ samba_server_firewall_allowed_sources | product([samba_port_smb, samba_port_netbios]) | list }}"
|
||||||
|
retries: 5
|
||||||
|
delay: 2
|
||||||
|
register: ufw_result
|
||||||
|
until: ufw_result is succeeded
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# {{ ansible_managed }}
|
||||||
|
[global]
|
||||||
|
workgroup = {{ samba_workgroup }}
|
||||||
|
server string = {{ samba_server_string }}
|
||||||
|
netbios name = {{ samba_netbios_name }}
|
||||||
|
server role = standalone server
|
||||||
|
security = user
|
||||||
|
passdb backend = tdbsam
|
||||||
|
map to guest = {{ samba_map_to_guest }}
|
||||||
|
guest account = {{ samba_guest_account }}
|
||||||
|
bind interfaces only = yes
|
||||||
|
interfaces = {{ samba_interfaces | join(' ') }}
|
||||||
|
log file = /var/log/samba/log.%m
|
||||||
|
max log size = 1000
|
||||||
|
logging = file
|
||||||
|
disable netbios = no
|
||||||
|
dns proxy = no
|
||||||
|
|
||||||
|
{% for share in samba_shares %}
|
||||||
|
[{{ share.name }}]
|
||||||
|
path = {{ share.path }}
|
||||||
|
{% if share.comment is defined %}
|
||||||
|
comment = {{ share.comment }}
|
||||||
|
{% endif %}
|
||||||
|
browseable = {{ share.browseable | default(true) | ternary('yes', 'no') }}
|
||||||
|
read only = {{ share.read_only | default(true) | ternary('yes', 'no') }}
|
||||||
|
guest ok = {{ share.guest_ok | default(false) | ternary('yes', 'no') }}
|
||||||
|
{% if share.valid_users is defined %}
|
||||||
|
valid users = {{ share.valid_users | join(' ') }}
|
||||||
|
{% endif %}
|
||||||
|
{% if share.write_list is defined %}
|
||||||
|
write list = {{ share.write_list | join(' ') }}
|
||||||
|
{% endif %}
|
||||||
|
{% if share.force_user is defined %}
|
||||||
|
force user = {{ share.force_user }}
|
||||||
|
{% endif %}
|
||||||
|
{% if share.force_group is defined %}
|
||||||
|
force group = {{ share.force_group }}
|
||||||
|
{% endif %}
|
||||||
|
create mask = {{ share.create_mask | default('0664') }}
|
||||||
|
directory mask = {{ share.directory_mask | default('0775') }}
|
||||||
|
{% if share.extra_options is defined %}
|
||||||
|
{% for k, v in share.extra_options.items() %}
|
||||||
|
{{ k }} = {{ v }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
# OS-dependent package names
|
||||||
|
tooling_dig_package: >-
|
||||||
|
{{ (ansible_facts['os_family'] == 'Archlinux') | ternary('bind', 'dnsutils') }}
|
||||||
|
tooling_netcat_package: >-
|
||||||
|
{{ (ansible_facts['os_family'] == 'Archlinux') | ternary('openbsd-netcat', 'netcat-openbsd') }}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
- name: Install command-line tooling
|
||||||
|
ansible.builtin.package:
|
||||||
|
name:
|
||||||
|
- usbutils
|
||||||
|
- htop
|
||||||
|
- bottom
|
||||||
|
- wget
|
||||||
|
- ethtool
|
||||||
|
- iperf3
|
||||||
|
- vim
|
||||||
|
- nano
|
||||||
|
- "{{ tooling_dig_package }}"
|
||||||
|
- "{{ tooling_netcat_package }}"
|
||||||
|
state: present
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Install usbutils
|
|
||||||
package:
|
|
||||||
name: usbutils
|
|
||||||
state: present
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Install htop
|
|
||||||
package:
|
|
||||||
name: htop
|
|
||||||
state: present
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Install bottom
|
|
||||||
package:
|
|
||||||
name: bottom
|
|
||||||
state: present
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Install wget
|
|
||||||
package:
|
|
||||||
name: wget
|
|
||||||
state: present
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Install dig utility
|
|
||||||
package:
|
|
||||||
name: "{{ (ansible_facts['os_family'] == 'Archlinux') | ternary('bind', 'dnsutils') }}"
|
|
||||||
state: present
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Install ethtool
|
|
||||||
package:
|
|
||||||
name: ethtool
|
|
||||||
state: present
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Install iperf3
|
|
||||||
package:
|
|
||||||
name: iperf3
|
|
||||||
state: present
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Install vim
|
|
||||||
package:
|
|
||||||
name: vim
|
|
||||||
state: present
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Install nano
|
|
||||||
package:
|
|
||||||
name: nano
|
|
||||||
state: present
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Install netcat
|
|
||||||
package:
|
|
||||||
name: "{{ (ansible_facts['os_family'] == 'Archlinux') | ternary('openbsd-netcat', 'netcat-openbsd') }}"
|
|
||||||
state: present
|
|
||||||
changed_when: false
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
server_name {{ uptime_kuma_nginx_hostname }};
|
server_name {{ uptime_kuma_nginx_hostname }};
|
||||||
|
|
||||||
# Certbot webroot for ACME challenges
|
# Certbot webroot for ACME challenges
|
||||||
@@ -18,6 +19,7 @@ server {
|
|||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
server_name {{ uptime_kuma_nginx_hostname }};
|
server_name {{ uptime_kuma_nginx_hostname }};
|
||||||
|
|
||||||
# Let's Encrypt certificates (managed by Certbot)
|
# Let's Encrypt certificates (managed by Certbot)
|
||||||
|
|||||||
@@ -15,3 +15,7 @@ wireguard_config_base_path: /etc/wireguard
|
|||||||
# endpoint: host:port # optional: peer's public endpoint
|
# endpoint: host:port # optional: peer's public endpoint
|
||||||
# persistent_keepalive: 25 # optional: keepalive interval (seconds)
|
# persistent_keepalive: 25 # optional: keepalive interval (seconds)
|
||||||
wireguard_tunnels: []
|
wireguard_tunnels: []
|
||||||
|
|
||||||
|
# OS-dependent package name
|
||||||
|
wireguard_package_name: >-
|
||||||
|
{{ (ansible_facts['os_family'] == 'Archlinux') | ternary('wireguard-tools', 'wireguard') }}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
- name: Install wireguard
|
- name: Install wireguard
|
||||||
ansible.builtin.package:
|
ansible.builtin.package:
|
||||||
name: "{{ (ansible_facts['os_family'] == 'Archlinux') | ternary('wireguard-tools', 'wireguard') }}"
|
name: "{{ wireguard_package_name }}"
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
# Use systemd-resolved for DNS management (modern approach on all distributions)
|
# Use systemd-resolved for DNS management (modern approach on all distributions)
|
||||||
|
|||||||
Reference in New Issue
Block a user