coxyz-cli 0.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.
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: coxyz-cli
3
+ Version: 0.2.0
4
+ Summary: CLI to manage Docker services under /srv/docker (ownership, permissions, ACLs)
5
+ Author: coxyz
6
+ Project-URL: Homepage, https://github.com/Ekyoz/Coxyz-CLI
7
+ Project-URL: Repository, https://github.com/Ekyoz/Coxyz-CLI
8
+ Project-URL: Issues, https://github.com/Ekyoz/Coxyz-CLI/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
+
21
+ # coxyz
22
+
23
+ CLI to manage Docker services under `/srv/docker` following coxyz rules
24
+ (ownership, permissions, POSIX ACLs).
25
+
26
+ Replaces `check_fix_permission.zsh` + `services.zsh` with a single typed Python
27
+ tool driven by a YAML configuration.
28
+
29
+ ## Install
30
+
31
+ coxyz is published on PyPI as the [`coxyz-cli`](https://pypi.org/project/coxyz-cli/)
32
+ package — the installed command stays `coxyz`. It needs root for most operations
33
+ (`chown` / `setfacl`), so install it **system-wide** and run it with `sudo`.
34
+
35
+ ```bash
36
+ sudo apt install -y pipx
37
+
38
+ # Install into an isolated venv under /opt, with the binary on the system PATH.
39
+ sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install coxyz-cli
40
+ ```
41
+
42
+ > With pipx ≥ 1.5 you can use the shorter `sudo pipx install --global coxyz-cli`
43
+ > instead. Debian 12 ships pipx 1.4.3, which needs the `env` form above.
44
+
45
+ Then run:
46
+
47
+ ```bash
48
+ sudo coxyz check
49
+ ```
50
+
51
+ Optionally enable shell completion for your user (no sudo):
52
+
53
+ ```bash
54
+ coxyz --install-completion
55
+ ```
56
+
57
+ ### Update
58
+
59
+ ```bash
60
+ sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx upgrade coxyz-cli
61
+ ```
62
+
63
+ ### Migrating from a manual install
64
+
65
+ Earlier setups used hand-written `coxyz` / `coxyz-update` wrapper scripts and a
66
+ venv in `/usr/local/libexec/coxyz`. Remove them before installing from PyPI:
67
+
68
+ ```bash
69
+ sudo rm -f /usr/local/bin/coxyz /usr/local/bin/coxyz-update
70
+ sudo rm -rf /usr/local/libexec/coxyz
71
+ rm -f ~/.zsh/completions/_coxyz ~/.zcompdump* # stale completion artefacts
72
+ ```
73
+
74
+ (`/etc/coxyz/config.yaml` is kept — it is your configuration, not part of the
75
+ install.)
76
+
77
+ ## Configuration
78
+
79
+ `coxyz` reads, in order: `--config FILE`, `/etc/coxyz/config.yaml`,
80
+ `~/.config/coxyz/config.yaml`, then the bundled defaults.
81
+
82
+ ```bash
83
+ coxyz show-config # inspect the resolved config
84
+ sudo coxyz edit # create/edit /etc/coxyz/config.yaml (seeded from defaults)
85
+ ```
86
+
87
+ Example excludes in `config.yaml`:
88
+
89
+ ```yaml
90
+ exclude:
91
+ - "*.bak"
92
+ - "*/do_not_touch/"
93
+ ```
94
+
95
+ ## Commands
96
+
97
+ ```bash
98
+ coxyz list # list services with image, ports, status
99
+ coxyz list -C apps # filter by category
100
+
101
+ coxyz check # audit all services (exit 1 on drift)
102
+ coxyz check bitwarden # audit one service
103
+ coxyz check apps/bitwarden -v # verbose (show OK findings too)
104
+
105
+ coxyz apply # preview planned fixes, confirm, then apply
106
+ coxyz apply bitwarden -y
107
+
108
+ coxyz create # interactive prompts
109
+ coxyz create -C apps -n myapp -i nginx:1.27 -p 80 --apply
110
+
111
+ coxyz show-config # print resolved config
112
+ coxyz edit # edit /etc/coxyz/config.yaml
113
+ ```
114
+
115
+ Most operations require root (`chown` / `setfacl`), so prefix with `sudo`.
116
+
117
+ ## How it works
118
+
119
+ - **Config** (`/etc/coxyz/config.yaml` or bundled default) defines:
120
+ - root dir, ACL principals, authorized categories
121
+ - `exclude` glob patterns to ignore paths during audit/apply
122
+ - per-path rules: mode, ACL perms, optional owner override, audit-only flag
123
+ - **`check`**: read-only audit. Reports drift and warn-only (`data/`, `.env`).
124
+ - **`apply`**: shows planned changes, asks for confirmation, then applies fixes.
125
+ - Touches: category/service dirs, `compose.yaml`, the `config/` directory.
126
+ - Never touches: `data/` contents, `.env` files (audit-only).
127
+ - Creates required missing directories before applying path fixes.
128
+ - **`create`**: scaffolds `<category>/<service>/{compose.yaml,config/,data/}`
129
+ with correct owners + perms + ACL.
130
+ - **`list`**: parses each `compose.yaml` for image/ports and runs an audit
131
+ to show a compliance status.
132
+
133
+ ### ACL handling
134
+
135
+ A path governed by an ACL rule is brought to compliance with a **single
136
+ `setfacl --set` call** that writes the base entries (`u::`/`g::`/`o::`, i.e. the
137
+ octal mode) and the named entries together. `setfacl` then recomputes the ACL
138
+ *mask* as the union of the owning group and every named entry, so each entry
139
+ stays fully effective — `getfacl` never shows an `#effective:` restriction.
140
+
141
+ `coxyz` deliberately never runs `chmod` on an ACL-managed path: a `chmod` after
142
+ a `setfacl` would rewrite the mask instead of the group bits and silently shrink
143
+ the effective rights of every named entry.
144
+
145
+ One consequence: when a named entry grants more than the owning group (e.g. a
146
+ principal with `rw` on a `750` directory), the mask widens and `ls -l` shows the
147
+ wider group digit (`770`). That is correct POSIX behaviour — the audit compares
148
+ ACL entries, not the displayed mode.
149
+
150
+ ## File layout (enforced)
151
+
152
+ ```
153
+ /srv/docker/<category>/<service>/
154
+ ├── compose.yaml 660 svc_<cat>:svc_<cat> + ACL principals
155
+ ├── config/ 750 svc_<cat>:svc_<cat> + ACL principals
156
+ │ └── ... (contents not audited)
157
+ └── data/ 750 svc_<cat>:svc_<cat> no ACL (audit only)
158
+ ```
159
+
160
+ ## Development
161
+
162
+ ```bash
163
+ make test # run the test suite
164
+ make build # build sdist + wheel into dist/
165
+ make release # tag the current version and push (CI publishes to PyPI)
166
+ ```
167
+
168
+ Releasing: bump `__version__` in `src/coxyz/__init__.py`, commit, then
169
+ `make release`. The tag `vX.Y.Z` triggers `.github/workflows/publish.yml`,
170
+ which publishes to PyPI via Trusted Publishing.
@@ -0,0 +1,150 @@
1
+ # coxyz
2
+
3
+ CLI to manage Docker services under `/srv/docker` following coxyz 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
+ coxyz is published on PyPI as the [`coxyz-cli`](https://pypi.org/project/coxyz-cli/)
12
+ package — the installed command stays `coxyz`. It needs root for most operations
13
+ (`chown` / `setfacl`), so install it **system-wide** and run it with `sudo`.
14
+
15
+ ```bash
16
+ sudo apt install -y pipx
17
+
18
+ # Install into an isolated venv under /opt, with the binary on the system PATH.
19
+ sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install coxyz-cli
20
+ ```
21
+
22
+ > With pipx ≥ 1.5 you can use the shorter `sudo pipx install --global coxyz-cli`
23
+ > instead. Debian 12 ships pipx 1.4.3, which needs the `env` form above.
24
+
25
+ Then run:
26
+
27
+ ```bash
28
+ sudo coxyz check
29
+ ```
30
+
31
+ Optionally enable shell completion for your user (no sudo):
32
+
33
+ ```bash
34
+ coxyz --install-completion
35
+ ```
36
+
37
+ ### Update
38
+
39
+ ```bash
40
+ sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx upgrade coxyz-cli
41
+ ```
42
+
43
+ ### Migrating from a manual install
44
+
45
+ Earlier setups used hand-written `coxyz` / `coxyz-update` wrapper scripts and a
46
+ venv in `/usr/local/libexec/coxyz`. Remove them before installing from PyPI:
47
+
48
+ ```bash
49
+ sudo rm -f /usr/local/bin/coxyz /usr/local/bin/coxyz-update
50
+ sudo rm -rf /usr/local/libexec/coxyz
51
+ rm -f ~/.zsh/completions/_coxyz ~/.zcompdump* # stale completion artefacts
52
+ ```
53
+
54
+ (`/etc/coxyz/config.yaml` is kept — it is your configuration, not part of the
55
+ install.)
56
+
57
+ ## Configuration
58
+
59
+ `coxyz` reads, in order: `--config FILE`, `/etc/coxyz/config.yaml`,
60
+ `~/.config/coxyz/config.yaml`, then the bundled defaults.
61
+
62
+ ```bash
63
+ coxyz show-config # inspect the resolved config
64
+ sudo coxyz edit # create/edit /etc/coxyz/config.yaml (seeded from defaults)
65
+ ```
66
+
67
+ Example excludes in `config.yaml`:
68
+
69
+ ```yaml
70
+ exclude:
71
+ - "*.bak"
72
+ - "*/do_not_touch/"
73
+ ```
74
+
75
+ ## Commands
76
+
77
+ ```bash
78
+ coxyz list # list services with image, ports, status
79
+ coxyz list -C apps # filter by category
80
+
81
+ coxyz check # audit all services (exit 1 on drift)
82
+ coxyz check bitwarden # audit one service
83
+ coxyz check apps/bitwarden -v # verbose (show OK findings too)
84
+
85
+ coxyz apply # preview planned fixes, confirm, then apply
86
+ coxyz apply bitwarden -y
87
+
88
+ coxyz create # interactive prompts
89
+ coxyz create -C apps -n myapp -i nginx:1.27 -p 80 --apply
90
+
91
+ coxyz show-config # print resolved config
92
+ coxyz edit # edit /etc/coxyz/config.yaml
93
+ ```
94
+
95
+ Most operations require root (`chown` / `setfacl`), so prefix with `sudo`.
96
+
97
+ ## How it works
98
+
99
+ - **Config** (`/etc/coxyz/config.yaml` or bundled default) defines:
100
+ - root dir, ACL principals, authorized categories
101
+ - `exclude` glob patterns to ignore paths during audit/apply
102
+ - per-path rules: mode, ACL perms, optional owner override, audit-only flag
103
+ - **`check`**: read-only audit. Reports drift and warn-only (`data/`, `.env`).
104
+ - **`apply`**: shows planned changes, asks for confirmation, then applies fixes.
105
+ - Touches: category/service dirs, `compose.yaml`, the `config/` directory.
106
+ - Never touches: `data/` contents, `.env` files (audit-only).
107
+ - Creates required missing directories before applying path fixes.
108
+ - **`create`**: scaffolds `<category>/<service>/{compose.yaml,config/,data/}`
109
+ with correct owners + perms + ACL.
110
+ - **`list`**: parses each `compose.yaml` for image/ports and runs an audit
111
+ to show a compliance status.
112
+
113
+ ### ACL handling
114
+
115
+ A path governed by an ACL rule is brought to compliance with a **single
116
+ `setfacl --set` call** that writes the base entries (`u::`/`g::`/`o::`, i.e. the
117
+ octal mode) and the named entries together. `setfacl` then recomputes the ACL
118
+ *mask* as the union of the owning group and every named entry, so each entry
119
+ stays fully effective — `getfacl` never shows an `#effective:` restriction.
120
+
121
+ `coxyz` deliberately never runs `chmod` on an ACL-managed path: a `chmod` after
122
+ a `setfacl` would rewrite the mask instead of the group bits and silently shrink
123
+ the effective rights of every named entry.
124
+
125
+ One consequence: when a named entry grants more than the owning group (e.g. a
126
+ principal with `rw` on a `750` directory), the mask widens and `ls -l` shows the
127
+ wider group digit (`770`). That is correct POSIX behaviour — the audit compares
128
+ ACL entries, not the displayed mode.
129
+
130
+ ## File layout (enforced)
131
+
132
+ ```
133
+ /srv/docker/<category>/<service>/
134
+ ├── compose.yaml 660 svc_<cat>:svc_<cat> + ACL principals
135
+ ├── config/ 750 svc_<cat>:svc_<cat> + ACL principals
136
+ │ └── ... (contents not audited)
137
+ └── data/ 750 svc_<cat>:svc_<cat> no ACL (audit only)
138
+ ```
139
+
140
+ ## Development
141
+
142
+ ```bash
143
+ make test # run the test suite
144
+ make build # build sdist + wheel into dist/
145
+ make release # tag the current version and push (CI publishes to PyPI)
146
+ ```
147
+
148
+ Releasing: bump `__version__` in `src/coxyz/__init__.py`, commit, then
149
+ `make release`. The tag `vX.Y.Z` triggers `.github/workflows/publish.yml`,
150
+ which publishes to PyPI via Trusted Publishing.
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "coxyz-cli"
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 = "coxyz" }]
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.urls]
27
+ Homepage = "https://github.com/Ekyoz/Coxyz-CLI"
28
+ Repository = "https://github.com/Ekyoz/Coxyz-CLI"
29
+ Issues = "https://github.com/Ekyoz/Coxyz-CLI/issues"
30
+
31
+ [project.scripts]
32
+ coxyz = "coxyz.cli:app"
33
+
34
+ [tool.setuptools.dynamic]
35
+ version = { attr = "coxyz.__version__" }
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+
40
+ [tool.setuptools.package-data]
41
+ coxyz = ["default_config.yaml", "templates/*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """coxyz — CLI to manage Docker services under /srv/docker."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,4 @@
1
+ from .cli import cli_main
2
+
3
+ if __name__ == "__main__":
4
+ cli_main()