infra/ansible/roles/dokku/tasks/main.yml

321 lines
9.5 KiB
YAML

---
# 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 }}"