├── .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 |
--------------------------------------------------------------------------------