clixz 1.2.0__tar.gz

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 (40) hide show
  1. clixz-1.2.0/PKG-INFO +259 -0
  2. clixz-1.2.0/README.md +237 -0
  3. clixz-1.2.0/pyproject.toml +66 -0
  4. clixz-1.2.0/setup.cfg +4 -0
  5. clixz-1.2.0/src/clixz/__init__.py +3 -0
  6. clixz-1.2.0/src/clixz/__main__.py +4 -0
  7. clixz-1.2.0/src/clixz/admind.py +445 -0
  8. clixz-1.2.0/src/clixz/archive.py +138 -0
  9. clixz-1.2.0/src/clixz/cli.py +1752 -0
  10. clixz-1.2.0/src/clixz/compat.py +56 -0
  11. clixz-1.2.0/src/clixz/config.py +482 -0
  12. clixz-1.2.0/src/clixz/default_config.yaml +127 -0
  13. clixz-1.2.0/src/clixz/dev.py +204 -0
  14. clixz-1.2.0/src/clixz/image.py +64 -0
  15. clixz-1.2.0/src/clixz/meta.py +301 -0
  16. clixz-1.2.0/src/clixz/policy.py +733 -0
  17. clixz-1.2.0/src/clixz/runnerd.py +224 -0
  18. clixz-1.2.0/src/clixz/scaffold.py +90 -0
  19. clixz-1.2.0/src/clixz/spec.py +423 -0
  20. clixz-1.2.0/src/clixz/system.py +286 -0
  21. clixz-1.2.0/src/clixz/update.py +214 -0
  22. clixz-1.2.0/src/clixz.egg-info/PKG-INFO +259 -0
  23. clixz-1.2.0/src/clixz.egg-info/SOURCES.txt +38 -0
  24. clixz-1.2.0/src/clixz.egg-info/dependency_links.txt +1 -0
  25. clixz-1.2.0/src/clixz.egg-info/entry_points.txt +4 -0
  26. clixz-1.2.0/src/clixz.egg-info/requires.txt +6 -0
  27. clixz-1.2.0/src/clixz.egg-info/top_level.txt +1 -0
  28. clixz-1.2.0/tests/test_acl.py +414 -0
  29. clixz-1.2.0/tests/test_admind.py +224 -0
  30. clixz-1.2.0/tests/test_apply_flow.py +125 -0
  31. clixz-1.2.0/tests/test_archive.py +129 -0
  32. clixz-1.2.0/tests/test_compat.py +73 -0
  33. clixz-1.2.0/tests/test_config.py +86 -0
  34. clixz-1.2.0/tests/test_dev.py +168 -0
  35. clixz-1.2.0/tests/test_image.py +215 -0
  36. clixz-1.2.0/tests/test_meta.py +168 -0
  37. clixz-1.2.0/tests/test_runnerd.py +101 -0
  38. clixz-1.2.0/tests/test_scaffold.py +77 -0
  39. clixz-1.2.0/tests/test_spec.py +243 -0
  40. clixz-1.2.0/tests/test_update.py +193 -0
