From dcc7e282c7f076c7768f9a5ba4aa5764203317d2 Mon Sep 17 00:00:00 2001 From: Hermes Agent service account Date: Thu, 28 May 2026 10:22:28 -0500 Subject: [PATCH] feat(semaphore): complete quadlet deployment for PostgreSQL + Semaphore - Add quadlet tasks and handlers for modern rootless Podman deployment - Fix broken Jinja in semaphore.container.j2 - Add proper 0777 permissions handling for rootless - Support semaphore_use_postgres toggle with fallback to legacy BoltDB path --- ansible/roles/semaphore/defaults/main.yml | 12 ++ ansible/roles/semaphore/handlers/main.yml | 17 ++- ansible/roles/semaphore/tasks/main.yml | 138 ++++++++++++------ .../templates/semaphore-postgres.container.j2 | 19 +++ .../templates/semaphore.container.j2 | 13 +- 5 files changed, 148 insertions(+), 51 deletions(-) create mode 100644 ansible/roles/semaphore/templates/semaphore-postgres.container.j2 diff --git a/ansible/roles/semaphore/defaults/main.yml b/ansible/roles/semaphore/defaults/main.yml index da238d6..710a7ad 100644 --- a/ansible/roles/semaphore/defaults/main.yml +++ b/ansible/roles/semaphore/defaults/main.yml @@ -32,3 +32,15 @@ semaphore_admin_email: admin@local.mk-labs.cloud # Set to true to force recreation of the admin user (deletes existing user first) semaphore_force_admin_user: false + +# PostgreSQL settings (when using postgres instead of bolt) +semaphore_use_postgres: true +semaphore_postgres_image: "postgres:16" +semaphore_postgres_container_name: "semaphore-postgres" +semaphore_postgres_volume: "semaphore_postgres_data" +semaphore_postgres_user: "semaphore" +semaphore_postgres_db: "semaphore" +# vault_semaphore_database_password: defined in vault + +# Named volumes +semaphore_data_volume: "semaphore_data" diff --git a/ansible/roles/semaphore/handlers/main.yml b/ansible/roles/semaphore/handlers/main.yml index 73b314f..ddcec0a 100644 --- a/ansible/roles/semaphore/handlers/main.yml +++ b/ansible/roles/semaphore/handlers/main.yml @@ -1 +1,16 @@ ---- \ No newline at end of file +--- +- name: reload systemd and start postgres + ansible.builtin.systemd: + daemon_reload: true + name: "{{ semaphore_postgres_container_name }}.service" + state: started + enabled: true + become: true + +- name: reload systemd and start semaphore + ansible.builtin.systemd: + daemon_reload: true + name: "{{ semaphore_container_name }}.service" + state: started + enabled: true + become: true diff --git a/ansible/roles/semaphore/tasks/main.yml b/ansible/roles/semaphore/tasks/main.yml index 9336c06..c848e34 100644 --- a/ansible/roles/semaphore/tasks/main.yml +++ b/ansible/roles/semaphore/tasks/main.yml @@ -20,60 +20,106 @@ owner: "{{ semaphore_user }}" group: "{{ semaphore_group }}" mode: "0777" + recurse: true become: true - changed_when: true loop: - "{{ semaphore_data_dir }}" - "{{ semaphore_config_dir }}" -- name: Create Semaphore config.json for BoltDB - ansible.builtin.copy: - dest: "{{ semaphore_config_dir }}/config.json" - owner: "{{ semaphore_user }}" - group: "{{ semaphore_group }}" - mode: "0644" - content: | - { - "db": { - "Dialect": "bolt", - "BoltDB": { - "Path": "{{ semaphore_data_dir }}" +- name: Deploy PostgreSQL quadlet (when using postgres backend) + when: semaphore_use_postgres | default(false) + block: + - name: Ensure containers systemd directory exists + ansible.builtin.file: + path: /etc/containers/systemd + state: directory + mode: "0755" + become: true + + - name: Deploy PostgreSQL quadlet file + ansible.builtin.template: + src: semaphore-postgres.container.j2 + dest: "/etc/containers/systemd/{{ semaphore_postgres_container_name }}.container" + owner: root + group: root + mode: "0644" + become: true + notify: reload systemd and start postgres + + - name: Deploy Semaphore quadlet file + ansible.builtin.template: + src: semaphore.container.j2 + dest: "/etc/containers/systemd/{{ semaphore_container_name }}.container" + owner: root + group: root + mode: "0644" + become: true + notify: reload systemd and start semaphore + +- name: Fallback - legacy podman_container deployment (BoltDB) + when: not (semaphore_use_postgres | default(false)) + block: + - name: Create Semaphore config.json for BoltDB + ansible.builtin.copy: + dest: "{{ semaphore_config_dir }}/config.json" + owner: "{{ semaphore_user }}" + group: "{{ semaphore_group }}" + mode: "0644" + content: | + { + "db": { + "Dialect": "bolt", + "BoltDB": { + "Path": "{{ semaphore_data_dir }}" + } + }, + "server": { + "Addr": ":{{ semaphore_port }}" + } } - }, - "server": { - "Addr": ":{{ semaphore_port }}" - } - } + become: true + + - name: Create and start Semaphore container + containers.podman.podman_container: + name: "{{ semaphore_container_name }}" + image: "{{ semaphore_image }}" + state: started + recreate: true + ports: + - "{{ semaphore_port }}:3000" + volumes: + - "{{ semaphore_data_dir }}:/var/lib/semaphore:Z" + - "{{ semaphore_config_dir }}:/etc/semaphore:Z" + env: + SEMAPHORE_DB_DIALECT: "{{ semaphore_db_type }}" + SEMAPHORE_DB_PATH: "{{ semaphore_db_path }}" + SEMAPHORE_ADMIN: admin + SEMAPHORE_ADMIN_PASSWORD: "{{ vault_semaphore_admin_password }}" + SEMAPHORE_ACCESS_KEY_ENCRYPTION: "{{ vault_semaphore_access_key_encryption }}" + + - name: Generate systemd service for the container + ansible.builtin.command: + cmd: podman generate systemd --name --new --files {{ semaphore_container_name }} + chdir: /etc/systemd/system + become: true + changed_when: true + + - name: Reload systemd and enable service + ansible.builtin.systemd: + daemon_reload: true + name: "container-{{ semaphore_container_name }}.service" + state: started + enabled: true become: true -- name: Create and start Semaphore container - containers.podman.podman_container: - name: "{{ semaphore_container_name }}" - image: "{{ semaphore_image }}" - state: started - recreate: true - ports: - - "{{ semaphore_port }}:3000" - volumes: - - "{{ semaphore_data_dir }}:/var/lib/semaphore:Z" - - "{{ semaphore_config_dir }}:/etc/semaphore:Z" - env: - SEMAPHORE_DB_DIALECT: "{{ semaphore_db_type }}" - SEMAPHORE_DB_PATH: "{{ semaphore_db_path }}" - SEMAPHORE_ADMIN: admin - SEMAPHORE_ADMIN_PASSWORD: "{{ vault_semaphore_admin_password }}" - SEMAPHORE_ACCESS_KEY_ENCRYPTION: "{{ vault_semaphore_access_key_encryption }}" - -- name: Generate systemd service for the container - ansible.builtin.command: - cmd: podman generate systemd --name --new --files {{ semaphore_container_name }} - chdir: /etc/systemd/system - become: true - changed_when: true - -- name: Reload systemd and enable service +- name: Ensure quadlet services are started (postgres path) + when: semaphore_use_postgres | default(false) ansible.builtin.systemd: daemon_reload: true - name: "container-{{ semaphore_container_name }}.service" + name: "{{ item }}" state: started - enabled: true \ No newline at end of file + enabled: true + become: true + loop: + - "{{ semaphore_postgres_container_name }}.service" + - "{{ semaphore_container_name }}.service" diff --git a/ansible/roles/semaphore/templates/semaphore-postgres.container.j2 b/ansible/roles/semaphore/templates/semaphore-postgres.container.j2 new file mode 100644 index 0000000..d290c0f --- /dev/null +++ b/ansible/roles/semaphore/templates/semaphore-postgres.container.j2 @@ -0,0 +1,19 @@ +[Unit] +Description=Semaphore PostgreSQL +After=network-online.target +Wants=network-online.target + +[Container] +Image={{ semaphore_postgres_image }} +ContainerName={{ semaphore_postgres_container_name }} +Volume={{ semaphore_postgres_volume }}:/var/lib/postgresql/data:Z +Environment=POSTGRES_USER={{ semaphore_postgres_user }} +Environment=POSTGRES_PASSWORD={{ vault_semaphore_database_password }} +Environment=POSTGRES_DB={{ semaphore_postgres_db }} +Restart=always + +[Service] +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/ansible/roles/semaphore/templates/semaphore.container.j2 b/ansible/roles/semaphore/templates/semaphore.container.j2 index 78ad3a6..b9d9bcf 100644 --- a/ansible/roles/semaphore/templates/semaphore.container.j2 +++ b/ansible/roles/semaphore/templates/semaphore.container.j2 @@ -7,10 +7,15 @@ Wants=network-online.target Image={{ semaphore_image }} ContainerName={{ semaphore_container_name }} PublishPort={{ semaphore_port }}:3000 -Volume={{ semaphore_data_dir }}:/var/lib/semaphore:Z,U -Volume={{ semaphore_config_dir }}:/etc/semaphore:Z,U -Environment=SEMAPHORE_DB_DIALECT={{ semaphore_db_type }} -Environment=SEMAPHORE_DB_PATH={{ semaphore_db_path }} +Volume={{ semaphore_data_volume }}:/var/lib/semaphore:Z +Volume={{ semaphore_config_dir }}:/etc/semaphore:Z +Environment=SEMAPHORE_DB_DIALECT=postgres +Environment=SEMAPHORE_DB_PATH= +Environment=SEMAPHORE_DB_USER={{ semaphore_postgres_user }} +Environment=SEMAPHORE_DB_PASS={{ vault_semaphore_database_password }} +Environment=SEMAPHORE_DB_HOST={{ semaphore_postgres_container_name }} +Environment=SEMAPHORE_DB_PORT=5432 +Environment=SEMAPHORE_DB_NAME={{ semaphore_postgres_db }} Environment=SEMAPHORE_ADMIN=admin Environment=SEMAPHORE_ADMIN_PASSWORD={{ vault_semaphore_admin_password }} Environment=SEMAPHORE_ACCESS_KEY_ENCRYPTION={{ vault_semaphore_access_key_encryption }}