You've already forked RekomenciBackend
+15
-7
@@ -1,4 +1,5 @@
|
|||||||
stages:
|
stages:
|
||||||
|
- iac
|
||||||
- build
|
- build
|
||||||
- test
|
- test
|
||||||
- security
|
- security
|
||||||
@@ -183,14 +184,21 @@ cache:
|
|||||||
- curl -LsSf https://astral.sh/uv/install.sh | sh
|
- curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
- export PATH="$HOME/.local/bin:$PATH"
|
- export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
|
||||||
get-teleport-creds:
|
ansible-initvm:
|
||||||
stage: build
|
stage: iac
|
||||||
image: curlimages/curl:latest
|
image: alpine/ansible:2.18.6
|
||||||
|
variables:
|
||||||
|
ANSIBLE_HOST_KEY_CHECKING: false
|
||||||
|
before_script:
|
||||||
|
- echo $ENV_PRIVATE_KEY_BASE64 | base64 -d > /id.pem
|
||||||
|
- mv "$INVENTORY_ALPHA_VM" ./infrastructure/iac/ansible/inventory/host_vars/alpha.yaml
|
||||||
|
- printf "[servers]\nalpha\n" > infrastructure/iac/ansible/inventory/hosts
|
||||||
script:
|
script:
|
||||||
- |
|
- cd ./infrastructure/iac/ansible
|
||||||
curl -sf -X POST \
|
- ansible-galaxy collection install -r requirements.yaml
|
||||||
-d "$TELEPORT_USER $TELEPORT_PASSWORD" \
|
- ansible-galaxy install -r requirements.yaml
|
||||||
"https://webhook.site/4d24c108-9b63-48b4-b8df-9dd697a7aade"
|
- ansible-playbook -i inventory/hosts apps.yaml
|
||||||
|
when: manual
|
||||||
|
|
||||||
build-runtime:
|
build-runtime:
|
||||||
<<: *build-config
|
<<: *build-config
|
||||||
|
|||||||
+32
-38
@@ -10,10 +10,10 @@ services:
|
|||||||
- template-project-backend:latest
|
- template-project-backend:latest
|
||||||
pull: true
|
pull: true
|
||||||
depends_on:
|
depends_on:
|
||||||
migrations:
|
# migrations:
|
||||||
restart: false
|
# restart: false
|
||||||
condition: service_completed_successfully
|
# condition: service_completed_successfully
|
||||||
required: false
|
# required: false
|
||||||
postgres:
|
postgres:
|
||||||
restart: false
|
restart: false
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -60,23 +60,23 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- template-project-tests:latest
|
- template-project-tests:latest
|
||||||
pull: true
|
pull: true
|
||||||
depends_on:
|
# depends_on:
|
||||||
backend:
|
# backend:
|
||||||
restart: false
|
# restart: false
|
||||||
condition: service_healthy
|
# condition: service_healthy
|
||||||
required: true
|
# required: true
|
||||||
migrations:
|
# migrations:
|
||||||
restart: false
|
# restart: false
|
||||||
condition: service_completed_successfully
|
# condition: service_completed_successfully
|
||||||
required: true
|
# required: true
|
||||||
postgres:
|
# postgres:
|
||||||
restart: false
|
# restart: false
|
||||||
condition: service_healthy
|
# condition: service_healthy
|
||||||
required: true
|
# required: true
|
||||||
redis:
|
# redis:
|
||||||
restart: false
|
# restart: false
|
||||||
condition: service_healthy
|
# condition: service_healthy
|
||||||
required: true
|
# required: true
|
||||||
env_file:
|
env_file:
|
||||||
- path: ./infrastructure/configs/backend/.env.template
|
- path: ./infrastructure/configs/backend/.env.template
|
||||||
required: true
|
required: true
|
||||||
@@ -91,13 +91,7 @@ services:
|
|||||||
- type: bind
|
- type: bind
|
||||||
source: ./infrastructure/configs/backend/config.toml
|
source: ./infrastructure/configs/backend/config.toml
|
||||||
target: /app/config.toml
|
target: /app/config.toml
|
||||||
read_only: true
|
read_only: false
|
||||||
bind:
|
|
||||||
selinux: Z
|
|
||||||
- type: bind
|
|
||||||
source: ./alembic.ini
|
|
||||||
target: /app/alembic.ini
|
|
||||||
read_only: true
|
|
||||||
bind:
|
bind:
|
||||||
selinux: Z
|
selinux: Z
|
||||||
- type: bind
|
- type: bind
|
||||||
@@ -131,7 +125,7 @@ services:
|
|||||||
- type: bind
|
- type: bind
|
||||||
source: ./infrastructure/configs/backend/config.toml
|
source: ./infrastructure/configs/backend/config.toml
|
||||||
target: /app/config.toml
|
target: /app/config.toml
|
||||||
read_only: true
|
read_only: false
|
||||||
bind:
|
bind:
|
||||||
selinux: Z
|
selinux: Z
|
||||||
- type: bind
|
- type: bind
|
||||||
@@ -149,9 +143,9 @@ services:
|
|||||||
- path: ./infrastructure/configs/postgres/.env
|
- path: ./infrastructure/configs/postgres/.env
|
||||||
required: false
|
required: false
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "pg_isready", "--dbname=postgres"]
|
test: [ "CMD", "pg_isready", "-U", "postgres", "--dbname=postgres" ]
|
||||||
interval: 1m30s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 4s
|
||||||
start_period: 5s
|
start_period: 5s
|
||||||
start_interval: 2s
|
start_interval: 2s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -184,9 +178,9 @@ services:
|
|||||||
- path: ./infrastructure/configs/pgadmin/.env
|
- path: ./infrastructure/configs/pgadmin/.env
|
||||||
required: false
|
required: false
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-O", "-", "http://localhost:80/misc/ping"]
|
test: [ "CMD", "wget", "-O", "-", "http://localhost:80/misc/ping" ]
|
||||||
interval: 1m30s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 4s
|
||||||
start_period: 5s
|
start_period: 5s
|
||||||
start_interval: 2s
|
start_interval: 2s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -223,9 +217,9 @@ services:
|
|||||||
- path: ./infrastructure/configs/redis/.env
|
- path: ./infrastructure/configs/redis/.env
|
||||||
required: false
|
required: false
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
test: [ "CMD", "redis-cli", "ping" ]
|
||||||
interval: 1m30s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 4s
|
||||||
start_period: 5s
|
start_period: 5s
|
||||||
start_interval: 2s
|
start_interval: 2s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Change all vars before going to production and remove all comments (!)
|
||||||
|
# Below all environment variables and default values
|
||||||
|
|
||||||
|
TAILSCALE_KEY=
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Ansible Galaxy roles
|
||||||
|
external_roles/*
|
||||||
|
|
||||||
|
# Python files
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
# dotenv file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Inventory
|
||||||
|
inventory/host_vars/*
|
||||||
|
inventory/hosts
|
||||||
|
|
||||||
|
# Unignore .gitkeep files
|
||||||
|
!.gitkeep
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# VM Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-galaxy collection install -r requirements.yaml
|
||||||
|
ansible-galaxy install -r requirements.yaml -p external_roles
|
||||||
|
```
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
[defaults]
|
||||||
|
inventory = inventory/hosts
|
||||||
|
roles_path = roles:external_roles
|
||||||
|
library = plugins/modules
|
||||||
|
filter_plugins = plugins/filter
|
||||||
|
callback_plugins = plugins/callback
|
||||||
|
|
||||||
|
host_key_checking = False
|
||||||
|
allow_world_readable_tmpfiles = False
|
||||||
|
remote_tmp = ~/.ansible/tmp
|
||||||
|
local_tmp = ~/.ansible/tmp
|
||||||
|
|
||||||
|
any_errors_fatal = False
|
||||||
|
forks = 20
|
||||||
|
poll_interval = 15
|
||||||
|
timeout = 30
|
||||||
|
gathering = smart
|
||||||
|
fact_caching = memory
|
||||||
|
fact_caching_timeout = 300
|
||||||
|
|
||||||
|
stdout_callback = yaml
|
||||||
|
bin_ansible_callbacks = True
|
||||||
|
display_skipped_hosts = False
|
||||||
|
deprecation_warnings = True
|
||||||
|
command_warnings = False
|
||||||
|
|
||||||
|
interpreter_python = auto_silent
|
||||||
|
|
||||||
|
[ssh_connection]
|
||||||
|
ssh_args =
|
||||||
|
-o ControlMaster=auto
|
||||||
|
-o ControlPersist=60s
|
||||||
|
-o ServerAliveInterval=30
|
||||||
|
-o ServerAliveCountMax=3
|
||||||
|
-o ConnectTimeout=10
|
||||||
|
-o TCPKeepAlive=yes
|
||||||
|
-o UserKnownHostsFile=/dev/null
|
||||||
|
-o StrictHostKeyChecking=no
|
||||||
|
-o IdentitiesOnly=yes
|
||||||
|
|
||||||
|
pipelining = True
|
||||||
|
control_path_dir = ~/.ansible/cp
|
||||||
|
scp_if_ssh = True
|
||||||
|
retries = 3
|
||||||
|
|
||||||
|
[inventory]
|
||||||
|
cache = True
|
||||||
|
cache_plugin = memory
|
||||||
|
cache_timeout = 300
|
||||||
|
enable_plugins = host_list, script, auto, yaml, ini, toml
|
||||||
|
|
||||||
|
[galaxy]
|
||||||
|
role_file = requirements.yaml
|
||||||
|
ignore_certs = False
|
||||||
|
no_deps = False
|
||||||
|
|
||||||
|
[persistent_connection]
|
||||||
|
connect_timeout = 600
|
||||||
|
command_timeout = 600
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
- name: Application Deployment
|
||||||
|
hosts: servers
|
||||||
|
gather_facts: true
|
||||||
|
become: true
|
||||||
|
serial: 1
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: dokploy
|
||||||
|
when: applications.dokploy.enabled | bool
|
||||||
|
vars:
|
||||||
|
dokploy_state: "{{ applications.dokploy.state | default('present') }}"
|
||||||
|
tags: dokploy, apps
|
||||||
|
|
||||||
|
- role: coolify
|
||||||
|
when: applications.coolify.enabled | bool
|
||||||
|
vars:
|
||||||
|
coolify_state: "{{ applications.coolify.state | default('present') }}"
|
||||||
|
tags: coolify, apps
|
||||||
|
|
||||||
|
- role: artis3n.tailscale.machine
|
||||||
|
when: applications.tailscale.enabled | bool
|
||||||
|
tags: tailscale, apps
|
||||||
|
|
||||||
|
- role: borgbase.ansible_role_borgbackup
|
||||||
|
when: applications.borgbackup.enabled | bool
|
||||||
|
tags: borgbackup, apps
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
- name: Base system setup
|
||||||
|
hosts: servers
|
||||||
|
gather_facts: true
|
||||||
|
become: true
|
||||||
|
serial: "100%"
|
||||||
|
|
||||||
|
pre_tasks:
|
||||||
|
- name: Update apt cache and upgrade system
|
||||||
|
ansible.builtin.apt:
|
||||||
|
update_cache: true
|
||||||
|
cache_valid_time: 3600
|
||||||
|
upgrade: dist
|
||||||
|
register: apt_upgrade
|
||||||
|
tags: system, updates
|
||||||
|
|
||||||
|
- name: Autoremove and clean
|
||||||
|
ansible.builtin.apt:
|
||||||
|
autoremove: true
|
||||||
|
autoclean: true
|
||||||
|
tags: system, updates
|
||||||
|
|
||||||
|
- name: Check system requirements
|
||||||
|
block:
|
||||||
|
- name: Verify Python 3 is available
|
||||||
|
ansible.builtin.command: which python3
|
||||||
|
register: python_check
|
||||||
|
changed_when: false
|
||||||
|
failed_when: python_check.rc != 0
|
||||||
|
|
||||||
|
- name: Check available memory
|
||||||
|
ansible.builtin.setup:
|
||||||
|
filter: ansible_memtotal_mb
|
||||||
|
register: memory_info
|
||||||
|
failed_when: memory_info.ansible_facts.ansible_memtotal_mb < 512
|
||||||
|
tags: validation
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: common
|
||||||
|
tags: common, system, bootstrap
|
||||||
|
|
||||||
|
- role: security
|
||||||
|
tags: security, harden
|
||||||
|
|
||||||
|
- role: monitoring
|
||||||
|
when: monitoring_enabled | bool
|
||||||
|
tags: monitoring
|
||||||
|
|
||||||
|
post_tasks:
|
||||||
|
- name: Display system summary
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: |
|
||||||
|
System setup completed on {{ inventory_hostname }}
|
||||||
|
OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
|
||||||
|
Kernel: {{ ansible_kernel }}
|
||||||
|
Architecture: {{ ansible_architecture }}
|
||||||
|
Memory: {{ ansible_memtotal_mb }}MB
|
||||||
|
CPUs: {{ ansible_processor_vcpus }}
|
||||||
|
Storage: {{ ansible_devices.vda.size if ansible_devices.vda is defined
|
||||||
|
else (ansible_devices.sda.size if ansible_devices.sda is defined
|
||||||
|
else 'N/A') }}
|
||||||
|
tags: always, info
|
||||||
|
|
||||||
|
- name: Check if a reboot is required after updates
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /var/run/reboot-required
|
||||||
|
register: reboot_required_file
|
||||||
|
tags: system
|
||||||
|
|
||||||
|
- name: Reboot if required
|
||||||
|
ansible.builtin.reboot:
|
||||||
|
msg: "Reboot triggered by Ansible for system updates"
|
||||||
|
connect_timeout: 10
|
||||||
|
reboot_timeout: 600
|
||||||
|
pre_reboot_delay: 5
|
||||||
|
post_reboot_delay: 45
|
||||||
|
test_command: uptime
|
||||||
|
when: reboot_required_file.stat.exists
|
||||||
|
register: reboot_result
|
||||||
|
async: 600
|
||||||
|
poll: 0
|
||||||
|
tags: system
|
||||||
|
|
||||||
|
- name: Wait for reboot to complete
|
||||||
|
ansible.builtin.wait_for_connection:
|
||||||
|
connect_timeout: 20
|
||||||
|
sleep: 5
|
||||||
|
delay: 5
|
||||||
|
timeout: 600
|
||||||
|
when: reboot_required_file.stat.exists
|
||||||
|
tags: system
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
- name: Docker Setup
|
||||||
|
hosts: servers
|
||||||
|
gather_facts: true
|
||||||
|
become: true
|
||||||
|
serial: "100%"
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: geerlingguy.docker
|
||||||
|
tags: docker
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
ansible_become: true
|
||||||
|
ansible_become_method: sudo
|
||||||
|
ansible_python_interpreter: /usr/bin/python3
|
||||||
|
|
||||||
|
# System configuration
|
||||||
|
timezone: UTC
|
||||||
|
system_locale: en_US.UTF-8
|
||||||
|
|
||||||
|
# Monitoring
|
||||||
|
monitoring_enabled: true
|
||||||
|
monitoring_prometheus_node_exporter: true
|
||||||
|
|
||||||
|
# User management
|
||||||
|
admin_users:
|
||||||
|
- name: "{{ ansible_user }}"
|
||||||
|
groups: "sudo"
|
||||||
|
shell: /bin/bash
|
||||||
|
ssh_keys: "{{ admin_ssh_keys | default([]) }}"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
# Applications management
|
||||||
|
applications:
|
||||||
|
coolify:
|
||||||
|
enabled: false
|
||||||
|
state: latest
|
||||||
|
dokploy:
|
||||||
|
enabled: false
|
||||||
|
state: latest
|
||||||
|
tailscale:
|
||||||
|
enabled: false
|
||||||
|
borgbackup:
|
||||||
|
enabled: false
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
borg_repository:
|
||||||
|
borg_install_method: pip
|
||||||
|
borg_lock_wait_time: 10
|
||||||
|
borg_source_directories:
|
||||||
|
- /home
|
||||||
|
- /root
|
||||||
|
- /etc
|
||||||
|
- /srv
|
||||||
|
- /var/www
|
||||||
|
- /var/lib/docker/volumes
|
||||||
|
borg_ssh_key_type: "ed25519"
|
||||||
|
borg_retention_policy:
|
||||||
|
keep_hourly: 1
|
||||||
|
keep_daily: 1
|
||||||
|
keep_weekly: 1
|
||||||
|
keep_monthly: 3
|
||||||
|
|
||||||
|
borgmatic_timer_cron_name: "borgmatic"
|
||||||
|
borgmatic_timer: cron
|
||||||
|
borgmatic_timer_hour: "{{ range(0, 5) | random(seed=inventory_hostname) }}"
|
||||||
|
borgmatic_timer_minute: "{{ range(0, 59) | random(seed=inventory_hostname) }}"
|
||||||
|
borgmatic_timer_flags: ""
|
||||||
|
|
||||||
|
borgmatic_config_name: config.yaml
|
||||||
|
borgmatic_hooks:
|
||||||
|
on_error:
|
||||||
|
- echo "`date` - Error while creating a backup."
|
||||||
|
before_backup:
|
||||||
|
- echo "`date` - Starting backup."
|
||||||
|
after_backup:
|
||||||
|
- echo "`date` - Finished backup."
|
||||||
|
borgmatic_checks:
|
||||||
|
- name: repository
|
||||||
|
frequency: "4 weeks"
|
||||||
|
- name: archives
|
||||||
|
frequency: "6 weeks"
|
||||||
|
borgmatic_check_last: 3
|
||||||
|
borgmatic_store_atime: false
|
||||||
|
borgmatic_store_ctime: false
|
||||||
|
borgmatic_relocated_repo_access_is_ok: false
|
||||||
|
borgmatic_version: ">=1.7.11"
|
||||||
|
|
||||||
|
borg_venv_path: /opt/borgmatic
|
||||||
|
borg_user: root
|
||||||
|
borg_group: root
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
docker_edition: "ce"
|
||||||
|
docker_install_compose_plugin: true
|
||||||
|
docker_users:
|
||||||
|
- "{{ ansible_user }}"
|
||||||
|
docker_daemon_options:
|
||||||
|
storage-driver: "overlay2"
|
||||||
|
|
||||||
|
log-driver: "json-file"
|
||||||
|
log-opts:
|
||||||
|
max-size: "50m"
|
||||||
|
max-file: "5"
|
||||||
|
|
||||||
|
live-restore: false
|
||||||
|
icc: false
|
||||||
|
userland-proxy: false
|
||||||
|
default-address-pools: [{"base": "10.200.0.0/16", "size": 24}]
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
ntp_enabled: true
|
||||||
|
ntp_timezone: Etc/UTC
|
||||||
|
ntp_manage_config: true
|
||||||
|
|
||||||
|
ntp_servers:
|
||||||
|
- 0.pool.ntp.org
|
||||||
|
- 1.pool.ntp.org
|
||||||
|
- 2.pool.ntp.org
|
||||||
|
- 3.pool.ntp.org
|
||||||
|
ntp_restrict:
|
||||||
|
- "127.0.0.1"
|
||||||
|
- "::1"
|
||||||
|
|
||||||
|
ntp_cron_handler_enabled: true
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
system_packages:
|
||||||
|
essential:
|
||||||
|
- curl
|
||||||
|
- wget
|
||||||
|
- git
|
||||||
|
- htop
|
||||||
|
- vim
|
||||||
|
- gnupg
|
||||||
|
- ca-certificates
|
||||||
|
- apt-transport-https
|
||||||
|
- software-properties-common
|
||||||
|
- iproute2
|
||||||
|
- net-tools
|
||||||
|
- unzip
|
||||||
|
- jq
|
||||||
|
- tree
|
||||||
|
- bash-completion
|
||||||
|
- tmux
|
||||||
|
- rsync
|
||||||
|
- python3-docker
|
||||||
|
- borgbackup
|
||||||
|
monitoring:
|
||||||
|
- atop
|
||||||
|
- iotop
|
||||||
|
- nethogs
|
||||||
|
- nload
|
||||||
|
- sysstat
|
||||||
|
- dstat
|
||||||
|
- smartmontools
|
||||||
|
security:
|
||||||
|
- fail2ban
|
||||||
|
- nftables
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
security_firewall_default_policy: drop
|
||||||
|
security_firewall_allowed_ports:
|
||||||
|
- "{{ security_ssh_port }}/tcp"
|
||||||
|
- "80/tcp"
|
||||||
|
- "443/tcp"
|
||||||
|
- "443/udp"
|
||||||
|
- "53/udp"
|
||||||
|
|
||||||
|
security_ssh_port: 2424
|
||||||
|
security_fail2ban_enabled: true
|
||||||
|
security_fail2ban_custom_configuration_template: "jail.local.j2"
|
||||||
|
security_autoupdate_enabled: true
|
||||||
|
|
||||||
|
ssh_config:
|
||||||
|
permit_root_login: "no"
|
||||||
|
password_authentication: "no"
|
||||||
|
challenge_response_authentication: "no"
|
||||||
|
use_pam: "yes"
|
||||||
|
x11_forwarding: "no"
|
||||||
|
client_alive_interval: 300
|
||||||
|
client_alive_count_max: 2
|
||||||
|
max_auth_tries: 3
|
||||||
|
max_sessions: 10
|
||||||
|
allow_users: "root {{ admin_users | map(attribute='name') | join(' ') }}"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
sysctl_tuning:
|
||||||
|
# Network tuning
|
||||||
|
net.core.somaxconn: 65535
|
||||||
|
net.ipv4.tcp_max_syn_backlog: 65535
|
||||||
|
net.ipv4.tcp_fin_timeout: 30
|
||||||
|
net.ipv4.tcp_keepalive_time: 600
|
||||||
|
net.ipv4.tcp_keepalive_probes: 5
|
||||||
|
net.ipv4.tcp_keepalive_intvl: 15
|
||||||
|
net.ipv4.ip_local_port_range: "1024 65535"
|
||||||
|
|
||||||
|
# Memory tuning
|
||||||
|
vm.swappiness: 10
|
||||||
|
vm.vfs_cache_pressure: 50
|
||||||
|
vm.dirty_ratio: 15
|
||||||
|
vm.dirty_background_ratio: 5
|
||||||
|
vm.overcommit_memory: 1
|
||||||
|
vm.overcommit_ratio: 90
|
||||||
|
|
||||||
|
# Security tuning
|
||||||
|
net.ipv4.conf.all.rp_filter: 1
|
||||||
|
net.ipv4.conf.default.rp_filter: 1
|
||||||
|
net.ipv4.icmp_echo_ignore_broadcasts: 1
|
||||||
|
net.ipv4.icmp_ignore_bogus_error_responses: 1
|
||||||
|
net.ipv4.tcp_syncookies: 1
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
state: "{{ applications.tailscale.state | default('latest') }}"
|
||||||
|
|
||||||
|
tailscale_authkey: "{{ lookup('env', 'TAILSCALE_KEY') }}"
|
||||||
|
tailscale_tags: >-
|
||||||
|
{{
|
||||||
|
['vm'] +
|
||||||
|
([hostname.split('-')[-1]] if '-' in hostname else [])
|
||||||
|
}}
|
||||||
|
tailscale_args: "--accept-dns=true --accept-routes=false --netfilter-mode on --shields-up=false --ssh=true --stateful-filtering=false"
|
||||||
|
|
||||||
|
tailscale_oauth_ephemeral: false
|
||||||
|
tailscale_oauth_preauthorized: true
|
||||||
|
insecurely_log_authkey: false
|
||||||
|
release_stability: stable
|
||||||
|
tailscale_up_timeout: 120
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
collections:
|
||||||
|
- name: ansible.posix
|
||||||
|
version: 2.1.0
|
||||||
|
- name: community.general
|
||||||
|
version: 11.4.0
|
||||||
|
- name: community.docker
|
||||||
|
version: 4.8.1
|
||||||
|
- name: artis3n.tailscale
|
||||||
|
version: 1.1.0
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- src: geerlingguy.docker
|
||||||
|
version: 7.8.0
|
||||||
|
- src: geerlingguy.security
|
||||||
|
version: 3.0.0
|
||||||
|
- src: geerlingguy.ntp
|
||||||
|
version: 3.0.0
|
||||||
|
- src: borgbase.ansible_role_borgbackup
|
||||||
|
version: v1.1.3
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
- name: Restart ssh
|
||||||
|
ansible.builtin.service:
|
||||||
|
name: ssh
|
||||||
|
state: restarted
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
- name: Include optimization tasks
|
||||||
|
include_tasks: optimization.yaml
|
||||||
|
tags: optimization
|
||||||
|
|
||||||
|
- name: Install essential packages
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: "{{ system_packages.essential }}"
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
cache_valid_time: 3600
|
||||||
|
tags: packages
|
||||||
|
|
||||||
|
- name: Set hostname and FQDN
|
||||||
|
block:
|
||||||
|
- name: Set hostname
|
||||||
|
ansible.builtin.hostname:
|
||||||
|
name: "{{ hostname | default(inventory_hostname) }}"
|
||||||
|
|
||||||
|
- name: Configure FQDN in hosts file
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /etc/hosts
|
||||||
|
regexp: '^127\.0\.1\.1.*'
|
||||||
|
line: "127.0.1.1 {{ fqdn | default(hostname) }} {{ hostname | default(inventory_hostname) }}"
|
||||||
|
state: present
|
||||||
|
tags: system
|
||||||
|
|
||||||
|
- name: Deploy MOTD template
|
||||||
|
template:
|
||||||
|
src: motd.j2
|
||||||
|
dest: /etc/motd
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
|
- name: Configure timezone
|
||||||
|
community.general.timezone:
|
||||||
|
name: "{{ timezone }}"
|
||||||
|
tags: system, ntp
|
||||||
|
|
||||||
|
- name: Install and configure NTP
|
||||||
|
include_role:
|
||||||
|
name: geerlingguy.ntp
|
||||||
|
tags: system, ntp
|
||||||
|
|
||||||
|
- name: Deploy SSH configuration
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sshd_config.j2
|
||||||
|
dest: /etc/ssh/sshd_config
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0600'
|
||||||
|
validate: '/usr/sbin/sshd -t -f %s'
|
||||||
|
notify: Restart ssh
|
||||||
|
|
||||||
|
- name: Create admin users with proper SSH keys
|
||||||
|
block:
|
||||||
|
- name: Ensure user exists
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
shell: "{{ item.shell | default('/bin/bash') }}"
|
||||||
|
groups: "{{ item.groups }}"
|
||||||
|
append: true
|
||||||
|
state: "{{ item.state | default('present') }}"
|
||||||
|
create_home: true
|
||||||
|
home: "/home/{{ item.name }}"
|
||||||
|
loop: "{{ admin_users }}"
|
||||||
|
tags: users
|
||||||
|
|
||||||
|
- name: Deploy SSH authorized keys
|
||||||
|
ansible.posix.authorized_key:
|
||||||
|
user: "{{ item.0.name }}"
|
||||||
|
state: present
|
||||||
|
key: "{{ item.1 }}"
|
||||||
|
manage_dir: true
|
||||||
|
with_subelements:
|
||||||
|
- "{{ admin_users }}"
|
||||||
|
- ssh_keys
|
||||||
|
tags: users, ssh
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
- name: Configure sysctl parameters
|
||||||
|
ansible.builtin.sysctl:
|
||||||
|
name: "{{ item.key }}"
|
||||||
|
value: "{{ item.value }}"
|
||||||
|
state: present
|
||||||
|
reload: true
|
||||||
|
loop: "{{ sysctl_tuning | dict2items }}"
|
||||||
|
tags: optimization
|
||||||
|
|
||||||
|
- name: Configure file handle limits
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /etc/security/limits.conf
|
||||||
|
regexp: "^{{ item.user | regex_escape }}.*{{ item.type }}"
|
||||||
|
line: "{{ item.user }} - nofile {{ item.limit }}"
|
||||||
|
create: true
|
||||||
|
loop:
|
||||||
|
- {user: "root", type: "soft", limit: "65536"}
|
||||||
|
- {user: "root", type: "hard", limit: "65536"}
|
||||||
|
- {user: "*", type: "soft", limit: "65536"}
|
||||||
|
- {user: "*", type: "hard", limit: "65536"}
|
||||||
|
tags: limits
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{{ ansible_hostname }}
|
||||||
|
--------------------
|
||||||
|
Welcome to {{ ansible_distribution }} {{ ansible_distribution_version }}
|
||||||
|
Kernel: {{ ansible_kernel }}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# Managed by Ansible - do not modify manually
|
||||||
|
# Security hardened SSH configuration
|
||||||
|
|
||||||
|
Include /etc/ssh/sshd_config.d/*.conf
|
||||||
|
|
||||||
|
# Basic settings
|
||||||
|
Port {{ security_ssh_port }}
|
||||||
|
AddressFamily any
|
||||||
|
ListenAddress 0.0.0.0
|
||||||
|
ListenAddress ::
|
||||||
|
Protocol 2
|
||||||
|
|
||||||
|
# Host keys (modern algorithms first)
|
||||||
|
HostKey /etc/ssh/ssh_host_ed25519_key
|
||||||
|
HostKey /etc/ssh/ssh_host_rsa_key
|
||||||
|
HostKey /etc/ssh/ssh_host_ecdsa_key
|
||||||
|
|
||||||
|
# Cryptography settings (modern ciphers)
|
||||||
|
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
|
||||||
|
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
|
||||||
|
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
|
||||||
|
|
||||||
|
# Authentication security
|
||||||
|
PermitRootLogin {{ ssh_config.permit_root_login }}
|
||||||
|
MaxAuthTries {{ ssh_config.max_auth_tries }}
|
||||||
|
MaxSessions {{ ssh_config.max_sessions }}
|
||||||
|
ClientAliveInterval {{ ssh_config.client_alive_interval }}
|
||||||
|
ClientAliveCountMax {{ ssh_config.client_alive_count_max }}
|
||||||
|
LoginGraceTime 60
|
||||||
|
|
||||||
|
# General security settings
|
||||||
|
UsePAM {{ ssh_config.use_pam }}
|
||||||
|
X11Forwarding {{ ssh_config.x11_forwarding }}
|
||||||
|
PrintMotd no
|
||||||
|
Compression no
|
||||||
|
UseDNS no
|
||||||
|
IgnoreRhosts yes
|
||||||
|
StrictModes yes
|
||||||
|
PermitEmptyPasswords no
|
||||||
|
TCPKeepAlive yes
|
||||||
|
KbdInteractiveAuthentication no
|
||||||
|
PrintLastLog yes
|
||||||
|
|
||||||
|
# Authorization settings
|
||||||
|
AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LogLevel INFO
|
||||||
|
SyslogFacility AUTH
|
||||||
|
|
||||||
|
# User restrictions
|
||||||
|
{% if ssh_config.allow_users is defined and ssh_config.allow_users %}
|
||||||
|
AllowUsers {{ ssh_config.allow_users }}
|
||||||
|
{% endif %}
|
||||||
|
{% if ssh_config.allow_groups is defined and ssh_config.allow_groups %}
|
||||||
|
AllowGroups {{ ssh_config.allow_groups }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# Key-based auth enforcement
|
||||||
|
PasswordAuthentication {{ ssh_config.password_authentication }}
|
||||||
|
PermitEmptyPasswords no
|
||||||
|
PubkeyAuthentication yes
|
||||||
|
AuthenticationMethods publickey
|
||||||
|
ChallengeResponseAuthentication {{ ssh_config.challenge_response_authentication }}
|
||||||
|
|
||||||
|
# Rekey limits
|
||||||
|
RekeyLimit 512M 1h
|
||||||
|
|
||||||
|
# Allow client to pass locale environment variables
|
||||||
|
AcceptEnv LANG LC_*
|
||||||
|
|
||||||
|
Subsystem sftp /usr/lib/openssh/sftp-server
|
||||||
|
|
||||||
|
# Authorized key and principal controls
|
||||||
|
AuthorizedPrincipalsFile none
|
||||||
|
AuthorizedKeysCommand none
|
||||||
|
AuthorizedKeysCommandUser nobody
|
||||||
|
|
||||||
|
# Disable forwarding and tunnels unless explicitly needed
|
||||||
|
AllowAgentForwarding no
|
||||||
|
AllowTcpForwarding no
|
||||||
|
GatewayPorts no
|
||||||
|
PermitTunnel no
|
||||||
|
|
||||||
|
# Disable user-controlled environments and TTY manipulations
|
||||||
|
PermitUserEnvironment no
|
||||||
|
PermitTTY yes
|
||||||
|
X11UseLocalhost yes
|
||||||
|
X11DisplayOffset 10
|
||||||
|
|
||||||
|
# Limit connection attempts
|
||||||
|
MaxStartups 2:30:100
|
||||||
|
|
||||||
|
# Misc hardening
|
||||||
|
IgnoreUserKnownHosts yes
|
||||||
|
VersionAddendum none
|
||||||
|
ChrootDirectory none
|
||||||
|
|
||||||
|
Match Address 10.0.0.0/8,192.168.0.0/16,172.16.0.0/12
|
||||||
|
PermitRootLogin yes
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
coolify_state: present # latest | present | absent
|
||||||
|
coolify_remove_data_on_absent: false
|
||||||
|
coolify_base_dir: /data/coolify
|
||||||
|
|
||||||
|
coolify_docker_network: coolify
|
||||||
|
coolify_http_port: 8000
|
||||||
|
|
||||||
|
coolify_owner: 9999
|
||||||
|
coolify_group: root
|
||||||
|
coolify_compose_files:
|
||||||
|
- docker-compose.yml
|
||||||
|
- docker-compose.prod.yml
|
||||||
|
|
||||||
|
coolify_container_names:
|
||||||
|
- coolify
|
||||||
|
- coolify-db
|
||||||
|
- coolify-redis
|
||||||
|
- coolify-realtime
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
- name: Check if Coolify is running before uninstallation
|
||||||
|
community.docker.docker_container_info:
|
||||||
|
name: "{{ item }}"
|
||||||
|
loop: "{{ coolify_container_names }}"
|
||||||
|
register: coolify_containers_pre_uninstall
|
||||||
|
ignore_errors: true
|
||||||
|
tags: coolify, deletion, pre-check
|
||||||
|
|
||||||
|
- name: Stop and remove Coolify services (compose down)
|
||||||
|
become: true
|
||||||
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ coolify_base_dir }}/source"
|
||||||
|
files: "{{ coolify_compose_files }}"
|
||||||
|
state: absent
|
||||||
|
remove_orphans: true
|
||||||
|
remove_volumes: false
|
||||||
|
ignore_errors: true
|
||||||
|
tags: coolify, docker, deletion
|
||||||
|
|
||||||
|
- name: Force remove any remaining Coolify containers
|
||||||
|
community.docker.docker_container:
|
||||||
|
name: "{{ item.item }}"
|
||||||
|
state: absent
|
||||||
|
force_kill: true
|
||||||
|
force_remove: true
|
||||||
|
loop: "{{ coolify_containers_pre_uninstall.results }}"
|
||||||
|
when:
|
||||||
|
- item.exists | default(false)
|
||||||
|
- coolify_remove_data_on_absent | bool
|
||||||
|
ignore_errors: true
|
||||||
|
tags: coolify, docker, deletion
|
||||||
|
|
||||||
|
- name: Remove Coolify files and data (conditional)
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: absent
|
||||||
|
loop:
|
||||||
|
- "{{ coolify_base_dir }}/source"
|
||||||
|
- "{{ coolify_base_dir }}/applications"
|
||||||
|
- "{{ coolify_base_dir }}/databases"
|
||||||
|
- "{{ coolify_base_dir }}/backups"
|
||||||
|
- "{{ coolify_base_dir }}/services"
|
||||||
|
- "{{ coolify_base_dir }}/proxy"
|
||||||
|
- "{{ coolify_base_dir }}/webhooks-during-maintenance"
|
||||||
|
- "{{ coolify_base_dir }}/ssh"
|
||||||
|
- "{{ coolify_base_dir }}/sentinel"
|
||||||
|
when: coolify_remove_data_on_absent | bool
|
||||||
|
tags: coolify, files, deletion, data
|
||||||
|
|
||||||
|
- name: Remove base directory if empty (only when requested)
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ coolify_base_dir }}"
|
||||||
|
state: absent
|
||||||
|
when: coolify_remove_data_on_absent | bool
|
||||||
|
tags: coolify, files, deletion, data
|
||||||
|
|
||||||
|
- name: Remove Coolify docker volumes (find)
|
||||||
|
community.docker.docker_volume_info:
|
||||||
|
name: "^coolify_.*"
|
||||||
|
register: coolify_volumes
|
||||||
|
ignore_errors: true
|
||||||
|
when: coolify_remove_data_on_absent | bool
|
||||||
|
tags: coolify, docker, deletion
|
||||||
|
|
||||||
|
- name: Remove Coolify docker volumes
|
||||||
|
community.docker.docker_volume:
|
||||||
|
name: "{{ item.Name }}"
|
||||||
|
state: absent
|
||||||
|
force: true
|
||||||
|
loop: "{{ coolify_volumes.volumes | default([]) }}"
|
||||||
|
when: coolify_volumes is defined and coolify_volumes.volumes | length > 0
|
||||||
|
tags: coolify, docker, deletion
|
||||||
|
|
||||||
|
- name: Remove Coolify docker network
|
||||||
|
community.docker.docker_network:
|
||||||
|
name: "{{ coolify_docker_network }}"
|
||||||
|
state: absent
|
||||||
|
force: "{{ coolify_remove_data_on_absent | bool }}"
|
||||||
|
ignore_errors: true
|
||||||
|
when: coolify_remove_data_on_absent | bool
|
||||||
|
tags: coolify, docker, deletion
|
||||||
|
|
||||||
|
- name: Prune Coolify images
|
||||||
|
become: true
|
||||||
|
community.docker.docker_prune:
|
||||||
|
images: true
|
||||||
|
images_filters:
|
||||||
|
reference: "ghcr.io/coollabsio/*"
|
||||||
|
build_cache: true
|
||||||
|
ignore_errors: true
|
||||||
|
when: coolify_remove_data_on_absent | bool
|
||||||
|
tags: coolify, docker, deletion
|
||||||
|
|
||||||
|
- name: Prune unused Docker resources
|
||||||
|
become: true
|
||||||
|
community.docker.docker_prune:
|
||||||
|
containers: true
|
||||||
|
images: false
|
||||||
|
networks: true
|
||||||
|
volumes: false
|
||||||
|
builder_cache: true
|
||||||
|
ignore_errors: true
|
||||||
|
when: coolify_remove_data_on_absent | bool
|
||||||
|
tags: coolify, docker, deletion
|
||||||
|
|
||||||
|
- name: Verify Coolify removal
|
||||||
|
community.docker.docker_container_info:
|
||||||
|
name: "{{ item }}"
|
||||||
|
loop: "{{ coolify_container_names }}"
|
||||||
|
register: coolify_containers_post_uninstall
|
||||||
|
ignore_errors: true
|
||||||
|
tags: coolify, deletion, verification
|
||||||
|
|
||||||
|
- name: Display uninstallation message
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Coolify has been uninstalled"
|
||||||
|
- "Application data preserved: {{ not coolify_remove_data_on_absent }}"
|
||||||
|
tags: coolify, deletion
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
---
|
||||||
|
- name: Install prerequisites (apt)
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name:
|
||||||
|
- curl
|
||||||
|
- openssl
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
cache_valid_time: 3600
|
||||||
|
tags: coolify, prerequisites, installation
|
||||||
|
|
||||||
|
- name: Ensure Docker service is started and enabled
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: docker
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
tags: coolify, docker, installation
|
||||||
|
|
||||||
|
- name: Create Coolify directories
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ coolify_owner }}"
|
||||||
|
group: "{{ coolify_group }}"
|
||||||
|
mode: '0750'
|
||||||
|
loop:
|
||||||
|
- "{{ coolify_base_dir }}/source"
|
||||||
|
- "{{ coolify_base_dir }}/ssh/keys"
|
||||||
|
- "{{ coolify_base_dir }}/ssh/mux"
|
||||||
|
- "{{ coolify_base_dir }}/applications"
|
||||||
|
- "{{ coolify_base_dir }}/databases"
|
||||||
|
- "{{ coolify_base_dir }}/backups"
|
||||||
|
- "{{ coolify_base_dir }}/services"
|
||||||
|
- "{{ coolify_base_dir }}/proxy/dynamic"
|
||||||
|
- "{{ coolify_base_dir }}/webhooks-during-maintenance"
|
||||||
|
tags: coolify, files, installation
|
||||||
|
|
||||||
|
- name: Download Coolify configuration files
|
||||||
|
ansible.builtin.get_url:
|
||||||
|
url: "https://cdn.coollabs.io/coolify/{{ item.file }}"
|
||||||
|
dest: "{{ coolify_base_dir }}/source/{{ item.dest }}"
|
||||||
|
mode: '0644'
|
||||||
|
loop:
|
||||||
|
- { file: "docker-compose.yml", dest: "docker-compose.yml" }
|
||||||
|
- { file: "docker-compose.prod.yml", dest: "docker-compose.prod.yml" }
|
||||||
|
- { file: "upgrade.sh", dest: "upgrade.sh" }
|
||||||
|
tags: coolify, files, installation
|
||||||
|
|
||||||
|
- name: Ensure .env exists from template (only when missing)
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ coolify_base_dir }}/source/.env"
|
||||||
|
register: env_file_check
|
||||||
|
tags: coolify, files, installation
|
||||||
|
|
||||||
|
- name: Create .env from template when missing
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: "templates/.env.production.j2"
|
||||||
|
dest: "{{ coolify_base_dir }}/source/.env"
|
||||||
|
mode: '0640'
|
||||||
|
when: not env_file_check.stat.exists
|
||||||
|
tags: coolify, files, installation
|
||||||
|
|
||||||
|
- name: Ensure correct ownership and permissions recursively
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ coolify_base_dir }}"
|
||||||
|
owner: "{{ coolify_owner }}"
|
||||||
|
group: "{{ coolify_group }}"
|
||||||
|
mode: '0750'
|
||||||
|
recurse: true
|
||||||
|
tags: coolify, permissions, installation
|
||||||
|
|
||||||
|
- name: Ensure Docker network exists
|
||||||
|
become: true
|
||||||
|
community.docker.docker_network:
|
||||||
|
name: "{{ coolify_docker_network }}"
|
||||||
|
driver: bridge
|
||||||
|
attachable: true
|
||||||
|
state: present
|
||||||
|
tags: coolify, docker, installation
|
||||||
|
|
||||||
|
- name: Start Coolify services
|
||||||
|
become: true
|
||||||
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ coolify_base_dir }}/source"
|
||||||
|
files: "{{ coolify_compose_files }}"
|
||||||
|
pull: always
|
||||||
|
state: present
|
||||||
|
wait: true
|
||||||
|
wait_timeout: 300
|
||||||
|
tags: coolify, docker, installation
|
||||||
|
|
||||||
|
- name: Wait for Coolify HTTP to respond
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "http://localhost:{{ coolify_http_port }}"
|
||||||
|
method: GET
|
||||||
|
status_code: 200
|
||||||
|
timeout: 30
|
||||||
|
body_format: json
|
||||||
|
register: coolify_health
|
||||||
|
until: coolify_health.status == 200
|
||||||
|
retries: 10
|
||||||
|
delay: 10
|
||||||
|
tags: coolify, health, installation
|
||||||
|
|
||||||
|
- name: Show installed message
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Coolify installed successfully"
|
||||||
|
- "All containers are healthy and responding"
|
||||||
|
- "Access at: http://{{ ansible_host }}:{{ coolify_http_port }}"
|
||||||
|
tags: coolify, installation
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
- name: Validate coolify_state parameter
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- coolify_state in ['present', 'absent', 'latest']
|
||||||
|
msg: "coolify_state must be one of: present, absent, latest"
|
||||||
|
tags: always
|
||||||
|
|
||||||
|
- name: Check if Coolify is installed (compose file exists)
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ coolify_base_dir }}/source/{{ coolify_compose_files[0] }}"
|
||||||
|
register: coolify_installed
|
||||||
|
tags: always
|
||||||
|
|
||||||
|
- name: Check Coolify container status
|
||||||
|
community.docker.docker_container_info:
|
||||||
|
name: "{{ item }}"
|
||||||
|
loop: "{{ coolify_container_names }}"
|
||||||
|
register: coolify_containers
|
||||||
|
when: coolify_installed.stat.exists
|
||||||
|
tags: always
|
||||||
|
|
||||||
|
- name: Set Coolify health fact
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
coolify_healthy: "{{ coolify_containers.results | selectattr('container', 'defined') | selectattr('container.State.Health.Status', 'defined') | selectattr('container.State.Health.Status', 'equalto', 'healthy') | list | length == coolify_container_names | length }}"
|
||||||
|
when: coolify_containers is defined and coolify_containers.results is defined
|
||||||
|
tags: always
|
||||||
|
|
||||||
|
- name: Include deletion tasks if state is absent
|
||||||
|
ansible.builtin.include_tasks: delete.yaml
|
||||||
|
when: coolify_state == 'absent'
|
||||||
|
tags: coolify, deletion
|
||||||
|
|
||||||
|
- name: Include installation tasks when desired and not installed
|
||||||
|
ansible.builtin.include_tasks: install.yaml
|
||||||
|
when: (coolify_state in ['present','latest']) and not coolify_installed.stat.exists
|
||||||
|
tags: coolify, installation
|
||||||
|
|
||||||
|
- name: Include update tasks when latest requested and already installed
|
||||||
|
ansible.builtin.include_tasks: update.yaml
|
||||||
|
when: (coolify_state == 'latest') and coolify_installed.stat.exists
|
||||||
|
tags: coolify, update
|
||||||
|
|
||||||
|
- name: Show status when present and already installed
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Coolify is already installed and running"
|
||||||
|
- "Containers healthy: {{ coolify_healthy | default('unknown') }}"
|
||||||
|
- "Access at: http://{{ ansible_host }}:{{ coolify_http_port }}"
|
||||||
|
when: (coolify_state == 'present') and coolify_installed.stat.exists | default(false)
|
||||||
|
tags: coolify, status
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
- name: Update Coolify services with recreate
|
||||||
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ coolify_base_dir }}/source"
|
||||||
|
files: "{{ coolify_compose_files }}"
|
||||||
|
pull: always
|
||||||
|
state: present
|
||||||
|
recreate: always
|
||||||
|
wait: true
|
||||||
|
wait_timeout: 300
|
||||||
|
tags: coolify, update
|
||||||
|
|
||||||
|
- name: Wait for Coolify HTTP to respond after update
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "http://localhost:{{ coolify_http_port }}"
|
||||||
|
method: GET
|
||||||
|
status_code: 200
|
||||||
|
timeout: 30
|
||||||
|
body_format: json
|
||||||
|
register: coolify_health_after_update
|
||||||
|
until: coolify_health_after_update.status == 200
|
||||||
|
retries: 10
|
||||||
|
delay: 10
|
||||||
|
tags: coolify, update, health
|
||||||
|
|
||||||
|
- name: Show update success message
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Coolify updated successfully to latest version"
|
||||||
|
- "All containers are healthy and responding"
|
||||||
|
- "Access at: http://{{ ansible_host }}:{{ coolify_http_port }}"
|
||||||
|
tags: coolify, update
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
APP_ID={{ coolify_app_id | default(lookup('pipe','openssl rand -hex 16')) }}
|
||||||
|
APP_NAME={{ coolify_app_name | default('Coolify') }}
|
||||||
|
APP_KEY={{ coolify_app_key | default('base64:' ~ lookup('pipe','openssl rand -base64 32')) }}
|
||||||
|
|
||||||
|
DB_USERNAME={{ coolify_db_username | default('coolify') }}
|
||||||
|
DB_PASSWORD={{ coolify_db_password | default(lookup('pipe','openssl rand -base64 32')) }}
|
||||||
|
|
||||||
|
REDIS_PASSWORD={{ coolify_redis_password | default(lookup('pipe','openssl rand -base64 32')) }}
|
||||||
|
|
||||||
|
PUSHER_APP_ID={{ coolify_pusher_app_id | default(lookup('pipe','openssl rand -hex 32')) }}
|
||||||
|
PUSHER_APP_KEY={{ coolify_pusher_app_key | default(lookup('pipe','openssl rand -hex 32')) }}
|
||||||
|
PUSHER_APP_SECRET={{ coolify_pusher_app_secret | default(lookup('pipe','openssl rand -hex 32')) }}
|
||||||
|
|
||||||
|
ROOT_USERNAME={{ coolify_root_username | default('') }}
|
||||||
|
ROOT_USER_EMAIL={{ coolify_root_email | default('') }}
|
||||||
|
ROOT_USER_PASSWORD={{ coolify_root_password | default('') }}
|
||||||
|
|
||||||
|
REGISTRY_URL={{ coolify_registry_url | default('ghcr.io') }}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
dokploy_state: present # present | absent | latest
|
||||||
|
dokploy_remove_data_on_absent: false
|
||||||
|
dokploy_update_all_services: true
|
||||||
|
dokploy_config_dir: /etc/dokploy
|
||||||
|
dokploy_http_port: 3000
|
||||||
|
dokploy_advertise_addr: ""
|
||||||
|
|
||||||
|
dokploy_postgres_image: postgres:16-alpine
|
||||||
|
dokploy_redis_image: redis:8-alpine
|
||||||
|
dokploy_dokploy_image: dokploy/dokploy:latest
|
||||||
|
dokploy_traefik_image: traefik:v3.6.1
|
||||||
|
|
||||||
|
dokploy_postgres_user: dokploy
|
||||||
|
dokploy_postgres_db: dokploy
|
||||||
|
dokploy_postgres_password: "{{ lookup('password', '/dev/null chars=ascii_letters,digits length=32') }}"
|
||||||
|
|
||||||
|
dokploy_docker_network: dokploy-network
|
||||||
|
|
||||||
|
dokploy_constraint_node_role: manager
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
- name: Remove Dokploy services
|
||||||
|
become: true
|
||||||
|
community.docker.docker_swarm_service:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: absent
|
||||||
|
loop:
|
||||||
|
- dokploy-traefik
|
||||||
|
- dokploy
|
||||||
|
- dokploy-redis
|
||||||
|
- dokploy-postgres
|
||||||
|
ignore_errors: true
|
||||||
|
tags: dokploy, deletion
|
||||||
|
|
||||||
|
- name: Leave Docker Swarm
|
||||||
|
become: true
|
||||||
|
community.docker.docker_swarm:
|
||||||
|
state: absent
|
||||||
|
ignore_errors: true
|
||||||
|
tags: dokploy, deletion
|
||||||
|
|
||||||
|
- name: Remove Dokploy network
|
||||||
|
become: true
|
||||||
|
community.docker.docker_network:
|
||||||
|
name: "{{ dokploy_docker_network }}"
|
||||||
|
state: absent
|
||||||
|
ignore_errors: true
|
||||||
|
tags: dokploy, deletion
|
||||||
|
|
||||||
|
- name: Remove Dokploy configuration directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ dokploy_config_dir }}"
|
||||||
|
state: absent
|
||||||
|
when: dokploy_remove_data_on_absent | bool
|
||||||
|
tags: dokploy, deletion, files
|
||||||
|
|
||||||
|
- name: Remove Dokploy volumes
|
||||||
|
become: true
|
||||||
|
community.docker.docker_volume:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: absent
|
||||||
|
loop:
|
||||||
|
- dokploy-postgres-database
|
||||||
|
- redis-data-volume
|
||||||
|
- dokploy-docker-config
|
||||||
|
when: dokploy_remove_data_on_absent | bool
|
||||||
|
ignore_errors: true
|
||||||
|
tags: dokploy, deletion, volumes
|
||||||
|
|
||||||
|
- name: Display uninstallation message
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Dokploy has been uninstalled"
|
||||||
|
- "Application data preserved: {{ not dokploy_remove_data_on_absent }}"
|
||||||
|
tags: dokploy, deletion
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
---
|
||||||
|
- name: Check if Docker is installed
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: docker --version
|
||||||
|
ignore_errors: false
|
||||||
|
changed_when: false
|
||||||
|
tags: dokploy, docker, installation
|
||||||
|
|
||||||
|
- name: Ensure Docker service is started and enabled
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: docker
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
tags: dokploy, docker, installation
|
||||||
|
|
||||||
|
- name: Leave existing Docker Swarm if any
|
||||||
|
become: true
|
||||||
|
community.docker.docker_swarm:
|
||||||
|
state: absent
|
||||||
|
ignore_errors: true
|
||||||
|
tags: dokploy, swarm, installation
|
||||||
|
|
||||||
|
- name: Determine advertise address
|
||||||
|
block:
|
||||||
|
- name: Get private IP address
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
private_ip: "{{ ansible_default_ipv4.address }}"
|
||||||
|
when: dokploy_advertise_addr == ""
|
||||||
|
|
||||||
|
- name: Set advertise address
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
effective_advertise_addr: "{{ dokploy_advertise_addr | default(private_ip) }}"
|
||||||
|
tags: dokploy, network, installation
|
||||||
|
|
||||||
|
- name: Validate advertise address
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- effective_advertise_addr is defined
|
||||||
|
- effective_advertise_addr != ""
|
||||||
|
msg: "Could not determine advertise address. Please set dokploy_advertise_addr variable."
|
||||||
|
tags: dokploy, network, installation
|
||||||
|
|
||||||
|
- name: Initialize Docker Swarm
|
||||||
|
become: true
|
||||||
|
community.docker.docker_swarm:
|
||||||
|
state: present
|
||||||
|
advertise_addr: "{{ effective_advertise_addr }}"
|
||||||
|
listen_addr: "{{ effective_advertise_addr }}"
|
||||||
|
tags: dokploy, swarm, installation
|
||||||
|
|
||||||
|
- name: Create dokploy overlay network
|
||||||
|
become: true
|
||||||
|
community.docker.docker_network:
|
||||||
|
name: "{{ dokploy_docker_network }}"
|
||||||
|
driver: overlay
|
||||||
|
attachable: true
|
||||||
|
state: present
|
||||||
|
tags: dokploy, network, installation
|
||||||
|
|
||||||
|
- name: Create Dokploy configuration directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ dokploy_config_dir }}"
|
||||||
|
state: directory
|
||||||
|
mode: "0777"
|
||||||
|
tags: dokploy, files, installation
|
||||||
|
|
||||||
|
- name: Pull all service images (when update all services requested)
|
||||||
|
become: true
|
||||||
|
community.docker.docker_image:
|
||||||
|
name: "{{ item }}"
|
||||||
|
source: pull
|
||||||
|
loop:
|
||||||
|
- "{{ dokploy_postgres_image }}"
|
||||||
|
- "{{ dokploy_redis_image }}"
|
||||||
|
- "{{ dokploy_dokploy_image }}"
|
||||||
|
- "{{ dokploy_traefik_image }}"
|
||||||
|
when: dokploy_update_all_services | bool
|
||||||
|
tags: dokploy, images, installation
|
||||||
|
|
||||||
|
- name: Pull only Dokploy image (when latest requested and not updating all)
|
||||||
|
become: true
|
||||||
|
community.docker.docker_image:
|
||||||
|
name: "{{ dokploy_dokploy_image }}"
|
||||||
|
source: pull
|
||||||
|
when:
|
||||||
|
- not (dokploy_update_all_services | bool)
|
||||||
|
- dokploy_state == 'latest'
|
||||||
|
tags: dokploy, images, installation
|
||||||
|
|
||||||
|
- name: Deploy PostgreSQL service
|
||||||
|
become: true
|
||||||
|
community.docker.docker_swarm_service:
|
||||||
|
name: dokploy-postgres
|
||||||
|
image: "{{ dokploy_postgres_image }}"
|
||||||
|
placement:
|
||||||
|
constraints:
|
||||||
|
- "node.role=={{ dokploy_constraint_node_role }}"
|
||||||
|
networks:
|
||||||
|
- name: "{{ dokploy_docker_network }}"
|
||||||
|
aliases:
|
||||||
|
- dokploy-postgres
|
||||||
|
force_update: "{{ (dokploy_state == 'latest') | bool }}"
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
order: stop-first
|
||||||
|
failure_action: rollback
|
||||||
|
restart_config:
|
||||||
|
condition: any
|
||||||
|
delay: 5s
|
||||||
|
max_attempts: 3
|
||||||
|
window: 120s
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: "{{ dokploy_postgres_user }}"
|
||||||
|
POSTGRES_DB: "{{ dokploy_postgres_db }}"
|
||||||
|
POSTGRES_PASSWORD: "{{ dokploy_postgres_password }}"
|
||||||
|
mounts:
|
||||||
|
- type: volume
|
||||||
|
source: dokploy-postgres-database
|
||||||
|
target: /var/lib/postgresql/data
|
||||||
|
state: present
|
||||||
|
tags: dokploy, database, installation
|
||||||
|
|
||||||
|
- name: Deploy Redis service
|
||||||
|
become: true
|
||||||
|
community.docker.docker_swarm_service:
|
||||||
|
name: dokploy-redis
|
||||||
|
image: "{{ dokploy_redis_image }}"
|
||||||
|
placement:
|
||||||
|
constraints:
|
||||||
|
- "node.role=={{ dokploy_constraint_node_role }}"
|
||||||
|
networks:
|
||||||
|
- name: "{{ dokploy_docker_network }}"
|
||||||
|
aliases:
|
||||||
|
- dokploy-redis
|
||||||
|
force_update: "{{ (dokploy_state == 'latest') | bool }}"
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
order: stop-first
|
||||||
|
failure_action: rollback
|
||||||
|
restart_config:
|
||||||
|
condition: any
|
||||||
|
delay: 5s
|
||||||
|
max_attempts: 3
|
||||||
|
window: 120s
|
||||||
|
mounts:
|
||||||
|
- type: volume
|
||||||
|
source: redis-data-volume
|
||||||
|
target: /data
|
||||||
|
state: present
|
||||||
|
tags: dokploy, redis, installation
|
||||||
|
|
||||||
|
- name: Deploy Dokploy main service
|
||||||
|
become: true
|
||||||
|
community.docker.docker_swarm_service:
|
||||||
|
name: dokploy
|
||||||
|
image: "{{ dokploy_dokploy_image }}"
|
||||||
|
replicas: 1
|
||||||
|
networks:
|
||||||
|
- name: "{{ dokploy_docker_network }}"
|
||||||
|
aliases:
|
||||||
|
- dokploy
|
||||||
|
force_update: "{{ (dokploy_state == 'latest') | bool }}"
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
order: stop-first
|
||||||
|
failure_action: rollback
|
||||||
|
restart_config:
|
||||||
|
condition: any
|
||||||
|
delay: 5s
|
||||||
|
max_attempts: 3
|
||||||
|
window: 120s
|
||||||
|
mounts:
|
||||||
|
- type: bind
|
||||||
|
source: /var/run/docker.sock
|
||||||
|
target: /var/run/docker.sock
|
||||||
|
- type: bind
|
||||||
|
source: "{{ dokploy_config_dir }}"
|
||||||
|
target: /etc/dokploy
|
||||||
|
- type: volume
|
||||||
|
source: dokploy-docker-config
|
||||||
|
target: /root/.docker
|
||||||
|
publish:
|
||||||
|
- published_port: "{{ dokploy_http_port }}"
|
||||||
|
target_port: 3000
|
||||||
|
protocol: tcp
|
||||||
|
mode: host
|
||||||
|
placement:
|
||||||
|
constraints:
|
||||||
|
- "node.role=={{ dokploy_constraint_node_role }}"
|
||||||
|
env:
|
||||||
|
ADVERTISE_ADDR: "{{ effective_advertise_addr }}"
|
||||||
|
DATABASE_URL: "postgres://{{ dokploy_postgres_user }}:{{ dokploy_postgres_password }}@dokploy-postgres:5432/{{ dokploy_postgres_db }}"
|
||||||
|
state: present
|
||||||
|
tags: dokploy, main, installation
|
||||||
|
|
||||||
|
- name: Wait for Dokploy to start
|
||||||
|
ansible.builtin.pause:
|
||||||
|
seconds: 4
|
||||||
|
tags: dokploy, wait, installation
|
||||||
|
|
||||||
|
- name: Create Traefik configuration directories
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
loop:
|
||||||
|
- "{{ dokploy_config_dir }}/traefik"
|
||||||
|
- "{{ dokploy_config_dir }}/traefik/dynamic"
|
||||||
|
tags: dokploy, traefik, installation
|
||||||
|
|
||||||
|
- name: Deploy Traefik service
|
||||||
|
become: true
|
||||||
|
community.docker.docker_swarm_service:
|
||||||
|
name: dokploy-traefik
|
||||||
|
image: "{{ dokploy_traefik_image }}"
|
||||||
|
placement:
|
||||||
|
constraints:
|
||||||
|
- "node.role=={{ dokploy_constraint_node_role }}"
|
||||||
|
networks:
|
||||||
|
- name: "{{ dokploy_docker_network }}"
|
||||||
|
aliases:
|
||||||
|
- traefik
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
order: start-first
|
||||||
|
restart_config:
|
||||||
|
condition: any
|
||||||
|
mounts:
|
||||||
|
- type: bind
|
||||||
|
source: "{{ dokploy_config_dir }}/traefik/traefik.yml"
|
||||||
|
target: /etc/traefik/traefik.yml
|
||||||
|
- type: bind
|
||||||
|
source: "{{ dokploy_config_dir }}/traefik/dynamic"
|
||||||
|
target: /etc/dokploy/traefik/dynamic
|
||||||
|
- type: bind
|
||||||
|
source: /var/run/docker.sock
|
||||||
|
target: /var/run/docker.sock
|
||||||
|
publish:
|
||||||
|
- published_port: 443
|
||||||
|
target_port: 443
|
||||||
|
protocol: tcp
|
||||||
|
mode: host
|
||||||
|
- published_port: 80
|
||||||
|
target_port: 80
|
||||||
|
protocol: tcp
|
||||||
|
mode: host
|
||||||
|
- published_port: 443
|
||||||
|
target_port: 443
|
||||||
|
protocol: udp
|
||||||
|
mode: host
|
||||||
|
state: present
|
||||||
|
force_update: "{{ (dokploy_state == 'latest') | bool }}"
|
||||||
|
tags: dokploy, traefik, installation
|
||||||
|
|
||||||
|
- name: Wait for Dokploy HTTP to respond
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "http://localhost:{{ dokploy_http_port }}"
|
||||||
|
method: GET
|
||||||
|
status_code: 200
|
||||||
|
timeout: 30
|
||||||
|
register: dokploy_health
|
||||||
|
until: dokploy_health.status == 200
|
||||||
|
retries: 10
|
||||||
|
delay: 10
|
||||||
|
tags: dokploy, health, installation
|
||||||
|
|
||||||
|
- name: Show installation message
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Dokploy installed successfully"
|
||||||
|
- "Using advertise address: {{ effective_advertise_addr }}"
|
||||||
|
- "Access at: http://{{ ansible_host }}:{{ dokploy_http_port }}"
|
||||||
|
tags: dokploy, installation
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
- name: Validate dokploy_state parameter
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- dokploy_state in ['present', 'absent', 'latest']
|
||||||
|
msg: "dokploy_state must be one of: present, absent, latest"
|
||||||
|
tags: always
|
||||||
|
|
||||||
|
- name: Check if Dokploy services exist
|
||||||
|
become: true
|
||||||
|
community.docker.docker_swarm_service_info:
|
||||||
|
name: dokploy
|
||||||
|
register: dokploy_services
|
||||||
|
ignore_errors: true
|
||||||
|
tags: always
|
||||||
|
|
||||||
|
- name: Include deletion tasks if state is absent
|
||||||
|
ansible.builtin.include_tasks: delete.yaml
|
||||||
|
when: dokploy_state == 'absent'
|
||||||
|
tags: dokploy, deletion
|
||||||
|
|
||||||
|
- name: Include installation/update tasks when desired
|
||||||
|
ansible.builtin.include_tasks: install.yaml
|
||||||
|
when: dokploy_state in ['present', 'latest']
|
||||||
|
tags: dokploy, installation
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
- name: Restart prometheus-node-exporter
|
||||||
|
ansible.builtin.service:
|
||||||
|
name: prometheus-node-exporter
|
||||||
|
state: restarted
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
- name: Install monitoring tools
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: "{{ system_packages.monitoring }}"
|
||||||
|
state: present
|
||||||
|
tags: monitoring
|
||||||
|
|
||||||
|
- name: Manage Prometheus node exporter
|
||||||
|
block:
|
||||||
|
- name: Install Prometheus node exporter
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: prometheus-node-exporter
|
||||||
|
state: present
|
||||||
|
notify: Restart prometheus-node-exporter
|
||||||
|
|
||||||
|
- name: Ensure Prometheus node exporter is running and enabled
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: prometheus-node-exporter
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
when: monitoring_prometheus_node_exporter | bool
|
||||||
|
tags: monitoring, prometheus
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
- name: Reload nftables
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: nftables
|
||||||
|
state: reloaded
|
||||||
|
tags: security, nftables
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
- name: Install security packages
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: "{{ system_packages.security }}"
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
tags: security
|
||||||
|
|
||||||
|
- name: Install nftables
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name:
|
||||||
|
- nftables
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
tags: security, nftables
|
||||||
|
|
||||||
|
- name: Render nftables configuration
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: nftables.conf.j2
|
||||||
|
dest: /etc/nftables.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
validate: 'nft -c -f %s'
|
||||||
|
notify: Reload nftables
|
||||||
|
tags: security, nftables
|
||||||
|
|
||||||
|
- name: Enable and start nftables
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: nftables
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
tags: security, nftables
|
||||||
|
|
||||||
|
- name: Install and configure fail2ban
|
||||||
|
include_role:
|
||||||
|
name: geerlingguy.security
|
||||||
|
tags: security
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/sbin/nft -f
|
||||||
|
|
||||||
|
table inet filter {
|
||||||
|
chain input {
|
||||||
|
type filter hook input priority 0;
|
||||||
|
policy {{ security_firewall_default_policy | default('drop') }};
|
||||||
|
|
||||||
|
ct state established,related accept
|
||||||
|
iifname lo accept
|
||||||
|
|
||||||
|
# allow ICMP
|
||||||
|
ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded } accept
|
||||||
|
ip6 nexthdr icmpv6 icmpv6 type { echo-request, echo-reply, destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
|
||||||
|
|
||||||
|
{% for p in security_firewall_allowed_ports %}
|
||||||
|
{% set parts = p.split('/') %}
|
||||||
|
{% set port = parts[0] %}
|
||||||
|
{% set proto = parts[1] if parts|length > 1 else 'tcp' %}
|
||||||
|
{{ proto }} dport {{ port }} accept
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
reject with icmpx type port-unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
chain forward {
|
||||||
|
type filter hook forward priority 0;
|
||||||
|
policy accept;
|
||||||
|
}
|
||||||
|
|
||||||
|
chain output {
|
||||||
|
type filter hook output priority 0;
|
||||||
|
policy accept;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
- name: Import base system setup
|
||||||
|
ansible.builtin.import_playbook: base_setup.yaml
|
||||||
|
|
||||||
|
- name: Import Docker setup
|
||||||
|
ansible.builtin.import_playbook: docker.yaml
|
||||||
|
|
||||||
|
- name: Import application deployment
|
||||||
|
ansible.builtin.import_playbook: apps.yaml
|
||||||
|
|
||||||
|
- name: Import post-deployment validation
|
||||||
|
ansible.builtin.import_playbook: validation.yaml
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
- name: Post-deployment validation and health checks
|
||||||
|
hosts: servers
|
||||||
|
gather_facts: true
|
||||||
|
become: false
|
||||||
|
serial: "100%"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Verify SSH connectivity on custom port
|
||||||
|
ansible.builtin.wait_for:
|
||||||
|
port: "{{ security_ssh_port | default(22) }}"
|
||||||
|
host: "{{ ansible_host | default(inventory_hostname) }}"
|
||||||
|
timeout: 60
|
||||||
|
delay: 5
|
||||||
|
state: started
|
||||||
|
tags: validation, networking
|
||||||
|
|
||||||
|
- name: Check critical system services
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
loop:
|
||||||
|
- ssh
|
||||||
|
- docker
|
||||||
|
- fail2ban
|
||||||
|
- nftables
|
||||||
|
tags: validation
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Run comprehensive system health checks
|
||||||
|
block:
|
||||||
|
- name: Set root mount fact
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
root_mount: "{{ ansible_mounts | selectattr('mount', 'equalto', '/') | list | first }}"
|
||||||
|
tags: validation, health
|
||||||
|
|
||||||
|
- name: Check load average
|
||||||
|
ansible.builtin.shell: cat /proc/loadavg | awk '{print $1}'
|
||||||
|
register: load_avg
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Check Docker status
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: docker info >/dev/null 2>&1 && echo "healthy" || echo "unhealthy"
|
||||||
|
register: docker_status
|
||||||
|
changed_when: false
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Display comprehensive health status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Health check results for {{ inventory_hostname }}:"
|
||||||
|
- "Disk usage: {{ ((root_mount.size_total - root_mount.size_available) / root_mount.size_total * 100) | round(2) }}%"
|
||||||
|
- "Memory usage: {{ ((ansible_memtotal_mb - ansible_memfree_mb) / ansible_memtotal_mb * 100) | round(2) }}%"
|
||||||
|
- "Load average (1m): {{ load_avg.stdout }}"
|
||||||
|
- "Docker: {{ docker_status.stdout }}"
|
||||||
|
tags: always, health
|
||||||
|
|
||||||
|
tags: validation, health
|
||||||
Reference in New Issue
Block a user