agent-container 0.1.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.
- agent_container-0.1.0/.env.example +25 -0
- agent_container-0.1.0/.github/workflows/ci.yml +33 -0
- agent_container-0.1.0/.github/workflows/publish.yml +54 -0
- agent_container-0.1.0/.gitignore +29 -0
- agent_container-0.1.0/CLAUDE.md +47 -0
- agent_container-0.1.0/Dockerfile +166 -0
- agent_container-0.1.0/LICENSE +21 -0
- agent_container-0.1.0/PKG-INFO +753 -0
- agent_container-0.1.0/README.md +727 -0
- agent_container-0.1.0/bin/agent-container +1327 -0
- agent_container-0.1.0/bin/tests/conftest.py +92 -0
- agent_container-0.1.0/bin/tests/test_cli.py +198 -0
- agent_container-0.1.0/bin/tests/test_command_construction.py +444 -0
- agent_container-0.1.0/bin/tests/test_completions.sh +242 -0
- agent_container-0.1.0/bin/tests/test_entrypoint.sh +164 -0
- agent_container-0.1.0/bin/tests/test_entrypoint_tmux_layout.sh +127 -0
- agent_container-0.1.0/bin/tests/test_packaging.py +187 -0
- agent_container-0.1.0/bin/tests/test_pure_logic.py +329 -0
- agent_container-0.1.0/completions/agent-container.bash +172 -0
- agent_container-0.1.0/completions/agent-container.zsh +137 -0
- agent_container-0.1.0/completions/oh-my-zsh/agent-container/agent-container.plugin.zsh +49 -0
- agent_container-0.1.0/docs/agent-container-hosts.example +23 -0
- agent_container-0.1.0/docs/credentials.md +114 -0
- agent_container-0.1.0/docs/decisions/0001-runtime-and-base-image.md +84 -0
- agent_container-0.1.0/docs/orchestration.md +142 -0
- agent_container-0.1.0/docs/smoke-test.md +51 -0
- agent_container-0.1.0/entrypoint.sh +189 -0
- agent_container-0.1.0/orchestration/agent-container.container +37 -0
- agent_container-0.1.0/orchestration/compose.yaml +34 -0
- agent_container-0.1.0/pyproject.toml +72 -0
- agent_container-0.1.0/scripts/smoke-test.sh +162 -0
- agent_container-0.1.0/uv.lock +138 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copy to .env and fill in real values. .env is gitignored; never commit it.
|
|
2
|
+
# See docs/credentials.md for the rationale and security trade-offs.
|
|
3
|
+
|
|
4
|
+
# --- GitHub auth (HTTPS push) ---
|
|
5
|
+
# Create at: https://github.com/settings/tokens
|
|
6
|
+
# Scopes: repo (required), workflow (optional)
|
|
7
|
+
GH_TOKEN=
|
|
8
|
+
|
|
9
|
+
# --- Git identity (not secret) ---
|
|
10
|
+
GIT_USER_NAME=Your Name
|
|
11
|
+
GIT_USER_EMAIL=you@example.com
|
|
12
|
+
|
|
13
|
+
# --- Agent provider keys ---
|
|
14
|
+
# OPTIONAL: leave blank to log in interactively inside the container instead
|
|
15
|
+
# (claude -> /login, codex login). Interactive login persists on the container's
|
|
16
|
+
# per-name credential volume and auto-refreshes — preferred for subscription
|
|
17
|
+
# accounts. See docs/credentials.md and the README "Log in to agents" section.
|
|
18
|
+
# Claude Code (API-key billing):
|
|
19
|
+
ANTHROPIC_API_KEY=
|
|
20
|
+
# Codex (@openai/codex):
|
|
21
|
+
OPENAI_API_KEY=
|
|
22
|
+
# pi-coding-agent is multi-provider; uncomment the ones you actually use.
|
|
23
|
+
# GOOGLE_API_KEY=
|
|
24
|
+
# MISTRAL_API_KEY=
|
|
25
|
+
# GROQ_API_KEY=
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# CI gate for agent-container: runs the runtime-free pytest suite and builds the
|
|
2
|
+
# distributions on every push and PR. This does NOT run on tag pushes; the
|
|
3
|
+
# release itself is gated by publish.yml, which re-runs the suite before build.
|
|
4
|
+
name: ci
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: ["**"]
|
|
9
|
+
pull_request:
|
|
10
|
+
|
|
11
|
+
# Cancel superseded runs for the same ref so rapid successive pushes don't pile
|
|
12
|
+
# up runners (does not dedupe the push/pull_request pair — different refs).
|
|
13
|
+
concurrency:
|
|
14
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
test:
|
|
19
|
+
name: pytest + build
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- name: Install uv
|
|
24
|
+
uses: astral-sh/setup-uv@v5
|
|
25
|
+
- name: Run the pytest suite
|
|
26
|
+
# --no-project keeps the run hermetic; the --with pins mirror the
|
|
27
|
+
# bin/agent-container PEP 723 inline metadata (kept in sync with pyproject).
|
|
28
|
+
run: >
|
|
29
|
+
uv run --no-project --with pytest
|
|
30
|
+
--with 'typer>=0.12,<1' --with 'questionary>=2.0,<3' --with 'rich>=13,<15'
|
|
31
|
+
pytest bin/tests
|
|
32
|
+
- name: Build distributions
|
|
33
|
+
run: uv build
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Publish agent-container to PyPI via Trusted Publishing (OIDC — no stored secrets).
|
|
2
|
+
#
|
|
3
|
+
# Trigger: pushing a tag matching v* (e.g. v0.1.0). Builds an sdist + wheel with
|
|
4
|
+
# `uv build` and uploads them with pypa/gh-action-pypi-publish, which mints a
|
|
5
|
+
# short-lived OIDC token. The operator configures the PyPI trusted publisher once
|
|
6
|
+
# (project: agent-container, owner: ondrasek, repo: agent-container, workflow: publish.yml,
|
|
7
|
+
# environment: release) — no API token is ever stored in the repo.
|
|
8
|
+
name: publish
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
tags:
|
|
13
|
+
- "v*"
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
name: Build sdist + wheel
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v5
|
|
23
|
+
- name: Run the pytest suite
|
|
24
|
+
# Gate the release on green: mirrors ci.yml (ci.yml does NOT run on tag
|
|
25
|
+
# pushes, so the suite is re-run here). A failure here fails the build
|
|
26
|
+
# job, and `publish` needs: build — so a red tagged commit never ships.
|
|
27
|
+
# --with pins mirror the bin/agent-container PEP 723 inline metadata.
|
|
28
|
+
run: >
|
|
29
|
+
uv run --no-project --with pytest
|
|
30
|
+
--with 'typer>=0.12,<1' --with 'questionary>=2.0,<3' --with 'rich>=13,<15'
|
|
31
|
+
pytest bin/tests
|
|
32
|
+
- name: Build distributions
|
|
33
|
+
run: uv build
|
|
34
|
+
- name: Upload dist artifact
|
|
35
|
+
uses: actions/upload-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: dist
|
|
38
|
+
path: dist/
|
|
39
|
+
|
|
40
|
+
publish:
|
|
41
|
+
name: Publish to PyPI
|
|
42
|
+
needs: build
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
environment: release
|
|
45
|
+
permissions:
|
|
46
|
+
id-token: write # OIDC token for Trusted Publishing
|
|
47
|
+
steps:
|
|
48
|
+
- name: Download dist artifact
|
|
49
|
+
uses: actions/download-artifact@v4
|
|
50
|
+
with:
|
|
51
|
+
name: dist
|
|
52
|
+
path: dist/
|
|
53
|
+
- name: Publish to PyPI
|
|
54
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Runtime credentials and per-host config — see docs/credentials.md
|
|
2
|
+
.env
|
|
3
|
+
.env.local
|
|
4
|
+
.env.*.local
|
|
5
|
+
|
|
6
|
+
# Claude Code harness artifacts (workflow scripts saved per-session)
|
|
7
|
+
.claude/
|
|
8
|
+
|
|
9
|
+
# OS noise
|
|
10
|
+
.DS_Store
|
|
11
|
+
Thumbs.db
|
|
12
|
+
|
|
13
|
+
# Editor noise
|
|
14
|
+
*.swp
|
|
15
|
+
*.swo
|
|
16
|
+
.idea/
|
|
17
|
+
|
|
18
|
+
# Python build artifacts (bin/tests pytest suite)
|
|
19
|
+
__pycache__/
|
|
20
|
+
*.pyc
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
|
|
23
|
+
# Packaging / uv project mode (root pyproject.toml)
|
|
24
|
+
.venv/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
dist/
|
|
27
|
+
build/
|
|
28
|
+
# NOTE: uv.lock IS committed. agent-container is an application/CLI (not a library), so
|
|
29
|
+
# the lockfile is checked in for reproducible dev envs (`uv sync`). Do not ignore it.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project intent
|
|
6
|
+
|
|
7
|
+
Build a **containerized development environment** designed to run remotely (e.g., Hetzner VPS) as an always-on container that the user attaches to and detaches from over SSH. Inside, multiple AI coding agents (Claude Code, Codex, pi-coding-agent) and editors (nvim) run under tmux. Multiple such containers may run in parallel, each holding working copies of one or more git repositories.
|
|
8
|
+
|
|
9
|
+
This is a greenfield repo — no code has been written yet. Treat the section below as the design contract, not a description of existing code.
|
|
10
|
+
|
|
11
|
+
## Hard constraints
|
|
12
|
+
|
|
13
|
+
These are load-bearing design decisions, not preferences:
|
|
14
|
+
|
|
15
|
+
1. **No reliance on container persistence.** Every agent must `commit` AND `push` every change. The container is treated as ephemeral; if it dies, no work is lost. Any feature or workflow that depends on uncommitted state is wrong by construction.
|
|
16
|
+
2. **Editor-agnostic, not VSCode-locked.** The user explicitly rejected devcontainers because tooling support outside VSCode is nonexistent. Do not introduce `.devcontainer/` configs or any design that assumes a VSCode client. SSH + tmux is the canonical attach path.
|
|
17
|
+
3. **Multiple parallel containers.** Naming, port allocation, volume mounts, and git identity must all support N containers running simultaneously on the same host without collision.
|
|
18
|
+
4. **Push auth must work non-interactively.** Agents commit autonomously, so SSH keys / git credentials inside the container must be configured to push without prompts. Never embed long-lived secrets in the image — inject at runtime.
|
|
19
|
+
|
|
20
|
+
## Decisions
|
|
21
|
+
|
|
22
|
+
- **Runtime + base image:** Podman + `debian:12-slim`. See [`docs/decisions/0001-runtime-and-base-image.md`](docs/decisions/0001-runtime-and-base-image.md).
|
|
23
|
+
- **CLI:** a single tool, `agent-container` (`bin/agent-container`) — a PEP 723 uv script (Typer + questionary + rich) covering the whole lifecycle (build/up/attach/logs/down/purge) plus an interactive wizard. Its on-disk contract (container names `agent-container-<name>`, the `2200 + name-hash` port, `$XDG_STATE_HOME/agent-container/<name>.port` state files, `~/.config/agent-container/hosts.conf`) is the single source of truth; the shell completions read the same state files. Runtime default is platform-aware (docker-first on macOS, podman-first on Linux); override with `AGENT_CONTAINER_RUNTIME`.
|
|
24
|
+
- **Packaging + release:** ships to PyPI as the `agent_container` module via a hatchling `force-include` wheel (`bin/agent-container` → `agent_container/__init__.py`; completions → `agent_container/completions/*` package data). `REPO_ROOT` resolves location-independently (`AGENT_CONTAINER_REPO` → `Dockerfile`+`completions/` marker → `None`) so a **non-editable** PyPI install works standalone (`up/down/list/attach/logs/purge/completions`); only `build` needs a checkout (via `AGENT_CONTAINER_REPO`/`--context`). `uv tool install --editable .` remains the dev path. Push a `v*` tag → `.github/workflows/publish.yml` uploads via PyPI **Trusted Publishing** (OIDC, no stored secrets); `ci.yml` gates on the pytest suite + `uv build`. Licensed **MIT** ([`LICENSE`](LICENSE)).
|
|
25
|
+
|
|
26
|
+
## Architecture sketch (to be built)
|
|
27
|
+
|
|
28
|
+
Expect the repo to grow into roughly:
|
|
29
|
+
|
|
30
|
+
- **Container image** — base OS + tmux + SSH server + nvim + git + the agent CLIs (Claude Code, Codex, pi-coding-agent) and their language runtimes.
|
|
31
|
+
- **Orchestration layer** — scripts or compose/quadlet definitions to launch, name, attach to, and tear down containers on the remote host.
|
|
32
|
+
- **Bootstrap / entrypoint** — sets up git identity, injects credentials, starts sshd + a default tmux session, optionally clones configured repos.
|
|
33
|
+
- **Attach tooling** — a thin client-side helper for `ssh user@host -t tmux attach -t <session>` style flows across multiple hosts/containers.
|
|
34
|
+
|
|
35
|
+
When adding a component, keep these layers separate. Don't bake host-specific orchestration into the image.
|
|
36
|
+
|
|
37
|
+
## Conventions for future work
|
|
38
|
+
|
|
39
|
+
- Prefer **rootless / Podman-compatible** patterns where reasonable; avoid features that only work on Docker Desktop.
|
|
40
|
+
- Treat the **commit-and-push discipline** as a property of the agent configuration, not something to enforce via git hooks alone (hooks can be bypassed; the agents themselves should be configured to push).
|
|
41
|
+
- When proposing a tool or dependency, justify it against the constraints above — especially the "not VSCode-locked" one.
|
|
42
|
+
|
|
43
|
+
## Out of scope (don't add unless asked)
|
|
44
|
+
|
|
45
|
+
- IDE integrations beyond plain SSH/tmux/nvim.
|
|
46
|
+
- Multi-user / multi-tenant access controls — single operator (the user) is assumed.
|
|
47
|
+
- Kubernetes manifests — the target is a single VPS running a container runtime, not a cluster.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# syntax-compatible with both `docker build` and `podman build`.
|
|
2
|
+
# No BuildKit-specific features. See docs/decisions/0001-runtime-and-base-image.md.
|
|
3
|
+
#
|
|
4
|
+
# Layering rationale (cheapest-to-rebuild last):
|
|
5
|
+
# 1. apt base packages — rarely changes; large; cached aggressively.
|
|
6
|
+
# 2. Node 22 LTS (NodeSource) — rarely changes; medium.
|
|
7
|
+
# 3. Agent CLIs (npm globals) — changes with agent releases.
|
|
8
|
+
# 4. Neovim upstream tarball — changes with nvim releases.
|
|
9
|
+
# 5. User + sshd config — cheap; placed late so earlier layers cache.
|
|
10
|
+
# 6. entrypoint.sh — changes most often (item C); last.
|
|
11
|
+
|
|
12
|
+
FROM debian:12-slim
|
|
13
|
+
|
|
14
|
+
# Fail fast on any unhandled error inside RUN blocks.
|
|
15
|
+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
|
16
|
+
|
|
17
|
+
ENV DEBIAN_FRONTEND=noninteractive \
|
|
18
|
+
LANG=C.UTF-8 \
|
|
19
|
+
LC_ALL=C.UTF-8 \
|
|
20
|
+
TZ=UTC
|
|
21
|
+
|
|
22
|
+
# --- Layer 1: base OS packages ----------------------------------------------
|
|
23
|
+
# Single RUN so the apt cache cleanup lands in the same layer as the install.
|
|
24
|
+
RUN apt-get update \
|
|
25
|
+
&& apt-get install -y --no-install-recommends \
|
|
26
|
+
ca-certificates \
|
|
27
|
+
curl \
|
|
28
|
+
gnupg \
|
|
29
|
+
git \
|
|
30
|
+
openssh-server \
|
|
31
|
+
tmux \
|
|
32
|
+
zsh \
|
|
33
|
+
sudo \
|
|
34
|
+
locales \
|
|
35
|
+
less \
|
|
36
|
+
jq \
|
|
37
|
+
build-essential \
|
|
38
|
+
python3 \
|
|
39
|
+
python3-pip \
|
|
40
|
+
python3-venv \
|
|
41
|
+
&& sed -i 's/^# *\(C.UTF-8\)/\1/' /etc/locale.gen || true \
|
|
42
|
+
&& locale-gen C.UTF-8 \
|
|
43
|
+
&& apt-get clean \
|
|
44
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
45
|
+
|
|
46
|
+
# --- Layer 2: Node 22 LTS via NodeSource ------------------------------------
|
|
47
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
|
48
|
+
&& apt-get install -y --no-install-recommends nodejs \
|
|
49
|
+
&& apt-get clean \
|
|
50
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
51
|
+
|
|
52
|
+
# --- Layer 3: agent CLIs (global npm installs) ------------------------------
|
|
53
|
+
# pi-coding-agent needs --ignore-scripts (postinstall pulls native bits we
|
|
54
|
+
# don't want resolved at build time).
|
|
55
|
+
RUN npm i -g @anthropic-ai/claude-code \
|
|
56
|
+
&& npm i -g @openai/codex \
|
|
57
|
+
&& npm i -g --ignore-scripts @earendil-works/pi-coding-agent \
|
|
58
|
+
&& npm cache clean --force
|
|
59
|
+
|
|
60
|
+
# --- Layer 4: Neovim from upstream stable tarball ---------------------------
|
|
61
|
+
# Debian 12's nvim package is too old. Upstream ships per-architecture tarballs;
|
|
62
|
+
# pick the one matching the build platform (amd64 -> x86_64 / linux64 legacy
|
|
63
|
+
# name; arm64 -> linux-arm64). The asset name for amd64 also changed across
|
|
64
|
+
# releases (nvim-linux64.tar.gz -> nvim-linux-x86_64.tar.gz); fall through.
|
|
65
|
+
RUN set -eux; \
|
|
66
|
+
cd /tmp; \
|
|
67
|
+
BASE="https://github.com/neovim/neovim/releases/latest/download"; \
|
|
68
|
+
ARCH="$(dpkg --print-architecture)"; \
|
|
69
|
+
case "${ARCH}" in \
|
|
70
|
+
amd64) CANDIDATES="nvim-linux-x86_64.tar.gz nvim-linux64.tar.gz" ;; \
|
|
71
|
+
arm64) CANDIDATES="nvim-linux-arm64.tar.gz" ;; \
|
|
72
|
+
*) echo "unsupported architecture: ${ARCH}" >&2; exit 1 ;; \
|
|
73
|
+
esac; \
|
|
74
|
+
TARBALL=""; \
|
|
75
|
+
for CAND in ${CANDIDATES}; do \
|
|
76
|
+
if curl -fsSLO "${BASE}/${CAND}"; then TARBALL="${CAND}"; break; fi; \
|
|
77
|
+
done; \
|
|
78
|
+
if [ -z "${TARBALL}" ]; then \
|
|
79
|
+
echo "no neovim release asset matched for arch=${ARCH} (tried: ${CANDIDATES})" >&2; \
|
|
80
|
+
exit 1; \
|
|
81
|
+
fi; \
|
|
82
|
+
tar -C /usr/local -xzf "${TARBALL}" --strip-components=1; \
|
|
83
|
+
rm -f "${TARBALL}"; \
|
|
84
|
+
nvim --version | head -n1
|
|
85
|
+
|
|
86
|
+
# --- Layer 5: user + sudo + sshd config -------------------------------------
|
|
87
|
+
RUN useradd --create-home --home-dir /home/dev --shell /bin/bash --uid 1000 dev \
|
|
88
|
+
&& echo 'dev ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/90-dev \
|
|
89
|
+
&& chmod 0440 /etc/sudoers.d/90-dev \
|
|
90
|
+
&& mkdir -p /workspace \
|
|
91
|
+
&& chown dev:dev /workspace \
|
|
92
|
+
&& chmod 0755 /workspace
|
|
93
|
+
|
|
94
|
+
# sshd: dev-only, key-based, no root, no passwords.
|
|
95
|
+
# Host keys are deliberately NOT generated here — entrypoint does it at first
|
|
96
|
+
# launch so every container instance gets a distinct identity.
|
|
97
|
+
RUN mkdir -p /etc/ssh \
|
|
98
|
+
&& rm -f /etc/ssh/ssh_host_* \
|
|
99
|
+
&& { \
|
|
100
|
+
echo 'AllowUsers dev'; \
|
|
101
|
+
echo 'PasswordAuthentication no'; \
|
|
102
|
+
echo 'PermitRootLogin no'; \
|
|
103
|
+
echo 'PubkeyAuthentication yes'; \
|
|
104
|
+
echo 'Port 22'; \
|
|
105
|
+
echo 'UsePAM yes'; \
|
|
106
|
+
echo 'ChallengeResponseAuthentication no'; \
|
|
107
|
+
echo 'PrintMotd no'; \
|
|
108
|
+
echo 'AcceptEnv LANG LC_*'; \
|
|
109
|
+
} > /etc/ssh/sshd_config.d/10-agent-container.conf \
|
|
110
|
+
&& mkdir -p /home/dev/.ssh \
|
|
111
|
+
&& chown dev:dev /home/dev/.ssh \
|
|
112
|
+
&& chmod 0700 /home/dev/.ssh
|
|
113
|
+
|
|
114
|
+
# --- Layer 5b: per-container volume mount points + persistent shell env -----
|
|
115
|
+
# Each of these dirs is the mount point of a per-container named volume
|
|
116
|
+
# (agent-container-<name>-{claude,codex,pi,shellenv,tmux}). Pre-creating them dev-owned
|
|
117
|
+
# (uid 1000) BEFORE the USER switch means a fresh, empty named volume
|
|
118
|
+
# initializes dev-owned too — the runtime seeds a new volume from the image
|
|
119
|
+
# directory's contents AND ownership/permissions. Without this the volume
|
|
120
|
+
# would come up root-owned and the agents could not write their credentials.
|
|
121
|
+
#
|
|
122
|
+
# pi-coding-agent's config/auth dir is ~/.pi (verified: package.json piConfig
|
|
123
|
+
# .configDir = ".pi"; dist/config.js getAgentDir() -> ~/.pi/agent, auth.json
|
|
124
|
+
# at ~/.pi/agent/auth.json). Overridable at runtime via PI_CODING_AGENT_DIR.
|
|
125
|
+
# The credential dirs are 0700; .agent-container (shell env) and .config/tmux (tmux.conf
|
|
126
|
+
# + tpm plugins) are non-secret and 0755. ~/.config/tmux is XDG-standard: tmux
|
|
127
|
+
# 3.x on debian:12 reads ~/.config/tmux/tmux.conf, so persisting that dir on its
|
|
128
|
+
# own named volume carries the operator's tmux.conf/plugins across down/up.
|
|
129
|
+
#
|
|
130
|
+
# Shell-env wiring: agents and the operator drop KEY=VALUE exports in
|
|
131
|
+
# ~/.agent-container/env (on the shellenv volume, so it survives down/up). BOTH bash and
|
|
132
|
+
# zsh source it, guarded so a malformed file cannot break the shell. dev's
|
|
133
|
+
# LOGIN SHELL stays bash (sshd/tmux/entrypoint assume bash); zsh is available
|
|
134
|
+
# but not the default.
|
|
135
|
+
#
|
|
136
|
+
# The hook lives in ~/.bashrc (read by interactive shells). Login shells — SSH
|
|
137
|
+
# login sessions AND tmux panes (tmux spawns the default shell as a LOGIN shell
|
|
138
|
+
# when no default-command is set) — read ~/.bash_profile, not ~/.bashrc, so
|
|
139
|
+
# ~/.bash_profile must chain to ~/.bashrc. We source Debian's stock ~/.profile
|
|
140
|
+
# to do that: it sources ~/.bashrc under bash (firing the hook) AND restores the
|
|
141
|
+
# ~/.local/bin + ~/bin PATH additions that ~/.bash_profile would otherwise shadow
|
|
142
|
+
# (e.g. for `pip install --user` tools). This bash_profile->profile->bashrc chain
|
|
143
|
+
# is load-bearing for login-shell panes; do not "simplify" it away.
|
|
144
|
+
RUN set -eux; \
|
|
145
|
+
mkdir -p /home/dev/.claude /home/dev/.codex /home/dev/.pi /home/dev/.agent-container /home/dev/.config/tmux; \
|
|
146
|
+
chown -R dev:dev /home/dev/.claude /home/dev/.codex /home/dev/.pi /home/dev/.agent-container /home/dev/.config; \
|
|
147
|
+
chmod 0700 /home/dev/.claude /home/dev/.codex /home/dev/.pi; \
|
|
148
|
+
chmod 0755 /home/dev/.agent-container /home/dev/.config/tmux; \
|
|
149
|
+
HOOK='if [ -f "$HOME/.agent-container/env" ]; then set -a; . "$HOME/.agent-container/env"; set +a; fi'; \
|
|
150
|
+
printf '\n# agent-container: source persistent shell env if present (guarded)\n%s\n' "$HOOK" >> /home/dev/.bashrc; \
|
|
151
|
+
printf '# agent-container: source persistent shell env if present (guarded)\n%s\n' "$HOOK" >> /home/dev/.zshrc; \
|
|
152
|
+
printf '# agent-container: login shells load ~/.profile (sources ~/.bashrc + adds ~/.local/bin to PATH)\n[ -f "$HOME/.profile" ] && . "$HOME/.profile"\n' >> /home/dev/.bash_profile; \
|
|
153
|
+
chown dev:dev /home/dev/.bashrc /home/dev/.zshrc /home/dev/.bash_profile
|
|
154
|
+
|
|
155
|
+
# --- Layer 6: entrypoint (most-frequently-changed; STUB until item C) -------
|
|
156
|
+
# Two-step COPY+chmod instead of `COPY --chmod=` so the file builds under both
|
|
157
|
+
# the legacy builder and BuildKit; --chmod requires BuildKit specifically.
|
|
158
|
+
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
159
|
+
RUN chmod 0755 /usr/local/bin/entrypoint.sh
|
|
160
|
+
|
|
161
|
+
EXPOSE 22
|
|
162
|
+
|
|
163
|
+
USER dev
|
|
164
|
+
WORKDIR /workspace
|
|
165
|
+
|
|
166
|
+
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ondrej Krajicek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|