@yunuseon/kayra 0.4.0 → 0.5.1

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/README.md CHANGED
@@ -66,10 +66,11 @@ kayra status
66
66
 
67
67
  | | |
68
68
  |---|---|
69
+ | [**The docs site**](https://yunuseon.github.io/kayra/) | architecture diagrams, getting started, the fleet, the CLI |
70
+ | [The app contract](https://yunuseon.github.io/kayra/concepts/app-contract/) | what an app provides and what it may not do |
71
+ | [CI isolation](https://yunuseon.github.io/kayra/concepts/ci-isolation/) | why app code cannot reach the host or the platform |
72
+ | [Authentication](https://yunuseon.github.io/kayra/concepts/authentication/) | staff login (Authelia), adding people, recovery |
69
73
  | [`CLAUDE.md`](CLAUDE.md) | how everything fits together, and the rules that keep it working |
70
- | [`docs/app-contract.md`](docs/app-contract.md) | what an app provides and what it may not do |
71
- | [`docs/authentication.md`](docs/authentication.md) | staff login (Authelia), adding people, recovery |
72
- | [`docs/ci-isolation.md`](docs/ci-isolation.md) | why app code cannot reach the host or the platform |
73
74
 
74
75
  ## Contributing
75
76
 
@@ -8,6 +8,11 @@
8
8
  #
9
9
  # Never deletes: a stack dropped from the list keeps running until it is
10
10
  # taken down by hand (docker compose -p <name> down).
11
+ #
12
+ # Private images: compose_registry_logins (in secrets.yml, a list of
13
+ # {registry, username, password}) are logged in with a Docker config kept in
14
+ # <dir>/.docker, which every compose call here uses (DOCKER_CONFIG) — the
15
+ # host's own Docker config is never written.
11
16
  # =============================================================================
12
17
 
13
18
  - name: Create stack directories
@@ -52,7 +57,28 @@
52
57
  loop: "{{ compose_stacks.stacks | select('in', compose_stack_secrets | default({})) }}"
53
58
  no_log: true
54
59
 
60
+ - name: Create the stacks' Docker config directory
61
+ ansible.builtin.file:
62
+ path: "{{ compose_stacks.dir }}/.docker"
63
+ state: directory
64
+ owner: root
65
+ group: root
66
+ mode: "0700"
67
+ when: compose_registry_logins | default([]) | length > 0
68
+
69
+ - name: Log in to private registries
70
+ community.docker.docker_login:
71
+ registry_url: "{{ item.registry }}"
72
+ username: "{{ item.username }}"
73
+ password: "{{ item.password }}"
74
+ config_path: "{{ compose_stacks.dir }}/.docker/config.json"
75
+ loop: "{{ compose_registry_logins | default([]) }}"
76
+ loop_control: {label: "{{ item.registry }}"}
77
+ no_log: true
78
+
55
79
  - name: Start stacks
80
+ environment:
81
+ DOCKER_CONFIG: "{{ compose_stacks.dir }}/.docker"
56
82
  community.docker.docker_compose_v2:
57
83
  project_src: "{{ compose_stacks.dir }}/{{ item }}"
58
84
  project_name: "{{ item }}"
@@ -64,6 +90,8 @@
64
90
  # when a file it mounts did (a Caddyfile, Homepage's config): those take
65
91
  # effect on a restart.
66
92
  - name: Restart stacks whose files changed
93
+ environment:
94
+ DOCKER_CONFIG: "{{ compose_stacks.dir }}/.docker"
67
95
  ansible.builtin.command: docker compose --project-name {{ item.item }} --file compose.yaml restart
68
96
  args:
69
97
  chdir: "{{ compose_stacks.dir }}/{{ item.item }}"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  # =============================================================================
3
3
  # forgejo-config role — what a Forgejo holds beyond its own config file, as
4
- # code: users and their SSH keys, Actions runners, repos (created empty
4
+ # code: users and their SSH keys, organizations, Actions runners, repos (created empty
5
5
  # where missing) and their settings (issues, pull requests, actions), Actions
6
6
  # secrets, collaborators, webhooks and push mirrors, from
7
7
  # forgejo_config in the server's vars.yml. Adds what is missing through the
@@ -46,6 +46,45 @@
46
46
  loop_control: {label: "{{ item.name }}"}
47
47
  no_log: true
48
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
+
49
88
  - name: Look up SSH keys
50
89
  ansible.builtin.uri:
51
90
  url: "{{ forgejo_config.url }}/api/v1/users/{{ item.user }}/keys"
@@ -76,10 +115,14 @@
76
115
  # --keep-labels: the runner declares its labels when it starts, and a
77
116
  # register without it replaces them with none, so no job finds a runner
78
117
  # until the runner restarts.
118
+ # A scope (an owner, or owner/repo) limits a runner to those repos; without
119
+ # one it serves every repo on the instance.
79
120
  - name: Register runners
80
121
  ansible.builtin.command:
81
- argv: [docker, exec, -i, "{{ forgejo_config.container }}", forgejo, forgejo-cli, actions, register,
82
- --secret-file, /dev/stdin, --name, "{{ item.name }}", --scope, "{{ item.scope }}", --keep-labels]
122
+ argv: >-
123
+ {{ ['docker', 'exec', '-i', forgejo_config.container, 'forgejo', 'forgejo-cli', 'actions', 'register',
124
+ '--secret-file', '/dev/stdin', '--name', item.name, '--keep-labels']
125
+ + (['--scope', item.scope] if item.scope | default('') | length > 0 else []) }}
83
126
  stdin: "{{ forgejo_runner_secrets[item.name] }}"
84
127
  # Exactly 40 characters: a trailing newline would be a 41st.
85
128
  stdin_add_newline: false
@@ -103,7 +146,9 @@
103
146
  # the content is pushed by hand.
104
147
  - name: Create missing repos
105
148
  ansible.builtin.uri:
106
- url: "{{ forgejo_config.url }}/api/v1/admin/users/{{ item.name.split('/')[0] }}/repos"
149
+ url: >-
150
+ {{ forgejo_config.url }}/api/v1/{{ 'orgs' if item.name.split('/')[0] in (forgejo_config.orgs | default([]) | map(attribute='name'))
151
+ else 'admin/users' }}/{{ item.name.split('/')[0] }}/repos
107
152
  method: POST
108
153
  headers: {Authorization: "token {{ forgejo_admin_token }}"}
109
154
  body_format: json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yunuseon/kayra",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "A self-hosted app platform and the CLI that runs it: Traefik, Authelia, Jenkins, a registry and rootless BuildKit per server, set up with OpenTofu and Ansible.",
5
5
  "license": "MIT",
6
6
  "author": "Yunus Öztürk",