@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.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -2
  3. package/ansible/.ansible-lint +13 -0
  4. package/ansible/ansible.cfg +12 -0
  5. package/ansible/playbooks/adopt.yml +35 -0
  6. package/ansible/playbooks/app.yml +21 -0
  7. package/ansible/playbooks/apps-prune.yml +30 -0
  8. package/ansible/playbooks/edge.yml +41 -0
  9. package/ansible/playbooks/nas.yml +47 -0
  10. package/ansible/playbooks/platform.yml +62 -0
  11. package/ansible/requirements.yml +7 -0
  12. package/ansible/roles/app/defaults/main.yml +9 -0
  13. package/ansible/roles/app/tasks/main.yml +107 -0
  14. package/ansible/roles/app/templates/app.env.j2 +9 -0
  15. package/ansible/roles/app/templates/casc-app.yaml.j2 +68 -0
  16. package/ansible/roles/backup-check/files/backup-check.service +9 -0
  17. package/ansible/roles/backup-check/files/backup-check.timer +10 -0
  18. package/ansible/roles/backup-check/tasks/main.yml +59 -0
  19. package/ansible/roles/backup-check/templates/backup-check.sh.j2 +117 -0
  20. package/ansible/roles/common/handlers/main.yml +16 -0
  21. package/ansible/roles/common/tasks/main.yml +100 -0
  22. package/ansible/roles/compose-stacks/tasks/main.yml +102 -0
  23. package/ansible/roles/docker/defaults/main.yml +3 -0
  24. package/ansible/roles/docker/files/buildkit-rootless +23 -0
  25. package/ansible/roles/docker/handlers/main.yml +12 -0
  26. package/ansible/roles/docker/tasks/main.yml +82 -0
  27. package/ansible/roles/edge-proxy/files/docker-compose.yml +13 -0
  28. package/ansible/roles/edge-proxy/handlers/main.yml +6 -0
  29. package/ansible/roles/edge-proxy/tasks/main.yml +34 -0
  30. package/ansible/roles/edge-proxy/templates/Caddyfile.j2 +25 -0
  31. package/ansible/roles/forgejo-config/tasks/main.yml +333 -0
  32. package/ansible/roles/platform/defaults/main.yml +76 -0
  33. package/ansible/roles/platform/tasks/main.yml +298 -0
  34. package/ansible/roles/platform/templates/authelia-users.yml.j2 +17 -0
  35. package/ansible/roles/platform/templates/platform.env.j2 +41 -0
  36. package/ansible/roles/restic-backup/files/docker-compose.postgres.yml +27 -0
  37. package/ansible/roles/restic-backup/files/docker-compose.yml +12 -0
  38. package/ansible/roles/restic-backup/handlers/main.yml +7 -0
  39. package/ansible/roles/restic-backup/tasks/main.yml +121 -0
  40. package/ansible/roles/restic-backup/templates/backup.env.j2 +9 -0
  41. package/ansible/roles/restic-backup/templates/backup.sh.j2 +28 -0
  42. package/ansible/roles/restic-backup/templates/docker-compose.override.yml.j2 +26 -0
  43. package/ansible/roles/restic-backup/templates/follow-restore-test.sh.j2 +28 -0
  44. package/ansible/roles/restic-backup/templates/restore-test.sh.j2 +51 -0
  45. package/ansible/roles/restic-backup/templates/root.crontab.j2 +6 -0
  46. package/ansible/roles/restic-backup/templates/ssh_config.j2 +12 -0
  47. package/ansible/roles/wireguard/handlers/main.yml +9 -0
  48. package/ansible/roles/wireguard/tasks/main.yml +33 -0
  49. package/ansible/roles/wireguard/templates/wg0.conf.j2 +12 -0
  50. package/bin/kayra.js +29 -0
  51. package/cli/dist/brand.js +41 -0
  52. package/cli/dist/cli.js +21 -0
  53. package/cli/dist/commands/doctor.js +79 -0
  54. package/cli/dist/commands/server.js +215 -0
  55. package/cli/dist/commands/status.js +43 -0
  56. package/cli/dist/fleet.js +134 -0
  57. package/cli/dist/interpret.js +98 -0
  58. package/cli/dist/load.js +49 -0
  59. package/cli/dist/main.js +94 -0
  60. package/cli/dist/plan.js +31 -0
  61. package/cli/dist/result.js +5 -0
  62. package/cloud-init.yml.tpl +14 -0
  63. package/compose/authelia/configuration.yml +138 -0
  64. package/compose/buildkit/buildkitd.toml +14 -0
  65. package/compose/docker-compose.acme-dns.yml +12 -0
  66. package/compose/docker-compose.ci.yml +156 -0
  67. package/compose/docker-compose.yml +83 -0
  68. package/compose/jenkins/Dockerfile +40 -0
  69. package/compose/jenkins/casc/00-platform.yaml +158 -0
  70. package/compose/jenkins/plugins.txt +97 -0
  71. package/compose/jenkins-agent/Dockerfile +24 -0
  72. package/compose/traefik/dynamic/authelia.yml +18 -0
  73. package/compose/traefik/dynamic/middlewares.yml +14 -0
  74. package/compose/traefik/dynamic/ratelimit.yml +21 -0
  75. package/compose/traefik/dynamic/tls.yml +9 -0
  76. package/compose/traefik/traefik.yml +46 -0
  77. package/hosts.conf +6 -0
  78. package/package.json +73 -4
  79. package/scripts/local/app-manifests.sh +26 -0
  80. package/scripts/local/authelia-dev-init.sh +58 -0
  81. package/scripts/local/certs.sh +55 -0
  82. package/scripts/local/home-image.sh +55 -0
  83. package/scripts/local/home-reach.sh +41 -0
  84. package/scripts/local/require-main.sh +38 -0
  85. package/scripts/local/setup-hosts.sh +30 -0
  86. package/scripts/server/app-purge +26 -0
  87. package/scripts/server/app-remove +30 -0
  88. package/scripts/server/jenkins-api +28 -0
  89. package/scripts/server/jenkins-app-apply +36 -0
  90. package/scripts/server/jenkins-reload +12 -0
  91. package/terraform/modules/hcloud-firewall/main.tf +54 -0
  92. package/terraform/modules/hcloud-firewall/outputs.tf +9 -0
  93. package/terraform/modules/hcloud-firewall/variables.tf +22 -0
  94. package/terraform/modules/hcloud-server/main.tf +62 -0
  95. package/terraform/modules/hcloud-server/outputs.tf +19 -0
  96. package/terraform/modules/hcloud-server/templates/inventory.ini.tpl +5 -0
  97. package/terraform/modules/hcloud-server/variables.tf +50 -0
  98. package/terraform/modules/multipass-instance/main.tf +67 -0
  99. package/terraform/modules/multipass-instance/outputs.tf +19 -0
  100. package/terraform/modules/multipass-instance/templates/inventory.ini.tpl +5 -0
  101. 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"