tenseleyflow/shithub / 7441984

Browse files

deploy: add shithubd-runner ansible role

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
74419845481703448e78aa5bcd47286372587740
Parents
0bffad9
Tree
9f5a811

9 changed files

StatusFile+-
M deploy/ansible/inventory/production.example 9 0
M deploy/ansible/inventory/staging.example 6 0
A deploy/ansible/roles/shithubd-runner/defaults/main.yml 21 0
A deploy/ansible/roles/shithubd-runner/handlers/main.yml 8 0
A deploy/ansible/roles/shithubd-runner/tasks/main.yml 109 0
A deploy/ansible/roles/shithubd-runner/templates/config.toml.j2 26 0
A deploy/ansible/roles/shithubd-runner/templates/runner.env.j2 4 0
M deploy/ansible/site.yml 3 0
A deploy/systemd/shithubd-runner.service 37 0
deploy/ansible/inventory/production.examplemodified
@@ -43,3 +43,12 @@ wg_metal_pubkey=REPLACE_ME
4343
 grafana_cloud_prom_url=https://prometheus-prod-XX-prod-REGION.grafana.net/api/prom/push
4444
 grafana_cloud_prom_user=REPLACE_ME    # numeric tenant id
4545
 grafana_cloud_prom_token=REPLACE_ME   # access-policy token
46
+
47
+# Optional Actions runner on this host. Generate the token with:
48
+#   shithubd admin runner register --name prod-runner-1 --labels self-hosted,linux,ubuntu-latest --capacity 1
49
+# Store the real token in ansible-vault or your secret manager.
50
+# shithub_runner_enabled=true
51
+# shithub_runner_token=REPLACE_ME
52
+# shithub_runner_labels=self-hosted,linux,ubuntu-latest
53
+# shithub_runner_capacity=1
54
+# shithub_runner_default_image=ghcr.io/shithub/runner-nix:1.0
deploy/ansible/inventory/staging.examplemodified
@@ -22,3 +22,9 @@ shithub_email_backend=stdout # no real outbound mail in staging
2222
 # s3_secret_access_key=REPLACE_ME
2323
 # s3_use_ssl=true
2424
 # s3_force_path_style=false
