@yunuseon/kayra 0.0.0-stage → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +81 -2
- package/ansible/.ansible-lint +13 -0
- package/ansible/ansible.cfg +12 -0
- package/ansible/playbooks/adopt.yml +35 -0
- package/ansible/playbooks/app.yml +21 -0
- package/ansible/playbooks/apps-prune.yml +30 -0
- package/ansible/playbooks/edge.yml +41 -0
- package/ansible/playbooks/nas.yml +47 -0
- package/ansible/playbooks/platform.yml +62 -0
- package/ansible/requirements.yml +7 -0
- package/ansible/roles/app/defaults/main.yml +9 -0
- package/ansible/roles/app/tasks/main.yml +107 -0
- package/ansible/roles/app/templates/app.env.j2 +9 -0
- package/ansible/roles/app/templates/casc-app.yaml.j2 +68 -0
- package/ansible/roles/backup-check/files/backup-check.service +9 -0
- package/ansible/roles/backup-check/files/backup-check.timer +10 -0
- package/ansible/roles/backup-check/tasks/main.yml +59 -0
- package/ansible/roles/backup-check/templates/backup-check.sh.j2 +117 -0
- package/ansible/roles/common/handlers/main.yml +16 -0
- package/ansible/roles/common/tasks/main.yml +100 -0
- package/ansible/roles/compose-stacks/tasks/main.yml +102 -0
- package/ansible/roles/docker/defaults/main.yml +3 -0
- package/ansible/roles/docker/files/buildkit-rootless +23 -0
- package/ansible/roles/docker/handlers/main.yml +12 -0
- package/ansible/roles/docker/tasks/main.yml +82 -0
- package/ansible/roles/edge-proxy/files/docker-compose.yml +13 -0
- package/ansible/roles/edge-proxy/handlers/main.yml +6 -0
- package/ansible/roles/edge-proxy/tasks/main.yml +34 -0
- package/ansible/roles/edge-proxy/templates/Caddyfile.j2 +25 -0
- package/ansible/roles/forgejo-config/tasks/main.yml +333 -0
- package/ansible/roles/platform/defaults/main.yml +76 -0
- package/ansible/roles/platform/tasks/main.yml +298 -0
- package/ansible/roles/platform/templates/authelia-users.yml.j2 +17 -0
- package/ansible/roles/platform/templates/platform.env.j2 +41 -0
- package/ansible/roles/restic-backup/files/docker-compose.postgres.yml +27 -0
- package/ansible/roles/restic-backup/files/docker-compose.yml +12 -0
- package/ansible/roles/restic-backup/handlers/main.yml +7 -0
- package/ansible/roles/restic-backup/tasks/main.yml +121 -0
- package/ansible/roles/restic-backup/templates/backup.env.j2 +9 -0
- package/ansible/roles/restic-backup/templates/backup.sh.j2 +28 -0
- package/ansible/roles/restic-backup/templates/docker-compose.override.yml.j2 +26 -0
- package/ansible/roles/restic-backup/templates/follow-restore-test.sh.j2 +28 -0
- package/ansible/roles/restic-backup/templates/restore-test.sh.j2 +51 -0
- package/ansible/roles/restic-backup/templates/root.crontab.j2 +6 -0
- package/ansible/roles/restic-backup/templates/ssh_config.j2 +12 -0
- package/ansible/roles/wireguard/handlers/main.yml +9 -0
- package/ansible/roles/wireguard/tasks/main.yml +33 -0
- package/ansible/roles/wireguard/templates/wg0.conf.j2 +12 -0
- package/bin/kayra.js +29 -0
- package/cli/dist/brand.js +41 -0
- package/cli/dist/cli.js +21 -0
- package/cli/dist/commands/doctor.js +79 -0
- package/cli/dist/commands/server.js +215 -0
- package/cli/dist/commands/status.js +43 -0
- package/cli/dist/fleet.js +134 -0
- package/cli/dist/interpret.js +98 -0
- package/cli/dist/load.js +49 -0
- package/cli/dist/main.js +94 -0
- package/cli/dist/plan.js +31 -0
- package/cli/dist/result.js +5 -0
- package/cloud-init.yml.tpl +14 -0
- package/compose/authelia/configuration.yml +138 -0
- package/compose/buildkit/buildkitd.toml +14 -0
- package/compose/docker-compose.acme-dns.yml +12 -0
- package/compose/docker-compose.ci.yml +156 -0
- package/compose/docker-compose.yml +83 -0
- package/compose/jenkins/Dockerfile +40 -0
- package/compose/jenkins/casc/00-platform.yaml +158 -0
- package/compose/jenkins/plugins.txt +97 -0
- package/compose/jenkins-agent/Dockerfile +24 -0
- package/compose/traefik/dynamic/authelia.yml +18 -0
- package/compose/traefik/dynamic/middlewares.yml +14 -0
- package/compose/traefik/dynamic/ratelimit.yml +21 -0
- package/compose/traefik/dynamic/tls.yml +9 -0
- package/compose/traefik/traefik.yml +46 -0
- package/hosts.conf +6 -0
- package/package.json +73 -4
- package/scripts/local/app-manifests.sh +26 -0
- package/scripts/local/authelia-dev-init.sh +58 -0
- package/scripts/local/certs.sh +55 -0
- package/scripts/local/home-image.sh +55 -0
- package/scripts/local/home-reach.sh +41 -0
- package/scripts/local/require-main.sh +38 -0
- package/scripts/local/setup-hosts.sh +30 -0
- package/scripts/server/app-purge +26 -0
- package/scripts/server/app-remove +30 -0
- package/scripts/server/jenkins-api +28 -0
- package/scripts/server/jenkins-app-apply +36 -0
- package/scripts/server/jenkins-reload +12 -0
- package/terraform/modules/hcloud-firewall/main.tf +54 -0
- package/terraform/modules/hcloud-firewall/outputs.tf +9 -0
- package/terraform/modules/hcloud-firewall/variables.tf +22 -0
- package/terraform/modules/hcloud-server/main.tf +62 -0
- package/terraform/modules/hcloud-server/outputs.tf +19 -0
- package/terraform/modules/hcloud-server/templates/inventory.ini.tpl +5 -0
- package/terraform/modules/hcloud-server/variables.tf +50 -0
- package/terraform/modules/multipass-instance/main.tf +67 -0
- package/terraform/modules/multipass-instance/outputs.tf +19 -0
- package/terraform/modules/multipass-instance/templates/inventory.ini.tpl +5 -0
- package/terraform/modules/multipass-instance/variables.tf +39 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
---
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# forgejo-config role — what a Forgejo holds beyond its own config file, as
|
|
4
|
+
# code: users and their SSH keys, organizations, Actions runners, repos (created empty
|
|
5
|
+
# where missing) and their settings (issues, pull requests, actions), Actions
|
|
6
|
+
# secrets, collaborators, webhooks and push mirrors, from
|
|
7
|
+
# forgejo_config in the server's vars.yml. Adds what is missing through the
|
|
8
|
+
# API, adopts what already matches, never deletes. Talks to Forgejo on the
|
|
9
|
+
# server itself (forgejo_config.url) with forgejo_admin_token, and mints the
|
|
10
|
+
# tokens the platform's own services use there (forgejo_config.tokens).
|
|
11
|
+
# =============================================================================
|
|
12
|
+
|
|
13
|
+
- name: Wait for Forgejo's API
|
|
14
|
+
ansible.builtin.uri:
|
|
15
|
+
url: "{{ forgejo_config.url }}/api/v1/version"
|
|
16
|
+
register: forgejo_version
|
|
17
|
+
until: forgejo_version.status == 200
|
|
18
|
+
retries: 30
|
|
19
|
+
delay: 2
|
|
20
|
+
|
|
21
|
+
- name: Look up users
|
|
22
|
+
ansible.builtin.uri:
|
|
23
|
+
url: "{{ forgejo_config.url }}/api/v1/users/{{ item.name }}"
|
|
24
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
25
|
+
status_code: [200, 404]
|
|
26
|
+
register: forgejo_users
|
|
27
|
+
loop: "{{ forgejo_config.users | default([]) }}"
|
|
28
|
+
loop_control: {label: "{{ item.name }}"}
|
|
29
|
+
no_log: true
|
|
30
|
+
|
|
31
|
+
# Random password, never stored: these users work with tokens.
|
|
32
|
+
- name: Create missing users
|
|
33
|
+
ansible.builtin.uri:
|
|
34
|
+
url: "{{ forgejo_config.url }}/api/v1/admin/users"
|
|
35
|
+
method: POST
|
|
36
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
37
|
+
body_format: json
|
|
38
|
+
body:
|
|
39
|
+
username: "{{ item.name }}"
|
|
40
|
+
email: "{{ item.email }}"
|
|
41
|
+
password: "{{ lookup('ansible.builtin.password', '/dev/null', length=32) }}"
|
|
42
|
+
must_change_password: false
|
|
43
|
+
status_code: [201]
|
|
44
|
+
changed_when: true
|
|
45
|
+
loop: "{{ forgejo_users.results | selectattr('status', 'eq', 404) | map(attribute='item') | list }}"
|
|
46
|
+
loop_control: {label: "{{ item.name }}"}
|
|
47
|
+
no_log: true
|
|
48
|
+
|
|
49
|
+
- name: Look up organizations
|
|
50
|
+
ansible.builtin.uri:
|
|
51
|
+
url: "{{ forgejo_config.url }}/api/v1/orgs/{{ item.name }}"
|
|
52
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
53
|
+
status_code: [200, 404]
|
|
54
|
+
register: forgejo_orgs
|
|
55
|
+
loop: "{{ forgejo_config.orgs | default([]) }}"
|
|
56
|
+
loop_control: {label: "{{ item.name }}"}
|
|
57
|
+
no_log: true
|
|
58
|
+
|
|
59
|
+
# An organization owns repos and packages alike, and its visibility is
|
|
60
|
+
# theirs: a private one keeps what it owns, images included, from the world.
|
|
61
|
+
# Created with its visibility, so it is never public for a moment.
|
|
62
|
+
- name: Create missing organizations
|
|
63
|
+
ansible.builtin.uri:
|
|
64
|
+
url: "{{ forgejo_config.url }}/api/v1/admin/users/{{ item.owner }}/orgs"
|
|
65
|
+
method: POST
|
|
66
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
67
|
+
body_format: json
|
|
68
|
+
body: {username: "{{ item.name }}", visibility: "{{ item.visibility }}"}
|
|
69
|
+
status_code: [201]
|
|
70
|
+
changed_when: true
|
|
71
|
+
loop: "{{ forgejo_orgs.results | selectattr('status', 'eq', 404) | map(attribute='item') | list }}"
|
|
72
|
+
loop_control: {label: "{{ item.name }}"}
|
|
73
|
+
no_log: true
|
|
74
|
+
|
|
75
|
+
- name: Apply organization visibility
|
|
76
|
+
ansible.builtin.uri:
|
|
77
|
+
url: "{{ forgejo_config.url }}/api/v1/orgs/{{ item.item.name }}"
|
|
78
|
+
method: PATCH
|
|
79
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
80
|
+
body_format: json
|
|
81
|
+
body: {visibility: "{{ item.item.visibility }}"}
|
|
82
|
+
changed_when: true
|
|
83
|
+
when: item.status == 200 and item.json.visibility != item.item.visibility
|
|
84
|
+
loop: "{{ forgejo_orgs.results }}"
|
|
85
|
+
loop_control: {label: "{{ item.item.name }}"}
|
|
86
|
+
no_log: true
|
|
87
|
+
|
|
88
|
+
- name: Look up SSH keys
|
|
89
|
+
ansible.builtin.uri:
|
|
90
|
+
url: "{{ forgejo_config.url }}/api/v1/users/{{ item.user }}/keys"
|
|
91
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
92
|
+
register: forgejo_keys
|
|
93
|
+
loop: "{{ forgejo_config.ssh_keys | default([]) }}"
|
|
94
|
+
loop_control: {label: "{{ item.user }} {{ item.title }}"}
|
|
95
|
+
no_log: true
|
|
96
|
+
|
|
97
|
+
# Matched by type and key, whatever title or comment either side carries.
|
|
98
|
+
- name: Add missing SSH keys
|
|
99
|
+
ansible.builtin.uri:
|
|
100
|
+
url: "{{ forgejo_config.url }}/api/v1/admin/users/{{ item.item.user }}/keys"
|
|
101
|
+
method: POST
|
|
102
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
103
|
+
body_format: json
|
|
104
|
+
body: {title: "{{ item.item.title }}", key: "{{ item.item.key }}", read_only: false}
|
|
105
|
+
status_code: [201]
|
|
106
|
+
changed_when: true
|
|
107
|
+
when: >-
|
|
108
|
+
(item.item.key.split()[:2] | join(' ')) not in
|
|
109
|
+
(item.json | map(attribute='key') | map('split') | map('batch', 2) | map('first') | map('join', ' ') | list)
|
|
110
|
+
loop: "{{ forgejo_keys.results }}"
|
|
111
|
+
loop_control: {label: "{{ item.item.user }} {{ item.item.title }}"}
|
|
112
|
+
no_log: true
|
|
113
|
+
|
|
114
|
+
# Idempotent with the same secret; the runner side registers itself with it.
|
|
115
|
+
# --keep-labels: the runner declares its labels when it starts, and a
|
|
116
|
+
# register without it replaces them with none, so no job finds a runner
|
|
117
|
+
# until the runner restarts.
|
|
118
|
+
- name: Register runners
|
|
119
|
+
ansible.builtin.command:
|
|
120
|
+
argv: [docker, exec, -i, "{{ forgejo_config.container }}", forgejo, forgejo-cli, actions, register,
|
|
121
|
+
--secret-file, /dev/stdin, --name, "{{ item.name }}", --scope, "{{ item.scope }}", --keep-labels]
|
|
122
|
+
stdin: "{{ forgejo_runner_secrets[item.name] }}"
|
|
123
|
+
# Exactly 40 characters: a trailing newline would be a 41st.
|
|
124
|
+
stdin_add_newline: false
|
|
125
|
+
changed_when: false
|
|
126
|
+
loop: "{{ forgejo_config.runners | default([]) }}"
|
|
127
|
+
loop_control: {label: "{{ item.name }}"}
|
|
128
|
+
no_log: true
|
|
129
|
+
|
|
130
|
+
- name: Look up repos
|
|
131
|
+
ansible.builtin.uri:
|
|
132
|
+
url: "{{ forgejo_config.url }}/api/v1/repos/{{ item.name }}"
|
|
133
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
134
|
+
status_code: [200, 404]
|
|
135
|
+
register: forgejo_repos
|
|
136
|
+
loop: "{{ forgejo_config.repos }}"
|
|
137
|
+
loop_control: {label: "{{ item.name }}"}
|
|
138
|
+
no_log: true
|
|
139
|
+
|
|
140
|
+
# Empty, and private from the start where its settings say so (the settings
|
|
141
|
+
# are applied below too, but a public repo, even for a moment, is published);
|
|
142
|
+
# the content is pushed by hand.
|
|
143
|
+
- name: Create missing repos
|
|
144
|
+
ansible.builtin.uri:
|
|
145
|
+
url: >-
|
|
146
|
+
{{ forgejo_config.url }}/api/v1/{{ 'orgs' if item.name.split('/')[0] in (forgejo_config.orgs | default([]) | map(attribute='name'))
|
|
147
|
+
else 'admin/users' }}/{{ item.name.split('/')[0] }}/repos
|
|
148
|
+
method: POST
|
|
149
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
150
|
+
body_format: json
|
|
151
|
+
body:
|
|
152
|
+
name: "{{ item.name.split('/')[1] }}"
|
|
153
|
+
private: "{{ item.settings.private | default(false) }}"
|
|
154
|
+
status_code: [201]
|
|
155
|
+
changed_when: true
|
|
156
|
+
loop: "{{ forgejo_repos.results | selectattr('status', 'eq', 404) | map(attribute='item') | list }}"
|
|
157
|
+
loop_control: {label: "{{ item.name }}"}
|
|
158
|
+
no_log: true
|
|
159
|
+
|
|
160
|
+
- name: Look up collaborator permissions
|
|
161
|
+
ansible.builtin.uri:
|
|
162
|
+
url: "{{ forgejo_config.url }}/api/v1/repos/{{ item.0.name }}/collaborators/{{ item.1.user }}/permission"
|
|
163
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
164
|
+
status_code: [200, 403, 404]
|
|
165
|
+
register: forgejo_permissions
|
|
166
|
+
loop: "{{ forgejo_config.repos | subelements('collaborators', skip_missing=True) }}"
|
|
167
|
+
loop_control: {label: "{{ item.0.name }} {{ item.1.user }}"}
|
|
168
|
+
no_log: true
|
|
169
|
+
|
|
170
|
+
- name: Grant collaborators their permission
|
|
171
|
+
ansible.builtin.uri:
|
|
172
|
+
url: "{{ forgejo_config.url }}/api/v1/repos/{{ item.item.0.name }}/collaborators/{{ item.item.1.user }}"
|
|
173
|
+
method: PUT
|
|
174
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
175
|
+
body_format: json
|
|
176
|
+
body: {permission: "{{ item.item.1.permission }}"}
|
|
177
|
+
status_code: [204]
|
|
178
|
+
changed_when: true
|
|
179
|
+
when: item.status != 200 or item.json.permission != item.item.1.permission
|
|
180
|
+
loop: "{{ forgejo_permissions.results }}"
|
|
181
|
+
loop_control: {label: "{{ item.item.0.name }} {{ item.item.1.user }}"}
|
|
182
|
+
no_log: true
|
|
183
|
+
|
|
184
|
+
# A converted mirror keeps issues and pull requests off.
|
|
185
|
+
- name: Apply repo settings
|
|
186
|
+
ansible.builtin.uri:
|
|
187
|
+
url: "{{ forgejo_config.url }}/api/v1/repos/{{ item.item.name }}"
|
|
188
|
+
method: PATCH
|
|
189
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
190
|
+
body_format: json
|
|
191
|
+
body: "{{ item.item.settings }}"
|
|
192
|
+
changed_when: true
|
|
193
|
+
# Differs if laying the wanted settings over the repo changes anything.
|
|
194
|
+
when: item.json | combine(item.item.settings) != item.json
|
|
195
|
+
loop: "{{ forgejo_repos.results | selectattr('item.settings', 'defined') | list }}"
|
|
196
|
+
loop_control: {label: "{{ item.item.name }}"}
|
|
197
|
+
no_log: true
|
|
198
|
+
|
|
199
|
+
# Write-only in Forgejo, so no comparing: the same value every run.
|
|
200
|
+
- name: Set Actions secrets
|
|
201
|
+
ansible.builtin.uri:
|
|
202
|
+
url: "{{ forgejo_config.url }}/api/v1/repos/{{ item.repo }}/actions/secrets/{{ item.name }}"
|
|
203
|
+
method: PUT
|
|
204
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
205
|
+
body_format: json
|
|
206
|
+
body: {data: "{{ item.value }}"}
|
|
207
|
+
status_code: [201, 204]
|
|
208
|
+
changed_when: false
|
|
209
|
+
loop: "{{ forgejo_action_secrets | default([]) }}"
|
|
210
|
+
loop_control: {label: "{{ item.repo }} {{ item.name }}"}
|
|
211
|
+
no_log: true
|
|
212
|
+
|
|
213
|
+
- name: Look up webhooks and push mirrors
|
|
214
|
+
ansible.builtin.uri:
|
|
215
|
+
url: "{{ forgejo_config.url }}/api/v1/repos/{{ item.0.name }}/{{ item.1 }}"
|
|
216
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
217
|
+
register: forgejo_existing
|
|
218
|
+
loop: "{{ forgejo_config.repos | product(['hooks', 'push_mirrors']) | list }}"
|
|
219
|
+
loop_control: {label: "{{ item.0.name }} {{ item.1 }}"}
|
|
220
|
+
no_log: true
|
|
221
|
+
|
|
222
|
+
# A push to any branch calls the URL (with notifyCommit: Jenkins polls).
|
|
223
|
+
- name: Add missing webhooks
|
|
224
|
+
ansible.builtin.uri:
|
|
225
|
+
url: "{{ forgejo_config.url }}/api/v1/repos/{{ item.0.name }}/hooks"
|
|
226
|
+
method: POST
|
|
227
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
228
|
+
body_format: json
|
|
229
|
+
body:
|
|
230
|
+
type: forgejo
|
|
231
|
+
active: true
|
|
232
|
+
events: [push]
|
|
233
|
+
branch_filter: "*"
|
|
234
|
+
config: {url: "{{ item.1 }}", content_type: json, http_method: post}
|
|
235
|
+
status_code: [201]
|
|
236
|
+
changed_when: true
|
|
237
|
+
when: >-
|
|
238
|
+
item.1 not in (forgejo_existing.results
|
|
239
|
+
| selectattr('item.0.name', 'eq', item.0.name) | selectattr('item.1', 'eq', 'hooks')
|
|
240
|
+
| first).json | map(attribute='config.url') | list
|
|
241
|
+
loop: "{{ forgejo_config.repos | subelements('webhooks', skip_missing=True) }}"
|
|
242
|
+
loop_control: {label: "{{ item.0.name }} {{ item.1 }}"}
|
|
243
|
+
no_log: true
|
|
244
|
+
|
|
245
|
+
- name: Add missing push mirrors
|
|
246
|
+
ansible.builtin.uri:
|
|
247
|
+
url: "{{ forgejo_config.url }}/api/v1/repos/{{ item.name }}/push_mirrors"
|
|
248
|
+
method: POST
|
|
249
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
250
|
+
body_format: json
|
|
251
|
+
body:
|
|
252
|
+
remote_address: "{{ item.push_mirror }}"
|
|
253
|
+
remote_username: "{{ forgejo_push_mirror_credentials.username }}"
|
|
254
|
+
remote_password: "{{ forgejo_push_mirror_credentials.password }}"
|
|
255
|
+
sync_on_commit: true
|
|
256
|
+
interval: 8h0m0s
|
|
257
|
+
status_code: [200, 201]
|
|
258
|
+
changed_when: true
|
|
259
|
+
register: forgejo_new_mirrors
|
|
260
|
+
when: >-
|
|
261
|
+
item.push_mirror is defined and item.push_mirror not in (forgejo_existing.results
|
|
262
|
+
| selectattr('item.0.name', 'eq', item.name) | selectattr('item.1', 'eq', 'push_mirrors')
|
|
263
|
+
| first).json | map(attribute='remote_address') | list
|
|
264
|
+
loop: "{{ forgejo_config.repos }}"
|
|
265
|
+
loop_control: {label: "{{ item.name }}"}
|
|
266
|
+
no_log: true
|
|
267
|
+
|
|
268
|
+
# A new push mirror would wait for the next push or its interval.
|
|
269
|
+
- name: Sync new push mirrors once
|
|
270
|
+
ansible.builtin.uri:
|
|
271
|
+
url: "{{ forgejo_config.url }}/api/v1/repos/{{ item.item.name }}/push_mirrors-sync"
|
|
272
|
+
method: POST
|
|
273
|
+
headers: {Authorization: "token {{ forgejo_admin_token }}"}
|
|
274
|
+
changed_when: true
|
|
275
|
+
when: item is changed
|
|
276
|
+
loop: "{{ forgejo_new_mirrors.results }}"
|
|
277
|
+
loop_control: {label: "{{ item.item.name }}"}
|
|
278
|
+
no_log: true
|
|
279
|
+
|
|
280
|
+
# Tokens for the platform's services on this server (Jenkins, Renovate), into
|
|
281
|
+
# the files the platform role would otherwise fill from the vault. Nobody ever
|
|
282
|
+
# sees one: kept only in that file, minted again when it is missing, wrong or
|
|
283
|
+
# revoked. Unique names, since the admin token cannot delete a user's tokens.
|
|
284
|
+
- name: Read service tokens
|
|
285
|
+
ansible.builtin.slurp:
|
|
286
|
+
src: "{{ platform_dir }}/platform-secrets/{{ item.secret }}"
|
|
287
|
+
register: service_token_files
|
|
288
|
+
loop: "{{ forgejo_config.tokens | default([]) }}"
|
|
289
|
+
loop_control: {label: "{{ item.secret }}"}
|
|
290
|
+
become: true
|
|
291
|
+
no_log: true
|
|
292
|
+
|
|
293
|
+
- name: Check service tokens
|
|
294
|
+
ansible.builtin.uri:
|
|
295
|
+
url: "{{ forgejo_config.url }}/api/v1/user"
|
|
296
|
+
headers: {Authorization: "token {{ item.content | b64decode | trim }}"}
|
|
297
|
+
status_code: [200, 400, 401, 403]
|
|
298
|
+
register: service_token_checks
|
|
299
|
+
loop: "{{ service_token_files.results }}"
|
|
300
|
+
loop_control: {label: "{{ item.item.secret }}"}
|
|
301
|
+
no_log: true
|
|
302
|
+
|
|
303
|
+
- name: Mint service tokens
|
|
304
|
+
ansible.builtin.command:
|
|
305
|
+
argv: [docker, exec, "{{ forgejo_config.container }}", forgejo, admin, user, generate-access-token,
|
|
306
|
+
--username, "{{ item.item.item.user }}",
|
|
307
|
+
--token-name, "{{ item.item.item.name }}-{{ now(utc=true, fmt='%Y%m%d%H%M') }}",
|
|
308
|
+
--scopes, "{{ (item.item.item.scopes + ['read:user']) | unique | join(',') }}", --raw]
|
|
309
|
+
register: service_tokens_minted
|
|
310
|
+
changed_when: true
|
|
311
|
+
when: item.status != 200 or item.json.login != item.item.item.user
|
|
312
|
+
loop: "{{ service_token_checks.results }}"
|
|
313
|
+
loop_control: {label: "{{ item.item.item.secret }}"}
|
|
314
|
+
no_log: true
|
|
315
|
+
|
|
316
|
+
- name: Store service tokens
|
|
317
|
+
ansible.builtin.copy:
|
|
318
|
+
content: "{{ item.stdout | trim }}"
|
|
319
|
+
dest: "{{ platform_dir }}/platform-secrets/{{ item.item.item.item.secret }}"
|
|
320
|
+
owner: "1000"
|
|
321
|
+
group: "1000"
|
|
322
|
+
mode: "0400"
|
|
323
|
+
when: item is changed
|
|
324
|
+
loop: "{{ service_tokens_minted.results }}"
|
|
325
|
+
loop_control: {label: "{{ item.item.item.item.secret }}"}
|
|
326
|
+
become: true
|
|
327
|
+
no_log: true
|
|
328
|
+
|
|
329
|
+
- name: Reload JCasC with the new tokens
|
|
330
|
+
ansible.builtin.command: "{{ platform_dir }}/bin/jenkins-reload"
|
|
331
|
+
when: service_tokens_minted is changed
|
|
332
|
+
become: true
|
|
333
|
+
changed_when: true
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# Default variables for the platform role
|
|
4
|
+
# App-agnostic: only knows about infrastructure concerns
|
|
5
|
+
# =============================================================================
|
|
6
|
+
# The server-side contract apps rely on: they drop files into platform_dir and
|
|
7
|
+
# live under apps_dir/<name>. Changing either means changing every app.
|
|
8
|
+
platform_dir: /opt/platform
|
|
9
|
+
apps_dir: /opt/apps
|
|
10
|
+
# The ref of git_remote_url (kayra's repo) Jenkins loads the pipeline library
|
|
11
|
+
# and platform jobs from: the tag of the release the operator ran
|
|
12
|
+
# (kayra_version, passed by kayra), so the Mac and the server run the same
|
|
13
|
+
# kayra. Only a test server sets a branch here instead.
|
|
14
|
+
platform_ref: "v{{ kayra_version }}"
|
|
15
|
+
domain: localhost
|
|
16
|
+
# The engine's files (compose files, scripts) lie above the playbooks, wherever
|
|
17
|
+
# the fleet it serves is; the fleet's (servers/, .cache/) above the server's
|
|
18
|
+
# folder.
|
|
19
|
+
kayra_root: "{{ playbook_dir }}/../.."
|
|
20
|
+
fleet_root: "{{ server_dir }}/../.."
|
|
21
|
+
|
|
22
|
+
# Certificates by DNS-01 instead of HTTP-01, for a server the internet never
|
|
23
|
+
# reaches (target home): "hetzner" (the only provider so far), with
|
|
24
|
+
# acme_dns_token, a token of the Hetzner project holding the zone. Traefik then
|
|
25
|
+
# gets a certificate per hostname that way (each router names its resolver,
|
|
26
|
+
# so an entrypoint wildcard would go unused), and no mkcert cert is used.
|
|
27
|
+
acme_dns: ""
|
|
28
|
+
acme_dns_token: ""
|
|
29
|
+
|
|
30
|
+
# Jenkins
|
|
31
|
+
jenkins_admin_user: admin
|
|
32
|
+
jenkins_admin_password: admin
|
|
33
|
+
|
|
34
|
+
# Git
|
|
35
|
+
git_remote_url: ""
|
|
36
|
+
git_token: ""
|
|
37
|
+
|
|
38
|
+
# owner/repo of the repos this server's platform-renovate keeps up to date
|
|
39
|
+
# (weekly version-bump PRs); the job exists only where the list is not empty.
|
|
40
|
+
# renovate_token is a PAT reaching each of them: Contents, Pull requests and
|
|
41
|
+
# Issues (the dependency dashboard) read & write. On a server whose own
|
|
42
|
+
# Forgejo hosts them, forgejo_config.tokens mints it (and git_token) instead.
|
|
43
|
+
renovate_repositories: []
|
|
44
|
+
renovate_token: ""
|
|
45
|
+
# When platform-renovate runs, as a Jenkins cron spec: weekly suits other
|
|
46
|
+
# people's releases; a server whose repos carry the operator's own images
|
|
47
|
+
# runs it more often, so a new build reaches a PR soon after it is pushed.
|
|
48
|
+
renovate_schedule: "H 4 * * 1"
|
|
49
|
+
# Where those repos live: github, or forgejo with renovate_endpoint, its API
|
|
50
|
+
# on platform-internal (http://forgejo:3000/api/v1). With a Forgejo, also
|
|
51
|
+
# renovate_git_url: endpoint, so Renovate clones over that address and not the
|
|
52
|
+
# public one the API reports, and a renovate_host_rules entry allowing it as
|
|
53
|
+
# an internal host.
|
|
54
|
+
renovate_platform: github
|
|
55
|
+
renovate_endpoint: ""
|
|
56
|
+
renovate_git_url: ""
|
|
57
|
+
renovate_host_rules: []
|
|
58
|
+
# Off GitHub, Renovate still looks versions up there (github-releases,
|
|
59
|
+
# github-tags) and refuses to without a token: a fine-grained one with
|
|
60
|
+
# public-repository read access only, no other permissions.
|
|
61
|
+
renovate_github_com_token: ""
|
|
62
|
+
|
|
63
|
+
# --- Authelia ---------------------------------------------------------------
|
|
64
|
+
# Secrets are per server and belong in servers/<server>/vars.yml (vault for prod).
|
|
65
|
+
authelia_session_secret: ""
|
|
66
|
+
authelia_storage_encryption_key: ""
|
|
67
|
+
# Leave the address empty to fall the notifier back to a file, which is what
|
|
68
|
+
# dev does. Passkey enrolment needs real SMTP; see the runbook.
|
|
69
|
+
authelia_smtp_address: ""
|
|
70
|
+
authelia_smtp_username: ""
|
|
71
|
+
authelia_smtp_sender: ""
|
|
72
|
+
authelia_smtp_password: ""
|
|
73
|
+
authelia_users: {}
|
|
74
|
+
|
|
75
|
+
# two_factor or one_factor. Lower it per environment, never here.
|
|
76
|
+
authelia_policy: "two_factor"
|