clixz-1.2.0/PKG-INFO ADDED
@@ -0,0 +1,259 @@
1
+ Metadata-Version: 2.4
2
+ Name: clixz
3
+ Version: 1.2.0
4
+ Summary: CLI to manage Docker services under /srv/docker (ownership, permissions, ACLs)
5
+ Author: clixz
6
+ Project-URL: Homepage, https://github.com/Coxyz/Clixz
7
+ Project-URL: Repository, https://github.com/Coxyz/Clixz
8
+ Project-URL: Issues, https://github.com/Coxyz/Clixz/issues
9
+ Keywords: docker,acl,permissions,cli,sysadmin
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Topic :: System :: Systems Administration
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: typer>=0.12
18
+ Requires-Dist: rich>=13.7
19
+ Requires-Dist: pyyaml>=6.0
20
+ Provides-Extra: release
21
+ Requires-Dist: bump-my-version>=0.20; extra == "release"
22
+
23
+ # clixz
24
+
25
+ CLI to manage Docker services under `/srv/docker` following clixz rules
26
+ (ownership, permissions, POSIX ACLs).
27
+
28
+ Replaces `check_fix_permission.zsh` + `services.zsh` with a single typed Python
29
+ tool driven by a YAML configuration.
30
+
31
+ ## Install
32
+
33
+ clixz is published on PyPI as the [`clixz`](https://pypi.org/project/clixz/)
34
+ package — the installed command stays `clixz`. It needs root for most operations
35
+ (`chown` / `setfacl`), so install it **system-wide**. Commands that need root
36
+ re-exec themselves through `sudo` automatically — you no longer have to prefix
37
+ them yourself (set `CLIXZ_NO_SUDO=1` to opt out, e.g. in containers running as
38
+ root).
39
+
40
+ ```bash
41
+ sudo apt install -y pipx
42
+
43
+ # Install into an isolated venv under /opt, with the binary on the system PATH.
44
+ sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install clixz
45
+ ```
46
+
47
+ > With pipx ≥ 1.5 you can use the shorter `sudo pipx install --global clixz`
48
+ > instead. Debian 12 ships pipx 1.4.3, which needs the `env` form above.
49
+
50
+ Then run:
51
+
52
+ ```bash
53
+ clixz check
54
+ ```
55
+
56
+ Optionally enable shell completion for your user (no sudo):
57
+
58
+ ```bash
59
+ clixz --install-completion
60
+ ```
61
+
62
+ Once installed, completion suggests service names for commands that take a
63
+ service argument (`check`, `apply`, `dev add/remove`, `meta scaffold/validate`),
64
+ categories for `-C/--category`, and existing images for `image remove`.
65
+
66
+ ### Update
67
+
68
+ ```bash
69
+ sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx upgrade clixz
70
+ ```
71
+
72
+ ### Migrating from a manual install
73
+
74
+ Earlier setups used hand-written `clixz` / `clixz-update` wrapper scripts and a
75
+ venv in `/usr/local/libexec/clixz`. Remove them before installing from PyPI:
76
+
77
+ ```bash
78
+ sudo rm -f /usr/local/bin/clixz /usr/local/bin/clixz-update
79
+ sudo rm -rf /usr/local/libexec/clixz
80
+ rm -f ~/.zsh/completions/_clixz ~/.zsh/completions/_coxyz ~/.zcompdump* # stale completion artefacts (incl. pre-rename)
81
+ ```
82
+
83
+ (`/etc/clixz/config.yaml` is kept — it is your configuration, not part of the
84
+ install.)
85
+
86
+ ## Configuration
87
+
88
+ `clixz` reads, in order: `--config FILE`, `/etc/clixz/config.yaml`,
89
+ `~/.config/clixz/config.yaml`, then the bundled defaults.
90
+
91
+ ```bash
92
+ clixz show-config # inspect the resolved config
93
+ clixz edit # create/edit /etc/clixz/config.yaml (seeded from defaults)
94
+ ```
95
+
96
+ Example excludes in `config.yaml`:
97
+
98
+ ```yaml
99
+ exclude:
100
+ - "*.bak"
101
+ - "*/do_not_touch/"
102
+ ```
103
+
104
+ ## Commands
105
+
106
+ ```bash
107
+ clixz list # list services with image, ports, status
108
+ clixz list -C apps # filter by category
109
+
110
+ clixz check # validate config + audit all services (exit 1 on drift)
111
+ clixz check bitwarden # audit one service
112
+ clixz check apps/bitwarden -v # verbose (show OK findings too)
113
+
114
+ clixz apply # preview planned fixes, confirm, then apply
115
+ clixz apply bitwarden -y
116
+
117
+ clixz create # interactive prompts, confirm, then create
118
+ clixz create -C apps -n myapp -y
119
+
120
+ clixz manifest # aggregate every service.yaml → API manifest
121
+ clixz manifest --dry-run # validate + preview without writing
122
+ clixz meta scaffold apps/nginx # add a service.yaml template to an existing service
123
+ clixz meta validate # validate all service.yaml descriptors
124
+
125
+ clixz dev add apps/nginx # make a service editable via code-server
126
+ clixz dev remove apps/nginx # revoke it
127
+ clixz dev list # show dev-enabled services
128
+
129
+ clixz image add api # scaffold a self-built image context in /opt/images
130
+ clixz image remove api # delete an image build context
131
+ clixz image list # list image build contexts
132
+
133
+ clixz show-config # print resolved config
134
+ clixz edit # edit /etc/clixz/config.yaml
135
+ ```
136
+
137
+ Most operations require root (`chown` / `setfacl`). clixz elevates itself with
138
+ `sudo` automatically when needed, so the examples above work without a prefix
139
+ (use `CLIXZ_NO_SUDO=1` to disable auto-elevation).
140
+
141
+ ## How it works
142
+
143
+ - **Config** (`/etc/clixz/config.yaml` or bundled default) defines:
144
+ - root dir, ACL principals, authorized categories
145
+ - `exclude` glob patterns to ignore paths during audit/apply
146
+ - per-path rules: mode, ACL perms, optional owner override, audit-only flag
147
+ - **`check`**: read-only. First validates the config's *structure* (missing keys,
148
+ bad values, sections nested in the wrong place), then audits permissions/ACL —
149
+ reporting drift and warn-only (`data/`, `.env`).
150
+ - **`apply`**: shows planned changes, asks for confirmation, then applies fixes.
151
+ - Touches: category/service dirs, `compose.yaml`, the `config/` directory.
152
+ - Never touches: `data/` contents, `.env` files (audit-only).
153
+ - Creates required missing directories before applying path fixes.
154
+ - **Dev-mode awareness**: both `check` and `apply` first read the code-server
155
+ compose to learn which services are dev-enabled. For those services the dev
156
+ principal's recursive ACL **and** the default ACL on `config/` and `data/` are
157
+ treated as *expected* — not drift — and any fix is non-destructive (it never
158
+ uses `setfacl --set`/`-b`, which would wipe the dev grant). A leftover dev ACL
159
+ on a service that is *not* dev-enabled is still correctly flagged for removal.
160
+ - **`create`**: scaffolds `<category>/<service>/{config/,data/}` plus **empty**
161
+ `compose.yaml` and `.env`, a `service.yaml` template, with correct owners +
162
+ perms + ACL. It does not template `compose.yaml` — you fill it in. Then it
163
+ refreshes the dashboard manifest.
164
+ - **`service.yaml`** (dashboard descriptor): a per-service file describing how
165
+ the service appears on the clixz dashboard — `name`, `icon`, `description`,
166
+ `public` (true ⇒ exposed by the API, false ⇒ hidden entirely), optional
167
+ `url`/`kind`/`container`/`tags`, and a `details:` block (summary, features,
168
+ internal `ports`, `depends_on`, `tech`). Put only **non-sensitive** info here.
169
+ Its permissions are governed by the `service_file` rule (default `640`).
170
+ - **`manifest`**: reads every `service.yaml`, validates it, and aggregates the
171
+ **public** ones into the JSON file at `api.manifest`
172
+ (default `/srv/docker/apps/api/data/manifest.json`, mode `644`), which the
173
+ clixz-api container mounts read-only and serves at `/api/services`. Private
174
+ descriptors never reach the manifest.
175
+ - **`meta scaffold <service>`**: drops a `service.yaml` template into an existing
176
+ service (won't overwrite). **`meta validate`**: validates descriptors only.
177
+ - **`check`** also validates every `service.yaml` (a missing one is a warning; a
178
+ malformed one is an error that fails the check).
179
+ - **Self-built images** live **outside** the service tree, in their own build
180
+ context under `images.dir` (default `/opt/images/<name>/` — Dockerfile +
181
+ sources). The matching service under `/srv/docker` stays empty: it just
182
+ *consumes* the built image, exactly like a third-party image. Same convention
183
+ for source repos under `repos.dir` (default `/opt/repos`).
184
+ - **`image add/remove/list`**: `add` scaffolds `<images.dir>/<name>/` with the
185
+ configured `owner`/`mode` (default `boxyz_dev:boxyz_dev`, `775`) plus a
186
+ Dockerfile template; `remove` deletes the whole context; `list` shows each
187
+ context with its Dockerfile/compliance.
188
+ - The `775` mode means the dev principal's group can edit while *others* (the
189
+ root Komodo Periphery process) can read — so Komodo builds the context with
190
+ no per-service ACL and **without** touching `/srv/docker` isolation.
191
+ - Configure the locations under the `images:` and `repos:` sections; `check`/
192
+ `apply` then enforce the owner/mode of every `<dir>/<name>` directory.
193
+ - Optional `acl:` on either section adds named ACL entries on every
194
+ `<dir>/<name>` directory (same `{ principal: perms }` form as a rule), for
195
+ when owner/mode alone isn't enough:
196
+
197
+ ```yaml
198
+ repos:
199
+ dir: /opt/repos
200
+ owner: "boxyz_dev:boxyz_dev"
201
+ mode: "775"
202
+ acl:
203
+ komodo: "rx" # grant the komodo principal read+exec via ACL
204
+ ```
205
+
206
+ ```bash
207
+ # Build with Komodo (or the CLI): context = the image's own directory.
208
+ docker build -t api-clixz:latest /opt/images/api
209
+ ```
210
+ - **`list`**: parses each `compose.yaml` for image/ports and runs an audit
211
+ to show a compliance status.
212
+ - **`dev add/remove/list`**: makes a service editable through code-server.
213
+ `add` grants the `dev.principal` group (default `boxyz_dev`) a recursive
214
+ read/write ACL on the service's `config/` and `data/` (existing files **and**
215
+ a default ACL so new files inherit it), and mounts both dirs into the
216
+ code-server compose under `/workspace/services/<category>/<service>/`. `remove`
217
+ revokes *only* that group's ACL entry and unmounts. The managed mounts live in
218
+ a marker-delimited block (`# >>> clixz dev ... >>>`) that is the single source
219
+ of truth — `list` reads it; everything else in the compose is left untouched.
220
+ Configured under the `dev:` key in `config.yaml`.
221
+
222
+ ### ACL handling
223
+
224
+ A path governed by an ACL rule is brought to compliance with a **single
225
+ `setfacl --set` call** that writes the base entries (`u::`/`g::`/`o::`, i.e. the
226
+ octal mode) and the named entries together. `setfacl` then recomputes the ACL
227
+ *mask* as the union of the owning group and every named entry, so each entry
228
+ stays fully effective — `getfacl` never shows an `#effective:` restriction.
229
+
230
+ `clixz` deliberately never runs `chmod` on an ACL-managed path: a `chmod` after
231
+ a `setfacl` would rewrite the mask instead of the group bits and silently shrink
232
+ the effective rights of every named entry.
233
+
234
+ One consequence: when a named entry grants more than the owning group (e.g. a
235
+ principal with `rw` on a `750` directory), the mask widens and `ls -l` shows the
236
+ wider group digit (`770`). That is correct POSIX behaviour — the audit compares
237
+ ACL entries, not the displayed mode.
238
+
239
+ ## File layout (enforced)
240
+
241
+ ```
242
+ /srv/docker/<category>/<service>/
243
+ ├── compose.yaml 660 svc_<cat>:svc_<cat> + ACL principals
244
+ ├── config/ 750 svc_<cat>:svc_<cat> + ACL principals
245
+ │ └── ... (contents not audited)
246
+ └── data/ 750 svc_<cat>:svc_<cat> no ACL (audit only)
247
+ ```
248
+
249
+ ## Development
250
+
251
+ ```bash
252
+ make test # run the test suite
253
+ make build # build sdist + wheel into dist/
254
+ make release # tag the current version and push (CI publishes to PyPI)
255
+ ```
256
+
257
+ Releasing: bump `__version__` in `src/clixz/__init__.py`, commit, then
258
+ `make release`. The tag `vX.Y.Z` triggers `.github/workflows/publish.yml`,
259
+ which publishes to PyPI via Trusted Publishing.
clixz-1.2.0/README.md ADDED
@@ -0,0 +1,237 @@
1
+ # clixz
2
+
3
+ CLI to manage Docker services under `/srv/docker` following clixz rules
4
+ (ownership, permissions, POSIX ACLs).
5
+
6
+ Replaces `check_fix_permission.zsh` + `services.zsh` with a single typed Python
7
+ tool driven by a YAML configuration.
8
+
9
+ ## Install
10
+
11
+ clixz is published on PyPI as the [`clixz`](https://pypi.org/project/clixz/)
12
+ package — the installed command stays `clixz`. It needs root for most operations
13
+ (`chown` / `setfacl`), so install it **system-wide**. Commands that need root
14
+ re-exec themselves through `sudo` automatically — you no longer have to prefix
15
+ them yourself (set `CLIXZ_NO_SUDO=1` to opt out, e.g. in containers running as
16
+ root).
17
+
18
+ ```bash
19
+ sudo apt install -y pipx
20
+
21
+ # Install into an isolated venv under /opt, with the binary on the system PATH.
22
+ sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install clixz
23
+ ```
24
+
25
+ > With pipx ≥ 1.5 you can use the shorter `sudo pipx install --global clixz`
26
+ > instead. Debian 12 ships pipx 1.4.3, which needs the `env` form above.
27
+
28
+ Then run:
29
+
30
+ ```bash
31
+ clixz check
32
+ ```
33
+
34
+ Optionally enable shell completion for your user (no sudo):
35
+
36
+ ```bash
37
+ clixz --install-completion
38
+ ```
39
+
40
+ Once installed, completion suggests service names for commands that take a
41
+ service argument (`check`, `apply`, `dev add/remove`, `meta scaffold/validate`),
42
+ categories for `-C/--category`, and existing images for `image remove`.
43
+
44
+ ### Update
45
+
46
+ ```bash
47
+ sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx upgrade clixz
48
+ ```
49
+
50
+ ### Migrating from a manual install
51
+
52
+ Earlier setups used hand-written `clixz` / `clixz-update` wrapper scripts and a
53
+ venv in `/usr/local/libexec/clixz`. Remove them before installing from PyPI:
54
+
55
+ ```bash
56
+ sudo rm -f /usr/local/bin/clixz /usr/local/bin/clixz-update
57
+ sudo rm -rf /usr/local/libexec/clixz
58
+ rm -f ~/.zsh/completions/_clixz ~/.zsh/completions/_coxyz ~/.zcompdump* # stale completion artefacts (incl. pre-rename)
59
+ ```
60
+
61
+ (`/etc/clixz/config.yaml` is kept — it is your configuration, not part of the
62
+ install.)
63
+
64
+ ## Configuration
65
+
66
+ `clixz` reads, in order: `--config FILE`, `/etc/clixz/config.yaml`,
67
+ `~/.config/clixz/config.yaml`, then the bundled defaults.
68
+
69
+ ```bash
70
+ clixz show-config # inspect the resolved config
71
+ clixz edit # create/edit /etc/clixz/config.yaml (seeded from defaults)
72
+ ```
73
+
74
+ Example excludes in `config.yaml`:
75
+
76
+ ```yaml
77
+ exclude:
78
+ - "*.bak"
79
+ - "*/do_not_touch/"
80
+ ```
81
+
82
+ ## Commands
83
+
84
+ ```bash
85
+ clixz list # list services with image, ports, status
86
+ clixz list -C apps # filter by category
87
+
88
+ clixz check # validate config + audit all services (exit 1 on drift)
89
+ clixz check bitwarden # audit one service
90
+ clixz check apps/bitwarden -v # verbose (show OK findings too)
91
+
92
+ clixz apply # preview planned fixes, confirm, then apply
93
+ clixz apply bitwarden -y
94
+
95
+ clixz create # interactive prompts, confirm, then create
96
+ clixz create -C apps -n myapp -y
97
+
98
+ clixz manifest # aggregate every service.yaml → API manifest
99
+ clixz manifest --dry-run # validate + preview without writing
100
+ clixz meta scaffold apps/nginx # add a service.yaml template to an existing service
101
+ clixz meta validate # validate all service.yaml descriptors
102
+
103
+ clixz dev add apps/nginx # make a service editable via code-server
104
+ clixz dev remove apps/nginx # revoke it
105
+ clixz dev list # show dev-enabled services
106
+
107
+ clixz image add api # scaffold a self-built image context in /opt/images
108
+ clixz image remove api # delete an image build context
109
+ clixz image list # list image build contexts
110
+
111
+ clixz show-config # print resolved config
112
+ clixz edit # edit /etc/clixz/config.yaml
113
+ ```
114
+
115
+ Most operations require root (`chown` / `setfacl`). clixz elevates itself with
116
+ `sudo` automatically when needed, so the examples above work without a prefix
117
+ (use `CLIXZ_NO_SUDO=1` to disable auto-elevation).
118
+
119
+ ## How it works
120
+
121
+ - **Config** (`/etc/clixz/config.yaml` or bundled default) defines:
122
+ - root dir, ACL principals, authorized categories
123
+ - `exclude` glob patterns to ignore paths during audit/apply
124
+ - per-path rules: mode, ACL perms, optional owner override, audit-only flag
125
+ - **`check`**: read-only. First validates the config's *structure* (missing keys,
126
+ bad values, sections nested in the wrong place), then audits permissions/ACL —
127
+ reporting drift and warn-only (`data/`, `.env`).
128
+ - **`apply`**: shows planned changes, asks for confirmation, then applies fixes.
129
+ - Touches: category/service dirs, `compose.yaml`, the `config/` directory.
130
+ - Never touches: `data/` contents, `.env` files (audit-only).
131
+ - Creates required missing directories before applying path fixes.
132
+ - **Dev-mode awareness**: both `check` and `apply` first read the code-server
133
+ compose to learn which services are dev-enabled. For those services the dev
134
+ principal's recursive ACL **and** the default ACL on `config/` and `data/` are
135
+ treated as *expected* — not drift — and any fix is non-destructive (it never
136
+ uses `setfacl --set`/`-b`, which would wipe the dev grant). A leftover dev ACL
137
+ on a service that is *not* dev-enabled is still correctly flagged for removal.
138
+ - **`create`**: scaffolds `<category>/<service>/{config/,data/}` plus **empty**
139
+ `compose.yaml` and `.env`, a `service.yaml` template, with correct owners +
140
+ perms + ACL. It does not template `compose.yaml` — you fill it in. Then it
141
+ refreshes the dashboard manifest.
142
+ - **`service.yaml`** (dashboard descriptor): a per-service file describing how
143
+ the service appears on the clixz dashboard — `name`, `icon`, `description`,
144
+ `public` (true ⇒ exposed by the API, false ⇒ hidden entirely), optional
145
+ `url`/`kind`/`container`/`tags`, and a `details:` block (summary, features,
146
+ internal `ports`, `depends_on`, `tech`). Put only **non-sensitive** info here.
147
+ Its permissions are governed by the `service_file` rule (default `640`).
148
+ - **`manifest`**: reads every `service.yaml`, validates it, and aggregates the
149
+ **public** ones into the JSON file at `api.manifest`
150
+ (default `/srv/docker/apps/api/data/manifest.json`, mode `644`), which the
151
+ clixz-api container mounts read-only and serves at `/api/services`. Private
152
+ descriptors never reach the manifest.
153
+ - **`meta scaffold <service>`**: drops a `service.yaml` template into an existing
154
+ service (won't overwrite). **`meta validate`**: validates descriptors only.
155
+ - **`check`** also validates every `service.yaml` (a missing one is a warning; a
156
+ malformed one is an error that fails the check).
157
+ - **Self-built images** live **outside** the service tree, in their own build
158
+ context under `images.dir` (default `/opt/images/<name>/` — Dockerfile +
159
+ sources). The matching service under `/srv/docker` stays empty: it just
160
+ *consumes* the built image, exactly like a third-party image. Same convention
161
+ for source repos under `repos.dir` (default `/opt/repos`).
162
+ - **`image add/remove/list`**: `add` scaffolds `<images.dir>/<name>/` with the
163
+ configured `owner`/`mode` (default `boxyz_dev:boxyz_dev`, `775`) plus a
164
+ Dockerfile template; `remove` deletes the whole context; `list` shows each
165
+ context with its Dockerfile/compliance.
166
+ - The `775` mode means the dev principal's group can edit while *others* (the
167
+ root Komodo Periphery process) can read — so Komodo builds the context with
168
+ no per-service ACL and **without** touching `/srv/docker` isolation.
169
+ - Configure the locations under the `images:` and `repos:` sections; `check`/
170
+ `apply` then enforce the owner/mode of every `<dir>/<name>` directory.
171
+ - Optional `acl:` on either section adds named ACL entries on every
172
+ `<dir>/<name>` directory (same `{ principal: perms }` form as a rule), for
173
+ when owner/mode alone isn't enough:
174
+
175
+ ```yaml
176
+ repos:
177
+ dir: /opt/repos
178
+ owner: "boxyz_dev:boxyz_dev"
179
+ mode: "775"
180
+ acl:
181
+ komodo: "rx" # grant the komodo principal read+exec via ACL
182
+ ```
183
+
184
+ ```bash
185
+ # Build with Komodo (or the CLI): context = the image's own directory.
186
+ docker build -t api-clixz:latest /opt/images/api
187
+ ```
188
+ - **`list`**: parses each `compose.yaml` for image/ports and runs an audit
189
+ to show a compliance status.
190
+ - **`dev add/remove/list`**: makes a service editable through code-server.
191
+ `add` grants the `dev.principal` group (default `boxyz_dev`) a recursive
192
+ read/write ACL on the service's `config/` and `data/` (existing files **and**
193
+ a default ACL so new files inherit it), and mounts both dirs into the
194
+ code-server compose under `/workspace/services/<category>/<service>/`. `remove`
195
+ revokes *only* that group's ACL entry and unmounts. The managed mounts live in
196
+ a marker-delimited block (`# >>> clixz dev ... >>>`) that is the single source
197
+ of truth — `list` reads it; everything else in the compose is left untouched.
198
+ Configured under the `dev:` key in `config.yaml`.
199
+
200
+ ### ACL handling
201
+
202
+ A path governed by an ACL rule is brought to compliance with a **single
203
+ `setfacl --set` call** that writes the base entries (`u::`/`g::`/`o::`, i.e. the
204
+ octal mode) and the named entries together. `setfacl` then recomputes the ACL
205
+ *mask* as the union of the owning group and every named entry, so each entry
206
+ stays fully effective — `getfacl` never shows an `#effective:` restriction.
207
+
208
+ `clixz` deliberately never runs `chmod` on an ACL-managed path: a `chmod` after
209
+ a `setfacl` would rewrite the mask instead of the group bits and silently shrink
210
+ the effective rights of every named entry.
211
+
212
+ One consequence: when a named entry grants more than the owning group (e.g. a
213
+ principal with `rw` on a `750` directory), the mask widens and `ls -l` shows the
214
+ wider group digit (`770`). That is correct POSIX behaviour — the audit compares
215
+ ACL entries, not the displayed mode.
216
+
217
+ ## File layout (enforced)
218
+
219
+ ```
220
+ /srv/docker/<category>/<service>/
221
+ ├── compose.yaml 660 svc_<cat>:svc_<cat> + ACL principals
222
+ ├── config/ 750 svc_<cat>:svc_<cat> + ACL principals
223
+ │ └── ... (contents not audited)
224
+ └── data/ 750 svc_<cat>:svc_<cat> no ACL (audit only)
225
+ ```
226
+
227
+ ## Development
228
+
229
+ ```bash
230
+ make test # run the test suite
231
+ make build # build sdist + wheel into dist/
232
+ make release # tag the current version and push (CI publishes to PyPI)
233
+ ```
234
+
235
+ Releasing: bump `__version__` in `src/clixz/__init__.py`, commit, then
236
+ `make release`. The tag `vX.Y.Z` triggers `.github/workflows/publish.yml`,
237
+ which publishes to PyPI via Trusted Publishing.
@@ -0,0 +1,66 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "clixz"
7
+ dynamic = ["version"]
8
+ description = "CLI to manage Docker services under /srv/docker (ownership, permissions, ACLs)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [{ name = "clixz" }]
12
+ keywords = ["docker", "acl", "permissions", "cli", "sysadmin"]
13
+ classifiers = [
14
+ "Environment :: Console",
15
+ "Intended Audience :: System Administrators",
16
+ "Operating System :: POSIX :: Linux",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Topic :: System :: Systems Administration",
19
+ ]
20
+ dependencies = [
21
+ "typer>=0.12",
22
+ "rich>=13.7",
23
+ "pyyaml>=6.0",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ release = ["bump-my-version>=0.20"]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/Coxyz/Clixz"
31
+ Repository = "https://github.com/Coxyz/Clixz"
32
+ Issues = "https://github.com/Coxyz/Clixz/issues"
33
+
34
+ [project.scripts]
35
+ clixz = "clixz.cli:app"
36
+ # Privileged daemon, started by systemd as root. Installing it as a console
37
+ # script places its code under the root-owned venv, so the ExecStart target
38
+ # cannot be rewritten by an application group.
39
+ clixz-admind = "clixz.admind:main"
40
+ # Unprivileged gateway for the MCP container; same reasoning on the install path.
41
+ clixz-runnerd = "clixz.runnerd:main"
42
+
43
+ [tool.setuptools.dynamic]
44
+ version = { attr = "clixz.__version__" }
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["src"]
48
+
49
+ [tool.setuptools.package-data]
50
+ clixz = ["default_config.yaml"]
51
+
52
+ # `make release` drives this: bumps the version, commits, and creates a vX.Y.Z
53
+ # tag. Pushing that tag triggers .github/workflows/publish.yml → PyPI.
54
+ [tool.bumpversion]
55
+ current_version = "1.2.0"
56
+ allow_dirty = false
57
+ commit = true
58
+ message = "Bump version: {current_version} → {new_version}"
59
+ tag = true
60
+ tag_name = "v{new_version}"
61
+ tag_message = "Release v{new_version}"
62
+
63
+ [[tool.bumpversion.files]]
64
+ filename = "src/clixz/__init__.py"
65
+ search = '__version__ = "{current_version}"'
66
+ replace = '__version__ = "{new_version}"'
clixz-1.2.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """clixz — CLI to manage Docker services under /srv/docker."""
2
+
3
+ __version__ = "1.2.0"
@@ -0,0 +1,4 @@
1
+ from .cli import cli_main
2
+
3
+ if __name__ == "__main__":
4
+ cli_main()