25
+
26
+# Optional Actions runner on this host.
27
+# shithub_runner_enabled=true
28
+# shithub_runner_token=REPLACE_ME
29
+# shithub_runner_labels=self-hosted,linux,ubuntu-latest
30
+# shithub_runner_capacity=1
deploy/ansible/roles/shithubd-runner/defaults/main.ymladded
@@ -0,0 +1,21 @@
1
+---
2
+# SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+shithub_runner_enabled: false
5
+shithub_runner_server_url: "https://{{ shithub_domain }}"
6
+shithub_runner_token: ""
7
+shithub_runner_labels:
8
+  - self-hosted
9
+  - linux
10
+  - ubuntu-latest
11
+shithub_runner_capacity: 1
12
+shithub_runner_poll_interval: 5s
13
+shithub_runner_workspace_root: /var/lib/shithubd-runner/workspaces
14
+shithub_runner_workspace_ttl: 24h
15
+shithub_runner_engine: docker
16
+shithub_runner_default_image: ghcr.io/shithub/runner-nix:1.0
17
+shithub_runner_network: bridge
18
+shithub_runner_memory: 2g
19
+shithub_runner_cpus: "2"
20
+shithub_runner_log_level: info
21
+shithub_runner_log_format: text
deploy/ansible/roles/shithubd-runner/handlers/main.ymladded
@@ -0,0 +1,8 @@
1
+---
2
+# SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+- name: daemon-reload
5
+  systemd: { daemon_reload: yes }
6
+
7
+- name: restart shithubd-runner
8
+  systemd: { name: shithubd-runner, state: restarted, enabled: yes }
deploy/ansible/roles/shithubd-runner/tasks/main.ymladded
@@ -0,0 +1,109 @@
1
+---
2
+# SPDX-License-Identifier: AGPL-3.0-or-later
3
+#
4
+# shithubd-runner role: installs the runner binary, config, default
5
+# container image, and systemd unit. Docker itself is a host prerequisite.
6
+
7
+- name: Runner token is configured
8
+  assert:
9
+    that:
10
+      - shithub_runner_token | length > 0
11
+      - shithub_runner_engine == "docker"
12
+    fail_msg: >-
13
+      shithub_runner_token is required and the Ansible role supports
14
+      shithub_runner_engine=docker. Generate a token with
15
+      `shithubd admin runner register`, store it in the inventory or
16
+      vault, and keep Docker installed on the runner host.
17
+  no_log: true
18
+
19
+- name: Runner workspace is inside the systemd write path
20
+  assert:
21
+    that:
22
+      - (shithub_runner_workspace_root | string) is match("^/var/lib/shithubd-runner(/|$)")
23
+    fail_msg: >-
24
+      shithub_runner_workspace_root must stay under /var/lib/shithubd-runner
25
+      unless the shithubd-runner systemd unit's ReadWritePaths= hardening is
26
+      updated with the matching path.
27
+
28
+- name: Docker group exists
29
+  getent:
30
+    database: group
31
+    key: docker
32
+  when: shithub_runner_engine == "docker"
33
+
34
+- name: Runner group
35
+  group:
36
+    name: shithub-runner
37
+    system: yes
38
+
39
+- name: Runner user
40
+  user:
41
+    name: shithub-runner
42
+    group: shithub-runner
43
+    groups: docker
44
+    append: yes
45
+    system: yes
46
+    create_home: no
47
+    home: /var/lib/shithubd-runner
48
+    shell: /usr/sbin/nologin
49
+
50
+- name: Runner directories
51
+  file:
52
+    path: "{{ item.path }}"
53
+    state: directory
54
+    owner: "{{ item.owner }}"
55
+    group: "{{ item.group }}"
56
+    mode: "{{ item.mode }}"
57
+  loop:
58
+    - { path: /etc/shithubd-runner, owner: root, group: shithub-runner, mode: "0750" }
59
+    - { path: /var/lib/shithubd-runner, owner: shithub-runner, group: shithub-runner, mode: "0750" }
60
+    - { path: "{{ shithub_runner_workspace_root }}", owner: shithub-runner, group: shithub-runner, mode: "0750" }
61
+    - { path: /var/lib/shithubd-runner/binaries, owner: shithub-runner, group: shithub-runner, mode: "0750" }
62
+
63
+- name: Upload shithubd-runner binary (built by `make build` locally)
64
+  copy:
65
+    src: "{{ playbook_dir }}/../../bin/shithubd-runner"
66
+    dest: /usr/local/bin/shithubd-runner
67
+    mode: "0755"
68
+    owner: root
69
+    group: root
70
+  notify: restart shithubd-runner
71
+
72
+- name: Archive a versioned runner binary copy
73
+  shell: cp /usr/local/bin/shithubd-runner /var/lib/shithubd-runner/binaries/shithubd-runner-$(date +%Y%m%d-%H%M%S)
74
+  args:
75
+    creates: /var/lib/shithubd-runner/binaries/shithubd-runner-{{ ansible_date_time.iso8601_basic_short }}
76
+
77
+- name: Runner config file
78
+  template:
79
+    src: config.toml.j2
80
+    dest: /etc/shithubd-runner/config.toml
81
+    owner: root
82
+    group: shithub-runner
83
+    mode: "0640"
84
+  notify: restart shithubd-runner
85
+
86
+- name: Runner env file
87
+  template:
88
+    src: runner.env.j2
89
+    dest: /etc/shithubd-runner/runner.env
90
+    owner: shithub-runner
91
+    group: shithub-runner
92
+    mode: "0600"
93
+  no_log: true
94
+  notify: restart shithubd-runner
95
+
96
+- name: Runner systemd unit
97
+  copy:
98
+    src: "{{ playbook_dir }}/../systemd/shithubd-runner.service"
99
+    dest: /etc/systemd/system/shithubd-runner.service
100
+    mode: "0644"
101
+  notify: [daemon-reload, restart shithubd-runner]
102
+
103
+- name: Pull default runner image
104
+  command: "{{ shithub_runner_engine }} pull {{ shithub_runner_default_image }}"
105
+  changed_when: false
106
+  when: not ansible_check_mode
107
+
108
+- name: Enable + start shithubd-runner
109
+  systemd: { name: shithubd-runner, state: started, enabled: yes }
deploy/ansible/roles/shithubd-runner/templates/config.toml.j2added
@@ -0,0 +1,26 @@
1
+# Managed by Ansible. Secrets live in runner.env.
2
+
3
+[server]
4
+base_url = "{{ shithub_runner_server_url }}"
5
+
6
+[runner]
7
+{% if shithub_runner_labels is string %}
8
+labels = {{ shithub_runner_labels.split(",") | map("trim") | list | to_json }}
9
+{% else %}
10
+labels = {{ shithub_runner_labels | to_json }}
11
+{% endif %}
12
+capacity = {{ shithub_runner_capacity }}
13
+poll_interval = "{{ shithub_runner_poll_interval }}"
14
+workspace_root = "{{ shithub_runner_workspace_root }}"
15
+workspace_ttl = "{{ shithub_runner_workspace_ttl }}"
16
+
17
+[engine]
18
+kind = "{{ shithub_runner_engine }}"
19
+default_image = "{{ shithub_runner_default_image }}"
20
+network = "{{ shithub_runner_network }}"
21
+memory = "{{ shithub_runner_memory }}"
22
+cpus = "{{ shithub_runner_cpus }}"
23
+
24
+[log]
25
+level = "{{ shithub_runner_log_level }}"
26
+format = "{{ shithub_runner_log_format }}"
deploy/ansible/roles/shithubd-runner/templates/runner.env.j2added
@@ -0,0 +1,4 @@
1
+# Managed by Ansible — 0600.
2
+# Sourced by shithubd-runner.service via EnvironmentFile=.
3
+
4
+SHITHUB_RUNNER_TOKEN={{ shithub_runner_token }}
deploy/ansible/site.ymlmodified
@@ -23,6 +23,9 @@
2323
       tags: [db, postgres]
2424
     - role: shithubd
2525
       tags: [app, shithubd]
26
+    - role: shithubd-runner
27
+      tags: [app, shithubd-runner, actions-runner]
28
+      when: shithub_runner_enabled | default(false) | bool
2629
     - role: caddy
2730
       tags: [edge, caddy]
2831
     - role: wireguard
deploy/systemd/shithubd-runner.serviceadded
@@ -0,0 +1,37 @@
1
+[Unit]
2
+Description=shithub Actions runner
3
+After=network-online.target docker.service
4
+Wants=network-online.target docker.service
5
+
6
+[Service]
7
+Type=simple
8
+User=shithub-runner
9
+Group=shithub-runner
10
+SupplementaryGroups=docker
11
+EnvironmentFile=/etc/shithubd-runner/runner.env
12
+ExecStart=/usr/local/bin/shithubd-runner run --config /etc/shithubd-runner/config.toml
13
+Restart=on-failure
14
+RestartSec=2
15
+LimitNOFILE=65535
16
+
17
+# Docker access is intentionally broad in S41d. S41e narrows network and
18
+# secret exposure; until then this unit is only for trusted runner hosts.
19
+NoNewPrivileges=yes
20
+ProtectSystem=strict
21
+ProtectHome=yes
22
+PrivateTmp=yes
23
+ReadWritePaths=/var/lib/shithubd-runner
24
+ProtectKernelTunables=yes
25
+ProtectKernelModules=yes
26
+ProtectKernelLogs=yes
27
+ProtectControlGroups=yes
28
+RestrictNamespaces=yes
29
+RestrictRealtime=yes
30
+# Match shithubd-web's posture. Docker and git may need setgid semantics
31
+# inside their own managed trees; S41e revisits runner hardening in depth.
32
+RestrictSUIDSGID=no
33
+LockPersonality=yes
34
+SystemCallArchitectures=native
35
+
36
+[Install]
37
+WantedBy=multi-user.target