├── .gitignore ├── .yamllint.yml ├── handlers └── main.yml ├── molecule └── default │ ├── converge.yml │ ├── verify.yml │ └── molecule.yml ├── meta └── main.yml ├── templates └── systemd.service.j2 ├── .github └── workflows │ └── test.yml ├── tasks ├── setup-mysql.yml └── main.yml ├── README.md └── defaults └── main.yml /.gitignore: -------------------------------------------------------------------------------- 1 | *.retry 2 | .vagrant 3 | *.pyc 4 | *.log 5 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | truthy: 6 | check-keys: false 7 | line-length: disable 8 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Semaphore restart 3 | ansible.builtin.systemd: 4 | name: "{{ semaphore_identifier }}" 5 | daemon_reload: true 6 | state: restarted 7 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: true 5 | tasks: 6 | - name: "Include morbidick.test" 7 | ansible.builtin.include_role: 8 | name: "morbidick.semaphore" 9 | -------------------------------------------------------------------------------- /molecule/default/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is an example playbook to execute Ansible tests. 3 | 4 | - name: Verify 5 | hosts: all 6 | gather_facts: false 7 | tasks: 8 | - name: Ensure Semaphore service is runnning 9 | ansible.builtin.uri: 10 | url: http://localhost:3000 11 | return_content: true 12 | register: this 13 | failed_when: "'Ansible Semaphore' not in this.content" 14 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: morbidick 4 | role_name: semaphore 5 | description: install and configure the Ansible UI Semaphore 6 | license: MIT 7 | min_ansible_version: "2.2" 8 | 9 | platforms: 10 | - name: Debian 11 | versions: 12 | - all 13 | - name: Ubuntu 14 | versions: 15 | - all 16 | - name: EL 17 | versions: 18 | - all 19 | 20 | galaxy_tags: 21 | - automation 22 | - ansible 23 | - web 24 | 25 | dependencies: [] 26 | -------------------------------------------------------------------------------- /templates/systemd.service.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | 3 | [Unit] 4 | Description=Semaphore Ansible 5 | Documentation=https://github.com/ansible-semaphore/semaphore 6 | Wants=network-online.target 7 | After=network-online.target 8 | 9 | [Service] 10 | WorkingDirectory={{ semaphore_path }} 11 | ExecStart={{ semaphore_command }} server 12 | ExecReload=/bin/kill -HUP $MAINPID 13 | Restart=always 14 | StandardOutput=syslog 15 | StandardError=syslog 16 | SyslogIdentifier={{ semaphore_identifier }} 17 | User={{ semaphore_user }} 18 | Group={{ semaphore_user }} 19 | 20 | [Install] 21 | WantedBy=multi-user.target 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test 3 | 4 | on: [push, fork] 5 | 6 | jobs: 7 | molecule: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | 13 | - name: Set up Python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: '3.x' 17 | 18 | - name: Install dependencies. 19 | run: pip3 install yamllint ansible-lint ansible "molecule[lint,podman]" 20 | 21 | - name: Install Galaxy dependencies. 22 | run: ansible-galaxy collection install containers.podman 23 | 24 | - name: Run molecule 25 | run: "molecule test" 26 | env: 27 | ANSIBLE_FORCE_COLOR: '1' 28 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: podman 6 | platforms: 7 | - name: centos8 8 | image: quay.io/centos/centos:stream8 9 | tmpfs: 10 | - /run 11 | - /tmp 12 | volumes: 13 | - /sys/fs/cgroup:/sys/fs/cgroup:ro 14 | capabilities: 15 | - SYS_ADMIN 16 | - CAP_NET_BIND_SERVICE 17 | command: "/usr/sbin/init" 18 | pre_build_image: true 19 | published_ports: 20 | - 127.0.0.1:3000:3000 21 | - name: ubuntu2004 22 | image: docker.io/geerlingguy/docker-ubuntu2004-ansible 23 | tmpfs: 24 | - /run 25 | - /tmp 26 | volumes: 27 | - /sys/fs/cgroup:/sys/fs/cgroup:ro 28 | capabilities: 29 | - SYS_ADMIN 30 | command: "/lib/systemd/systemd" 31 | pre_build_image: true 32 | - name: ubuntu2204 33 | image: docker.io/geerlingguy/docker-ubuntu2204-ansible 34 | tmpfs: 35 | - /run 36 | - /tmp 37 | volumes: 38 | - /sys/fs/cgroup:/sys/fs/cgroup:ro 39 | capabilities: 40 | - SYS_ADMIN 41 | command: "/lib/systemd/systemd" 42 | pre_build_image: true 43 | provisioner: 44 | name: ansible 45 | verifier: 46 | name: ansible 47 | lint: | 48 | set -e 49 | yamllint -c .yamllint.yml . 50 | ansible-lint . 51 | -------------------------------------------------------------------------------- /tasks/setup-mysql.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install MySQL-Server 3 | ansible.builtin.package: 4 | name: "{{ semaphore_mysql_server_package }}" 5 | update_cache: true 6 | state: present 7 | register: mysql_install 8 | when: semaphore_mysql_install 9 | 10 | - name: Install MySQL-Client 11 | ansible.builtin.package: 12 | name: "{{ semaphore_mysql_client_package }}" 13 | update_cache: true 14 | state: present 15 | when: semaphore_mysql_install 16 | 17 | - name: Install Python MySQL module 18 | ansible.builtin.package: 19 | name: "{{ semaphore_python_mysql_module }}" 20 | update_cache: true 21 | state: present 22 | when: semaphore_mysql_install or semaphore_mysql_create_db 23 | 24 | - name: Start the MySQL service 25 | ansible.builtin.systemd: 26 | name: "{{ semaphore_mysql_service_name }}" 27 | state: started 28 | enabled: true 29 | when: semaphore_mysql_install 30 | 31 | - name: Update mysql root password for all root accounts 32 | community.mysql.mysql_user: 33 | name: root 34 | host: "{{ item }}" 35 | password: "{{ mysql_root_password }}" 36 | login_user: root 37 | login_password: "{{ mysql_root_password }}" 38 | check_implicit_admin: true 39 | priv: "*.*:ALL,GRANT" 40 | with_items: 41 | - "{{ ansible_hostname }}" 42 | - 127.0.0.1 43 | - ::1 44 | - localhost 45 | when: mysql_install.changed # noqa: no-handler 46 | 47 | - name: Setup semaphore database 48 | community.mysql.mysql_db: 49 | name: "{{ semaphore_mysql_db }}" 50 | state: present 51 | login_user: root 52 | login_password: "{{ mysql_root_password }}" 53 | login_host: "{{ semaphore_mysql_host }}" 54 | login_port: "{{ semaphore_mysql_port }}" 55 | when: semaphore_mysql_create_db 56 | 57 | - name: Setup semaphore database user 58 | community.mysql.mysql_user: 59 | name: "{{ semaphore_mysql_user }}" 60 | host: "{{ semaphore_mysql_allowed_host }}" 61 | password: "{{ semaphore_mysql_password }}" 62 | login_user: root 63 | login_password: "{{ mysql_root_password }}" 64 | login_host: "{{ semaphore_mysql_host }}" 65 | login_port: "{{ semaphore_mysql_port }}" 66 | priv: "{{ semaphore_mysql_db }}.*:ALL,GRANT" 67 | when: semaphore_mysql_create_db 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible UI Semaphore 2 | 3 | Ansible role to install and configure the [Ansible UI Semaphore](https://github.com/ansible-semaphore/semaphore). 4 | 5 | ## Requirements 6 | 7 | None. But for a production environment you should install a webserver as proxy for ssl termination. 8 | 9 | ## Example playbook 10 | 11 | ````yaml 12 | - hosts: all 13 | become: yes 14 | 15 | roles: 16 | - semaphore 17 | 18 | vars: 19 | semaphore_addn_config: 20 | email_alert: true 21 | email_sender: "semaphore@example.com" 22 | ```` 23 | 24 | ### Using an existing database/mariadb 25 | 26 | Just set `semaphore_mysql_install: false` and provide the credentials `semaphore_mysql_*`. 27 | 28 | ## Role variables 29 | 30 | None of the variables below are required. 31 | 32 | | Variable | Default | Comment | 33 | | :--- | :--- | :--- | 34 | | `semaphore_version` | `v2.8.77` | the version to download, also see `semaphore_download_url` and `semaphore_download_checksum` | 35 | | `semaphore_mysql_install` | `true` | whether to install mysql on the host, installs with the password `mysql_root_password` | 36 | | `semaphore_mysql_create_db` | `true` | whether to create the mysql db and user | 37 | | `semaphore_mysql_host`:`semaphore_mysql_port` | `127.0.0.1`:`3306` | the mysql host | 38 | | `semaphore_mysql_db` | semaphore | the mysql database | 39 | | `semaphore_mysql_user` | semaphore | the mysql user | 40 | | `semaphore_mysql_password` | semaphore | the mysql user password | 41 | | `semaphore_user` | semaphore | the user and systemd identifier semaphore runs as | 42 | | `semaphore_port` | `3000` | the port semaphore binds to | 43 | | `semaphore_path` | /opt/semaphore | destination for the binary | 44 | | `semaphore_addn_config` | `{}` | for all options see the [source](https://github.com/ansible-semaphore/semaphore/blob/master/util/config.go#L36-L72) | 45 | | `semaphore_config_path` | /etc/semaphore/semaphore.json | config file | 46 | | `semaphore_default_user` | admin | login name of the default user | 47 | | `semaphore_default_user_name` | `semaphore_default_user` | his human readable name | 48 | | `semaphore_default_user_password` | admin | the password | 49 | | `semaphore_default_user_mail` | admin@example.com | and mail adress | 50 | 51 | For all options see [defaults/main.yml](defaults/main.yml) 52 | 53 | ## Demo/Development 54 | 55 | Molecule is used for testing, the webinterface of the centos machine will be exposed and can be used as demo. 56 | 57 | * run `molecule converge` 58 | * open your browser at [127.0.0.1:3000](http://127.0.0.1:3000) 59 | * and login with user and password `admin`. 60 | 61 | ## License 62 | 63 | MIT 64 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # whether to install mysql on the host and the root password to set 3 | mysql_root_password: "root" 4 | semaphore_mysql_install: true 5 | 6 | # whether to create the mysql db and user 7 | semaphore_mysql_create_db: true 8 | 9 | # database options 10 | semaphore_mysql_host: "127.0.0.1" 11 | semaphore_mysql_port: "3306" 12 | semaphore_mysql_db: "semaphore" 13 | semaphore_mysql_user: "semaphore" 14 | semaphore_mysql_password: "semaphore" 15 | semaphore_mysql_allowed_host: "localhost" 16 | 17 | # the default user that should be created 18 | semaphore_default_user: "admin" 19 | semaphore_default_user_name: "{{ semaphore_default_user }}" 20 | semaphore_default_user_password: "admin" 21 | semaphore_default_user_mail: "admin@example.com" 22 | 23 | # sempahore binary source 24 | semaphore_version: "2.8.77" 25 | semaphore_download_url: "https://github.com/ansible-semaphore/semaphore/releases/download/v{{ semaphore_version }}/semaphore_{{ semaphore_version }}_linux_amd64.tar.gz" 26 | semaphore_download_checksum: "sha256:a67a6ef4a49bf7613e87628bf35db7d239050a99d7123a278c6cc85b2bff7997" 27 | 28 | # binary paths and service identifiers 29 | semaphore_user: "semaphore" 30 | semaphore_path: "/opt/semaphore" 31 | semaphore_config_path: "/etc/semaphore/semaphore.json" 32 | semaphore_tmp_path: "{{ semaphore_path }}/tmp" 33 | semaphore_executable: "{{ semaphore_path }}/semaphore" 34 | semaphore_command: "{{ semaphore_executable }} --config {{ semaphore_config_path | quote }}" 35 | semaphore_version_dir: "{{ semaphore_executable }}_{{ semaphore_version }}" 36 | semaphore_identifier: "{{ semaphore_user }}" 37 | semaphore_systemd_unit_path: "/etc/systemd/system/{{ semaphore_identifier }}.service" 38 | 39 | # semaphore config options 40 | semaphore_port: 3000 41 | semaphore_addn_config: {} 42 | 43 | # mysql command lines to create default user 44 | semaphore_mysql_client_package: "{% if ansible_distribution == 'CentOS' or ansible_distribution == 'Redhat' %}mariadb{% elif ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' %}mysql-client{% endif %}" 45 | semaphore_mysql_server_package: "{% if ansible_distribution == 'CentOS' or ansible_distribution == 'Redhat' %}mariadb-server{% elif ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' %}mysql-server{% endif %}" 46 | semaphore_mysql_service_name: "{% if ansible_distribution == 'CentOS' or ansible_distribution == 'Redhat' %}mariadb{% elif ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' %}mysql{% endif %}" 47 | semaphore_python_mysql_module: "{% if ansible_distribution == 'CentOS' or ansible_distribution == 'RedHat' %}python3-PyMySQL{% elif ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' %}python3-mysqldb{% endif %}" 48 | 49 | # generated secrets in case they dont exist yet 50 | semaphore_config_previous: {} 51 | semaphore_config_base_generated_secrets: 52 | cookie_hash: "{{ lookup('password', '/dev/null length=32') | b64encode }}" 53 | cookie_encryption: "{{ lookup('password', '/dev/null length=32') | b64encode }}" 54 | access_key_encryption: "{{ lookup('password', '/dev/null length=32') | b64encode }}" 55 | 56 | # temporary object to merge the previous given options with the generated ones 57 | semaphore_config_object: 58 | mysql: 59 | host: "{{ semaphore_mysql_host }}:{{ semaphore_mysql_port }}" 60 | user: "{{ semaphore_mysql_user }}" 61 | pass: "{{ semaphore_mysql_password }}" 62 | name: "{{ semaphore_mysql_db }}" 63 | port: ":{{ semaphore_port }}" 64 | tmp_path: "{{ semaphore_tmp_path }}" 65 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure MySQL Server 3 | ansible.builtin.import_tasks: setup-mysql.yml 4 | when: semaphore_mysql_install or semaphore_mysql_create_db 5 | 6 | - name: Install dependencies 7 | ansible.builtin.package: 8 | name: "{{ item }}" 9 | update_cache: true 10 | state: present 11 | with_items: 12 | - git 13 | - python3-pip 14 | 15 | - name: Install ansible 16 | ansible.builtin.pip: 17 | name: ansible 18 | state: present 19 | 20 | - name: Add semaphore user 21 | ansible.builtin.user: 22 | name: "{{ semaphore_user }}" 23 | createhome: false 24 | 25 | - name: Create application directories 26 | ansible.builtin.file: 27 | path: "{{ item }}" 28 | state: directory 29 | owner: "{{ semaphore_user }}" 30 | group: "{{ semaphore_user }}" 31 | mode: 0755 32 | with_items: 33 | - "{{ semaphore_path }}" 34 | - "{{ semaphore_config_path | dirname }}" 35 | - "{{ semaphore_tmp_path }}" 36 | - "{{ semaphore_version_dir }}" 37 | 38 | - name: Download latest release 39 | ansible.builtin.get_url: 40 | url: "{{ semaphore_download_url }}" 41 | checksum: "{{ semaphore_download_checksum | default(omit) }}" 42 | dest: "{{ semaphore_version_dir }}.tar.gz" 43 | owner: "{{ semaphore_user }}" 44 | mode: 0644 45 | 46 | - name: Extract latest release 47 | ansible.builtin.unarchive: 48 | src: "{{ semaphore_version_dir }}.tar.gz" 49 | dest: "{{ semaphore_version_dir }}" 50 | remote_src: true 51 | creates: "{{ semaphore_version_dir }}/semaphore" 52 | owner: "{{ semaphore_user }}" 53 | group: "{{ semaphore_user }}" 54 | 55 | - name: Create current symlink 56 | ansible.builtin.file: 57 | src: "{{ semaphore_version_dir }}/semaphore" 58 | dest: "{{ semaphore_executable }}" 59 | state: "link" 60 | notify: 61 | - Semaphore restart 62 | 63 | - name: Read previous config 64 | ansible.builtin.slurp: 65 | src: "{{ semaphore_config_path }}" 66 | register: semaphore_previous_config_content 67 | ignore_errors: true 68 | failed_when: false 69 | 70 | - name: Parse previous config 71 | ansible.builtin.set_fact: 72 | semaphore_config_previous: "{{ semaphore_previous_config_content.content | b64decode | from_json }}" 73 | when: semaphore_previous_config_content.content is defined 74 | 75 | - name: Ensure secrets in config 76 | ansible.builtin.set_fact: 77 | semaphore_config_previous_with_secrets: 78 | cookie_hash: "{{ semaphore_config_previous.cookie_hash | default(semaphore_config_base_generated_secrets.cookie_hash) }}" 79 | cookie_encryption: "{{ semaphore_config_previous.cookie_encryption | default(semaphore_config_base_generated_secrets.cookie_encryption) }}" 80 | access_key_encryption: "{{ semaphore_config_previous.access_key_encryption | default(semaphore_config_base_generated_secrets.access_key_encryption) }}" 81 | 82 | - name: Create config file 83 | ansible.builtin.copy: 84 | content: "{{ semaphore_config_previous_with_secrets | combine(semaphore_config_object) | combine(semaphore_addn_config) | to_nice_json }}" 85 | dest: "{{ semaphore_config_path }}" 86 | mode: 0644 87 | notify: 88 | - Semaphore restart 89 | 90 | - name: Setup db migrations 91 | ansible.builtin.command: "{{ semaphore_command }} migrate" 92 | register: semaphore_migrations 93 | changed_when: "'Executing migration' in semaphore_migrations.stdout" 94 | notify: 95 | - Semaphore restart 96 | 97 | - name: Check users 98 | ansible.builtin.command: "{{ semaphore_command }} user list" 99 | register: semaphore_users 100 | changed_when: false 101 | 102 | - name: Create default user 103 | ansible.builtin.command: "{{ semaphore_command }} user add --admin --name {{ semaphore_default_user_name | quote }} --login {{ semaphore_default_user | quote }} --email {{ semaphore_default_user_mail | quote }} --password {{ semaphore_default_user_password | quote }}" 104 | when: semaphore_users.stdout == "" 105 | changed_when: true 106 | 107 | - name: Deploy systemd service file 108 | ansible.builtin.template: 109 | src: "systemd.service.j2" 110 | dest: "{{ semaphore_systemd_unit_path }}" 111 | mode: 0644 112 | notify: 113 | - Semaphore restart 114 | 115 | - name: Service start 116 | ansible.builtin.systemd: 117 | name: "{{ semaphore_identifier }}" 118 | state: "started" 119 | enabled: true 120 | --------------------------------------------------------------------------------