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
This commit is contained in:
Hermes Agent service account
2026-05-28 10:22:28 -05:00
parent 6d5fc7c5c6
commit dcc7e282c7
5 changed files with 148 additions and 51 deletions

View File

@@ -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) # Set to true to force recreation of the admin user (deletes existing user first)
semaphore_force_admin_user: false 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"

View File

@@ -1 +1,16 @@
--- ---
- 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

View File

@@ -20,60 +20,106 @@
owner: "{{ semaphore_user }}" owner: "{{ semaphore_user }}"
group: "{{ semaphore_group }}" group: "{{ semaphore_group }}"
mode: "0777" mode: "0777"
recurse: true
become: true become: true
changed_when: true
loop: loop:
- "{{ semaphore_data_dir }}" - "{{ semaphore_data_dir }}"
- "{{ semaphore_config_dir }}" - "{{ semaphore_config_dir }}"
- name: Create Semaphore config.json for BoltDB - name: Deploy PostgreSQL quadlet (when using postgres backend)
ansible.builtin.copy: when: semaphore_use_postgres | default(false)
dest: "{{ semaphore_config_dir }}/config.json" block:
owner: "{{ semaphore_user }}" - name: Ensure containers systemd directory exists
group: "{{ semaphore_group }}" ansible.builtin.file:
mode: "0644" path: /etc/containers/systemd
content: | state: directory
{ mode: "0755"
"db": { become: true
"Dialect": "bolt",
"BoltDB": { - name: Deploy PostgreSQL quadlet file
"Path": "{{ semaphore_data_dir }}" 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 }}"
}
} }
}, become: true
"server": {
"Addr": ":{{ semaphore_port }}" - 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 become: true
- name: Create and start Semaphore container - name: Ensure quadlet services are started (postgres path)
containers.podman.podman_container: when: semaphore_use_postgres | default(false)
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: ansible.builtin.systemd:
daemon_reload: true daemon_reload: true
name: "container-{{ semaphore_container_name }}.service" name: "{{ item }}"
state: started state: started
enabled: true enabled: true
become: true
loop:
- "{{ semaphore_postgres_container_name }}.service"
- "{{ semaphore_container_name }}.service"

View File

@@ -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

View File

@@ -7,10 +7,15 @@ Wants=network-online.target
Image={{ semaphore_image }} Image={{ semaphore_image }}
ContainerName={{ semaphore_container_name }} ContainerName={{ semaphore_container_name }}
PublishPort={{ semaphore_port }}:3000 PublishPort={{ semaphore_port }}:3000
Volume={{ semaphore_data_dir }}:/var/lib/semaphore:Z,U Volume={{ semaphore_data_volume }}:/var/lib/semaphore:Z
Volume={{ semaphore_config_dir }}:/etc/semaphore:Z,U Volume={{ semaphore_config_dir }}:/etc/semaphore:Z
Environment=SEMAPHORE_DB_DIALECT={{ semaphore_db_type }} Environment=SEMAPHORE_DB_DIALECT=postgres
Environment=SEMAPHORE_DB_PATH={{ semaphore_db_path }} 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=admin
Environment=SEMAPHORE_ADMIN_PASSWORD={{ vault_semaphore_admin_password }} Environment=SEMAPHORE_ADMIN_PASSWORD={{ vault_semaphore_admin_password }}
Environment=SEMAPHORE_ACCESS_KEY_ENCRYPTION={{ vault_semaphore_access_key_encryption }} Environment=SEMAPHORE_ACCESS_KEY_ENCRYPTION={{ vault_semaphore_access_key_encryption }}