update to reflect 380 prod
This commit is contained in:
parent
231c9744aa
commit
47158aa771
81 changed files with 1893 additions and 1253 deletions
|
|
@ -14,12 +14,12 @@ bind_dnssec_validation: auto
|
|||
# Secondaries that should receive NOTIFY
|
||||
bind_also_notify:
|
||||
- 204.87.183.53 # ns2
|
||||
- 172.245.56.83 # vm1.w5isp.com
|
||||
- 172.245.56.83 # git.mcintire.me
|
||||
|
||||
bind_allow_transfer:
|
||||
- 23.128.97.53 # ns-global.kjsl.com
|
||||
- 204.87.183.53 # ns2
|
||||
- 172.245.56.83 # vm1.w5isp.com
|
||||
- 172.245.56.83 # git.mcintire.me
|
||||
|
||||
# Zone definitions are loaded from individual files in zones/ directory
|
||||
bind_zones:
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ mcintire_me_zone:
|
|||
ip: 204.110.191.8
|
||||
- name: git
|
||||
ip: 172.245.56.83
|
||||
- name: vm1
|
||||
ip: 172.245.56.83
|
||||
other_name_servers:
|
||||
- ha.mcintire.me. IN CNAME q1l09qiycnaagngmjsg1qas0rhqcfoou.ui.nabu.casa.
|
||||
- _acme-challenge.ha.mcintire.me. IN CNAME _acme-challenge.q1l09qiycnaagngmjsg1qas0rhqcfoou.ui.nabu.casa.
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ w5isp_com_zone:
|
|||
ip: 204.110.191.8
|
||||
- name: vaprs
|
||||
ip: 204.110.191.8
|
||||
- name: aprs
|
||||
ip: 204.110.191.232
|
||||
other_name_servers:
|
||||
- autodiscover.w5isp.com. IN CNAME mail.w5isp.com.
|
||||
- autoconfig.w5isp.com. IN CNAME mail.w5isp.com.
|
||||
|
|
|
|||
7
ansible/group_vars/proxmox_servers.yml
Normal file
7
ansible/group_vars/proxmox_servers.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
ansible_user: root
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
firewall_enabled: false
|
||||
8
ansible/group_vars/vntx_servers/main.yml
Normal file
8
ansible/group_vars/vntx_servers/main.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
firewall_trusted_subnets:
|
||||
- subnet: 100.64.0.0/10
|
||||
comment: Tailscale
|
||||
- subnet: 204.110.188.0/22
|
||||
comment: VNTX
|
||||
- subnet: 10.0.0.0/8
|
||||
comment: Internal
|
||||
37
ansible/host_vars/aprs.w5isp.com.yml
Normal file
37
ansible/host_vars/aprs.w5isp.com.yml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
ansible_user: graham
|
||||
ansible_host: 204.110.191.232
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 10152
|
||||
proto: tcp
|
||||
comment: APRS-IS full feed
|
||||
- port: 10152
|
||||
proto: udp
|
||||
comment: APRS-IS full feed UDP
|
||||
- port: 14580
|
||||
proto: tcp
|
||||
comment: APRS-IS client
|
||||
- port: 14580
|
||||
proto: udp
|
||||
comment: APRS-IS client UDP
|
||||
- port: 8080
|
||||
proto: udp
|
||||
comment: APRS-IS UDP submit
|
||||
- port: 8080
|
||||
proto: tcp
|
||||
comment: APRS-IS HTTP upload
|
||||
- port: 14501
|
||||
proto: tcp
|
||||
comment: APRS-IS HTTP status
|
||||
|
||||
aprsc_server_id: T2DALLAS
|
||||
aprsc_passcode: "28302"
|
||||
aprsc_admin: "Graham McIntire, W5ISP"
|
||||
aprsc_email: graham@mcintire.me
|
||||
aprsc_bind_address: 204.110.191.232
|
||||
aprsc_uplink: hub-rotate.aprs2.net
|
||||
aprsc_uplink_port: 20152
|
||||
142
ansible/host_vars/db.aprs.me.yml
Normal file
142
ansible/host_vars/db.aprs.me.yml
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
---
|
||||
ansible_user: graham
|
||||
ansible_host: 10.0.15.22
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
firewall_enabled: false
|
||||
|
||||
# Override role defaults - LXC container with default Debian paths
|
||||
postgresql_version: "17"
|
||||
postgresql_storage_setup_enabled: false
|
||||
postgresql_data_directory: /var/lib/postgresql/17/main
|
||||
postgresql_wal_directory: /var/lib/postgresql/17/main/pg_wal
|
||||
postgresql_config_directory: /etc/postgresql/17/main
|
||||
postgresql_backup_enabled: false
|
||||
|
||||
# PostgreSQL base config (postgresql.conf)
|
||||
postgresql_config:
|
||||
# Connection
|
||||
listen_addresses: "'*'"
|
||||
max_connections: 100
|
||||
port: 5432
|
||||
|
||||
# Memory (tuned for 8GB RAM, Intel N100 2 vCPU)
|
||||
shared_buffers: 2GB
|
||||
effective_cache_size: 6GB
|
||||
work_mem: 16MB
|
||||
maintenance_work_mem: 512MB
|
||||
huge_pages: try
|
||||
|
||||
# NVMe SSD tuning
|
||||
random_page_cost: 1.1
|
||||
effective_io_concurrency: 200
|
||||
maintenance_io_concurrency: 200
|
||||
|
||||
# WAL & Checkpoints (write-heavy APRS workload)
|
||||
wal_buffers: 64MB
|
||||
max_wal_size: 2GB
|
||||
min_wal_size: 512MB
|
||||
wal_compression: zstd
|
||||
checkpoint_completion_target: 0.9
|
||||
checkpoint_timeout: 10min
|
||||
synchronous_commit: off
|
||||
|
||||
# Parallelism (constrained by 2 vCPUs)
|
||||
max_worker_processes: 4
|
||||
max_parallel_workers: 2
|
||||
max_parallel_workers_per_gather: 1
|
||||
max_parallel_maintenance_workers: 1
|
||||
|
||||
# Autovacuum (aggressive for high-churn packets table)
|
||||
autovacuum_vacuum_scale_factor: 0.05
|
||||
autovacuum_analyze_scale_factor: 0.02
|
||||
autovacuum_vacuum_cost_limit: 1000
|
||||
autovacuum_naptime: 30s
|
||||
|
||||
# Query planner
|
||||
default_statistics_target: 200
|
||||
|
||||
# Monitoring & Logging
|
||||
shared_preload_libraries: "'pg_stat_statements'"
|
||||
pg_stat_statements.track: top
|
||||
track_io_timing: on
|
||||
log_min_duration_statement: 500
|
||||
log_lock_waits: on
|
||||
log_temp_files: 0
|
||||
log_autovacuum_min_duration: 250
|
||||
log_line_prefix: "'%m [%p] %q%u@%d '"
|
||||
log_timezone: "'Etc/UTC'"
|
||||
cluster_name: "'17/main'"
|
||||
|
||||
# Locale
|
||||
datestyle: "'iso, mdy'"
|
||||
timezone: "'Etc/UTC'"
|
||||
lc_messages: "'C'"
|
||||
lc_monetary: "'C'"
|
||||
lc_numeric: "'C'"
|
||||
lc_time: "'C'"
|
||||
default_text_search_config: "'pg_catalog.english'"
|
||||
|
||||
# SSL (snakeoil certs)
|
||||
ssl: on
|
||||
ssl_cert_file: "'/etc/ssl/certs/ssl-cert-snakeoil.pem'"
|
||||
ssl_key_file: "'/etc/ssl/private/ssl-cert-snakeoil.key'"
|
||||
|
||||
# pg_hba.conf entries
|
||||
postgresql_hba_entries:
|
||||
- type: local
|
||||
database: all
|
||||
user: postgres
|
||||
auth_method: peer
|
||||
- type: local
|
||||
database: all
|
||||
user: all
|
||||
auth_method: md5
|
||||
- type: host
|
||||
database: all
|
||||
user: all
|
||||
address: 127.0.0.1/32
|
||||
auth_method: scram-sha-256
|
||||
- type: host
|
||||
database: all
|
||||
user: all
|
||||
address: 0.0.0.0/0
|
||||
auth_method: md5
|
||||
- type: host
|
||||
database: all
|
||||
user: all
|
||||
address: "::1/128"
|
||||
auth_method: scram-sha-256
|
||||
- type: local
|
||||
database: replication
|
||||
user: all
|
||||
auth_method: peer
|
||||
- type: host
|
||||
database: replication
|
||||
user: all
|
||||
address: 127.0.0.1/32
|
||||
auth_method: scram-sha-256
|
||||
- type: host
|
||||
database: replication
|
||||
user: all
|
||||
address: "::1/128"
|
||||
auth_method: scram-sha-256
|
||||
|
||||
# Database
|
||||
postgresql_databases:
|
||||
- name: aprsme_prod
|
||||
owner: aprs
|
||||
|
||||
postgresql_users:
|
||||
- name: aprs
|
||||
password: "{{ vault_postgresql_aprs_password | default(omit) }}"
|
||||
role_attr_flags: CREATEDB
|
||||
|
||||
postgresql_extensions:
|
||||
aprsme_prod:
|
||||
- citext
|
||||
- pg_stat_statements
|
||||
- pg_trgm
|
||||
- postgis
|
||||
150
ansible/host_vars/db.mcintire.me.yml
Normal file
150
ansible/host_vars/db.mcintire.me.yml
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
---
|
||||
ansible_user: graham
|
||||
ansible_host: 10.0.15.20
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
firewall_enabled: false
|
||||
|
||||
# Override role defaults - this server uses default Debian paths, not RAID
|
||||
postgresql_version: "17"
|
||||
postgresql_storage_setup_enabled: false
|
||||
postgresql_data_directory: /var/lib/postgresql/17/main
|
||||
postgresql_wal_directory: /var/lib/postgresql/17/main/pg_wal
|
||||
postgresql_config_directory: /etc/postgresql/17/main
|
||||
postgresql_backup_enabled: false
|
||||
|
||||
# TimescaleDB
|
||||
timescaledb_enabled: true
|
||||
timescaledb_max_background_workers: 16
|
||||
timescaledb_apt_repo: "deb https://packagecloud.io/timescale/timescaledb/debian/ trixie main"
|
||||
|
||||
# PostgreSQL config (matches live server tuning)
|
||||
postgresql_config:
|
||||
# Connection
|
||||
listen_addresses: "'*'"
|
||||
max_connections: 100
|
||||
port: 5432
|
||||
|
||||
# Memory (tuned for 8GB ARM server)
|
||||
shared_buffers: 2014MB
|
||||
work_mem: 5157kB
|
||||
maintenance_work_mem: 1031576kB
|
||||
effective_cache_size: 6044MB
|
||||
|
||||
# Disk
|
||||
random_page_cost: 1.1
|
||||
effective_io_concurrency: 256
|
||||
|
||||
# Workers
|
||||
max_worker_processes: 23
|
||||
max_parallel_workers: 4
|
||||
max_parallel_workers_per_gather: 2
|
||||
|
||||
# WAL
|
||||
wal_buffers: 16MB
|
||||
max_wal_size: 1GB
|
||||
min_wal_size: 512MB
|
||||
checkpoint_completion_target: 0.9
|
||||
|
||||
# Autovacuum
|
||||
autovacuum_max_workers: 10
|
||||
autovacuum_naptime: 10
|
||||
|
||||
# Query tuning
|
||||
default_statistics_target: 100
|
||||
jit: off
|
||||
|
||||
# Compression and locking
|
||||
default_toast_compression: lz4
|
||||
max_locks_per_transaction: 128
|
||||
|
||||
# Logging
|
||||
log_line_prefix: "'%m [%p] %q%u@%d '"
|
||||
log_timezone: "'UTC'"
|
||||
cluster_name: "'17/main'"
|
||||
|
||||
# Locale
|
||||
datestyle: "'iso, dmy'"
|
||||
timezone: "'Europe/London'"
|
||||
lc_messages: "'en_GB.UTF-8'"
|
||||
lc_monetary: "'en_GB.UTF-8'"
|
||||
lc_numeric: "'en_GB.UTF-8'"
|
||||
lc_time: "'en_GB.UTF-8'"
|
||||
default_text_search_config: "'pg_catalog.english'"
|
||||
|
||||
# Extensions
|
||||
shared_preload_libraries: "'pg_stat_statements,timescaledb'"
|
||||
|
||||
# TimescaleDB
|
||||
timescaledb.max_background_workers: 16
|
||||
|
||||
# SSL
|
||||
ssl: on
|
||||
ssl_cert_file: "'server.crt'"
|
||||
ssl_key_file: "'server.key'"
|
||||
|
||||
# pg_hba.conf entries (matches live server)
|
||||
postgresql_hba_entries:
|
||||
- type: local
|
||||
database: all
|
||||
user: postgres
|
||||
auth_method: peer
|
||||
- type: local
|
||||
database: all
|
||||
user: all
|
||||
auth_method: peer
|
||||
- type: host
|
||||
database: all
|
||||
user: all
|
||||
address: 127.0.0.1/32
|
||||
auth_method: scram-sha-256
|
||||
- type: host
|
||||
database: all
|
||||
user: all
|
||||
address: "::1/128"
|
||||
auth_method: scram-sha-256
|
||||
- type: local
|
||||
database: replication
|
||||
user: all
|
||||
auth_method: peer
|
||||
- type: host
|
||||
database: replication
|
||||
user: all
|
||||
address: 127.0.0.1/32
|
||||
auth_method: scram-sha-256
|
||||
- type: host
|
||||
database: replication
|
||||
user: all
|
||||
address: "::1/128"
|
||||
auth_method: scram-sha-256
|
||||
- type: hostssl
|
||||
database: all
|
||||
user: all
|
||||
address: 10.0.8.0/21
|
||||
auth_method: scram-sha-256
|
||||
|
||||
# Databases and users (matches live server)
|
||||
postgresql_databases:
|
||||
- name: aprsme_prod
|
||||
owner: aprsme
|
||||
- name: gridmap
|
||||
owner: gridmap
|
||||
- name: towerops
|
||||
owner: towerops
|
||||
|
||||
postgresql_users:
|
||||
- name: aprsme
|
||||
password: "{{ vault_postgresql_aprsme_password | default(omit) }}"
|
||||
- name: gridmap
|
||||
password: "{{ vault_postgresql_gridmap_password | default(omit) }}"
|
||||
- name: towerops
|
||||
password: "{{ vault_postgresql_towerops_password | default(omit) }}"
|
||||
|
||||
postgresql_extensions:
|
||||
aprsme_prod:
|
||||
- citext
|
||||
- pg_stat_statements
|
||||
- pg_trgm
|
||||
- postgis
|
||||
|
|
@ -4,3 +4,11 @@ ansible_host: 204.110.191.218
|
|||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/ansible
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 80
|
||||
proto: tcp
|
||||
comment: HTTP
|
||||
- port: 443
|
||||
proto: tcp
|
||||
comment: HTTPS
|
||||
|
|
|
|||
|
|
@ -6,6 +6,23 @@ ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
|||
public_ip: 172.245.56.83
|
||||
dns_backend: bind9
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 53
|
||||
proto: tcp
|
||||
comment: DNS
|
||||
- port: 53
|
||||
proto: udp
|
||||
comment: DNS
|
||||
- port: 80
|
||||
proto: tcp
|
||||
comment: HTTP
|
||||
- port: 443
|
||||
proto: tcp
|
||||
comment: HTTPS
|
||||
- port: 2222
|
||||
proto: tcp
|
||||
comment: Forgejo SSH
|
||||
|
||||
# Forgejo runner
|
||||
forgejo_runner_token: "I7rqgwumFl3BjuXnaEfroto7RHxGhsIUzM0lgMmM"
|
||||
forgejo_runner_name: "vm1-runner"
|
||||
3
ansible/host_vars/librenms.vntx.net.vault.yml
Normal file
3
ansible/host_vars/librenms.vntx.net.vault.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
vault_librenms_db_password: tqEoiUsDyEMBe
|
||||
vault_librenms_app_key: "base64:2Ro8xSViclOmQjFoXgm13h5Mx6WaLlihe/Q7O9qbReA="
|
||||
17
ansible/host_vars/librenms.vntx.net.yml
Normal file
17
ansible/host_vars/librenms.vntx.net.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
ansible_user: graham
|
||||
ansible_host: 204.110.191.227
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 80
|
||||
proto: tcp
|
||||
comment: HTTP
|
||||
- port: 443
|
||||
proto: tcp
|
||||
comment: HTTPS
|
||||
- port: 161
|
||||
proto: udp
|
||||
comment: SNMP
|
||||
6
ansible/host_vars/logs.vntx.net.yml
Normal file
6
ansible/host_vars/logs.vntx.net.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
ansible_user: ansible
|
||||
ansible_host: 204.110.191.229
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/ansible
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
|
@ -1,4 +1,27 @@
|
|||
---
|
||||
# Host-specific variables for mail.mcintire.me
|
||||
ansible_user: ansible
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 25
|
||||
proto: tcp
|
||||
comment: SMTP
|
||||
- port: 80
|
||||
proto: tcp
|
||||
comment: HTTP
|
||||
- port: 443
|
||||
proto: tcp
|
||||
comment: HTTPS
|
||||
- port: 465
|
||||
proto: tcp
|
||||
comment: SMTPS
|
||||
- port: 587
|
||||
proto: tcp
|
||||
comment: SMTP Submission
|
||||
- port: 993
|
||||
proto: tcp
|
||||
comment: IMAPS
|
||||
- port: 995
|
||||
proto: tcp
|
||||
comment: POP3S
|
||||
|
|
@ -4,3 +4,11 @@ ansible_host: 204.110.191.241
|
|||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 80
|
||||
proto: tcp
|
||||
comment: HTTP
|
||||
- port: 443
|
||||
proto: tcp
|
||||
comment: HTTPS
|
||||
|
|
|
|||
|
|
@ -8,6 +8,14 @@ ansible_python_interpreter: /usr/bin/python3
|
|||
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 80
|
||||
proto: tcp
|
||||
comment: HTTP
|
||||
- port: 443
|
||||
proto: tcp
|
||||
comment: HTTPS
|
||||
|
||||
# Override defaults for NetBox installation
|
||||
netbox_port: 8001
|
||||
netbox_secret_key: "{{ vault_netbox_secret_key }}"
|
||||
|
|
|
|||
|
|
@ -5,3 +5,11 @@ ansible_python_interpreter: /usr/bin/python3
|
|||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
public_ip: 204.110.191.222
|
||||
dns_backend: bind9
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 53
|
||||
proto: tcp
|
||||
comment: DNS
|
||||
- port: 53
|
||||
proto: udp
|
||||
comment: DNS
|
||||
|
|
|
|||
|
|
@ -4,3 +4,17 @@ ansible_host: 204.110.191.248
|
|||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 1812
|
||||
proto: udp
|
||||
comment: RADIUS Auth
|
||||
- port: 1812
|
||||
proto: tcp
|
||||
comment: RADIUS Auth
|
||||
- port: 1813
|
||||
proto: udp
|
||||
comment: RADIUS Accounting
|
||||
- port: 1813
|
||||
proto: tcp
|
||||
comment: RADIUS Accounting
|
||||
|
|
|
|||
6
ansible/host_vars/resolver1.vntx.net.yml
Normal file
6
ansible/host_vars/resolver1.vntx.net.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
ansible_user: graham
|
||||
ansible_host: 204.110.191.240
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
6
ansible/host_vars/resolver2.vntx.net.yml
Normal file
6
ansible/host_vars/resolver2.vntx.net.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
ansible_user: graham
|
||||
ansible_host: 204.110.191.250
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
|
@ -4,3 +4,14 @@ ansible_host: 204.110.191.8
|
|||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/ansible
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
network:
|
||||
skip: true
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 80
|
||||
proto: tcp
|
||||
comment: HTTP
|
||||
- port: 443
|
||||
proto: tcp
|
||||
comment: HTTPS
|
||||
|
|
|
|||
16
ansible/host_vars/staging.towerops.net.yml
Normal file
16
ansible/host_vars/staging.towerops.net.yml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
ansible_user: graham
|
||||
ansible_host: 204.110.191.231
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 80
|
||||
proto: tcp
|
||||
comment: HTTP
|
||||
- port: 443
|
||||
proto: tcp
|
||||
comment: HTTPS
|
||||
|
||||
dokku_domain: staging.towerops.net
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
# Vault file for sync.w5isp.com - Mailcow secrets
|
||||
|
||||
vault_mailcow_api_key: "drVv1AtEHuJw5C0+MxlKGGOMYiLx7C6dlfcAFlh2wZY="
|
||||
vault_mailcow_dbpass: "om30XAgUbHTcPy3uGNzJAp8L+uGvNyoS"
|
||||
vault_mailcow_dbroot: "jhIXyrY/DOS8wuRPSmCuRR/DqqXzi2Lq"
|
||||
vault_mailcow_redispass: "9YZCv2mqofPYl7p4ep5DvL2PVBIBhMK/"
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
---
|
||||
# Host-specific variables for sync.w5isp.com
|
||||
ansible_user: ansible
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
|
||||
# Mailcow configuration
|
||||
mailcow_hostname: mail.w5isp.com
|
||||
mailcow_http_bind: "127.0.0.1"
|
||||
mailcow_https_bind: "127.0.0.1"
|
||||
mailcow_api_key: "{{ vault_mailcow_api_key }}"
|
||||
mailcow_dbpass: "{{ vault_mailcow_dbpass }}"
|
||||
mailcow_dbroot: "{{ vault_mailcow_dbroot }}"
|
||||
|
||||
# Since we're using Caddy as reverse proxy
|
||||
mailcow_skip_lets_encrypt: "y"
|
||||
23
ansible/host_vars/uisp.vntx.net.yml
Normal file
23
ansible/host_vars/uisp.vntx.net.yml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
ansible_user: graham
|
||||
ansible_host: 204.110.191.224
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 80
|
||||
proto: tcp
|
||||
comment: HTTP
|
||||
- port: 443
|
||||
proto: tcp
|
||||
comment: HTTPS
|
||||
- port: 81
|
||||
proto: tcp
|
||||
comment: UISP device management
|
||||
- port: 8089
|
||||
proto: tcp
|
||||
comment: UISP HTTPS alt
|
||||
- port: 2055
|
||||
proto: udp
|
||||
comment: NetFlow
|
||||
|
|
@ -4,3 +4,23 @@ ansible_host: 204.110.191.247
|
|||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_private_key_file: /Users/graham/.ssh/ansible
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
||||
|
||||
firewall_trusted_subnets:
|
||||
- subnet: 100.64.0.0/10
|
||||
comment: Tailscale
|
||||
- subnet: 204.110.188.0/22
|
||||
comment: VNTX
|
||||
- subnet: 10.0.0.0/8
|
||||
comment: Internal
|
||||
|
||||
firewall_allow_rules:
|
||||
- port: 51820
|
||||
proto: udp
|
||||
comment: WireGuard
|
||||
|
||||
firewall_default_routed: allow
|
||||
firewall_ip_forward: true
|
||||
firewall_masquerade_rules:
|
||||
- source: 10.6.0.0/24
|
||||
out_interface: eth0
|
||||
comment: wireguard-nat-rule
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
[vntx_servers]
|
||||
#logs.vntx.net
|
||||
logs.vntx.net
|
||||
radius.vntx.net
|
||||
vpn.vntx.net
|
||||
netbox.vntx.net
|
||||
#ntp.vntx.net
|
||||
unimus.vntx.net
|
||||
monitor.vntx.net
|
||||
librenms.vntx.net
|
||||
uisp.vntx.net
|
||||
|
||||
[librenms_servers]
|
||||
librenms.vntx.net
|
||||
|
||||
[resolvers]
|
||||
resolver1.vntx.net
|
||||
resolver2.vntx.net
|
||||
|
||||
[monitoring_servers]
|
||||
monitor.vntx.net
|
||||
|
|
@ -13,22 +23,40 @@ monitor.vntx.net
|
|||
skippy.w5isp.com
|
||||
mail.mcintire.me ansible_host=107.174.178.20
|
||||
dokku.w5isp.com
|
||||
camper
|
||||
aprs.w5isp.com
|
||||
staging.towerops.net
|
||||
|
||||
[dokku_servers]
|
||||
staging.towerops.net
|
||||
|
||||
[aprsc_servers]
|
||||
aprs.w5isp.com
|
||||
|
||||
[caddy_servers]
|
||||
skippy.w5isp.com
|
||||
|
||||
#[syncthing_servers]
|
||||
#sync.w5isp.com
|
||||
|
||||
[bind9_servers]
|
||||
ns1.as393837.net ansible_host=204.110.191.222
|
||||
vm1.w5isp.com ansible_host=172.245.56.83
|
||||
git.mcintire.me ansible_host=172.245.56.83
|
||||
|
||||
[dns_servers:children]
|
||||
bind9_servers
|
||||
|
||||
[forgejo_servers]
|
||||
vm1.w5isp.com
|
||||
git.mcintire.me
|
||||
|
||||
[uisp_servers]
|
||||
uisp.vntx.net
|
||||
|
||||
[proxmox_servers]
|
||||
node1 ansible_host=10.0.15.101
|
||||
node2 ansible_host=10.0.15.102
|
||||
node3 ansible_host=10.0.15.103
|
||||
|
||||
[postgresql_servers]
|
||||
db.mcintire.me ansible_host=10.0.15.20
|
||||
db.aprs.me ansible_host=10.0.15.22
|
||||
|
||||
[home_cluster:children]
|
||||
proxmox_servers
|
||||
postgresql_servers
|
||||
|
|
|
|||
|
|
@ -15,12 +15,23 @@
|
|||
community.general.timezone:
|
||||
name: "{{ timezone }}"
|
||||
|
||||
# Resolver play disabled - no hosts in resolvers group
|
||||
#- name: Apply resolver config
|
||||
# hosts: resolvers
|
||||
# gather_facts: true
|
||||
# roles:
|
||||
# - { role: resolvers, become: true }
|
||||
- name: Apply firewall configuration
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: true
|
||||
tags:
|
||||
- firewall
|
||||
roles:
|
||||
- firewall
|
||||
|
||||
- name: Apply resolver config
|
||||
hosts: resolvers
|
||||
become: true
|
||||
gather_facts: true
|
||||
tags:
|
||||
- resolvers
|
||||
roles:
|
||||
- resolvers
|
||||
|
||||
# NS play disabled - replaced by BIND9 play below
|
||||
#- name: Apply ns config
|
||||
|
|
@ -113,41 +124,6 @@
|
|||
roles:
|
||||
- caddy
|
||||
|
||||
- name: Configure PostgreSQL servers
|
||||
hosts: postgresql_servers
|
||||
become: true
|
||||
gather_facts: true
|
||||
tags:
|
||||
- postgresql
|
||||
- database
|
||||
|
||||
pre_tasks:
|
||||
- name: Set hostname
|
||||
hostname:
|
||||
name: "{{ inventory_hostname }}"
|
||||
|
||||
- name: Update /etc/hosts
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
regexp: '^127\.0\.1\.1'
|
||||
line: "127.0.1.1 {{ inventory_hostname }}"
|
||||
state: present
|
||||
|
||||
- name: Update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
|
||||
- name: Install Python dependencies for Ansible
|
||||
apt:
|
||||
name:
|
||||
- python3-apt
|
||||
- python3-psycopg2
|
||||
state: present
|
||||
|
||||
roles:
|
||||
- raid
|
||||
- postgresql
|
||||
|
||||
- name: Provision BIND9 VM on Proxmox
|
||||
hosts: localhost
|
||||
|
|
@ -296,3 +272,39 @@
|
|||
- forgejo
|
||||
roles:
|
||||
- forgejo
|
||||
|
||||
- name: Configure LibreNMS servers
|
||||
hosts: librenms_servers
|
||||
become: yes
|
||||
gather_facts: true
|
||||
tags:
|
||||
- librenms
|
||||
roles:
|
||||
- librenms
|
||||
|
||||
- name: Configure aprsc servers
|
||||
hosts: aprsc_servers
|
||||
become: yes
|
||||
gather_facts: true
|
||||
tags:
|
||||
- aprsc
|
||||
roles:
|
||||
- aprsc
|
||||
|
||||
- name: Configure UISP servers
|
||||
hosts: uisp_servers
|
||||
become: yes
|
||||
gather_facts: true
|
||||
tags:
|
||||
- uisp
|
||||
roles:
|
||||
- uisp
|
||||
|
||||
- name: Configure Dokku servers
|
||||
hosts: dokku_servers
|
||||
become: yes
|
||||
gather_facts: true
|
||||
tags:
|
||||
- dokku
|
||||
roles:
|
||||
- dokku
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
# One-time playbook to regenerate SSH host keys on existing hosts
|
||||
# Usage: ansible-playbook -l "vm1,pangolin,camper,birdnet" regenerate-ssh-keys.yml
|
||||
# Usage: ansible-playbook -l "vm1,pangolin,birdnet" regenerate-ssh-keys.yml
|
||||
#
|
||||
# After running this playbook, you'll need to update your local known_hosts:
|
||||
# ssh-keygen -R hostname
|
||||
|
|
|
|||
35
ansible/roles/aprsc/defaults/main.yml
Normal file
35
ansible/roles/aprsc/defaults/main.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
aprsc_server_id: NOCALL
|
||||
aprsc_passcode: "0"
|
||||
aprsc_admin: "Server Admin"
|
||||
aprsc_email: admin@example.com
|
||||
|
||||
aprsc_path: /opt/aprsc
|
||||
aprsc_user: aprsc
|
||||
|
||||
# Upstream connection
|
||||
aprsc_uplink: hub-rotate.aprs2.net
|
||||
aprsc_uplink_port: 20152
|
||||
aprsc_uplink_type: full
|
||||
aprsc_bind_address: ""
|
||||
|
||||
# Listeners
|
||||
aprsc_fullfeed_port: 10152
|
||||
aprsc_client_port: 14580
|
||||
aprsc_udpsubmit_port: 8080
|
||||
aprsc_http_status_port: 14501
|
||||
aprsc_http_upload_port: 8080
|
||||
|
||||
# Timeouts
|
||||
aprsc_upstream_timeout: 15s
|
||||
aprsc_client_timeout: 48h
|
||||
|
||||
# Logging
|
||||
aprsc_log_rotate_size: 10
|
||||
aprsc_log_rotate_count: 5
|
||||
|
||||
# Resource limits
|
||||
aprsc_file_limit: 10000
|
||||
|
||||
# APT repository
|
||||
aprsc_apt_repo: "deb http://aprsc-dist.he.fi/aprsc/apt {{ ansible_distribution_release }} main"
|
||||
5
ansible/roles/aprsc/handlers/main.yml
Normal file
5
ansible/roles/aprsc/handlers/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
- name: restart aprsc
|
||||
systemd:
|
||||
name: aprsc
|
||||
state: restarted
|
||||
33
ansible/roles/aprsc/tasks/main.yml
Normal file
33
ansible/roles/aprsc/tasks/main.yml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
- name: Add aprsc APT repository
|
||||
apt_repository:
|
||||
repo: "{{ aprsc_apt_repo }}"
|
||||
state: present
|
||||
filename: aprsc
|
||||
|
||||
- name: Install aprsc
|
||||
apt:
|
||||
name: aprsc
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Deploy aprsc configuration
|
||||
template:
|
||||
src: aprsc.conf.j2
|
||||
dest: "{{ aprsc_path }}/etc/aprsc.conf"
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify: restart aprsc
|
||||
|
||||
- name: Enable and start aprsc-chroot service
|
||||
systemd:
|
||||
name: aprsc-chroot
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Enable and start aprsc service
|
||||
systemd:
|
||||
name: aprsc
|
||||
state: started
|
||||
enabled: true
|
||||
39
ansible/roles/aprsc/templates/aprsc.conf.j2
Normal file
39
ansible/roles/aprsc/templates/aprsc.conf.j2
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Configuration for aprsc, an APRS-IS server for core servers
|
||||
# Managed by Ansible
|
||||
|
||||
ServerId {{ aprsc_server_id }}
|
||||
PassCode {{ aprsc_passcode }}
|
||||
MyAdmin "{{ aprsc_admin }}"
|
||||
MyEmail {{ aprsc_email }}
|
||||
|
||||
### Directories #########
|
||||
RunDir data
|
||||
|
||||
LogRotate {{ aprsc_log_rotate_size }} {{ aprsc_log_rotate_count }}
|
||||
|
||||
### Intervals and timers #########
|
||||
UpstreamTimeout {{ aprsc_upstream_timeout }}
|
||||
ClientTimeout {{ aprsc_client_timeout }}
|
||||
|
||||
### TCP listener ##########
|
||||
Listen "Full feed" fullfeed tcp :: {{ aprsc_fullfeed_port }} hidden
|
||||
Listen "" fullfeed udp :: {{ aprsc_fullfeed_port }} hidden
|
||||
|
||||
Listen "Client-Defined Filters" igate tcp :: {{ aprsc_client_port }}
|
||||
Listen "" igate udp :: {{ aprsc_client_port }}
|
||||
|
||||
Listen "UDP submit" udpsubmit udp :: {{ aprsc_udpsubmit_port }}
|
||||
|
||||
### Uplink configuration ########
|
||||
Uplink "Core rotate" {{ aprsc_uplink_type }} tcp {{ aprsc_uplink }} {{ aprsc_uplink_port }}
|
||||
|
||||
{% if aprsc_bind_address is defined and aprsc_bind_address %}
|
||||
UplinkBind {{ aprsc_bind_address }}
|
||||
{% endif %}
|
||||
|
||||
### HTTP server ##########
|
||||
HTTPStatus 0.0.0.0 {{ aprsc_http_status_port }}
|
||||
HTTPUpload 0.0.0.0 {{ aprsc_http_upload_port }}
|
||||
|
||||
### Environment ############
|
||||
FileLimit {{ aprsc_file_limit }}
|
||||
|
|
@ -65,14 +65,21 @@ base_packages_by_distribution:
|
|||
AlmaLinux:
|
||||
- bind-utils
|
||||
- lsof
|
||||
- mosh
|
||||
- nano
|
||||
- net-tools
|
||||
- mlocate
|
||||
- fail2ban
|
||||
- pv
|
||||
- selinux-policy
|
||||
- tar
|
||||
- unzip
|
||||
- qemu-guest-agent
|
||||
- python3-libselinux
|
||||
|
||||
# Packages only available on specific AlmaLinux versions
|
||||
# EL9 has mosh, mlocate, fail2ban via EPEL; EL10 does not yet
|
||||
base_packages_almalinux_9:
|
||||
- mosh
|
||||
- mlocate
|
||||
- fail2ban
|
||||
|
||||
base_packages_almalinux_10:
|
||||
- plocate
|
||||
|
|
|
|||
|
|
@ -46,3 +46,10 @@
|
|||
- include_role:
|
||||
name: alpine
|
||||
when: ansible_facts['os_family'] == "Alpine"
|
||||
|
||||
# Firewall configuration
|
||||
- include_role:
|
||||
name: firewall
|
||||
when: firewall_enabled | default(true)
|
||||
tags:
|
||||
- firewall
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
base_packages_combined: >-
|
||||
{{ (base_common_packages
|
||||
+ base_packages_by_os_family.get(ansible_facts['os_family'], [])
|
||||
+ base_packages_by_distribution.get(ansible_facts['distribution'], [])) | unique }}
|
||||
+ base_packages_by_distribution.get(ansible_facts['distribution'], [])
|
||||
+ (base_packages_almalinux_9 if ansible_facts['distribution'] == 'AlmaLinux' and ansible_facts['distribution_major_version'] == '9' else [])
|
||||
+ (base_packages_almalinux_10 if ansible_facts['distribution'] == 'AlmaLinux' and ansible_facts['distribution_major_version'] == '10' else [])) | unique }}
|
||||
tags:
|
||||
- packages
|
||||
- common
|
||||
|
|
|
|||
20
ansible/roles/dokku/defaults/main.yml
Normal file
20
ansible/roles/dokku/defaults/main.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
dokku_domain: "{{ inventory_hostname }}"
|
||||
dokku_apt_suite: bookworm # trixie packages may not exist yet
|
||||
dokku_ssh_keys:
|
||||
- name: graham
|
||||
key: "https://github.com/gmcintire.keys"
|
||||
dokku_postgres_plugin_url: https://github.com/dokku/dokku-postgres.git
|
||||
dokku_postgres_image: "timescale/timescaledb"
|
||||
dokku_postgres_image_version: "latest-pg17"
|
||||
dokku_redis_plugin_url: https://github.com/dokku/dokku-redis.git
|
||||
dokku_redis_image: "valkey/valkey"
|
||||
dokku_redis_image_version: "8"
|
||||
dokku_apps:
|
||||
- name: towerops
|
||||
domains:
|
||||
- staging.towerops.net
|
||||
postgres:
|
||||
service_name: towerops-db
|
||||
redis:
|
||||
service_name: towerops-redis
|
||||
3
ansible/roles/dokku/handlers/main.yml
Normal file
3
ansible/roles/dokku/handlers/main.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
- name: restart dokku nginx
|
||||
command: dokku nginx:reload
|
||||
321
ansible/roles/dokku/tasks/main.yml
Normal file
321
ansible/roles/dokku/tasks/main.yml
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
---
|
||||
# Install Docker (required by Dokku)
|
||||
- name: Install prerequisites
|
||||
apt:
|
||||
name:
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
- apt-transport-https
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Add Docker GPG key
|
||||
ansible.builtin.get_url:
|
||||
url: https://download.docker.com/linux/debian/gpg
|
||||
dest: /usr/share/keyrings/docker-archive-keyring.asc
|
||||
mode: '0644'
|
||||
|
||||
- name: Add Docker repository
|
||||
ansible.builtin.deb822_repository:
|
||||
name: docker
|
||||
types: deb
|
||||
uris: https://download.docker.com/linux/debian
|
||||
suites: "{{ ansible_distribution_release }}"
|
||||
components: stable
|
||||
architectures: "{{ ansible_architecture | replace('x86_64', 'amd64') }}"
|
||||
signed_by: /usr/share/keyrings/docker-archive-keyring.asc
|
||||
state: present
|
||||
|
||||
- name: Install Docker packages
|
||||
apt:
|
||||
name:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-compose-plugin
|
||||
- docker-buildx-plugin
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Ensure Docker service is started and enabled
|
||||
systemd:
|
||||
name: docker
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
# Install Dokku
|
||||
- name: Add Dokku GPG key
|
||||
ansible.builtin.get_url:
|
||||
url: https://packagecloud.io/dokku/dokku/gpgkey
|
||||
dest: /usr/share/keyrings/dokku-archive-keyring.asc
|
||||
mode: '0644'
|
||||
|
||||
- name: Add Dokku repository
|
||||
ansible.builtin.deb822_repository:
|
||||
name: dokku
|
||||
types: deb
|
||||
uris: https://packagecloud.io/dokku/dokku/debian
|
||||
suites: "{{ dokku_apt_suite }}"
|
||||
components: main
|
||||
signed_by: /usr/share/keyrings/dokku-archive-keyring.asc
|
||||
state: present
|
||||
|
||||
- name: Preseed Dokku configuration
|
||||
ansible.builtin.debconf:
|
||||
name: dokku
|
||||
question: "{{ item.question }}"
|
||||
value: "{{ item.value }}"
|
||||
vtype: "{{ item.vtype }}"
|
||||
loop:
|
||||
- question: dokku/vhost_enable
|
||||
value: "true"
|
||||
vtype: boolean
|
||||
- question: dokku/hostname
|
||||
value: "{{ dokku_domain }}"
|
||||
vtype: string
|
||||
- question: dokku/skip_key_file
|
||||
value: "true"
|
||||
vtype: boolean
|
||||
loop_control:
|
||||
label: "{{ item.question }}"
|
||||
|
||||
- name: Install Dokku
|
||||
apt:
|
||||
name: dokku
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Set Dokku global domain
|
||||
command: dokku domains:set-global {{ dokku_domain }}
|
||||
register: dokku_domain_result
|
||||
changed_when: "'Set' in dokku_domain_result.stdout"
|
||||
|
||||
# SSH keys for git push access
|
||||
- name: Fetch SSH keys
|
||||
ansible.builtin.uri:
|
||||
url: "{{ item.key }}"
|
||||
return_content: true
|
||||
register: ssh_key_responses
|
||||
loop: "{{ dokku_ssh_keys }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Check existing Dokku SSH keys
|
||||
command: dokku ssh-keys:list
|
||||
register: dokku_existing_keys
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Add SSH keys to Dokku
|
||||
shell: |
|
||||
echo '{{ item.content.split("\n") | first }}' | dokku ssh-keys:add {{ item.item.name }}
|
||||
when: "item.item.name not in dokku_existing_keys.stdout"
|
||||
loop: "{{ ssh_key_responses.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.name }}"
|
||||
|
||||
# Install dokku-postgres plugin
|
||||
- name: Check if postgres plugin is installed
|
||||
command: dokku plugin:list
|
||||
register: dokku_plugins
|
||||
changed_when: false
|
||||
|
||||
- name: Install dokku-postgres plugin
|
||||
command: dokku plugin:install {{ dokku_postgres_plugin_url }}
|
||||
when: "'postgres' not in dokku_plugins.stdout"
|
||||
|
||||
- name: Install dokku-redis plugin
|
||||
command: dokku plugin:install {{ dokku_redis_plugin_url }}
|
||||
when: "'redis' not in dokku_plugins.stdout"
|
||||
|
||||
# Create apps and postgres services
|
||||
- name: Check existing apps
|
||||
command: dokku apps:list
|
||||
register: dokku_apps_list
|
||||
changed_when: false
|
||||
|
||||
- name: Create Dokku apps
|
||||
command: "dokku apps:create {{ item.name }}"
|
||||
when: "item.name not in dokku_apps_list.stdout_lines"
|
||||
loop: "{{ dokku_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Set app domains
|
||||
shell: |
|
||||
current=$(dokku domains:report {{ item.name }} --domains-app-vhosts)
|
||||
for domain in {{ item.domains | join(' ') }}; do
|
||||
if ! echo "$current" | grep -q "$domain"; then
|
||||
dokku domains:add {{ item.name }} "$domain"
|
||||
fi
|
||||
done
|
||||
when: item.domains is defined
|
||||
loop: "{{ dokku_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
register: domains_result
|
||||
changed_when: "'added' in domains_result.stdout | default('')"
|
||||
|
||||
- name: Check if app has SSL cert
|
||||
shell: "dokku certs:report {{ item.name }} --ssl-enabled 2>&1"
|
||||
loop: "{{ dokku_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
register: certs_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Generate self-signed SSL cert for apps
|
||||
shell: |
|
||||
mkdir -p /home/dokku/{{ item.item.name }}/tls
|
||||
openssl req -new -x509 -days 3650 -nodes \
|
||||
-out /home/dokku/{{ item.item.name }}/tls/server.crt \
|
||||
-keyout /home/dokku/{{ item.item.name }}/tls/server.key \
|
||||
-subj "/CN={{ item.item.domains | first }}"
|
||||
chown -R dokku:dokku /home/dokku/{{ item.item.name }}/tls
|
||||
chmod 600 /home/dokku/{{ item.item.name }}/tls/server.key
|
||||
dokku proxy:build-config {{ item.item.name }}
|
||||
when:
|
||||
- item.item.domains is defined
|
||||
- "'true' not in item.stdout"
|
||||
loop: "{{ certs_check.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.name }}"
|
||||
|
||||
- name: Check existing postgres services
|
||||
command: dokku postgres:list
|
||||
register: dokku_postgres_list
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Create postgres services with TimescaleDB
|
||||
command: >
|
||||
dokku postgres:create {{ item.postgres.service_name }}
|
||||
--image {{ dokku_postgres_image }}
|
||||
--image-version {{ dokku_postgres_image_version }}
|
||||
when:
|
||||
- item.postgres is defined
|
||||
- "item.postgres.service_name not in dokku_postgres_list.stdout"
|
||||
loop: "{{ dokku_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
register: postgres_create_result
|
||||
|
||||
# Handle TimescaleDB SSL cert issue - container may get stuck restarting without certs
|
||||
- name: Wait for postgres services to stabilize
|
||||
pause:
|
||||
seconds: 10
|
||||
when: postgres_create_result.changed | default(false)
|
||||
|
||||
- name: Check postgres service status
|
||||
command: "dokku postgres:info {{ item.postgres.service_name }}"
|
||||
when:
|
||||
- item.postgres is defined
|
||||
- postgres_create_result.changed | default(false)
|
||||
loop: "{{ dokku_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
register: postgres_info
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Fix TimescaleDB SSL certs if container is restarting
|
||||
shell: |
|
||||
CONTAINER_ID=$(docker ps -a --filter "name=dokku.postgres.{{ item.postgres.service_name }}" --format '{{"{{"}}.ID{{"}}"}}')
|
||||
if [ -n "$CONTAINER_ID" ]; then
|
||||
DATA_DIR=$(docker inspect "$CONTAINER_ID" --format '{{"{{" }}range .Mounts{{"}}"}}{{"{{"}}if eq .Destination "/var/lib/postgresql/data"{{"}}"}}{{"{{"}} .Source {{"}}"}}{{"{{"}}end{{"}}"}}{{"{{"}}end{{"}}"}}')
|
||||
if [ -n "$DATA_DIR" ] && [ ! -f "$DATA_DIR/server.crt" ]; then
|
||||
openssl req -new -x509 -days 3650 -nodes \
|
||||
-out "$DATA_DIR/server.crt" \
|
||||
-keyout "$DATA_DIR/server.key" \
|
||||
-subj "/CN=dokku-postgres-{{ item.postgres.service_name }}"
|
||||
chown 999:999 "$DATA_DIR/server.crt" "$DATA_DIR/server.key"
|
||||
chmod 600 "$DATA_DIR/server.key"
|
||||
dokku postgres:restart {{ item.postgres.service_name }}
|
||||
fi
|
||||
fi
|
||||
when:
|
||||
- item.postgres is defined
|
||||
- postgres_create_result.changed | default(false)
|
||||
loop: "{{ dokku_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
changed_when: false
|
||||
|
||||
# Enable TimescaleDB extension
|
||||
- name: Enable timescaledb extension
|
||||
shell: |
|
||||
echo "CREATE EXTENSION IF NOT EXISTS timescaledb;" | dokku postgres:connect {{ item.postgres.service_name }}
|
||||
when:
|
||||
- item.postgres is defined
|
||||
loop: "{{ dokku_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
register: timescaledb_ext
|
||||
changed_when: "'CREATE EXTENSION' in timescaledb_ext.stdout"
|
||||
failed_when:
|
||||
- timescaledb_ext.rc != 0
|
||||
- "'already exists' not in timescaledb_ext.stderr"
|
||||
|
||||
# Link postgres to apps
|
||||
- name: Check postgres links
|
||||
shell: "dokku postgres:linked {{ item.postgres.service_name }} {{ item.name }} && echo linked || echo not_linked"
|
||||
when:
|
||||
- item.postgres is defined
|
||||
loop: "{{ dokku_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
register: postgres_link_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Link postgres to apps
|
||||
command: "dokku postgres:link {{ item.item.postgres.service_name }} {{ item.item.name }}"
|
||||
when:
|
||||
- item.item.postgres is defined
|
||||
- "'not_linked' in item.stdout"
|
||||
loop: "{{ postgres_link_check.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.name }}"
|
||||
|
||||
# Redis/Valkey services
|
||||
- name: Check existing redis services
|
||||
command: dokku redis:list
|
||||
register: dokku_redis_list
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Create redis services with Valkey
|
||||
command: >
|
||||
dokku redis:create {{ item.redis.service_name }}
|
||||
--image {{ dokku_redis_image }}
|
||||
--image-version {{ dokku_redis_image_version }}
|
||||
when:
|
||||
- item.redis is defined
|
||||
- "item.redis.service_name not in dokku_redis_list.stdout"
|
||||
loop: "{{ dokku_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Check redis links
|
||||
shell: "dokku redis:linked {{ item.redis.service_name }} {{ item.name }} && echo linked || echo not_linked"
|
||||
when:
|
||||
- item.redis is defined
|
||||
loop: "{{ dokku_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
register: redis_link_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Link redis to apps
|
||||
command: "dokku redis:link {{ item.item.redis.service_name }} {{ item.item.name }}"
|
||||
when:
|
||||
- item.item.redis is defined
|
||||
- "'not_linked' in item.stdout"
|
||||
loop: "{{ redis_link_check.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.name }}"
|
||||
38
ansible/roles/firewall/defaults/main.yml
Normal file
38
ansible/roles/firewall/defaults/main.yml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
# Trusted subnets - SSH and all ports allowed from these
|
||||
firewall_trusted_subnets:
|
||||
- subnet: 100.64.0.0/10
|
||||
comment: Tailscale
|
||||
- subnet: 204.110.188.0/22
|
||||
comment: VNTX
|
||||
|
||||
# Default policy
|
||||
firewall_default_incoming: deny
|
||||
firewall_default_outgoing: allow
|
||||
|
||||
# Per-host rules for publicly exposed services
|
||||
# Override in host_vars or group_vars as needed
|
||||
# Example:
|
||||
# firewall_allow_rules:
|
||||
# - port: 80
|
||||
# proto: tcp
|
||||
# comment: HTTP
|
||||
# - port: 443
|
||||
# proto: tcp
|
||||
# comment: HTTPS
|
||||
firewall_allow_rules: []
|
||||
|
||||
# Routed/forwarding policy (allow, deny, reject)
|
||||
# Set to allow on hosts that need to forward traffic (e.g. VPN gateways)
|
||||
firewall_default_routed: deny
|
||||
|
||||
# Enable IP forwarding (sysctl net.ipv4.ip_forward)
|
||||
firewall_ip_forward: false
|
||||
|
||||
# NAT masquerade rules for VPN/routing
|
||||
# Example:
|
||||
# firewall_masquerade_rules:
|
||||
# - source: 10.6.0.0/24
|
||||
# out_interface: eth0
|
||||
# comment: wireguard-nat-rule
|
||||
firewall_masquerade_rules: []
|
||||
4
ansible/roles/firewall/handlers/main.yml
Normal file
4
ansible/roles/firewall/handlers/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
- name: Reload ufw
|
||||
ufw:
|
||||
state: reloaded
|
||||
125
ansible/roles/firewall/tasks/main.yml
Normal file
125
ansible/roles/firewall/tasks/main.yml
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
---
|
||||
# Firewall configuration using ufw
|
||||
# Default deny incoming, allow outgoing
|
||||
# Trusted subnets get full access
|
||||
# Per-host rules for publicly exposed services
|
||||
|
||||
- name: Install ufw
|
||||
apt:
|
||||
name: ufw
|
||||
state: present
|
||||
update_cache: true
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Install firewalld
|
||||
dnf:
|
||||
name: firewalld
|
||||
state: present
|
||||
when: ansible_os_family == "RedHat"
|
||||
|
||||
- name: Ensure firewalld is started and enabled
|
||||
systemd:
|
||||
name: firewalld
|
||||
state: started
|
||||
enabled: true
|
||||
when: ansible_os_family == "RedHat"
|
||||
|
||||
# --- Firewalld (RedHat/AlmaLinux) ---
|
||||
|
||||
- name: Allow trusted subnets in firewalld
|
||||
ansible.posix.firewalld:
|
||||
zone: public
|
||||
source: "{{ item.subnet }}"
|
||||
permanent: true
|
||||
immediate: true
|
||||
state: enabled
|
||||
loop: "{{ firewall_trusted_subnets }}"
|
||||
when: ansible_os_family == "RedHat"
|
||||
|
||||
- name: Allow per-host public services in firewalld
|
||||
ansible.posix.firewalld:
|
||||
zone: public
|
||||
port: "{{ item.port | string }}/{{ item.proto }}"
|
||||
permanent: true
|
||||
immediate: true
|
||||
state: enabled
|
||||
loop: "{{ firewall_allow_rules }}"
|
||||
when:
|
||||
- ansible_os_family == "RedHat"
|
||||
- firewall_allow_rules | length > 0
|
||||
|
||||
# --- UFW (Debian/Ubuntu) ---
|
||||
|
||||
- name: Reset ufw to defaults
|
||||
ufw:
|
||||
state: reset
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Set default incoming policy to deny
|
||||
ufw:
|
||||
direction: incoming
|
||||
policy: "{{ firewall_default_incoming }}"
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Set default outgoing policy to allow
|
||||
ufw:
|
||||
direction: outgoing
|
||||
policy: "{{ firewall_default_outgoing }}"
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Set default routed policy
|
||||
ufw:
|
||||
direction: routed
|
||||
policy: "{{ firewall_default_routed }}"
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Allow all traffic from trusted subnets
|
||||
ufw:
|
||||
rule: allow
|
||||
from_ip: "{{ item.subnet }}"
|
||||
comment: "{{ item.comment }}"
|
||||
loop: "{{ firewall_trusted_subnets }}"
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Allow per-host public services
|
||||
ufw:
|
||||
rule: allow
|
||||
port: "{{ item.port | string }}"
|
||||
proto: "{{ item.proto }}"
|
||||
comment: "{{ item.comment }}"
|
||||
loop: "{{ firewall_allow_rules }}"
|
||||
when:
|
||||
- ansible_os_family == "Debian"
|
||||
- firewall_allow_rules | length > 0
|
||||
|
||||
- name: Enable IP forwarding
|
||||
ansible.posix.sysctl:
|
||||
name: net.ipv4.ip_forward
|
||||
value: '1'
|
||||
sysctl_set: true
|
||||
state: present
|
||||
reload: true
|
||||
when:
|
||||
- ansible_os_family == "Debian"
|
||||
- firewall_ip_forward
|
||||
|
||||
- name: Configure NAT masquerade rules in ufw before.rules
|
||||
ansible.builtin.blockinfile:
|
||||
path: /etc/ufw/before.rules
|
||||
insertbefore: "\\*filter"
|
||||
marker: "# {mark} ANSIBLE MANAGED - {{ item.comment }}"
|
||||
block: |
|
||||
*nat
|
||||
:POSTROUTING ACCEPT [0:0]
|
||||
-A POSTROUTING -s {{ item.source }} -o {{ item.out_interface }} -j MASQUERADE -m comment --comment "{{ item.comment }}"
|
||||
COMMIT
|
||||
loop: "{{ firewall_masquerade_rules }}"
|
||||
when:
|
||||
- ansible_os_family == "Debian"
|
||||
- firewall_masquerade_rules | length > 0
|
||||
notify: Reload ufw
|
||||
|
||||
- name: Enable ufw
|
||||
ufw:
|
||||
state: enabled
|
||||
when: ansible_os_family == "Debian"
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
forgejo_home: /opt/forgejo
|
||||
forgejo_version: "10"
|
||||
forgejo_version: "15.0-test"
|
||||
forgejo_http_port: 3000
|
||||
forgejo_ssh_port: 2222
|
||||
forgejo_domain: git.mcintire.me
|
||||
|
|
@ -10,7 +10,7 @@ forgejo_root_url: "https://{{ forgejo_domain }}"
|
|||
forgejo_app_name: "Forgejo"
|
||||
|
||||
# Runner settings
|
||||
forgejo_runner_version: "6.2.2"
|
||||
forgejo_runner_version: "12.7.1"
|
||||
forgejo_runner_token: ""
|
||||
forgejo_runner_name: "default-runner"
|
||||
forgejo_runner_labels: "docker:docker://node:20-bookworm,ubuntu-latest:docker://ubuntu:latest,ubuntu-22.04:docker://ubuntu:22.04"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
services:
|
||||
forgejo:
|
||||
image: codeberg.org/forgejo/forgejo:{{ forgejo_version }}
|
||||
image: codeberg.org/forgejo-experimental/forgejo:{{ forgejo_version }}
|
||||
container_name: forgejo
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@
|
|||
|
||||
- name: zones.conf
|
||||
template:
|
||||
src: templates/etc/icinga2/zones.conf.j2
|
||||
src: etc/icinga2/zones.conf.j2
|
||||
dest: /etc/icinga2/zones.conf
|
||||
owner: nagios
|
||||
group: nagios
|
||||
|
|
@ -104,7 +104,7 @@
|
|||
|
||||
- name: icinga2.conf
|
||||
template:
|
||||
src: templates/etc/icinga2/icinga2.conf.j2
|
||||
src: etc/icinga2/icinga2.conf.j2
|
||||
dest: /etc/icinga2/icinga2.conf
|
||||
owner: nagios
|
||||
group: nagios
|
||||
|
|
@ -114,7 +114,7 @@
|
|||
|
||||
- name: checks.conf
|
||||
template:
|
||||
src: templates/etc/icinga2/conf.d/checks.conf.j2
|
||||
src: etc/icinga2/conf.d/checks.conf.j2
|
||||
dest: /etc/icinga2/conf.d/checks.conf
|
||||
owner: nagios
|
||||
group: nagios
|
||||
|
|
|
|||
|
|
@ -122,9 +122,11 @@
|
|||
owner: root
|
||||
group: root
|
||||
mode: 00644
|
||||
follow: yes
|
||||
vars:
|
||||
domain: "{{ network.domain | default('local') }}"
|
||||
search_domains: "{{ network.search_domains | default([domain]) }}"
|
||||
nameservers: "{{ network.nameservers | default(['9.9.9.9', '149.112.112.112']) }}"
|
||||
when: network.skip | default(False) != True
|
||||
tags:
|
||||
- network_resolv_conf
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
// Managed by Ansible
|
||||
58
ansible/roles/general/templates/etc/icinga2/icinga2.conf.j2
Normal file
58
ansible/roles/general/templates/etc/icinga2/icinga2.conf.j2
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* Icinga 2 configuration file
|
||||
* - this is where you define settings for the Icinga application including
|
||||
* which hosts/services to check.
|
||||
*
|
||||
* For an overview of all available configuration options please refer
|
||||
* to the documentation that is distributed as part of Icinga 2.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The constants.conf defines global constants.
|
||||
*/
|
||||
include "constants.conf"
|
||||
|
||||
/**
|
||||
* The zones.conf defines zones for a cluster setup.
|
||||
* Not required for single instance setups.
|
||||
*/
|
||||
include "zones.conf"
|
||||
|
||||
/**
|
||||
* The Icinga Template Library (ITL) provides a number of useful templates
|
||||
* and command definitions.
|
||||
* Common monitoring plugin command definitions are included separately.
|
||||
*/
|
||||
include <itl>
|
||||
include <plugins>
|
||||
include <plugins-contrib>
|
||||
include <manubulon>
|
||||
|
||||
/**
|
||||
* This includes the Icinga 2 Windows plugins. These command definitions
|
||||
* are required on a master node when a client is used as command endpoint.
|
||||
*/
|
||||
include <windows-plugins>
|
||||
|
||||
/**
|
||||
* This includes the NSClient++ check commands. These command definitions
|
||||
* are required on a master node when a client is used as command endpoint.
|
||||
*/
|
||||
include <nscp>
|
||||
|
||||
/**
|
||||
* The features-available directory contains a number of configuration
|
||||
* files for features which can be enabled and disabled using the
|
||||
* icinga2 feature enable / icinga2 feature disable CLI commands.
|
||||
* These commands work by creating and removing symbolic links in
|
||||
* the features-enabled directory.
|
||||
*/
|
||||
include "features-enabled/*.conf"
|
||||
|
||||
/**
|
||||
* Although in theory you could define all your objects in this file
|
||||
* the preferred way is to create separate directories and files in the conf.d
|
||||
* directory. Each of these files must have the file extension ".conf".
|
||||
*/
|
||||
// Disabled by the node setup CLI command
|
||||
// include_recursive "conf.d"
|
||||
14
ansible/roles/general/templates/etc/icinga2/zones.conf.j2
Normal file
14
ansible/roles/general/templates/etc/icinga2/zones.conf.j2
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
object Endpoint "{{ inventory_hostname }}" {
|
||||
}
|
||||
|
||||
object Zone "master" {
|
||||
endpoints = [ "{{ inventory_hostname }}" ]
|
||||
}
|
||||
|
||||
object Zone "global-templates" {
|
||||
global = true
|
||||
}
|
||||
|
||||
object Zone "director-global" {
|
||||
global = true
|
||||
}
|
||||
24
ansible/roles/librenms/defaults/main.yml
Normal file
24
ansible/roles/librenms/defaults/main.yml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
librenms_user: librenms
|
||||
librenms_group: librenms
|
||||
librenms_path: /opt/librenms
|
||||
librenms_domain: librenms.vntx.net
|
||||
|
||||
# PHP
|
||||
librenms_php_version: "8.4"
|
||||
librenms_php_fpm_socket: /run/php-fpm-librenms.sock
|
||||
|
||||
# PHP-FPM pool settings
|
||||
librenms_fpm_max_children: 5
|
||||
librenms_fpm_start_servers: 2
|
||||
librenms_fpm_min_spare_servers: 1
|
||||
librenms_fpm_max_spare_servers: 3
|
||||
|
||||
# MariaDB
|
||||
librenms_db_name: librenms
|
||||
librenms_db_user: librenms
|
||||
librenms_db_password: "{{ vault_librenms_db_password }}"
|
||||
librenms_app_key: "{{ vault_librenms_app_key }}"
|
||||
|
||||
# Poller
|
||||
librenms_poller_threads: 16
|
||||
29
ansible/roles/librenms/handlers/main.yml
Normal file
29
ansible/roles/librenms/handlers/main.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
- name: restart php-fpm
|
||||
service:
|
||||
name: "php{{ librenms_php_version }}-fpm"
|
||||
state: restarted
|
||||
|
||||
- name: restart caddy
|
||||
service:
|
||||
name: caddy
|
||||
state: restarted
|
||||
|
||||
- name: restart snmpd
|
||||
service:
|
||||
name: snmpd
|
||||
state: restarted
|
||||
|
||||
- name: restart postfix
|
||||
service:
|
||||
name: postfix
|
||||
state: restarted
|
||||
|
||||
- name: reload systemd
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: restart librenms-scheduler
|
||||
systemd:
|
||||
name: librenms-scheduler.timer
|
||||
state: restarted
|
||||
241
ansible/roles/librenms/tasks/main.yml
Normal file
241
ansible/roles/librenms/tasks/main.yml
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
---
|
||||
- name: Install MariaDB APT repository
|
||||
ansible.builtin.deb822_repository:
|
||||
name: mariadb
|
||||
types: deb
|
||||
uris: https://mirror.rackspace.com/mariadb/repo/12.2/debian
|
||||
suites: trixie
|
||||
components: main
|
||||
signed_by: https://mariadb.org/mariadb_release_signing_key.pgp
|
||||
state: present
|
||||
|
||||
- name: Install PHP APT repository (sury)
|
||||
ansible.builtin.deb822_repository:
|
||||
name: php
|
||||
types: deb
|
||||
uris: https://packages.sury.org/php/
|
||||
suites: trixie
|
||||
components: main
|
||||
signed_by: https://packages.sury.org/php/apt.gpg
|
||||
state: present
|
||||
|
||||
- name: Install required packages
|
||||
apt:
|
||||
name:
|
||||
- acl
|
||||
- curl
|
||||
- fping
|
||||
- git
|
||||
- graphviz
|
||||
- imagemagick
|
||||
- mariadb-client
|
||||
- mariadb-server
|
||||
- mtr-tiny
|
||||
- nmap
|
||||
- python3-pymysql
|
||||
- rrdtool
|
||||
- snmp
|
||||
- snmpd
|
||||
- unzip
|
||||
- whois
|
||||
- "php{{ librenms_php_version }}-bcmath"
|
||||
- "php{{ librenms_php_version }}-cli"
|
||||
- "php{{ librenms_php_version }}-curl"
|
||||
- "php{{ librenms_php_version }}-fpm"
|
||||
- "php{{ librenms_php_version }}-gd"
|
||||
- "php{{ librenms_php_version }}-gmp"
|
||||
- "php{{ librenms_php_version }}-intl"
|
||||
- "php{{ librenms_php_version }}-mbstring"
|
||||
- "php{{ librenms_php_version }}-mysql"
|
||||
- "php{{ librenms_php_version }}-snmp"
|
||||
- "php{{ librenms_php_version }}-xml"
|
||||
- "php{{ librenms_php_version }}-zip"
|
||||
- postfix
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Create librenms group
|
||||
group:
|
||||
name: "{{ librenms_group }}"
|
||||
system: true
|
||||
state: present
|
||||
|
||||
- name: Create librenms user
|
||||
user:
|
||||
name: "{{ librenms_user }}"
|
||||
group: "{{ librenms_group }}"
|
||||
home: "{{ librenms_path }}"
|
||||
shell: /usr/bin/bash
|
||||
system: true
|
||||
create_home: false
|
||||
state: present
|
||||
|
||||
- name: Check if LibreNMS is already installed
|
||||
stat:
|
||||
path: "{{ librenms_path }}/lnms"
|
||||
register: librenms_installed
|
||||
|
||||
- name: Clone LibreNMS repository
|
||||
git:
|
||||
repo: https://github.com/librenms/librenms.git
|
||||
dest: "{{ librenms_path }}"
|
||||
version: master
|
||||
depth: 1
|
||||
become_user: "{{ librenms_user }}"
|
||||
when: not librenms_installed.stat.exists
|
||||
|
||||
- name: Set LibreNMS directory ownership
|
||||
file:
|
||||
path: "{{ librenms_path }}"
|
||||
owner: "{{ librenms_user }}"
|
||||
group: "{{ librenms_group }}"
|
||||
recurse: true
|
||||
changed_when: false
|
||||
|
||||
- name: Set LibreNMS directory permissions
|
||||
file:
|
||||
path: "{{ librenms_path }}"
|
||||
mode: '0771'
|
||||
state: directory
|
||||
|
||||
- name: Ensure writable directories exist
|
||||
file:
|
||||
path: "{{ librenms_path }}/{{ item }}"
|
||||
owner: "{{ librenms_user }}"
|
||||
group: "{{ librenms_group }}"
|
||||
mode: '0775'
|
||||
state: directory
|
||||
loop:
|
||||
- rrd
|
||||
- storage
|
||||
- storage/logs
|
||||
- bootstrap/cache
|
||||
|
||||
- name: Set ACLs for LibreNMS directories
|
||||
acl:
|
||||
path: "{{ librenms_path }}/{{ item }}"
|
||||
entry: "default:group::rwx"
|
||||
state: present
|
||||
loop:
|
||||
- rrd
|
||||
- storage
|
||||
- storage/logs
|
||||
- bootstrap/cache
|
||||
|
||||
- name: Deploy LibreNMS .env file
|
||||
template:
|
||||
src: env.j2
|
||||
dest: "{{ librenms_path }}/.env"
|
||||
owner: "{{ librenms_user }}"
|
||||
group: "{{ librenms_group }}"
|
||||
mode: '0640'
|
||||
|
||||
- name: Configure PHP-FPM pool for LibreNMS
|
||||
template:
|
||||
src: php-fpm-librenms.conf.j2
|
||||
dest: "/etc/php/{{ librenms_php_version }}/fpm/pool.d/librenms.conf"
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify: restart php-fpm
|
||||
|
||||
- name: Set PHP memory_limit
|
||||
lineinfile:
|
||||
path: "/etc/php/{{ librenms_php_version }}/fpm/php.ini"
|
||||
regexp: '^memory_limit\s*='
|
||||
line: 'memory_limit = 512M'
|
||||
notify: restart php-fpm
|
||||
|
||||
- name: Configure Caddy for LibreNMS
|
||||
template:
|
||||
src: Caddyfile.j2
|
||||
dest: /etc/caddy/Caddyfile
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify: restart caddy
|
||||
|
||||
- name: Deploy LibreNMS cron jobs
|
||||
template:
|
||||
src: cron-librenms.j2
|
||||
dest: /etc/cron.d/librenms
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
|
||||
- name: Deploy LibreNMS scheduler service
|
||||
template:
|
||||
src: librenms-scheduler.service.j2
|
||||
dest: /etc/systemd/system/librenms-scheduler.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify: reload systemd
|
||||
|
||||
- name: Deploy LibreNMS scheduler timer
|
||||
template:
|
||||
src: librenms-scheduler.timer.j2
|
||||
dest: /etc/systemd/system/librenms-scheduler.timer
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify:
|
||||
- reload systemd
|
||||
- restart librenms-scheduler
|
||||
|
||||
- name: Enable and start LibreNMS scheduler timer
|
||||
systemd:
|
||||
name: librenms-scheduler.timer
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Configure SNMPD
|
||||
template:
|
||||
src: snmpd.conf.j2
|
||||
dest: /etc/snmp/snmpd.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0600'
|
||||
notify: restart snmpd
|
||||
|
||||
- name: Configure Postfix for local-only delivery
|
||||
lineinfile:
|
||||
path: /etc/postfix/main.cf
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.line }}"
|
||||
loop:
|
||||
- regexp: '^inet_interfaces\s*='
|
||||
line: 'inet_interfaces = loopback-only'
|
||||
- regexp: '^mydestination\s*='
|
||||
line: 'mydestination = $myhostname, localhost.$mydomain, localhost'
|
||||
notify: restart postfix
|
||||
|
||||
- name: Ensure MariaDB is running
|
||||
service:
|
||||
name: mariadb
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Ensure Caddy is running
|
||||
service:
|
||||
name: caddy
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Ensure PHP-FPM is running
|
||||
service:
|
||||
name: "php{{ librenms_php_version }}-fpm"
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Ensure SNMPD is running
|
||||
service:
|
||||
name: snmpd
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Ensure Postfix is running
|
||||
service:
|
||||
name: postfix
|
||||
state: started
|
||||
enabled: true
|
||||
16
ansible/roles/librenms/templates/Caddyfile.j2
Normal file
16
ansible/roles/librenms/templates/Caddyfile.j2
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{{ librenms_domain }} {
|
||||
root * {{ librenms_path }}/html
|
||||
|
||||
encode gzip
|
||||
|
||||
php_fastcgi unix/{{ librenms_php_fpm_socket }}
|
||||
|
||||
file_server
|
||||
|
||||
@dotfiles path */.*
|
||||
@wellknown path /.well-known/*
|
||||
handle @wellknown {
|
||||
file_server
|
||||
}
|
||||
respond @dotfiles 403
|
||||
}
|
||||
12
ansible/roles/librenms/templates/cron-librenms.j2
Normal file
12
ansible/roles/librenms/templates/cron-librenms.j2
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
33 */6 * * * {{ librenms_user }} {{ librenms_path }}/cronic {{ librenms_path }}/discovery-wrapper.py 1
|
||||
*/5 * * * * {{ librenms_user }} {{ librenms_path }}/discovery.php -h new >> /dev/null 2>&1
|
||||
|
||||
*/5 * * * * {{ librenms_user }} {{ librenms_path }}/cronic {{ librenms_path }}/poller-wrapper.py {{ librenms_poller_threads }}
|
||||
* * * * * {{ librenms_user }} {{ librenms_path }}/alerts.php >> /dev/null 2>&1
|
||||
|
||||
*/5 * * * * {{ librenms_user }} {{ librenms_path }}/poll-billing.php >> /dev/null 2>&1
|
||||
01 * * * * {{ librenms_user }} {{ librenms_path }}/billing-calculate.php >> /dev/null 2>&1
|
||||
*/5 * * * * {{ librenms_user }} {{ librenms_path }}/check-services.php >> /dev/null 2>&1
|
||||
|
||||
# Daily maintenance script. DO NOT DISABLE!
|
||||
19 0 * * * {{ librenms_user }} {{ librenms_path }}/daily.sh >> /dev/null 2>&1
|
||||
5
ansible/roles/librenms/templates/env.j2
Normal file
5
ansible/roles/librenms/templates/env.j2
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
DB_DATABASE={{ librenms_db_name }}
|
||||
DB_USERNAME={{ librenms_db_user }}
|
||||
DB_PASSWORD={{ librenms_db_password }}
|
||||
APP_KEY={{ librenms_app_key }}
|
||||
NODE_ID={{ librenms_node_id | default('695bd7d80d63b') }}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=LibreNMS Scheduler
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
WorkingDirectory={{ librenms_path }}/
|
||||
ExecStart=/usr/bin/env php artisan schedule:run --no-ansi --no-interaction
|
||||
User={{ librenms_user }}
|
||||
Group={{ librenms_group }}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=LibreNMS Scheduler
|
||||
|
||||
[Timer]
|
||||
OnCalendar=minutely
|
||||
AccuracySec=1second
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
14
ansible/roles/librenms/templates/php-fpm-librenms.conf.j2
Normal file
14
ansible/roles/librenms/templates/php-fpm-librenms.conf.j2
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[librenms]
|
||||
user = {{ librenms_user }}
|
||||
group = {{ librenms_group }}
|
||||
|
||||
listen = {{ librenms_php_fpm_socket }}
|
||||
|
||||
listen.owner = www-data
|
||||
listen.group = {{ librenms_group }}
|
||||
|
||||
pm = dynamic
|
||||
pm.max_children = {{ librenms_fpm_max_children }}
|
||||
pm.start_servers = {{ librenms_fpm_start_servers }}
|
||||
pm.min_spare_servers = {{ librenms_fpm_min_spare_servers }}
|
||||
pm.max_spare_servers = {{ librenms_fpm_max_spare_servers }}
|
||||
3
ansible/roles/librenms/templates/snmpd.conf.j2
Normal file
3
ansible/roles/librenms/templates/snmpd.conf.j2
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
rocommunity public 127.0.0.1
|
||||
syslocation "VNTX Data Center"
|
||||
syscontact admin@vntx.net
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
---
|
||||
# PostgreSQL version
|
||||
postgresql_version: 17
|
||||
|
||||
# Password management
|
||||
use_1password: false # Set to true to use 1Password for password storage
|
||||
op_account_id: "YOOATCZZSVGH7AD6VABUVPORLI" # Default 1Password account ID
|
||||
|
||||
# Storage configuration
|
||||
postgresql_storage_setup_enabled: false # Set to true to configure storage
|
||||
postgresql_storage_type: raid0 # Options: single_nvme, raid0
|
||||
postgresql_raid_device: /dev/md0
|
||||
postgresql_storage_device: "{{ postgresql_raid_device if postgresql_storage_type == 'raid0' else '/dev/nvme0n1p1' }}"
|
||||
postgresql_mount_point: /mnt/pgdata
|
||||
|
||||
# PostgreSQL configuration
|
||||
postgresql_data_directory: /mnt/pgdata/{{ postgresql_version }}/main
|
||||
postgresql_wal_directory: /mnt/pgdata/wal
|
||||
postgresql_archive_directory: /mnt/pgdata/archive
|
||||
postgresql_config_directory: /etc/postgresql/{{ postgresql_version }}/main
|
||||
|
||||
# Database settings for APRS.me
|
||||
postgresql_databases:
|
||||
- name: aprsme_prod
|
||||
owner: aprsme
|
||||
|
||||
postgresql_users:
|
||||
- name: aprsme
|
||||
password: "{{ vault_postgresql_aprsme_password }}" # Store in vault
|
||||
priv: "aprsme_prod:ALL"
|
||||
|
||||
# Performance tuning for CM3588 with 16GB RAM
|
||||
# These are optimized for APRS.me workload
|
||||
postgresql_config:
|
||||
# Connection settings
|
||||
listen_addresses: "'*'"
|
||||
max_connections: 200 # Increased for 16GB system
|
||||
|
||||
# Memory settings (for 16GB RAM system)
|
||||
shared_buffers: 4GB # 25% of RAM
|
||||
effective_cache_size: 12GB # 75% of RAM
|
||||
maintenance_work_mem: 1GB
|
||||
work_mem: 64MB # Increased for available memory
|
||||
|
||||
# Disk settings optimized for RAID0 SSDs
|
||||
random_page_cost: 1.1 # SSD optimization
|
||||
effective_io_concurrency: 200 # Modern SSD
|
||||
max_worker_processes: 4 # Pi 5 has 4 cores
|
||||
max_parallel_workers_per_gather: 2
|
||||
max_parallel_workers: 4
|
||||
max_parallel_maintenance_workers: 2
|
||||
|
||||
# Write performance settings
|
||||
wal_buffers: 32MB
|
||||
checkpoint_completion_target: 0.9
|
||||
checkpoint_timeout: 10min
|
||||
max_wal_size: 4GB
|
||||
min_wal_size: 512MB
|
||||
|
||||
# Logging
|
||||
log_min_duration_statement: 1000 # Log queries over 1 second
|
||||
log_checkpoints: on
|
||||
log_connections: on
|
||||
log_disconnections: on
|
||||
log_line_prefix: "'%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '"
|
||||
log_timezone: 'UTC'
|
||||
|
||||
# Query tuning
|
||||
default_statistics_target: 100
|
||||
|
||||
# Autovacuum tuning for high insert rate
|
||||
autovacuum: on
|
||||
autovacuum_max_workers: 4
|
||||
autovacuum_naptime: 30s # More frequent checks
|
||||
autovacuum_vacuum_threshold: 50
|
||||
autovacuum_analyze_threshold: 50
|
||||
autovacuum_vacuum_scale_factor: 0.1 # 10% for more frequent vacuums
|
||||
autovacuum_analyze_scale_factor: 0.05 # 5% for more frequent analyzes
|
||||
|
||||
# Enable query planning statistics
|
||||
track_activity_query_size: 2048
|
||||
track_io_timing: on
|
||||
track_functions: all
|
||||
|
||||
# PostGIS specific
|
||||
postgis.gdal_datapath: "'/usr/share/gdal'"
|
||||
|
||||
# Security settings
|
||||
postgresql_hba_entries:
|
||||
- type: local
|
||||
database: all
|
||||
user: postgres
|
||||
auth_method: peer
|
||||
- type: local
|
||||
database: all
|
||||
user: all
|
||||
auth_method: peer
|
||||
- type: host
|
||||
database: all
|
||||
user: all
|
||||
address: 127.0.0.1/32
|
||||
auth_method: scram-sha-256
|
||||
- type: host
|
||||
database: all
|
||||
user: all
|
||||
address: ::1/128
|
||||
auth_method: scram-sha-256
|
||||
- type: hostssl
|
||||
database: aprsme_prod
|
||||
user: aprsme
|
||||
address: 10.0.0.0/8 # Adjust for your network
|
||||
auth_method: scram-sha-256
|
||||
|
||||
# Backup settings
|
||||
postgresql_backup_enabled: true
|
||||
postgresql_backup_directory: /mnt/pgdata/backups
|
||||
postgresql_backup_schedule:
|
||||
minute: "0"
|
||||
hour: "2"
|
||||
day: "*"
|
||||
month: "*"
|
||||
weekday: "*"
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
- name: restart postgresql
|
||||
systemd:
|
||||
name: postgresql
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
|
||||
- name: reload postgresql
|
||||
systemd:
|
||||
name: postgresql
|
||||
state: reloaded
|
||||
|
||||
- name: restart pgbouncer
|
||||
systemd:
|
||||
name: pgbouncer
|
||||
state: restarted
|
||||
|
||||
- name: reload systemd
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: reload sysctl
|
||||
command: sysctl -p /etc/sysctl.d/30-postgresql.conf
|
||||
|
||||
- name: reload udev
|
||||
command: udevadm control --reload-rules && udevadm trigger
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
---
|
||||
# 1Password integration for PostgreSQL passwords
|
||||
# This file handles password generation and storage in 1Password
|
||||
|
||||
- name: Check if 1Password CLI is available
|
||||
command: which op
|
||||
register: op_cli_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Fail if 1Password CLI is not installed
|
||||
fail:
|
||||
msg: "1Password CLI (op) is not installed. Please install it first."
|
||||
when: op_cli_check.rc != 0
|
||||
|
||||
- name: Generate password for PostgreSQL user
|
||||
set_fact:
|
||||
generated_password: "{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}"
|
||||
when: vault_postgresql_aprsme_password is not defined
|
||||
no_log: true
|
||||
|
||||
- name: Create 1Password item for PostgreSQL password
|
||||
shell: |
|
||||
op item create \
|
||||
--category=database \
|
||||
--title="PostgreSQL {{ item.name }} - {{ ansible_hostname }}" \
|
||||
--vault="Private" \
|
||||
username="{{ item.name }}" \
|
||||
password="{{ generated_password | default(vault_postgresql_aprsme_password) }}" \
|
||||
hostname="{{ ansible_host }}" \
|
||||
port="5432" \
|
||||
database="{{ postgresql_databases[0].name }}" \
|
||||
--account="{{ op_account_id | default('YOOATCZZSVGH7AD6VABUVPORLI') }}" \
|
||||
--format=json || \
|
||||
op item edit \
|
||||
"PostgreSQL {{ item.name }} - {{ ansible_hostname }}" \
|
||||
password="{{ generated_password | default(vault_postgresql_aprsme_password) }}" \
|
||||
--account="{{ op_account_id | default('YOOATCZZSVGH7AD6VABUVPORLI') }}"
|
||||
delegate_to: localhost
|
||||
loop: "{{ postgresql_users }}"
|
||||
when: use_1password | default(false)
|
||||
no_log: true
|
||||
register: op_items
|
||||
|
||||
- name: Set password fact from 1Password or generated
|
||||
set_fact:
|
||||
postgresql_users_with_passwords: >-
|
||||
{%- set users = [] -%}
|
||||
{%- for user in postgresql_users -%}
|
||||
{%- set _ = user.update({'password': generated_password | default(vault_postgresql_aprsme_password)}) -%}
|
||||
{%- set _ = users.append(user) -%}
|
||||
{%- endfor -%}
|
||||
{{ users }}
|
||||
no_log: true
|
||||
|
|
@ -1,372 +0,0 @@
|
|||
---
|
||||
# Set up storage before installing PostgreSQL
|
||||
- name: Set up storage for PostgreSQL
|
||||
include_tasks: storage.yml
|
||||
when: postgresql_storage_setup_enabled | default(false)
|
||||
|
||||
- name: Download PostgreSQL GPG key
|
||||
ansible.builtin.get_url:
|
||||
url: https://www.postgresql.org/media/keys/ACCC4CF8.asc
|
||||
dest: /usr/share/keyrings/postgresql-archive-keyring.asc
|
||||
mode: '0644'
|
||||
|
||||
- name: Add PostgreSQL repository
|
||||
ansible.builtin.deb822_repository:
|
||||
name: pgdg
|
||||
types: deb
|
||||
uris: http://apt.postgresql.org/pub/repos/apt
|
||||
suites: "{{ ansible_distribution_release }}-pgdg"
|
||||
components: main
|
||||
signed_by: /usr/share/keyrings/postgresql-archive-keyring.asc
|
||||
state: present
|
||||
|
||||
- name: Install PostgreSQL and PostGIS
|
||||
apt:
|
||||
name:
|
||||
- postgresql-{{ postgresql_version }}
|
||||
- postgresql-client-{{ postgresql_version }}
|
||||
- postgresql-contrib-{{ postgresql_version }}
|
||||
- postgresql-{{ postgresql_version }}-postgis-3
|
||||
- postgresql-{{ postgresql_version }}-postgis-3-scripts
|
||||
- postgis
|
||||
- python3-psycopg2
|
||||
- libpq-dev
|
||||
- pgbouncer
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Stop PostgreSQL service if running
|
||||
systemd:
|
||||
name: postgresql
|
||||
state: stopped
|
||||
failed_when: false
|
||||
|
||||
- name: Disable PostgreSQL from starting automatically (we'll configure it first)
|
||||
systemd:
|
||||
name: postgresql
|
||||
enabled: false
|
||||
|
||||
- name: Remove any default PostgreSQL data directory
|
||||
file:
|
||||
path: /var/lib/postgresql/{{ postgresql_version }}/main
|
||||
state: absent
|
||||
|
||||
- name: Install locales package
|
||||
apt:
|
||||
name: locales
|
||||
state: present
|
||||
|
||||
- name: Generate en_US.UTF-8 locale
|
||||
locale_gen:
|
||||
name: en_US.UTF-8
|
||||
state: present
|
||||
|
||||
- name: Create PostgreSQL config directory structure
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: '0755'
|
||||
loop:
|
||||
- /etc/postgresql
|
||||
- /etc/postgresql/{{ postgresql_version }}
|
||||
- /etc/postgresql/{{ postgresql_version }}/main
|
||||
|
||||
- name: Create PostgreSQL cluster on RAID array
|
||||
command: |
|
||||
/usr/lib/postgresql/{{ postgresql_version }}/bin/initdb \
|
||||
-D {{ postgresql_data_directory }} \
|
||||
--wal-segsize=64 \
|
||||
--data-checksums \
|
||||
--encoding=UTF8 \
|
||||
--locale=C.UTF-8 \
|
||||
--lc-collate=C.UTF-8 \
|
||||
--lc-ctype=C.UTF-8
|
||||
args:
|
||||
creates: "{{ postgresql_data_directory }}/PG_VERSION"
|
||||
become_user: postgres
|
||||
environment:
|
||||
PGDATA: "{{ postgresql_data_directory }}"
|
||||
|
||||
- name: Set up SSL/TLS
|
||||
include_tasks: ssl.yml
|
||||
|
||||
- name: Configure PostgreSQL
|
||||
template:
|
||||
src: postgresql.conf.j2
|
||||
dest: "{{ postgresql_config_directory }}/postgresql.conf"
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: '0644'
|
||||
notify: restart postgresql
|
||||
|
||||
- name: Configure PostgreSQL authentication
|
||||
template:
|
||||
src: pg_hba.conf.j2
|
||||
dest: "{{ postgresql_config_directory }}/pg_hba.conf"
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: '0640'
|
||||
notify: restart postgresql
|
||||
|
||||
- name: Create sysctl configuration for PostgreSQL
|
||||
template:
|
||||
src: 30-postgresql.conf.j2
|
||||
dest: /etc/sysctl.d/30-postgresql.conf
|
||||
mode: '0644'
|
||||
notify: reload sysctl
|
||||
|
||||
- name: Create systemd override directory
|
||||
file:
|
||||
path: /etc/systemd/system/postgresql@.service.d
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Configure systemd limits for PostgreSQL
|
||||
template:
|
||||
src: postgresql-override.conf.j2
|
||||
dest: /etc/systemd/system/postgresql@.service.d/override.conf
|
||||
mode: '0644'
|
||||
notify:
|
||||
- reload systemd
|
||||
- restart postgresql
|
||||
|
||||
- name: Check if PostgreSQL cluster is properly initialized
|
||||
stat:
|
||||
path: "{{ postgresql_data_directory }}/PG_VERSION"
|
||||
register: pg_initialized
|
||||
|
||||
- name: Reinitialize PostgreSQL if corrupted
|
||||
block:
|
||||
- name: Stop PostgreSQL if running
|
||||
systemd:
|
||||
name: postgresql@{{ postgresql_version }}-main
|
||||
state: stopped
|
||||
failed_when: false
|
||||
|
||||
- name: Remove corrupted data directory
|
||||
file:
|
||||
path: "{{ postgresql_data_directory }}"
|
||||
state: absent
|
||||
|
||||
- name: Create data directory
|
||||
file:
|
||||
path: "{{ postgresql_data_directory }}"
|
||||
state: directory
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: '0700'
|
||||
|
||||
- name: Clear WAL directory for reinit
|
||||
file:
|
||||
path: "{{ postgresql_wal_directory }}"
|
||||
state: absent
|
||||
|
||||
- name: Create fresh WAL directory
|
||||
file:
|
||||
path: "{{ postgresql_wal_directory }}"
|
||||
state: directory
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: '0700'
|
||||
|
||||
- name: Initialize new PostgreSQL cluster
|
||||
command: |
|
||||
/usr/lib/postgresql/{{ postgresql_version }}/bin/initdb \
|
||||
-D {{ postgresql_data_directory }} \
|
||||
--wal-segsize=64 \
|
||||
--data-checksums \
|
||||
--encoding=UTF8 \
|
||||
--locale=C.UTF-8 \
|
||||
--lc-collate=C.UTF-8 \
|
||||
--lc-ctype=C.UTF-8 \
|
||||
--waldir={{ postgresql_wal_directory }}
|
||||
become_user: postgres
|
||||
environment:
|
||||
PGDATA: "{{ postgresql_data_directory }}"
|
||||
when: pg_initialized.stat.exists and force_reinit | default(false)
|
||||
|
||||
- name: Ensure WAL directory is properly configured
|
||||
block:
|
||||
- name: Check if WAL directory exists
|
||||
stat:
|
||||
path: "{{ postgresql_wal_directory }}"
|
||||
register: wal_dir_stat
|
||||
|
||||
- name: Create WAL directory if missing
|
||||
file:
|
||||
path: "{{ postgresql_wal_directory }}"
|
||||
state: directory
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: '0700'
|
||||
when: not wal_dir_stat.stat.exists
|
||||
|
||||
- name: Check if pg_wal is a symlink
|
||||
stat:
|
||||
path: "{{ postgresql_data_directory }}/pg_wal"
|
||||
register: pg_wal_stat
|
||||
|
||||
- name: Ensure pg_wal symlink has correct ownership
|
||||
file:
|
||||
path: "{{ postgresql_data_directory }}/pg_wal"
|
||||
owner: postgres
|
||||
group: postgres
|
||||
when: pg_wal_stat.stat.exists and pg_wal_stat.stat.islnk
|
||||
|
||||
- name: Start and enable PostgreSQL
|
||||
systemd:
|
||||
name: postgresql
|
||||
state: started
|
||||
enabled: true
|
||||
daemon_reload: true
|
||||
|
||||
- name: Wait for PostgreSQL to start
|
||||
wait_for:
|
||||
port: 5432
|
||||
host: localhost
|
||||
delay: 5
|
||||
|
||||
- name: Handle password management with 1Password
|
||||
include_tasks: 1password.yml
|
||||
when: use_1password | default(false)
|
||||
|
||||
- name: Create PostgreSQL users
|
||||
postgresql_user:
|
||||
name: "{{ item.name }}"
|
||||
password: "{{ item.password }}"
|
||||
encrypted: true
|
||||
state: present
|
||||
become: true
|
||||
become_user: postgres
|
||||
loop: "{{ postgresql_users }}"
|
||||
no_log: true
|
||||
|
||||
- name: Create databases
|
||||
postgresql_db:
|
||||
name: "{{ item.name }}"
|
||||
owner: "{{ item.owner }}"
|
||||
encoding: UTF-8
|
||||
lc_collate: en_US.UTF-8
|
||||
lc_ctype: en_US.UTF-8
|
||||
template: template0
|
||||
state: present
|
||||
become: true
|
||||
become_user: postgres
|
||||
loop: "{{ postgresql_databases }}"
|
||||
|
||||
- name: Enable PostGIS extension
|
||||
postgresql_ext:
|
||||
name: postgis
|
||||
db: "{{ item.name }}"
|
||||
state: present
|
||||
become: true
|
||||
become_user: postgres
|
||||
loop: "{{ postgresql_databases }}"
|
||||
|
||||
- name: Enable additional extensions
|
||||
postgresql_ext:
|
||||
name: "{{ item[1] }}"
|
||||
db: "{{ item[0].name }}"
|
||||
state: present
|
||||
become: true
|
||||
become_user: postgres
|
||||
loop: "{{ postgresql_databases | product(['pg_stat_statements', 'btree_gist', 'btree_gin']) | list }}"
|
||||
|
||||
- name: Grant privileges
|
||||
postgresql_privs:
|
||||
database: "{{ priv_item.split(':')[0] }}"
|
||||
privs: "{{ priv_item.split(':')[1] }}"
|
||||
type: database
|
||||
role: "{{ user.name }}"
|
||||
state: present
|
||||
become: true
|
||||
become_user: postgres
|
||||
loop: "{{ postgresql_users | selectattr('priv', 'defined') | map(attribute='priv') | list }}"
|
||||
loop_control:
|
||||
loop_var: priv_item
|
||||
vars:
|
||||
user: "{{ postgresql_users | selectattr('priv', 'equalto', priv_item) | first }}"
|
||||
|
||||
- name: Retrieve SCRAM hashes for PgBouncer
|
||||
postgresql_query:
|
||||
db: postgres
|
||||
query: "SELECT rolname, rolpassword FROM pg_authid WHERE rolname = %s"
|
||||
positional_args:
|
||||
- "{{ item.name }}"
|
||||
become: true
|
||||
become_user: postgres
|
||||
loop: "{{ postgresql_users }}"
|
||||
register: scram_hashes
|
||||
no_log: true
|
||||
|
||||
- name: Configure PgBouncer
|
||||
template:
|
||||
src: pgbouncer.ini.j2
|
||||
dest: /etc/pgbouncer/pgbouncer.ini
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: '0640'
|
||||
notify: restart pgbouncer
|
||||
|
||||
- name: Configure PgBouncer authentication
|
||||
template:
|
||||
src: pgbouncer-userlist.txt.j2
|
||||
dest: /etc/pgbouncer/userlist.txt
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: '0640'
|
||||
notify: restart pgbouncer
|
||||
|
||||
- name: Enable and start PgBouncer
|
||||
systemd:
|
||||
name: pgbouncer
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Set up backup script
|
||||
template:
|
||||
src: backup-postgresql.sh.j2
|
||||
dest: /usr/local/bin/backup-postgresql
|
||||
mode: '0755'
|
||||
when: postgresql_backup_enabled
|
||||
|
||||
- name: Create backup directory
|
||||
file:
|
||||
path: "{{ postgresql_backup_directory }}"
|
||||
state: directory
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: '0755'
|
||||
when: postgresql_backup_enabled
|
||||
|
||||
- name: Set up backup cron job
|
||||
cron:
|
||||
name: "PostgreSQL backup"
|
||||
minute: "{{ postgresql_backup_schedule.minute }}"
|
||||
hour: "{{ postgresql_backup_schedule.hour }}"
|
||||
day: "{{ postgresql_backup_schedule.day }}"
|
||||
month: "{{ postgresql_backup_schedule.month }}"
|
||||
weekday: "{{ postgresql_backup_schedule.weekday }}"
|
||||
user: postgres
|
||||
job: "/usr/local/bin/backup-postgresql"
|
||||
when: postgresql_backup_enabled
|
||||
|
||||
- name: Configure log rotation
|
||||
template:
|
||||
src: postgresql-logrotate.j2
|
||||
dest: /etc/logrotate.d/postgresql-custom
|
||||
mode: '0644'
|
||||
|
||||
- name: Install monitoring script
|
||||
template:
|
||||
src: check-postgresql.sh.j2
|
||||
dest: /usr/local/bin/check-postgresql
|
||||
mode: '0755'
|
||||
|
||||
- name: Set up monitoring cron job
|
||||
cron:
|
||||
name: "Check PostgreSQL health"
|
||||
minute: "*/5"
|
||||
job: "/usr/local/bin/check-postgresql || echo 'PostgreSQL health check failed' | logger -t postgresql-health"
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
---
|
||||
# SSL/TLS configuration for PostgreSQL
|
||||
|
||||
- name: Install OpenSSL
|
||||
apt:
|
||||
name: openssl
|
||||
state: present
|
||||
|
||||
- name: Create SSL directory
|
||||
file:
|
||||
path: "{{ postgresql_data_directory }}/ssl"
|
||||
state: directory
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: '0700'
|
||||
|
||||
- name: Check if SSL certificate exists
|
||||
stat:
|
||||
path: "{{ postgresql_data_directory }}/ssl/server.crt"
|
||||
register: ssl_cert
|
||||
|
||||
- name: Generate private key for PostgreSQL
|
||||
shell: |
|
||||
openssl genrsa -out {{ postgresql_data_directory }}/ssl/server.key 4096
|
||||
chmod 600 {{ postgresql_data_directory }}/ssl/server.key
|
||||
chown postgres:postgres {{ postgresql_data_directory }}/ssl/server.key
|
||||
when: not ssl_cert.stat.exists
|
||||
|
||||
- name: Generate self-signed certificate
|
||||
shell: |
|
||||
openssl req -new -x509 -days 3650 \
|
||||
-key {{ postgresql_data_directory }}/ssl/server.key \
|
||||
-out {{ postgresql_data_directory }}/ssl/server.crt \
|
||||
-subj "/C=US/ST=State/L=City/O=Organization/CN={{ ansible_hostname }}" \
|
||||
-addext "subjectAltName=DNS:{{ ansible_hostname }},DNS:{{ ansible_fqdn | default(ansible_hostname) }},IP:{{ ansible_default_ipv4.address }},IP:{{ ansible_host }}"
|
||||
chmod 644 {{ postgresql_data_directory }}/ssl/server.crt
|
||||
chown postgres:postgres {{ postgresql_data_directory }}/ssl/server.crt
|
||||
when: not ssl_cert.stat.exists
|
||||
|
||||
- name: Create CA certificate (copy server cert as CA for self-signed)
|
||||
copy:
|
||||
src: "{{ postgresql_data_directory }}/ssl/server.crt"
|
||||
dest: "{{ postgresql_data_directory }}/ssl/ca.crt"
|
||||
remote_src: true
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: '0644'
|
||||
|
||||
- name: Set proper permissions on SSL files
|
||||
file:
|
||||
path: "{{ item.path }}"
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: "{{ item.mode }}"
|
||||
loop:
|
||||
- { path: "{{ postgresql_data_directory }}/ssl/server.key", mode: '0600' }
|
||||
- { path: "{{ postgresql_data_directory }}/ssl/server.crt", mode: '0644' }
|
||||
- { path: "{{ postgresql_data_directory }}/ssl/ca.crt", mode: '0644' }
|
||||
|
||||
- name: Create symlinks in PostgreSQL config directory
|
||||
file:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
state: link
|
||||
owner: postgres
|
||||
group: postgres
|
||||
loop:
|
||||
- { src: "{{ postgresql_data_directory }}/ssl/server.crt", dest: "{{ postgresql_config_directory }}/server.crt" }
|
||||
- { src: "{{ postgresql_data_directory }}/ssl/server.key", dest: "{{ postgresql_config_directory }}/server.key" }
|
||||
- { src: "{{ postgresql_data_directory }}/ssl/ca.crt", dest: "{{ postgresql_config_directory }}/ca.crt" }
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
---
|
||||
# Storage setup for PostgreSQL on NVMe drives
|
||||
|
||||
- name: Install storage utilities
|
||||
apt:
|
||||
name:
|
||||
- mdadm
|
||||
- xfsprogs
|
||||
- nvme-cli
|
||||
- smartmontools
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Check for NVMe devices
|
||||
shell: nvme list -o json
|
||||
register: nvme_devices
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Create partition on NVMe drive
|
||||
parted:
|
||||
device: /dev/nvme0n1
|
||||
number: 1
|
||||
state: present
|
||||
label: gpt
|
||||
part_start: 0%
|
||||
part_end: 100%
|
||||
fs_type: ext4
|
||||
|
||||
- name: Create RAID0 array with single drive (for future expansion)
|
||||
command: |
|
||||
mdadm --create {{ postgresql_raid_device }} \
|
||||
--level=0 \
|
||||
--raid-devices=1 \
|
||||
--force \
|
||||
/dev/nvme0n1p1
|
||||
args:
|
||||
creates: "{{ postgresql_raid_device }}"
|
||||
when: postgresql_storage_type == "raid0"
|
||||
|
||||
- name: Wait for RAID array to be ready
|
||||
wait_for:
|
||||
path: "{{ postgresql_raid_device }}"
|
||||
state: present
|
||||
when: postgresql_storage_type == "raid0"
|
||||
|
||||
- name: Create filesystem on storage device
|
||||
filesystem:
|
||||
fstype: xfs
|
||||
dev: "{{ postgresql_storage_device }}"
|
||||
opts: "-L pgdata"
|
||||
when: postgresql_storage_device is defined
|
||||
|
||||
- name: Get filesystem UUID
|
||||
command: blkid -s UUID -o value {{ postgresql_storage_device }}
|
||||
register: fs_uuid
|
||||
changed_when: false
|
||||
when: postgresql_storage_device is defined
|
||||
|
||||
- name: Create mount points
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
loop:
|
||||
- "{{ postgresql_mount_point }}"
|
||||
- "{{ postgresql_data_directory }}"
|
||||
- "{{ postgresql_wal_directory }}"
|
||||
|
||||
- name: Mount PostgreSQL storage
|
||||
mount:
|
||||
path: "{{ postgresql_mount_point }}"
|
||||
src: "UUID={{ fs_uuid.stdout }}"
|
||||
fstype: xfs
|
||||
opts: "noatime,nodiratime"
|
||||
state: mounted
|
||||
when: fs_uuid.stdout is defined
|
||||
|
||||
- name: Save RAID configuration
|
||||
shell: mdadm --detail --scan >> /etc/mdadm/mdadm.conf
|
||||
when: postgresql_storage_type == "raid0"
|
||||
|
||||
- name: Update initramfs
|
||||
command: update-initramfs -u
|
||||
when: postgresql_storage_type == "raid0"
|
||||
|
||||
- name: Set optimal I/O scheduler for NVMe
|
||||
copy:
|
||||
content: |
|
||||
ACTION=="add|change", KERNEL=="nvme[0-9]n[0-9]", ATTR{queue/scheduler}="none"
|
||||
ACTION=="add|change", KERNEL=="nvme[0-9]n[0-9]", ATTR{queue/nr_requests}="1024"
|
||||
dest: /etc/udev/rules.d/60-nvme-scheduler.rules
|
||||
mode: '0644'
|
||||
notify: reload udev
|
||||
|
||||
- name: Configure sysctl for PostgreSQL performance
|
||||
sysctl:
|
||||
name: "{{ item.name }}"
|
||||
value: "{{ item.value }}"
|
||||
state: present
|
||||
reload: true
|
||||
loop:
|
||||
- { name: 'vm.dirty_background_ratio', value: '5' }
|
||||
- { name: 'vm.dirty_ratio', value: '10' }
|
||||
- { name: 'vm.swappiness', value: '10' }
|
||||
- { name: 'vm.overcommit_memory', value: '2' }
|
||||
- { name: 'vm.overcommit_ratio', value: '95' }
|
||||
register: sysctl_result
|
||||
failed_when:
|
||||
- sysctl_result.failed
|
||||
- "'No such file or directory' not in sysctl_result.msg | default('')"
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
# Kernel parameters for PostgreSQL optimization
|
||||
# For CM3588 with 16GB RAM and RAID0 SSDs
|
||||
|
||||
# Shared memory settings
|
||||
kernel.shmmax = 17179869184 # 16GB
|
||||
kernel.shmall = 4194304 # 16GB / 4096 (page size)
|
||||
kernel.shmmni = 4096
|
||||
|
||||
# Semaphores: semmsl, semmns, semopm, semmni
|
||||
kernel.sem = 250 32000 100 128
|
||||
|
||||
# Virtual memory
|
||||
vm.swappiness = 10
|
||||
vm.dirty_ratio = 15
|
||||
vm.dirty_background_ratio = 5
|
||||
vm.overcommit_memory = 2
|
||||
vm.overcommit_ratio = 95
|
||||
|
||||
# Huge pages (optional, can be enabled if needed)
|
||||
# vm.nr_hugepages = 4096 # 8GB of huge pages
|
||||
|
||||
# Network optimization for 2.5Gbit connection
|
||||
net.core.rmem_default = 262144
|
||||
net.core.rmem_max = 4194304
|
||||
net.core.wmem_default = 262144
|
||||
net.core.wmem_max = 4194304
|
||||
net.ipv4.tcp_rmem = 4096 87380 4194304
|
||||
net.ipv4.tcp_wmem = 4096 65536 4194304
|
||||
net.ipv4.tcp_keepalive_time = 60
|
||||
net.ipv4.tcp_keepalive_intvl = 10
|
||||
net.ipv4.tcp_keepalive_probes = 6
|
||||
|
||||
# File system
|
||||
fs.file-max = 65536
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
#!/bin/bash
|
||||
# PostgreSQL backup script for APRS.me
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
BACKUP_DIR="{{ postgresql_backup_directory }}"
|
||||
DB_NAME="aprsme_prod"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_${TIMESTAMP}.sql.gz"
|
||||
KEEP_DAYS=7
|
||||
|
||||
# Create backup directory if it doesn't exist
|
||||
mkdir -p "${BACKUP_DIR}"
|
||||
|
||||
# Log backup start
|
||||
echo "[$(date)] Starting backup of ${DB_NAME}" | logger -t postgresql-backup
|
||||
|
||||
# Perform backup with compression
|
||||
if pg_dump -Fc -Z9 "${DB_NAME}" > "${BACKUP_FILE}.tmp"; then
|
||||
mv "${BACKUP_FILE}.tmp" "${BACKUP_FILE}"
|
||||
echo "[$(date)] Backup completed: ${BACKUP_FILE}" | logger -t postgresql-backup
|
||||
|
||||
# Calculate backup size
|
||||
SIZE=$(du -h "${BACKUP_FILE}" | cut -f1)
|
||||
echo "[$(date)] Backup size: ${SIZE}" | logger -t postgresql-backup
|
||||
else
|
||||
echo "[$(date)] Backup failed for ${DB_NAME}" | logger -t postgresql-backup -p user.err
|
||||
rm -f "${BACKUP_FILE}.tmp"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up old backups
|
||||
echo "[$(date)] Cleaning up backups older than ${KEEP_DAYS} days" | logger -t postgresql-backup
|
||||
find "${BACKUP_DIR}" -name "${DB_NAME}_*.sql.gz" -type f -mtime +${KEEP_DAYS} -delete
|
||||
|
||||
# Verify backup integrity
|
||||
if pg_restore -l "${BACKUP_FILE}" > /dev/null 2>&1; then
|
||||
echo "[$(date)] Backup verification successful" | logger -t postgresql-backup
|
||||
else
|
||||
echo "[$(date)] Backup verification failed" | logger -t postgresql-backup -p user.err
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Optional: Copy to remote backup location
|
||||
# rsync -av "${BACKUP_FILE}" user@backup-server:/path/to/backups/
|
||||
|
||||
exit 0
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
#!/bin/bash
|
||||
# PostgreSQL health check script
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Check if PostgreSQL is running
|
||||
if ! systemctl is-active --quiet postgresql; then
|
||||
echo "PostgreSQL service is not running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we can connect to PostgreSQL
|
||||
if ! pg_isready -h localhost -p 5432 -U postgres -q; then
|
||||
echo "Cannot connect to PostgreSQL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check database connection
|
||||
if ! psql -h localhost -p 5432 -U postgres -d aprsme_prod -c "SELECT 1" > /dev/null 2>&1; then
|
||||
echo "Cannot connect to aprsme_prod database"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check PgBouncer
|
||||
if ! pg_isready -h localhost -p 6432 -U postgres -q; then
|
||||
echo "PgBouncer is not responding"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check replication lag (if applicable)
|
||||
LAG=$(psql -h localhost -p 5432 -U postgres -t -c "SELECT EXTRACT(EPOCH FROM (NOW() - pg_last_xact_replay_timestamp()))::INT" 2>/dev/null || echo "0")
|
||||
if [ "${LAG}" -gt "60" ]; then
|
||||
echo "Replication lag is too high: ${LAG} seconds"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check disk space
|
||||
DISK_USAGE=$(df -h /var/lib/postgresql | tail -1 | awk '{print $5}' | sed 's/%//')
|
||||
if [ "${DISK_USAGE}" -gt "90" ]; then
|
||||
echo "Disk usage is critical: ${DISK_USAGE}%"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check connection count
|
||||
CONNECTIONS=$(psql -h localhost -p 5432 -U postgres -t -c "SELECT COUNT(*) FROM pg_stat_activity" 2>/dev/null || echo "0")
|
||||
MAX_CONNECTIONS=$(psql -h localhost -p 5432 -U postgres -t -c "SHOW max_connections" 2>/dev/null || echo "200")
|
||||
if [ "${CONNECTIONS}" -gt "$(( ${MAX_CONNECTIONS} * 90 / 100 ))" ]; then
|
||||
echo "Connection count is high: ${CONNECTIONS}/${MAX_CONNECTIONS}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for long running queries
|
||||
LONG_QUERIES=$(psql -h localhost -p 5432 -U postgres -t -c "SELECT COUNT(*) FROM pg_stat_activity WHERE state != 'idle' AND query_start < NOW() - INTERVAL '5 minutes'" 2>/dev/null || echo "0")
|
||||
if [ "${LONG_QUERIES}" -gt "5" ]; then
|
||||
echo "Too many long running queries: ${LONG_QUERIES}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PostgreSQL health check passed"
|
||||
exit 0
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
# PostgreSQL Client Authentication Configuration File
|
||||
# ===================================================
|
||||
# Managed by Ansible - Do not edit manually
|
||||
|
||||
# TYPE DATABASE USER ADDRESS METHOD
|
||||
|
||||
{% for entry in postgresql_hba_entries %}
|
||||
{{ entry.type }} {{ entry.database }} {{ entry.user }} {{ entry.address | default('') }} {{ entry.auth_method }}
|
||||
{% endfor %}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# PgBouncer userlist file
|
||||
# Format: "username" "password_or_scram_hash"
|
||||
# Managed by Ansible
|
||||
# Note: When using SCRAM-SHA-256, this contains the SCRAM hash from pg_authid
|
||||
|
||||
{% for result in scram_hashes.results %}
|
||||
{% if result.query_result[0].rolpassword %}
|
||||
"{{ result.query_result[0].rolname }}" "{{ result.query_result[0].rolpassword }}"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
# PgBouncer stats user
|
||||
"pgbouncer" "pgbouncer"
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
[databases]
|
||||
# Connection string for aprsme database
|
||||
aprsme_prod = host=127.0.0.1 port=5432 dbname=aprsme_prod
|
||||
|
||||
[pgbouncer]
|
||||
# Connection settings
|
||||
listen_addr = *
|
||||
listen_port = 6432
|
||||
auth_type = scram-sha-256
|
||||
auth_file = /etc/pgbouncer/userlist.txt
|
||||
|
||||
# Pool settings optimized for APRS.me
|
||||
pool_mode = transaction
|
||||
max_client_conn = 1000
|
||||
default_pool_size = 25
|
||||
min_pool_size = 5
|
||||
reserve_pool_size = 5
|
||||
reserve_pool_timeout = 3
|
||||
|
||||
# Performance settings
|
||||
max_db_connections = 100
|
||||
max_user_connections = 100
|
||||
server_lifetime = 3600
|
||||
server_idle_timeout = 600
|
||||
server_connect_timeout = 15
|
||||
server_login_retry = 15
|
||||
query_timeout = 0
|
||||
query_wait_timeout = 120
|
||||
client_idle_timeout = 0
|
||||
client_login_timeout = 60
|
||||
|
||||
# Logging
|
||||
log_connections = 1
|
||||
log_disconnections = 1
|
||||
log_pooler_errors = 1
|
||||
stats_period = 60
|
||||
|
||||
# Admin settings
|
||||
admin_users = postgres
|
||||
stats_users = postgres, aprsme
|
||||
|
||||
# Security
|
||||
ignore_startup_parameters = extra_float_digits
|
||||
|
||||
# Resource limits
|
||||
pkt_buf = 4096
|
||||
max_packet_size = 2147483647
|
||||
listen_backlog = 128
|
||||
sbuf_loopcnt = 5
|
||||
tcp_defer_accept = 0
|
||||
tcp_socket_buffer = 0
|
||||
tcp_keepalive = 1
|
||||
tcp_keepcnt = 3
|
||||
tcp_keepidle = 60
|
||||
tcp_keepintvl = 10
|
||||
|
||||
# DNS settings
|
||||
dns_max_ttl = 15
|
||||
dns_nxdomain_ttl = 15
|
||||
|
||||
# TLS settings for client connections
|
||||
client_tls_sslmode = allow
|
||||
client_tls_cert_file = /etc/postgresql/{{ postgresql_version }}/main/server.crt
|
||||
client_tls_key_file = /etc/postgresql/{{ postgresql_version }}/main/server.key
|
||||
client_tls_ca_file = /etc/postgresql/{{ postgresql_version }}/main/ca.crt
|
||||
client_tls_protocols = secure
|
||||
client_tls_ciphers = HIGH:MEDIUM:+3DES:!aNULL
|
||||
|
||||
# TLS settings for server connections
|
||||
server_tls_sslmode = require
|
||||
server_tls_ca_file = /etc/postgresql/{{ postgresql_version }}/main/ca.crt
|
||||
server_tls_protocols = secure
|
||||
server_tls_ciphers = HIGH:MEDIUM:+3DES:!aNULL
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
/var/log/postgresql/*.log {
|
||||
daily
|
||||
rotate 7
|
||||
missingok
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
create 640 postgres postgres
|
||||
sharedscripts
|
||||
postrotate
|
||||
# Signal PostgreSQL to reopen log files
|
||||
if [ -f /var/run/postgresql/{{ postgresql_version }}-main.pid ]; then
|
||||
/usr/bin/pg_ctlcluster {{ postgresql_version }} main reload > /dev/null
|
||||
fi
|
||||
endscript
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
[Service]
|
||||
# Increase limits for PostgreSQL
|
||||
LimitNOFILE=65536
|
||||
LimitNPROC=4096
|
||||
|
||||
# Disable OOM killer for PostgreSQL
|
||||
OOMScoreAdjust=-1000
|
||||
|
||||
# CPU affinity - use all cores
|
||||
CPUAffinity=0-7
|
||||
|
||||
# Nice level for better priority
|
||||
Nice=-5
|
||||
|
||||
# I/O scheduling class and priority
|
||||
IOSchedulingClass=best-effort
|
||||
IOSchedulingPriority=0
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
# PostgreSQL configuration for APRS.me on CM3588
|
||||
# Optimized for RK3588 with RAID0 SSDs
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# FILE LOCATIONS
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
data_directory = '{{ postgresql_data_directory }}'
|
||||
hba_file = '{{ postgresql_config_directory }}/pg_hba.conf'
|
||||
ident_file = '{{ postgresql_config_directory }}/pg_ident.conf'
|
||||
external_pid_file = '/var/run/postgresql/{{ postgresql_version }}-main.pid'
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# CONNECTIONS AND AUTHENTICATION
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
{% for key, value in postgresql_config.items() %}
|
||||
{{ key }} = {{ value }}
|
||||
{% endfor %}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# RESOURCE USAGE (except WAL)
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# These settings are configured above in postgresql_config
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# WRITE-AHEAD LOG
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# WAL settings configured above
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# REPLICATION
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
wal_level = replica
|
||||
archive_mode = on
|
||||
archive_command = 'test ! -f {{ postgresql_archive_directory }}/%f && cp %p {{ postgresql_archive_directory }}/%f'
|
||||
max_wal_senders = 3
|
||||
wal_keep_size = 1GB
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# QUERY TUNING
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# Planner settings for PostGIS and complex queries
|
||||
enable_partitionwise_join = on
|
||||
enable_partitionwise_aggregate = on
|
||||
jit = on
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# REPORTING AND LOGGING
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# Logging configuration set above
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# STATISTICS
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# Stats configuration set above
|
||||
shared_preload_libraries = 'pg_stat_statements'
|
||||
pg_stat_statements.max = 10000
|
||||
pg_stat_statements.track = all
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# AUTOVACUUM
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# Autovacuum settings configured above
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# CLIENT CONNECTION DEFAULTS
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
datestyle = 'iso, mdy'
|
||||
timezone = 'UTC'
|
||||
lc_messages = 'en_US.UTF-8'
|
||||
lc_monetary = 'en_US.UTF-8'
|
||||
lc_numeric = 'en_US.UTF-8'
|
||||
lc_time = 'en_US.UTF-8'
|
||||
default_text_search_config = 'pg_catalog.english'
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# LOCK MANAGEMENT
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
deadlock_timeout = 1s
|
||||
max_locks_per_transaction = 128
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# VERSION/PLATFORM COMPATIBILITY
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# No specific settings needed
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# ERROR HANDLING
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
exit_on_error = off
|
||||
restart_after_crash = on
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# SSL/TLS CONFIGURATION
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
ssl = on
|
||||
ssl_cert_file = '/etc/postgresql/{{ postgresql_version }}/main/server.crt'
|
||||
ssl_key_file = '/etc/postgresql/{{ postgresql_version }}/main/server.key'
|
||||
ssl_ca_file = '/etc/postgresql/{{ postgresql_version }}/main/ca.crt'
|
||||
ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL'
|
||||
ssl_prefer_server_ciphers = on
|
||||
ssl_min_protocol_version = 'TLSv1.2'
|
||||
|
|
@ -1,15 +1,11 @@
|
|||
---
|
||||
# Handler for the webtier: handlers are called by other plays.
|
||||
# See http://docs.ansible.com/playbooks_intro.html for more information about handlers.
|
||||
|
||||
- name: Restart unbound
|
||||
ansible.builtin.service:
|
||||
name: unbound
|
||||
state: restarted
|
||||
become: true
|
||||
|
||||
- name: Restart firewalld
|
||||
ansible.builtin.service:
|
||||
name: firewalld
|
||||
state: restarted
|
||||
become: true
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,31 @@
|
|||
ansible.builtin.dnf:
|
||||
name:
|
||||
- unbound
|
||||
- certbot
|
||||
state: present
|
||||
|
||||
- name: Enable service Unbound and ensure it is not masked
|
||||
- name: Create systemd override directory for unbound
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/system/unbound.service.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
|
||||
- name: Fix unbound sd_notify failure (disable notify type)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/unbound.service.d/override.conf
|
||||
content: |
|
||||
[Service]
|
||||
Type=simple
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
register: unbound_override
|
||||
|
||||
- name: Reload systemd after override change
|
||||
ansible.builtin.systemd:
|
||||
name: unbound
|
||||
enabled: true
|
||||
masked: false
|
||||
daemon_reload: true
|
||||
when: unbound_override.changed
|
||||
|
||||
- name: Create Unbound config
|
||||
ansible.builtin.template:
|
||||
|
|
@ -20,25 +37,18 @@
|
|||
notify:
|
||||
- Restart unbound
|
||||
|
||||
- name: Enable and start Unbound
|
||||
ansible.builtin.systemd:
|
||||
name: unbound
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Permit traffic in default zone for dns service
|
||||
ansible.posix.firewalld:
|
||||
zone: public
|
||||
service: dns
|
||||
permanent: true
|
||||
state: enabled
|
||||
|
||||
- name: Permit traffic in default zone for http service
|
||||
ansible.posix.firewalld:
|
||||
zone: public
|
||||
service: http
|
||||
permanent: true
|
||||
state: enabled
|
||||
|
||||
- name: Permit traffic in default zone for https service
|
||||
ansible.posix.firewalld:
|
||||
zone: public
|
||||
service: https
|
||||
permanent: true
|
||||
state: enabled
|
||||
when: ansible_facts.services['firewalld.service'] is defined
|
||||
notify:
|
||||
- Restart firewalld
|
||||
|
|
|
|||
8
ansible/roles/uisp/defaults/main.yml
Normal file
8
ansible/roles/uisp/defaults/main.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
uisp_user: unms
|
||||
uisp_user_uid: 1001
|
||||
uisp_group: unms
|
||||
uisp_group_gid: 1001
|
||||
uisp_home: /home/unms
|
||||
uisp_app_dir: /home/unms/app
|
||||
uisp_data_dir: /home/unms/data
|
||||
14
ansible/roles/uisp/handlers/main.yml
Normal file
14
ansible/roles/uisp/handlers/main.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
- name: Reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Restart containerd
|
||||
ansible.builtin.service:
|
||||
name: containerd
|
||||
state: restarted
|
||||
|
||||
- name: Restart docker
|
||||
ansible.builtin.service:
|
||||
name: docker
|
||||
state: restarted
|
||||
103
ansible/roles/uisp/tasks/main.yml
Normal file
103
ansible/roles/uisp/tasks/main.yml
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
---
|
||||
- name: Install Docker prerequisites
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- apt-transport-https
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Add Docker GPG key
|
||||
ansible.builtin.get_url:
|
||||
url: https://download.docker.com/linux/debian/gpg
|
||||
dest: /etc/apt/keyrings/docker.asc
|
||||
mode: '0644'
|
||||
|
||||
- name: Add Docker APT repository
|
||||
ansible.builtin.apt_repository:
|
||||
repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable"
|
||||
filename: docker
|
||||
state: present
|
||||
|
||||
- name: Install Docker CE
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-buildx-plugin
|
||||
- docker-compose-plugin
|
||||
- docker-ce-rootless-extras
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Create UISP group
|
||||
ansible.builtin.group:
|
||||
name: "{{ uisp_group }}"
|
||||
gid: "{{ uisp_group_gid }}"
|
||||
state: present
|
||||
|
||||
- name: Create UISP user
|
||||
ansible.builtin.user:
|
||||
name: "{{ uisp_user }}"
|
||||
uid: "{{ uisp_user_uid }}"
|
||||
group: "{{ uisp_group }}"
|
||||
groups: docker
|
||||
append: yes
|
||||
home: "{{ uisp_home }}"
|
||||
shell: /bin/sh
|
||||
state: present
|
||||
|
||||
- name: Ensure Docker is running
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
- name: Create containerd killmode override directory
|
||||
ansible.builtin.file:
|
||||
path: /usr/lib/systemd/system/containerd.service.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
|
||||
- name: Configure containerd KillMode for UISP
|
||||
ansible.builtin.copy:
|
||||
dest: /usr/lib/systemd/system/containerd.service.d/unms-killmode.conf
|
||||
content: |
|
||||
[Service]
|
||||
KillMode=mixed
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify:
|
||||
- Reload systemd
|
||||
- Restart containerd
|
||||
|
||||
- name: Ensure UISP app directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ uisp_app_dir }}"
|
||||
state: directory
|
||||
owner: "{{ uisp_user }}"
|
||||
group: root
|
||||
mode: '0700'
|
||||
|
||||
- name: Ensure UISP data directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ uisp_data_dir }}"
|
||||
state: directory
|
||||
owner: "{{ uisp_user }}"
|
||||
group: root
|
||||
mode: '0700'
|
||||
|
||||
- name: Deploy UISP auto-update cron job
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/cron.d/unms-update
|
||||
content: |
|
||||
* * * * * {{ uisp_user }} {{ uisp_app_dir }}/update.sh --cron > /dev/null 2>&1 || true
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
|
|
@ -333,7 +333,7 @@ resource "cloudflare_zone_settings_override" "towerops_net" {
|
|||
always_use_https = "on"
|
||||
tls_1_3 = "zrt"
|
||||
zero_rtt = "on"
|
||||
ssl = "strict"
|
||||
ssl = "full"
|
||||
|
||||
security_header {
|
||||
enabled = true
|
||||
|
|
@ -397,6 +397,20 @@ resource "cloudflare_ruleset" "towerops_firewall" {
|
|||
kind = "zone"
|
||||
phase = "http_request_firewall_custom"
|
||||
|
||||
# Allow agent WebSocket connections (must come before block rules)
|
||||
rules {
|
||||
action = "skip"
|
||||
action_parameters {
|
||||
ruleset = "current"
|
||||
}
|
||||
expression = "(http.request.uri.path eq \"/socket/agent/websocket\")"
|
||||
description = "allow agent websocket connections"
|
||||
enabled = true
|
||||
logging {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
rules {
|
||||
action = "block"
|
||||
expression = local.firewall_expression
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue