chore: added iac

Signed-off-by: ITQ <itq.dev@ya.ru>
This commit is contained in:
ITQ
2025-11-21 18:16:52 +03:00
parent 5d66fcd0ca
commit 44df678c82
43 changed files with 1598 additions and 45 deletions
@@ -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;
}
}