kanibako-cli 1.5.0.dev14__py3-none-any.whl
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.
- kanibako/__init__.py +3 -0
- kanibako/__main__.py +6 -0
- kanibako/auth_browser.py +296 -0
- kanibako/auth_parser.py +51 -0
- kanibako/browser_sidecar.py +183 -0
- kanibako/browser_state.py +103 -0
- kanibako/bun_sea.py +144 -0
- kanibako/cli.py +344 -0
- kanibako/commands/__init__.py +0 -0
- kanibako/commands/archive.py +228 -0
- kanibako/commands/box/__init__.py +22 -0
- kanibako/commands/box/_duplicate.py +395 -0
- kanibako/commands/box/_migrate.py +574 -0
- kanibako/commands/box/_parser.py +1178 -0
- kanibako/commands/clean.py +166 -0
- kanibako/commands/crab_cmd.py +480 -0
- kanibako/commands/diagnose.py +239 -0
- kanibako/commands/fork_cmd.py +51 -0
- kanibako/commands/helper_cmd.py +669 -0
- kanibako/commands/image.py +1300 -0
- kanibako/commands/install.py +152 -0
- kanibako/commands/refresh_credentials.py +67 -0
- kanibako/commands/restore.py +298 -0
- kanibako/commands/setup_cmd.py +89 -0
- kanibako/commands/start.py +1600 -0
- kanibako/commands/stop.py +116 -0
- kanibako/commands/system_cmd.py +224 -0
- kanibako/commands/upgrade.py +161 -0
- kanibako/commands/vault_cmd.py +199 -0
- kanibako/commands/workset_cmd.py +552 -0
- kanibako/config.py +514 -0
- kanibako/config_interface.py +573 -0
- kanibako/config_io.py +36 -0
- kanibako/container.py +607 -0
- kanibako/containerfiles.py +58 -0
- kanibako/containers/Containerfile.kanibako +99 -0
- kanibako/containers/Containerfile.template-android +55 -0
- kanibako/containers/Containerfile.template-dotnet +29 -0
- kanibako/containers/Containerfile.template-js +43 -0
- kanibako/containers/Containerfile.template-jvm +27 -0
- kanibako/containers/Containerfile.template-systems +46 -0
- kanibako/containers/__init__.py +0 -0
- kanibako/crabs.py +89 -0
- kanibako/errors.py +33 -0
- kanibako/freshness.py +67 -0
- kanibako/git.py +114 -0
- kanibako/helper_client.py +132 -0
- kanibako/helper_listener.py +538 -0
- kanibako/helpers.py +339 -0
- kanibako/hygiene.py +296 -0
- kanibako/image_sharing.py +133 -0
- kanibako/instructions.py +160 -0
- kanibako/log.py +31 -0
- kanibako/names.py +248 -0
- kanibako/paths.py +1483 -0
- kanibako/plugins/__init__.py +10 -0
- kanibako/registry.py +71 -0
- kanibako/rig_bundle.py +121 -0
- kanibako/rig_meta.py +92 -0
- kanibako/rig_registry.py +132 -0
- kanibako/rig_resolve.py +182 -0
- kanibako/rig_source.py +245 -0
- kanibako/scripts/__init__.py +0 -0
- kanibako/scripts/helper-init.sh +45 -0
- kanibako/scripts/kanibako-entry +12 -0
- kanibako/settings_resolve.py +312 -0
- kanibako/settings_seeds.py +154 -0
- kanibako/settings_shares.py +154 -0
- kanibako/shellenv.py +75 -0
- kanibako/snapshots.py +281 -0
- kanibako/targets/__init__.py +173 -0
- kanibako/targets/base.py +243 -0
- kanibako/targets/no_agent.py +58 -0
- kanibako/templates.py +60 -0
- kanibako/templates_image.py +224 -0
- kanibako/tweakcc.py +140 -0
- kanibako/tweakcc_cache.py +171 -0
- kanibako/utils.py +136 -0
- kanibako/workset.py +347 -0
- kanibako_cli-1.5.0.dev14.dist-info/METADATA +15 -0
- kanibako_cli-1.5.0.dev14.dist-info/RECORD +85 -0
- kanibako_cli-1.5.0.dev14.dist-info/WHEEL +5 -0
- kanibako_cli-1.5.0.dev14.dist-info/entry_points.txt +5 -0
- kanibako_cli-1.5.0.dev14.dist-info/licenses/LICENSE.md +594 -0
- kanibako_cli-1.5.0.dev14.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Shared Containerfile for all kanibako image variants.
|
|
2
|
+
# The droste base tier is selected via --build-arg BASE_IMAGE=...
|
|
3
|
+
# (seed → min, fiber → oci, thread → lxc, hair → vm).
|
|
4
|
+
ARG BASE_IMAGE=ghcr.io/doctorjei/droste-fiber:1.1.0
|
|
5
|
+
FROM $BASE_IMAGE
|
|
6
|
+
|
|
7
|
+
ARG AGENT_USER=agent
|
|
8
|
+
ARG VARIANT=oci
|
|
9
|
+
|
|
10
|
+
USER root
|
|
11
|
+
|
|
12
|
+
# Rename droste user (UID 1000) to the desired agent username
|
|
13
|
+
RUN existing=$(getent passwd 1000 | cut -d: -f1); \
|
|
14
|
+
if [ -n "$existing" ] && [ "$existing" != "$AGENT_USER" ]; then \
|
|
15
|
+
usermod -l "$AGENT_USER" -d "/home/$AGENT_USER" -m "$existing" \
|
|
16
|
+
&& groupmod -n "$AGENT_USER" "$existing"; \
|
|
17
|
+
fi \
|
|
18
|
+
&& echo "$AGENT_USER ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$AGENT_USER \
|
|
19
|
+
&& chmod 0440 /etc/sudoers.d/$AGENT_USER
|
|
20
|
+
|
|
21
|
+
# Fix subuid/subgid ownership after user rename (droste -> agent)
|
|
22
|
+
RUN if [ -f /etc/subuid ]; then sed -i "s/^droste:/$AGENT_USER:/" /etc/subuid; fi \
|
|
23
|
+
&& if [ -f /etc/subgid ]; then sed -i "s/^droste:/$AGENT_USER:/" /etc/subgid; fi
|
|
24
|
+
|
|
25
|
+
# Packages kanibako needs that the droste bases at our tiers don't supply.
|
|
26
|
+
# nodejs/npm back MCP servers + tweakcc. cifs-utils/nfs-common/openssh-client
|
|
27
|
+
# are absent from seed (min) and fiber (oci); they ship in thread/hair
|
|
28
|
+
# (lxc/vm), where reinstalling them is a no-op.
|
|
29
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
30
|
+
cifs-utils \
|
|
31
|
+
nfs-common \
|
|
32
|
+
nodejs \
|
|
33
|
+
npm \
|
|
34
|
+
openssh-client \
|
|
35
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
36
|
+
|
|
37
|
+
# Lean CLI tools for the seed-based min variant only. fiber (oci) and the
|
|
38
|
+
# thread/hair tiers (lxc/vm) already ship these via droste, so install them
|
|
39
|
+
# only where the base lacks them. gh and uv are intentionally omitted from
|
|
40
|
+
# min — add them locally if a min box ever needs them.
|
|
41
|
+
RUN if [ "$VARIANT" = "min" ]; then \
|
|
42
|
+
apt-get update && apt-get install -y --no-install-recommends \
|
|
43
|
+
fd-find \
|
|
44
|
+
inotify-tools \
|
|
45
|
+
ripgrep \
|
|
46
|
+
sshpass \
|
|
47
|
+
&& rm -rf /var/lib/apt/lists/* \
|
|
48
|
+
&& ln -sf /usr/bin/fdfind /usr/local/bin/fd; \
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
RUN if command -v pip >/dev/null 2>&1; then \
|
|
52
|
+
pip install --break-system-packages kanibako-base build twine; \
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# tweakcc: custom Claude Code patching tool (requires node/npm)
|
|
56
|
+
RUN if command -v npm >/dev/null 2>&1; then \
|
|
57
|
+
npm install -g tweakcc 2>/dev/null || true; \
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# LXC-specific fixes: mask failing units, enable DHCP, configure rootless Podman
|
|
61
|
+
RUN if [ "$VARIANT" = "lxc" ]; then \
|
|
62
|
+
: > /etc/fstab \
|
|
63
|
+
&& systemctl mask \
|
|
64
|
+
systemd-remount-fs.service systemd-growfs-root.service \
|
|
65
|
+
dev-hugepages.mount dev-mqueue.mount run-lock.mount \
|
|
66
|
+
tmp.mount run-rpc_pipefs.mount \
|
|
67
|
+
&& systemctl enable getty@console.service \
|
|
68
|
+
&& mkdir -p /etc/systemd/network \
|
|
69
|
+
&& printf '[Match]\nName=eth0\n\n[Network]\nDHCP=yes\n' \
|
|
70
|
+
> /etc/systemd/network/80-dhcp.network \
|
|
71
|
+
&& mkdir -p /etc/containers \
|
|
72
|
+
&& printf '[engine]\ncgroup_manager = "cgroupfs"\n\n[network]\ndefault_rootless_network_cmd = "slirp4netns"\n' \
|
|
73
|
+
> /etc/containers/containers.conf \
|
|
74
|
+
&& mkdir -p /var/lib/systemd/linger \
|
|
75
|
+
&& touch "/var/lib/systemd/linger/$AGENT_USER"; \
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# LXC/VM need systemd as PID 1; OCI/min use /bin/bash
|
|
79
|
+
RUN if [ "$VARIANT" = "lxc" ] || [ "$VARIANT" = "vm" ]; then \
|
|
80
|
+
printf '#!/bin/bash\nexec /sbin/init\n' > /usr/local/bin/kanibako-entrypoint; \
|
|
81
|
+
else \
|
|
82
|
+
printf '#!/bin/bash\nexec /bin/bash "$@"\n' > /usr/local/bin/kanibako-entrypoint; \
|
|
83
|
+
fi \
|
|
84
|
+
&& chmod +x /usr/local/bin/kanibako-entrypoint
|
|
85
|
+
|
|
86
|
+
# Default tmux config (user can override via shell template or bind mount)
|
|
87
|
+
COPY tmux.conf /etc/tmux.conf
|
|
88
|
+
|
|
89
|
+
USER $AGENT_USER
|
|
90
|
+
WORKDIR /home/$AGENT_USER
|
|
91
|
+
|
|
92
|
+
ENV LANG=C.UTF-8
|
|
93
|
+
ENV PATH="/home/$AGENT_USER/.local/bin:${PATH}"
|
|
94
|
+
|
|
95
|
+
RUN mkdir -p workspace .local/state/kanibako .local/bin share-ro share-rw
|
|
96
|
+
|
|
97
|
+
WORKDIR /home/$AGENT_USER/workspace
|
|
98
|
+
|
|
99
|
+
ENTRYPOINT ["/usr/local/bin/kanibako-entrypoint"]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# kanibako-template: Android SDK command-line tools + NDK
|
|
2
|
+
# kanibako-template-check: java -version
|
|
3
|
+
# kanibako-template-check: sdkmanager --version
|
|
4
|
+
# kanibako-template-check: adb --version
|
|
5
|
+
# Android development template: command-line tools, platform-tools,
|
|
6
|
+
# platform android-34, build-tools 34.0.0, NDK, and a JDK.
|
|
7
|
+
# Built on top of any kanibako base image.
|
|
8
|
+
#
|
|
9
|
+
# Default base: kanibako-oci:latest. Override with:
|
|
10
|
+
# podman build --build-arg BASE_IMAGE=ghcr.io/doctorjei/kanibako-lxc:latest ...
|
|
11
|
+
ARG BASE_IMAGE=ghcr.io/doctorjei/kanibako-oci:latest
|
|
12
|
+
FROM $BASE_IMAGE
|
|
13
|
+
|
|
14
|
+
# Re-declare ARG after FROM so $BASE_IMAGE is in scope for RUN.
|
|
15
|
+
# Surfaces the actual base in build output (default or explicit).
|
|
16
|
+
ARG BASE_IMAGE
|
|
17
|
+
RUN echo "[kanibako template] Building android on BASE_IMAGE=$BASE_IMAGE"
|
|
18
|
+
|
|
19
|
+
USER root
|
|
20
|
+
|
|
21
|
+
# Android tooling needs a JDK; use the distro default (same as the jvm
|
|
22
|
+
# template, which builds cleanly on every base). unzip extracts the
|
|
23
|
+
# command-line tools archive.
|
|
24
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
25
|
+
default-jdk \
|
|
26
|
+
unzip \
|
|
27
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
28
|
+
|
|
29
|
+
ENV ANDROID_SDK_ROOT=/opt/android-sdk
|
|
30
|
+
ENV PATH="${PATH}:/opt/android-sdk/cmdline-tools/latest/bin:/opt/android-sdk/platform-tools"
|
|
31
|
+
|
|
32
|
+
# Install the Android command-line tools (pinned release) into
|
|
33
|
+
# $ANDROID_SDK_ROOT/cmdline-tools/latest/. The zip extracts a top-level
|
|
34
|
+
# cmdline-tools/ dir, which sdkmanager requires to live under latest/.
|
|
35
|
+
RUN curl -fsSL \
|
|
36
|
+
https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip \
|
|
37
|
+
-o /tmp/cmdline-tools.zip \
|
|
38
|
+
&& mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools" \
|
|
39
|
+
&& unzip -q /tmp/cmdline-tools.zip -d /tmp/android-cmdline \
|
|
40
|
+
&& mv /tmp/android-cmdline/cmdline-tools "$ANDROID_SDK_ROOT/cmdline-tools/latest" \
|
|
41
|
+
&& rm -rf /tmp/cmdline-tools.zip /tmp/android-cmdline
|
|
42
|
+
|
|
43
|
+
# Accept licenses and install a minimal SDK set (pinned versions).
|
|
44
|
+
RUN yes | sdkmanager --licenses \
|
|
45
|
+
&& sdkmanager \
|
|
46
|
+
"platform-tools" \
|
|
47
|
+
"platforms;android-34" \
|
|
48
|
+
"build-tools;34.0.0" \
|
|
49
|
+
"ndk;26.1.10909125"
|
|
50
|
+
|
|
51
|
+
# Hand the SDK tree to the agent user so it can run/update tooling.
|
|
52
|
+
RUN chown -R agent:agent "$ANDROID_SDK_ROOT"
|
|
53
|
+
|
|
54
|
+
USER agent
|
|
55
|
+
WORKDIR /home/agent/workspace
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# kanibako-template: .NET SDK (LTS)
|
|
2
|
+
# kanibako-template-check: dotnet --info
|
|
3
|
+
# .NET development template: .NET SDK on the 8.0 LTS channel.
|
|
4
|
+
# Built on top of any kanibako base image.
|
|
5
|
+
#
|
|
6
|
+
# Default base: kanibako-oci:latest. Override with:
|
|
7
|
+
# podman build --build-arg BASE_IMAGE=ghcr.io/doctorjei/kanibako-lxc:latest ...
|
|
8
|
+
ARG BASE_IMAGE=ghcr.io/doctorjei/kanibako-oci:latest
|
|
9
|
+
FROM $BASE_IMAGE
|
|
10
|
+
|
|
11
|
+
# Re-declare ARG after FROM so $BASE_IMAGE is in scope for RUN.
|
|
12
|
+
# Surfaces the actual base in build output (default or explicit).
|
|
13
|
+
ARG BASE_IMAGE
|
|
14
|
+
RUN echo "[kanibako template] Building dotnet on BASE_IMAGE=$BASE_IMAGE"
|
|
15
|
+
|
|
16
|
+
USER root
|
|
17
|
+
|
|
18
|
+
# Install the .NET SDK via the official script pinned to the 8.0 LTS channel.
|
|
19
|
+
# Using the script avoids apt-feed/distro coupling across base tiers.
|
|
20
|
+
RUN curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh \
|
|
21
|
+
&& bash /tmp/dotnet-install.sh --channel 8.0 --install-dir /usr/share/dotnet \
|
|
22
|
+
&& ln -s /usr/share/dotnet/dotnet /usr/local/bin/dotnet \
|
|
23
|
+
&& rm /tmp/dotnet-install.sh
|
|
24
|
+
|
|
25
|
+
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 \
|
|
26
|
+
DOTNET_NOLOGO=1
|
|
27
|
+
|
|
28
|
+
USER agent
|
|
29
|
+
WORKDIR /home/agent/workspace
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# kanibako-template: Node tooling: yarn, pnpm, bun, TypeScript
|
|
2
|
+
# kanibako-template-check: node --version
|
|
3
|
+
# kanibako-template-check: yarn --version
|
|
4
|
+
# kanibako-template-check: pnpm --version
|
|
5
|
+
# kanibako-template-check: tsc --version
|
|
6
|
+
# kanibako-template-check: bun --version
|
|
7
|
+
# JavaScript/TypeScript development template: yarn, pnpm, bun, TypeScript.
|
|
8
|
+
# node/npm already ship in every kanibako base.
|
|
9
|
+
# Built on top of any kanibako base image.
|
|
10
|
+
#
|
|
11
|
+
# Default base: kanibako-oci:latest. Override with:
|
|
12
|
+
# podman build --build-arg BASE_IMAGE=ghcr.io/doctorjei/kanibako-lxc:latest ...
|
|
13
|
+
ARG BASE_IMAGE=ghcr.io/doctorjei/kanibako-oci:latest
|
|
14
|
+
FROM $BASE_IMAGE
|
|
15
|
+
|
|
16
|
+
# Re-declare ARG after FROM so $BASE_IMAGE is in scope for RUN.
|
|
17
|
+
# Surfaces the actual base in build output (default or explicit).
|
|
18
|
+
ARG BASE_IMAGE
|
|
19
|
+
RUN echo "[kanibako template] Building js on BASE_IMAGE=$BASE_IMAGE"
|
|
20
|
+
|
|
21
|
+
USER root
|
|
22
|
+
|
|
23
|
+
# unzip is required by the bun installer and is not guaranteed on every base.
|
|
24
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
25
|
+
unzip \
|
|
26
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
27
|
+
|
|
28
|
+
# Install pinned package managers + TypeScript globally (the npm global prefix is
|
|
29
|
+
# root-owned, so these land as real binaries on PATH for every user).
|
|
30
|
+
# NOT corepack: corepack's `prepare --activate` pins per-user and runs here as
|
|
31
|
+
# root, but the container runs as `agent` at runtime — whose corepack cache has
|
|
32
|
+
# no pinned version, so corepack would re-resolve to the *latest* pnpm/yarn at
|
|
33
|
+
# runtime (pnpm 11 needs Node >=22.13 via node:sqlite; the base ships Node 20).
|
|
34
|
+
# Direct npm-global installs are deterministic and need no network at runtime.
|
|
35
|
+
# pnpm is pinned to 9.x (the last line that supports Node 20).
|
|
36
|
+
RUN npm install -g pnpm@9 yarn typescript ts-node
|
|
37
|
+
|
|
38
|
+
# bun: installed under the agent home so ~/.bun has correct ownership
|
|
39
|
+
USER agent
|
|
40
|
+
RUN curl -fsSL https://bun.sh/install | bash
|
|
41
|
+
ENV PATH="/home/agent/.bun/bin:${PATH}"
|
|
42
|
+
|
|
43
|
+
WORKDIR /home/agent/workspace
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# kanibako-template: Java, Kotlin, Maven (JVM toolchain)
|
|
2
|
+
# kanibako-template-check: java -version
|
|
3
|
+
# kanibako-template-check: kotlin -version
|
|
4
|
+
# kanibako-template-check: mvn -version
|
|
5
|
+
# JVM development template: Java, Kotlin, Maven.
|
|
6
|
+
# Built on top of any kanibako base image.
|
|
7
|
+
#
|
|
8
|
+
# Default base: kanibako-oci:latest. Override with:
|
|
9
|
+
# podman build --build-arg BASE_IMAGE=ghcr.io/doctorjei/kanibako-lxc:latest ...
|
|
10
|
+
ARG BASE_IMAGE=ghcr.io/doctorjei/kanibako-oci:latest
|
|
11
|
+
FROM $BASE_IMAGE
|
|
12
|
+
|
|
13
|
+
# Re-declare ARG after FROM so $BASE_IMAGE is in scope for RUN.
|
|
14
|
+
# Surfaces the actual base in build output (default or explicit).
|
|
15
|
+
ARG BASE_IMAGE
|
|
16
|
+
RUN echo "[kanibako template] Building jvm on BASE_IMAGE=$BASE_IMAGE"
|
|
17
|
+
|
|
18
|
+
USER root
|
|
19
|
+
|
|
20
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
21
|
+
default-jdk \
|
|
22
|
+
kotlin \
|
|
23
|
+
maven \
|
|
24
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
25
|
+
|
|
26
|
+
USER agent
|
|
27
|
+
WORKDIR /home/agent/workspace
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# kanibako-template: C/C++, Rust, cross-compilation toolchain
|
|
2
|
+
# kanibako-template-check: gcc --version
|
|
3
|
+
# kanibako-template-check: clang --version
|
|
4
|
+
# kanibako-template-check: cmake --version
|
|
5
|
+
# kanibako-template-check: cargo --version
|
|
6
|
+
# Systems development template: C/C++, Rust, cross-compilation tools.
|
|
7
|
+
# Built on top of any kanibako base image.
|
|
8
|
+
#
|
|
9
|
+
# Default base: kanibako-oci:latest. Override with:
|
|
10
|
+
# podman build --build-arg BASE_IMAGE=ghcr.io/doctorjei/kanibako-lxc:latest ...
|
|
11
|
+
ARG BASE_IMAGE=ghcr.io/doctorjei/kanibako-oci:latest
|
|
12
|
+
FROM $BASE_IMAGE
|
|
13
|
+
|
|
14
|
+
# Re-declare ARG after FROM so $BASE_IMAGE is in scope for RUN.
|
|
15
|
+
# Surfaces the actual base in build output (default or explicit).
|
|
16
|
+
ARG BASE_IMAGE
|
|
17
|
+
RUN echo "[kanibako template] Building systems on BASE_IMAGE=$BASE_IMAGE"
|
|
18
|
+
|
|
19
|
+
USER root
|
|
20
|
+
|
|
21
|
+
# Additional archive tools
|
|
22
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
23
|
+
p7zip-full \
|
|
24
|
+
unrar-free \
|
|
25
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
26
|
+
|
|
27
|
+
# C/C++ toolchain
|
|
28
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
29
|
+
gcc g++ clang llvm \
|
|
30
|
+
cmake make ninja-build meson \
|
|
31
|
+
gdb lldb \
|
|
32
|
+
nasm yasm \
|
|
33
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
34
|
+
|
|
35
|
+
# QEMU user-mode emulation for cross-compilation
|
|
36
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
37
|
+
qemu-user-static \
|
|
38
|
+
binfmt-support \
|
|
39
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
40
|
+
|
|
41
|
+
# Rust (installed as agent so ~/.cargo has correct ownership)
|
|
42
|
+
USER agent
|
|
43
|
+
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|
44
|
+
ENV PATH="/home/agent/.cargo/bin:${PATH}"
|
|
45
|
+
|
|
46
|
+
WORKDIR /home/agent/workspace
|
|
File without changes
|
kanibako/crabs.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Crab YAML configuration: load, write, and resolve per-crab settings."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from kanibako.config_io import dump_doc, load_doc
|
|
9
|
+
|
|
10
|
+
# Keys that live directly in [crab] as crab identity (not crab state).
|
|
11
|
+
IDENTITY_KEYS = frozenset({"name", "shell", "run_args"})
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class CrabConfig:
|
|
16
|
+
"""Per-crab configuration loaded from a crab YAML file.
|
|
17
|
+
|
|
18
|
+
Sections:
|
|
19
|
+
crab — identity (name, shell, run_args) plus crab-state knobs
|
|
20
|
+
(model, access, start_mode, autonomous, …)
|
|
21
|
+
env — raw env vars injected into container
|
|
22
|
+
shared — crab-level shared cache paths
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
name: str = ""
|
|
26
|
+
shell: str = "standard"
|
|
27
|
+
run_args: list[str] = field(default_factory=list)
|
|
28
|
+
state: dict[str, str] = field(default_factory=dict)
|
|
29
|
+
env: dict[str, str] = field(default_factory=dict)
|
|
30
|
+
shared_caches: dict[str, str] = field(default_factory=dict)
|
|
31
|
+
tweakcc: dict = field(default_factory=dict)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def crabs_dir(data_path: Path, paths_crabs: str = "crabs") -> Path:
|
|
35
|
+
"""Return the crabs directory under *data_path*."""
|
|
36
|
+
return data_path / (paths_crabs or "crabs")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def crab_toml_path(
|
|
40
|
+
data_path: Path, crab_id: str, paths_crabs: str = "crabs",
|
|
41
|
+
) -> Path:
|
|
42
|
+
"""Return the path to a crab's config file."""
|
|
43
|
+
return crabs_dir(data_path, paths_crabs) / f"{crab_id}.yaml"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def load_crab_config(path: Path) -> CrabConfig:
|
|
47
|
+
"""Read a crab config file and return a CrabConfig.
|
|
48
|
+
|
|
49
|
+
Returns defaults if the file does not exist.
|
|
50
|
+
"""
|
|
51
|
+
cfg = CrabConfig()
|
|
52
|
+
if not path.exists():
|
|
53
|
+
return cfg
|
|
54
|
+
|
|
55
|
+
data = load_doc(path)
|
|
56
|
+
|
|
57
|
+
crab_sec = data.get("crab", {})
|
|
58
|
+
cfg.name = str(crab_sec.get("name", ""))
|
|
59
|
+
cfg.shell = str(crab_sec.get("shell", "standard"))
|
|
60
|
+
raw_args = crab_sec.get("run_args", [])
|
|
61
|
+
cfg.run_args = [str(a) for a in raw_args] if isinstance(raw_args, list) else []
|
|
62
|
+
|
|
63
|
+
cfg.state = {
|
|
64
|
+
k: str(v) for k, v in crab_sec.items() if k not in IDENTITY_KEYS
|
|
65
|
+
}
|
|
66
|
+
cfg.env = {k: str(v) for k, v in data.get("env", {}).items()}
|
|
67
|
+
cfg.shared_caches = {k: str(v) for k, v in data.get("shared", {}).items()}
|
|
68
|
+
cfg.tweakcc = dict(data.get("tweakcc", {}))
|
|
69
|
+
|
|
70
|
+
return cfg
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def write_crab_config(path: Path, cfg: CrabConfig) -> None:
|
|
74
|
+
"""Write a CrabConfig to a YAML file."""
|
|
75
|
+
crab_sec: dict = {
|
|
76
|
+
"name": cfg.name,
|
|
77
|
+
"shell": cfg.shell,
|
|
78
|
+
"run_args": list(cfg.run_args),
|
|
79
|
+
}
|
|
80
|
+
for k, v in cfg.state.items():
|
|
81
|
+
crab_sec[k] = v
|
|
82
|
+
|
|
83
|
+
data: dict = {
|
|
84
|
+
"crab": crab_sec,
|
|
85
|
+
"env": dict(cfg.env),
|
|
86
|
+
"shared": dict(cfg.shared_caches),
|
|
87
|
+
"tweakcc": dict(cfg.tweakcc),
|
|
88
|
+
}
|
|
89
|
+
dump_doc(path, data)
|
kanibako/errors.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Kanibako error hierarchy."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class KanibakoError(Exception):
|
|
5
|
+
"""Base exception for all kanibako errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConfigError(KanibakoError):
|
|
9
|
+
"""Configuration file missing or malformed."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ProjectError(KanibakoError):
|
|
13
|
+
"""Project path does not exist or cannot be resolved."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ContainerError(KanibakoError):
|
|
17
|
+
"""Container runtime or image operation failed."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ArchiveError(KanibakoError):
|
|
21
|
+
"""Archive creation, extraction, or validation failed."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GitError(KanibakoError):
|
|
25
|
+
"""Git check failed (uncommitted changes, unpushed commits, etc.)."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WorksetError(KanibakoError):
|
|
29
|
+
"""Workset creation, loading, or manipulation failed."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class UserCancelled(KanibakoError):
|
|
33
|
+
"""User cancelled an interactive prompt."""
|
kanibako/freshness.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Non-blocking image freshness check: warn when a newer image is available."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from kanibako.container import ContainerRuntime
|
|
11
|
+
from kanibako.registry import get_remote_digest
|
|
12
|
+
|
|
13
|
+
_CACHE_TTL = 86400 # 24 hours
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def check_image_freshness(runtime: ContainerRuntime, image: str, cache_path: Path) -> None:
|
|
17
|
+
"""Compare local and remote digests; print a note to stderr if stale.
|
|
18
|
+
|
|
19
|
+
This function **never** raises — all exceptions are silently swallowed
|
|
20
|
+
so it cannot block container startup.
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
_check(runtime, image, cache_path)
|
|
24
|
+
except Exception:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _check(runtime: ContainerRuntime, image: str, cache_path: Path) -> None:
|
|
29
|
+
local_digest = runtime.get_local_digest(image)
|
|
30
|
+
if local_digest is None:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
remote_digest = _cached_remote_digest(image, cache_path)
|
|
34
|
+
if remote_digest is None:
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
if local_digest != remote_digest:
|
|
38
|
+
print(
|
|
39
|
+
f"Note: A newer version of {image} is available. "
|
|
40
|
+
f"Run 'kanibako rig rebuild' to update.",
|
|
41
|
+
file=sys.stderr,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _cached_remote_digest(image: str, cache_path: Path) -> str | None:
|
|
46
|
+
"""Return the remote digest, using a 24h file cache."""
|
|
47
|
+
cache_file = cache_path / "digest-cache.json"
|
|
48
|
+
now = time.time()
|
|
49
|
+
|
|
50
|
+
cache: dict = {}
|
|
51
|
+
if cache_file.is_file():
|
|
52
|
+
try:
|
|
53
|
+
cache = json.loads(cache_file.read_text())
|
|
54
|
+
except (json.JSONDecodeError, OSError):
|
|
55
|
+
cache = {}
|
|
56
|
+
|
|
57
|
+
entry = cache.get(image)
|
|
58
|
+
if entry and now - entry.get("ts", 0) < _CACHE_TTL:
|
|
59
|
+
return entry.get("digest")
|
|
60
|
+
|
|
61
|
+
digest = get_remote_digest(image)
|
|
62
|
+
if digest is not None:
|
|
63
|
+
cache[image] = {"digest": digest, "ts": now}
|
|
64
|
+
cache_path.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
cache_file.write_text(json.dumps(cache))
|
|
66
|
+
|
|
67
|
+
return digest
|
kanibako/git.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Git checks: uncommitted/unpushed detection, metadata extraction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from kanibako.errors import GitError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class GitMetadata:
|
|
14
|
+
"""Information about a project's git state."""
|
|
15
|
+
|
|
16
|
+
branch: str
|
|
17
|
+
commit: str
|
|
18
|
+
remotes: list[tuple[str, str]] # (name, url)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_git_repo(path: Path) -> bool:
|
|
22
|
+
"""Return True if *path* contains a .git directory."""
|
|
23
|
+
return (path / ".git").is_dir()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def check_uncommitted(path: Path) -> None:
|
|
27
|
+
"""Raise GitError if there are uncommitted changes in *path*."""
|
|
28
|
+
result = subprocess.run(
|
|
29
|
+
["git", "diff-index", "--quiet", "HEAD", "--"],
|
|
30
|
+
cwd=path,
|
|
31
|
+
capture_output=True,
|
|
32
|
+
)
|
|
33
|
+
if result.returncode != 0:
|
|
34
|
+
raise GitError(
|
|
35
|
+
"Uncommitted changes detected.\n"
|
|
36
|
+
"Commit your changes or use --allow-uncommitted to override."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def check_unpushed(path: Path) -> None:
|
|
41
|
+
"""Raise GitError if there are unpushed commits on the current branch."""
|
|
42
|
+
# Get current branch
|
|
43
|
+
branch_result = subprocess.run(
|
|
44
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
45
|
+
cwd=path,
|
|
46
|
+
capture_output=True,
|
|
47
|
+
text=True,
|
|
48
|
+
)
|
|
49
|
+
if branch_result.returncode != 0:
|
|
50
|
+
return # Cannot determine branch; skip check
|
|
51
|
+
|
|
52
|
+
# Check for upstream
|
|
53
|
+
upstream_result = subprocess.run(
|
|
54
|
+
["git", "rev-parse", "--abbrev-ref", "@{upstream}"],
|
|
55
|
+
cwd=path,
|
|
56
|
+
capture_output=True,
|
|
57
|
+
text=True,
|
|
58
|
+
)
|
|
59
|
+
if upstream_result.returncode != 0:
|
|
60
|
+
return # No upstream; skip check
|
|
61
|
+
|
|
62
|
+
upstream = upstream_result.stdout.strip()
|
|
63
|
+
count_result = subprocess.run(
|
|
64
|
+
["git", "rev-list", f"{upstream}..HEAD", "--count"],
|
|
65
|
+
cwd=path,
|
|
66
|
+
capture_output=True,
|
|
67
|
+
text=True,
|
|
68
|
+
)
|
|
69
|
+
if count_result.returncode != 0:
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
count = int(count_result.stdout.strip())
|
|
73
|
+
if count > 0:
|
|
74
|
+
raise GitError(
|
|
75
|
+
f"{count} unpushed commit(s) detected.\n"
|
|
76
|
+
"Push your changes or use --allow-unpushed to override."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_metadata(path: Path) -> GitMetadata | None:
|
|
81
|
+
"""Extract git branch, HEAD SHA, and fetch remotes. Returns None on failure."""
|
|
82
|
+
branch_result = subprocess.run(
|
|
83
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
84
|
+
cwd=path,
|
|
85
|
+
capture_output=True,
|
|
86
|
+
text=True,
|
|
87
|
+
)
|
|
88
|
+
commit_result = subprocess.run(
|
|
89
|
+
["git", "rev-parse", "HEAD"],
|
|
90
|
+
cwd=path,
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
)
|
|
94
|
+
if branch_result.returncode != 0 or commit_result.returncode != 0:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
remote_result = subprocess.run(
|
|
98
|
+
["git", "remote", "-v"],
|
|
99
|
+
cwd=path,
|
|
100
|
+
capture_output=True,
|
|
101
|
+
text=True,
|
|
102
|
+
)
|
|
103
|
+
remotes: list[tuple[str, str]] = []
|
|
104
|
+
for line in remote_result.stdout.splitlines():
|
|
105
|
+
if "(fetch)" in line:
|
|
106
|
+
parts = line.split()
|
|
107
|
+
if len(parts) >= 2:
|
|
108
|
+
remotes.append((parts[0], parts[1]))
|
|
109
|
+
|
|
110
|
+
return GitMetadata(
|
|
111
|
+
branch=branch_result.stdout.strip(),
|
|
112
|
+
commit=commit_result.stdout.strip(),
|
|
113
|
+
remotes=remotes,
|
|
114
|
+
)
|