CLI to manage Docker services under /srv/docker (ownership, permissions, ACLs)
Project description
coxyz
CLI to manage Docker services under /srv/docker following coxyz rules
(ownership, permissions, POSIX ACLs).
Replaces check_fix_permission.zsh + services.zsh with a single typed Python
tool driven by a YAML configuration.
Install
coxyz is published on PyPI as the coxyz-cli
package — the installed command stays coxyz. It needs root for most operations
(chown / setfacl), so install it system-wide and run it with sudo.
sudo apt install -y pipx
# Install into an isolated venv under /opt, with the binary on the system PATH.
sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install coxyz-cli
With pipx ≥ 1.5 you can use the shorter
sudo pipx install --global coxyz-cliinstead. Debian 12 ships pipx 1.4.3, which needs theenvform above.
Then run:
sudo coxyz check
Optionally enable shell completion for your user (no sudo):
coxyz --install-completion
Update
sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx upgrade coxyz-cli
Migrating from a manual install
Earlier setups used hand-written coxyz / coxyz-update wrapper scripts and a
venv in /usr/local/libexec/coxyz. Remove them before installing from PyPI:
sudo rm -f /usr/local/bin/coxyz /usr/local/bin/coxyz-update
sudo rm -rf /usr/local/libexec/coxyz
rm -f ~/.zsh/completions/_coxyz ~/.zcompdump* # stale completion artefacts
(/etc/coxyz/config.yaml is kept — it is your configuration, not part of the
install.)
Configuration
coxyz reads, in order: --config FILE, /etc/coxyz/config.yaml,
~/.config/coxyz/config.yaml, then the bundled defaults.
coxyz show-config # inspect the resolved config
sudo coxyz edit # create/edit /etc/coxyz/config.yaml (seeded from defaults)
Example excludes in config.yaml:
exclude:
- "*.bak"
- "*/do_not_touch/"
Commands
coxyz list # list services with image, ports, status
coxyz list -C apps # filter by category
coxyz check # validate config + audit all services (exit 1 on drift)
coxyz check bitwarden # audit one service
coxyz check apps/bitwarden -v # verbose (show OK findings too)
coxyz apply # preview planned fixes, confirm, then apply
coxyz apply bitwarden -y
coxyz create # interactive prompts, confirm, then create
coxyz create -C apps -n myapp -y
coxyz dev add apps/nginx # make a service editable via code-server
coxyz dev remove apps/nginx # revoke it
coxyz dev list # show dev-enabled services
coxyz show-config # print resolved config
coxyz edit # edit /etc/coxyz/config.yaml
Most operations require root (chown / setfacl), so prefix with sudo.
How it works
- Config (
/etc/coxyz/config.yamlor bundled default) defines:- root dir, ACL principals, authorized categories
excludeglob patterns to ignore paths during audit/apply- per-path rules: mode, ACL perms, optional owner override, audit-only flag
check: read-only. First validates the config's structure (missing keys, bad values, sections nested in the wrong place), then audits permissions/ACL — reporting drift and warn-only (data/,.env).apply: shows planned changes, asks for confirmation, then applies fixes.- Touches: category/service dirs,
compose.yaml, theconfig/directory. - Never touches:
data/contents,.envfiles (audit-only). - Creates required missing directories before applying path fixes.
- Touches: category/service dirs,
- Dev-mode awareness: both
checkandapplyfirst read the code-server compose to learn which services are dev-enabled. For those services the dev principal's recursive ACL and the default ACL onconfig/anddata/are treated as expected — not drift — and any fix is non-destructive (it never usessetfacl --set/-b, which would wipe the dev grant). A leftover dev ACL on a service that is not dev-enabled is still correctly flagged for removal. create: scaffolds<category>/<service>/{config/,data/}plus emptycompose.yamland.env, with correct owners + perms + ACL. It does not templatecompose.yaml— you fill it in.list: parses eachcompose.yamlfor image/ports and runs an audit to show a compliance status.dev add/remove/list: makes a service editable through code-server.addgrants thedev.principalgroup (defaultboxyz_dev) a recursive read/write ACL on the service'sconfig/anddata/(existing files and a default ACL so new files inherit it), and mounts both dirs into the code-server compose under/workspace/services/<category>/<service>/.removerevokes only that group's ACL entry and unmounts. The managed mounts live in a marker-delimited block (# >>> coxyz dev ... >>>) that is the single source of truth —listreads it; everything else in the compose is left untouched. Configured under thedev:key inconfig.yaml.
ACL handling
A path governed by an ACL rule is brought to compliance with a single
setfacl --set call that writes the base entries (u::/g::/o::, i.e. the
octal mode) and the named entries together. setfacl then recomputes the ACL
mask as the union of the owning group and every named entry, so each entry
stays fully effective — getfacl never shows an #effective: restriction.
coxyz deliberately never runs chmod on an ACL-managed path: a chmod after
a setfacl would rewrite the mask instead of the group bits and silently shrink
the effective rights of every named entry.
One consequence: when a named entry grants more than the owning group (e.g. a
principal with rw on a 750 directory), the mask widens and ls -l shows the
wider group digit (770). That is correct POSIX behaviour — the audit compares
ACL entries, not the displayed mode.
File layout (enforced)
/srv/docker/<category>/<service>/
├── compose.yaml 660 svc_<cat>:svc_<cat> + ACL principals
├── config/ 750 svc_<cat>:svc_<cat> + ACL principals
│ └── ... (contents not audited)
└── data/ 750 svc_<cat>:svc_<cat> no ACL (audit only)
Development
make test # run the test suite
make build # build sdist + wheel into dist/
make release # tag the current version and push (CI publishes to PyPI)
Releasing: bump __version__ in src/coxyz/__init__.py, commit, then
make release. The tag vX.Y.Z triggers .github/workflows/publish.yml,
which publishes to PyPI via Trusted Publishing.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file coxyz_cli-0.3.2.tar.gz.
File metadata
- Download URL: coxyz_cli-0.3.2.tar.gz
- Upload date:
- Size: 36.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8cbabf4f1a6b84ee76f4510b825fd08d5aa3de24c8ff86f336308673e6b02a60
|
|
| MD5 |
c5a2c66dd272c4644fa8758f45f70c75
|
|
| BLAKE2b-256 |
12109c9aeb10a71284687e8af2949d20ab3d2ef8a078bf94df34fb45a0250895
|
Provenance
The following attestation bundles were made for coxyz_cli-0.3.2.tar.gz:
Publisher:
publish.yml on Coxyz/Checker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
coxyz_cli-0.3.2.tar.gz -
Subject digest:
8cbabf4f1a6b84ee76f4510b825fd08d5aa3de24c8ff86f336308673e6b02a60 - Sigstore transparency entry: 1739837110
- Sigstore integration time:
-
Permalink:
Coxyz/Checker@a2f0157bbc7ab0931de95f44a7bfc95d5e22255d -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/Coxyz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a2f0157bbc7ab0931de95f44a7bfc95d5e22255d -
Trigger Event:
push
-
Statement type:
File details
Details for the file coxyz_cli-0.3.2-py3-none-any.whl.
File metadata
- Download URL: coxyz_cli-0.3.2-py3-none-any.whl
- Upload date:
- Size: 30.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b22fb0e086d0f1ee8738309b10a33db1327728ad289e198308549c3ac1e1117c
|
|
| MD5 |
4c9dce632bdd55dcffe44dd5dc62a4aa
|
|
| BLAKE2b-256 |
e676ca356b4dec887f3870e546981332bef8def1dce76535bc4b42b1fea9270e
|
Provenance
The following attestation bundles were made for coxyz_cli-0.3.2-py3-none-any.whl:
Publisher:
publish.yml on Coxyz/Checker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
coxyz_cli-0.3.2-py3-none-any.whl -
Subject digest:
b22fb0e086d0f1ee8738309b10a33db1327728ad289e198308549c3ac1e1117c - Sigstore transparency entry: 1739837134
- Sigstore integration time:
-
Permalink:
Coxyz/Checker@a2f0157bbc7ab0931de95f44a7bfc95d5e22255d -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/Coxyz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a2f0157bbc7ab0931de95f44a7bfc95d5e22255d -
Trigger Event:
push
-
Statement type: