Skip to main content

clixz

CLI to manage Docker services under /srv/docker following clixz rules (ownership, permissions, POSIX ACLs).

Replaces check_fix_permission.zsh + services.zsh with a single typed Python tool driven by a YAML configuration.

Install

clixz is published on PyPI as the clixz package — the installed command stays clixz. It needs root for most operations (chown / setfacl), so install it system-wide. Commands that need root re-exec themselves through sudo automatically — you no longer have to prefix them yourself (set CLIXZ_NO_SUDO=1 to opt out, e.g. in containers running as root).

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 clixz

With pipx ≥ 1.5 you can use the shorter sudo pipx install --global clixz instead. Debian 12 ships pipx 1.4.3, which needs the env form above.

Then run:

clixz check

Optionally enable shell completion for your user (no sudo):

clixz --install-completion

Once installed, completion suggests service names for commands that take a service argument (check, apply, dev add/remove, meta scaffold/validate), categories for -C/--category, and existing images for image remove.

Update

sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx upgrade clixz

Migrating from a manual install

Earlier setups used hand-written clixz / clixz-update wrapper scripts and a venv in /usr/local/libexec/clixz. Remove them before installing from PyPI:

sudo rm -f  /usr/local/bin/clixz /usr/local/bin/clixz-update
sudo rm -rf /usr/local/libexec/clixz
rm -f ~/.zsh/completions/_clixz ~/.zsh/completions/_coxyz ~/.zcompdump*   # stale completion artefacts (incl. pre-rename)

(/etc/clixz/config.yaml is kept — it is your configuration, not part of the install.)

Configuration

clixz reads, in order: --config FILE, /etc/clixz/config.yaml, ~/.config/clixz/config.yaml, then the bundled defaults.

clixz show-config       # inspect the resolved config
clixz edit              # create/edit /etc/clixz/config.yaml (seeded from defaults)

Example excludes in config.yaml:

exclude:
  - "*.bak"
  - "*/do_not_touch/"

Commands

clixz list                      # list services with image, ports, status
clixz list -C apps              # filter by category

clixz check                     # validate config + audit all services (exit 1 on drift)
clixz check bitwarden           # audit one service
clixz check apps/bitwarden -v   # verbose (show OK findings too)

clixz apply                     # preview planned fixes, confirm, then apply
clixz apply bitwarden -y

clixz create                    # interactive prompts, confirm, then create
clixz create -C apps -n myapp -y

clixz manifest                  # aggregate every service.yaml → API manifest
clixz manifest --dry-run        # validate + preview without writing
clixz meta scaffold apps/nginx  # add a service.yaml template to an existing service
clixz meta validate             # validate all service.yaml descriptors

clixz dev add apps/nginx        # make a service editable via code-server
clixz dev remove apps/nginx     # revoke it
clixz dev list                  # show dev-enabled services

clixz image add api             # scaffold a self-built image context in /opt/images
clixz image remove api          # delete an image build context
clixz image list                # list image build contexts

clixz show-config               # print resolved config
clixz edit                      # edit /etc/clixz/config.yaml

Most operations require root (chown / setfacl). clixz elevates itself with sudo automatically when needed, so the examples above work without a prefix (use CLIXZ_NO_SUDO=1 to disable auto-elevation).

How it works

  • Config (/etc/clixz/config.yaml or bundled default) defines:

    • root dir, ACL principals, authorized categories
    • exclude glob 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, the config/ directory.
    • Never touches: data/ contents, .env files (audit-only).
    • Creates required missing directories before applying path fixes.
  • Dev-mode awareness: both check and apply first 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 on config/ and data/ are treated as expected — not drift — and any fix is non-destructive (it never uses setfacl --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 empty compose.yaml and .env, a service.yaml template, with correct owners + perms + ACL. It does not template compose.yaml — you fill it in. Then it refreshes the dashboard manifest.

  • service.yaml (dashboard descriptor): a per-service file describing how the service appears on the clixz dashboard — name, icon, description, public (true ⇒ exposed by the API, false ⇒ hidden entirely), optional url/kind/container/tags, and a details: block (summary, features, internal ports, depends_on, tech). Put only non-sensitive info here. Its permissions are governed by the service_file rule (default 640).

  • manifest: reads every service.yaml, validates it, and aggregates the public ones into the JSON file at api.manifest (default /srv/docker/apps/api/data/manifest.json, mode 644), which the clixz-api container mounts read-only and serves at /api/services. Private descriptors never reach the manifest.

  • meta scaffold <service>: drops a service.yaml template into an existing service (won't overwrite). meta validate: validates descriptors only.

  • check also validates every service.yaml (a missing one is a warning; a malformed one is an error that fails the check).

  • Self-built images live outside the service tree, in their own build context under images.dir (default /opt/images/<name>/ — Dockerfile + sources). The matching service under /srv/docker stays empty: it just consumes the built image, exactly like a third-party image. Same convention for source repos under repos.dir (default /opt/repos).

    • image add/remove/list: add scaffolds <images.dir>/<name>/ with the configured owner/mode (default boxyz_dev:boxyz_dev, 775) plus a Dockerfile template; remove deletes the whole context; list shows each context with its Dockerfile/compliance.

    • The 775 mode means the dev principal's group can edit while others (the root Komodo Periphery process) can read — so Komodo builds the context with no per-service ACL and without touching /srv/docker isolation.

    • Configure the locations under the images: and repos: sections; check/ apply then enforce the owner/mode of every <dir>/<name> directory.

    • Optional acl: on either section adds named ACL entries on every <dir>/<name> directory (same { principal: perms } form as a rule), for when owner/mode alone isn't enough:

      repos:
        dir: /opt/repos
        owner: "boxyz_dev:boxyz_dev"
        mode: "775"
        acl:
          komodo: "rx"   # grant the komodo principal read+exec via ACL
      
    # Build with Komodo (or the CLI): context = the image's own directory.
    docker build -t api-clixz:latest /opt/images/api
    
  • list: parses each compose.yaml for image/ports and runs an audit to show a compliance status.

  • dev add/remove/list: makes a service editable through code-server. add grants the dev.principal group (default boxyz_dev) a recursive read/write ACL on the service's config/ and data/ (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>/. remove revokes only that group's ACL entry and unmounts. The managed mounts live in a marker-delimited block (# >>> clixz dev ... >>>) that is the single source of truth — list reads it; everything else in the compose is left untouched. Configured under the dev: key in config.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.

clixz 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/clixz/__init__.py, commit, then make release. The tag vX.Y.Z triggers .github/workflows/publish.yml, which publishes to PyPI via Trusted Publishing.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

clixz-1.2.1.tar.gz (84.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

clixz-1.2.1-py3-none-any.whl (70.0 kB view details)

Uploaded Python 3

File details

Details for the file clixz-1.2.1.tar.gz.

File metadata

  • Download URL: clixz-1.2.1.tar.gz
  • Upload date:
  • Size: 84.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for clixz-1.2.1.tar.gz
Algorithm Hash digest
SHA256 642ec668e4cbc47d58810c41aaafbf226ff86b9118df6e7e49b4ccf9508b27c4
MD5 a5f69b44fe42e7fb556994e85e94451d
BLAKE2b-256 57239cd6fd7128230d89149f22deedaa73126f697902e83e68b4a63db12cf2b8

See more details on using hashes here.

Provenance

The following attestation bundles were made for clixz-1.2.1.tar.gz:

Publisher: publish.yml on Coxyz/Clixz

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file clixz-1.2.1-py3-none-any.whl.

File metadata

  • Download URL: clixz-1.2.1-py3-none-any.whl
  • Upload date:
  • Size: 70.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for clixz-1.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 77234e346d3d91a497d7197ea3496e87bb069b7b5f0a1a4bc951c21e00a967e5
MD5 7ac32d338161b4ba5336415248dcb172
BLAKE2b-256 063ce3256fb061657c056446d5314128bae4f652b941e42af0fac8dfa387e2c6

See more details on using hashes here.

Provenance

The following attestation bundles were made for clixz-1.2.1-py3-none-any.whl:

Publisher: publish.yml on Coxyz/Clixz

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.2.1 This release

2 files

1.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page