devctk 0.1.0__tar.gz → 0.1.2__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.
- {devctk-0.1.0 → devctk-0.1.2}/.claude/settings.local.json +2 -1
- {devctk-0.1.0 → devctk-0.1.2}/DESIGN.md +4 -4
- {devctk-0.1.0 → devctk-0.1.2}/PKG-INFO +1 -1
- {devctk-0.1.0 → devctk-0.1.2}/pyproject.toml +3 -3
- devctk-0.1.2/src/devctk/__init__.py +3 -0
- devctk-0.1.2/src/devctk/__main__.py +3 -0
- {devctk-0.1.0/src/z_ctk → devctk-0.1.2/src/devctk}/cli.py +2 -2
- {devctk-0.1.0/src/z_ctk → devctk-0.1.2/src/devctk}/commands.py +7 -7
- {devctk-0.1.0/src/z_ctk → devctk-0.1.2/src/devctk}/helpers.py +10 -0
- {devctk-0.1.0/src/z_ctk → devctk-0.1.2/src/devctk}/paths.py +1 -1
- {devctk-0.1.0/src/z_ctk → devctk-0.1.2/src/devctk}/systemd.py +2 -2
- {devctk-0.1.0 → devctk-0.1.2}/tests/test_smoke.py +3 -3
- {devctk-0.1.0 → devctk-0.1.2}/uv.lock +15 -15
- devctk-0.1.0/src/z_ctk/__init__.py +0 -3
- devctk-0.1.0/src/z_ctk/__main__.py +0 -3
- {devctk-0.1.0 → devctk-0.1.2}/.gitignore +0 -0
- {devctk-0.1.0 → devctk-0.1.2}/archive/z-ctk-codex +0 -0
- {devctk-0.1.0/src/z_ctk → devctk-0.1.2/src/devctk}/util.py +0 -0
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
"Bash(git -C /home/y/Projects/Toys/z-ctk status)",
|
|
6
6
|
"Bash(mkdir -p /home/y/Projects/Toys/z-ctk/archive)",
|
|
7
7
|
"Bash(mv /home/y/Projects/Toys/z-ctk/z-ctk /home/y/Projects/Toys/z-ctk/archive/z-ctk-codex)",
|
|
8
|
-
"Bash(uv build:*)"
|
|
8
|
+
"Bash(uv build:*)",
|
|
9
|
+
"Bash(uv run:*)"
|
|
9
10
|
]
|
|
10
11
|
}
|
|
11
12
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# devctk
|
|
2
2
|
|
|
3
3
|
One-command setup for SSH-accessible rootless Podman dev containers, managed by systemd user services.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
uvx
|
|
6
|
+
uvx devctk --image ubuntu:24.04 --authorized-keys-file ~/.ssh/authorized_keys
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
You get: a running container with your UID mapped in, sshd on localhost, workspace bind-mounted, passwordless sudo, and auto-start via systemd.
|
|
10
10
|
|
|
11
11
|
## Distribution
|
|
12
12
|
|
|
13
|
-
- PyPI package, runnable via `uvx
|
|
13
|
+
- PyPI package, runnable via `uvx devctk`
|
|
14
14
|
- Pure Python, minimum 3.10
|
|
15
15
|
|
|
16
16
|
## Host Requirements
|
|
@@ -41,7 +41,7 @@ Podman (rootless), systemd, loginctl. Refuses to run as root.
|
|
|
41
41
|
|
|
42
42
|
```
|
|
43
43
|
~/.config/systemd/user/<name>.service, <name>-sshd.service
|
|
44
|
-
~/.local/state/
|
|
44
|
+
~/.local/state/devctk/<name>.json, <name>-container.sh, <name>-sshd.sh
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
## Constraints
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devctk"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "One-command SSH-accessible rootless Podman dev containers"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
license = "MIT"
|
|
@@ -16,10 +16,10 @@ classifiers = [
|
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
[tool.hatch.build.targets.wheel]
|
|
19
|
-
packages = ["src/
|
|
19
|
+
packages = ["src/devctk"]
|
|
20
20
|
|
|
21
21
|
[dependency-groups]
|
|
22
22
|
dev = ["pytest"]
|
|
23
23
|
|
|
24
24
|
[project.scripts]
|
|
25
|
-
devctk = "
|
|
25
|
+
devctk = "devctk.cli:main"
|
|
@@ -12,7 +12,7 @@ COMMANDS = {"init", "ls", "rm"}
|
|
|
12
12
|
|
|
13
13
|
def build_parser() -> argparse.ArgumentParser:
|
|
14
14
|
parser = argparse.ArgumentParser(
|
|
15
|
-
prog="
|
|
15
|
+
prog="devctk",
|
|
16
16
|
description="One-command SSH-accessible rootless Podman dev containers.",
|
|
17
17
|
)
|
|
18
18
|
sub = parser.add_subparsers(dest="command")
|
|
@@ -75,7 +75,7 @@ def validate_passthrough(args: list[str]) -> None:
|
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
def main() -> int:
|
|
78
|
-
from
|
|
78
|
+
from devctk.commands import cmd_init, cmd_ls, cmd_rm
|
|
79
79
|
|
|
80
80
|
if os.geteuid() == 0:
|
|
81
81
|
raise SystemExit("refuse to run as root")
|
|
@@ -9,10 +9,10 @@ import pathlib
|
|
|
9
9
|
import stat
|
|
10
10
|
import sys
|
|
11
11
|
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from
|
|
12
|
+
from devctk.paths import ManagedPaths, managed_paths, state_root, STATE_DIR_NAME
|
|
13
|
+
from devctk.helpers import render_container_helper, render_sshd_helper
|
|
14
|
+
from devctk.systemd import render_unit
|
|
15
|
+
from devctk.util import require_binary, run, write_text, unlink_if_exists
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
# ---------------------------------------------------------------------------
|
|
@@ -57,7 +57,7 @@ def build_workspace_mount(workspace: str | None, no_workspace: bool, container_u
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def cmd_init(args: argparse.Namespace, passthrough: list[str]) -> int:
|
|
60
|
-
from
|
|
60
|
+
from devctk.cli import NAME_RE
|
|
61
61
|
|
|
62
62
|
user = os.environ.get("USER") or pathlib.Path.home().name
|
|
63
63
|
container_user = args.container_user or user
|
|
@@ -145,7 +145,7 @@ def cmd_init(args: argparse.Namespace, passthrough: list[str]) -> int:
|
|
|
145
145
|
def cmd_ls() -> int:
|
|
146
146
|
names = list_names()
|
|
147
147
|
if not names:
|
|
148
|
-
print("no
|
|
148
|
+
print("no devctk containers")
|
|
149
149
|
return 0
|
|
150
150
|
|
|
151
151
|
podman = require_binary("podman")
|
|
@@ -178,7 +178,7 @@ def cmd_rm(args: argparse.Namespace) -> int:
|
|
|
178
178
|
if args.all:
|
|
179
179
|
names = list_names()
|
|
180
180
|
if not names:
|
|
181
|
-
print("no
|
|
181
|
+
print("no devctk containers")
|
|
182
182
|
return 0
|
|
183
183
|
else:
|
|
184
184
|
names = [args.container_name or f"{user}-dev"]
|
|
@@ -108,6 +108,16 @@ stop_sshd() {{
|
|
|
108
108
|
}}
|
|
109
109
|
|
|
110
110
|
bootstrap() {{
|
|
111
|
+
# Wait for container to be ready
|
|
112
|
+
n=0
|
|
113
|
+
while ! "$podman" exec "$name" true >/dev/null 2>&1; do
|
|
114
|
+
n=$((n + 1))
|
|
115
|
+
if [ "$n" -ge 30 ]; then
|
|
116
|
+
echo "container $name not ready after 30s" >&2; exit 1
|
|
117
|
+
fi
|
|
118
|
+
sleep 1
|
|
119
|
+
done
|
|
120
|
+
|
|
111
121
|
# Check apt-get
|
|
112
122
|
exec_root 'command -v apt-get >/dev/null 2>&1' >/dev/null 2>&1 || {{
|
|
113
123
|
echo "apt-get is required inside $name" >&2; exit 1
|
|
@@ -7,7 +7,7 @@ from string import Template
|
|
|
7
7
|
TEMPLATES = {
|
|
8
8
|
"container": """\
|
|
9
9
|
[Unit]
|
|
10
|
-
Description=
|
|
10
|
+
Description=devctk container $container_name
|
|
11
11
|
|
|
12
12
|
[Service]
|
|
13
13
|
Type=simple
|
|
@@ -24,7 +24,7 @@ WantedBy=default.target
|
|
|
24
24
|
""",
|
|
25
25
|
"sshd": """\
|
|
26
26
|
[Unit]
|
|
27
|
-
Description=
|
|
27
|
+
Description=devctk OpenSSH server in $container_name
|
|
28
28
|
Requires=$container_unit
|
|
29
29
|
After=$container_unit
|
|
30
30
|
BindsTo=$container_unit
|
|
@@ -7,7 +7,7 @@ import time
|
|
|
7
7
|
|
|
8
8
|
import pytest
|
|
9
9
|
|
|
10
|
-
CONTAINER_NAME = "
|
|
10
|
+
CONTAINER_NAME = "devctk-smoke-test"
|
|
11
11
|
PORT = 39999
|
|
12
12
|
IMAGE = "docker.io/library/ubuntu:24.04"
|
|
13
13
|
|
|
@@ -43,7 +43,7 @@ def container(tmp_path_factory):
|
|
|
43
43
|
# Init
|
|
44
44
|
subprocess.run(
|
|
45
45
|
[
|
|
46
|
-
"
|
|
46
|
+
"devctk", "init",
|
|
47
47
|
"--image", IMAGE,
|
|
48
48
|
"--port", str(PORT),
|
|
49
49
|
"--container-name", CONTAINER_NAME,
|
|
@@ -66,7 +66,7 @@ def container(tmp_path_factory):
|
|
|
66
66
|
yield str(key)
|
|
67
67
|
|
|
68
68
|
# Teardown
|
|
69
|
-
subprocess.run(["
|
|
69
|
+
subprocess.run(["devctk", "rm", CONTAINER_NAME], check=False, timeout=30)
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def test_whoami(container):
|
|
@@ -11,6 +11,21 @@ wheels = [
|
|
|
11
11
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
12
12
|
]
|
|
13
13
|
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "devctk"
|
|
16
|
+
version = "0.1.1"
|
|
17
|
+
source = { editable = "." }
|
|
18
|
+
|
|
19
|
+
[package.dev-dependencies]
|
|
20
|
+
dev = [
|
|
21
|
+
{ name = "pytest" },
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[package.metadata]
|
|
25
|
+
|
|
26
|
+
[package.metadata.requires-dev]
|
|
27
|
+
dev = [{ name = "pytest" }]
|
|
28
|
+
|
|
14
29
|
[[package]]
|
|
15
30
|
name = "exceptiongroup"
|
|
16
31
|
version = "1.3.1"
|
|
@@ -139,18 +154,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac8
|
|
|
139
154
|
wheels = [
|
|
140
155
|
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
|
141
156
|
]
|
|
142
|
-
|
|
143
|
-
[[package]]
|
|
144
|
-
name = "z-ctk"
|
|
145
|
-
version = "0.1.0"
|
|
146
|
-
source = { editable = "." }
|
|
147
|
-
|
|
148
|
-
[package.dev-dependencies]
|
|
149
|
-
dev = [
|
|
150
|
-
{ name = "pytest" },
|
|
151
|
-
]
|
|
152
|
-
|
|
153
|
-
[package.metadata]
|
|
154
|
-
|
|
155
|
-
[package.metadata.requires-dev]
|
|
156
|
-
dev = [{ name = "pytest" }]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|