Compare commits

...

3 commits

Author SHA1 Message Date
a56da84b7a Prepopulate container-home with project dirs
This is to fix the problem with docker creating those directories
to mount it inside of the container-home. This only happens when
the project path is inside of $HOME which is mounted to the
.local/share/opencode-container/cantainer-home

With it like this, the empty directories are owned by the local
user and not root and it's easier to clean up in the future.
2026-01-22 01:08:18 +09:00
8171760807 Add cleanup script 2026-01-22 00:29:20 +09:00
c387a7a365 Fix container username and home mount 2026-01-21 23:58:15 +09:00
5 changed files with 65 additions and 11 deletions

2
.gitignore vendored
View file

@ -1,2 +0,0 @@
container-home/*
!container-home/.gitkeep

View file

@ -9,7 +9,7 @@ the host.
- Arch Linuxbased image - Arch Linuxbased image
- Runs as the host user (same username, UID, GID) - Runs as the host user (same username, UID, GID)
- Mounts only the current project directory (same absolute path inside container) - Mounts only the current project directory (same absolute path inside container)
- Persists OpenCode state in ./container-home directory - Persists OpenCode state in XDG_DATA_HOME/opencode-container/container-home directory
- No access to SSH keys, passwords, or full `$HOME` - No access to SSH keys, passwords, or full `$HOME`
- Simple shell function (`opencode`) to launch interactively - Simple shell function (`opencode`) to launch interactively
@ -24,10 +24,14 @@ git clone https://git.jeena.net/jeena/opencode-container.git
Source the helper file `opencode.aliases` in your shell configuration Source the helper file `opencode.aliases` in your shell configuration
(`.bashrc` or `.zshrc`) so the `opencode` function is available in new sessions. (`.bashrc` or `.zshrc`) so the `opencode` function is available in new sessions.
We set up the ./container-home directory as a central $HOME inside of the We set up the XDG_DATA_HOME/opencode-container/container-home directory as a central $HOME inside of the
container, independent of the session or project directory we start in. This container, independent of the session or project directory we start in. This
presists the whole $HOME from inside the container so everything opencode persists the whole $HOME from inside the container so everything OpenCode
writes into config files, etc. presists there. writes into config files, etc. persists there.
## Environment Variables
- `XDG_DATA_HOME`: Override default data directory (default: ~/.local/share)
## Usage ## Usage

22
force-cleanup.sh Executable file
View file

@ -0,0 +1,22 @@
#!/bin/bash
# opencode-container-cleanup.sh - Complete cleanup script for OpenCode container
set -e
# Stop and remove all opencode containers
echo "Stopping and removing opencode containers..."
docker ps -q --filter "name=oc-" | xargs -r docker stop
docker ps -a -q --filter "name=oc-" | xargs -r docker rm -f
# Remove the Docker image
echo "Removing opencode-container image..."
docker rmi opencode-container:latest 2>/dev/null || true
# Remove entire opencode-container directory
CONTAINER_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/opencode-container"
if [ -d "$CONTAINER_DIR" ]; then
echo "Removing ~/.local/share/opencode-container directory..."
rm -rf "$CONTAINER_DIR"
fi
echo "Complete cleanup finished!"

View file

@ -16,14 +16,43 @@ class OpenCodeContainer:
def __init__(self): def __init__(self):
self.project_path = Path.cwd().resolve() self.project_path = Path.cwd().resolve()
self.project_id = self._project_id(self.project_path) self.project_id = self._project_id(self.project_path)
# Store host user info once
self.host_username = os.environ.get('USER', 'dev')
self.host_uid = os.getuid()
self.host_gid = os.getgid()
self.container_name = f"oc-{self.project_path.name}-{self.project_id}" self.container_name = f"oc-{self.project_path.name}-{self.project_id}"
self.docker_context_dir = Path(__file__).resolve().parent self.docker_context_dir = Path(__file__).resolve().parent
def _get_xdg_data_home(self) -> Path:
"""Get XDG_DATA_HOME with fallback to ~/.local/share"""
xdg_data = os.environ.get('XDG_DATA_HOME')
if xdg_data:
return Path(xdg_data)
return Path.home() / '.local' / 'share'
@property
def container_home_path(self) -> Path:
"""Container home directory in XDG_DATA_HOME"""
return self._get_xdg_data_home() / 'opencode-container' / 'container-home'
# ========================= # =========================
# Public entrypoint # Public entrypoint
# ========================= # =========================
def run(self, args: list[str]) -> None: def run(self, args: list[str]) -> None:
# Ensure container home directory exists
self.container_home_path.mkdir(parents=True, exist_ok=True)
# Pre-create project directory structure to prevent root-owned directories
try:
relative_path = self.project_path.relative_to(Path.home())
(self.container_home_path / relative_path).mkdir(parents=True, exist_ok=True)
except ValueError:
# Project is outside home directory - no action needed
pass
if not self.image_exists(): if not self.image_exists():
self.build_image() self.build_image()
@ -54,9 +83,12 @@ class OpenCodeContainer:
).returncode == 0 ).returncode == 0
def build_image(self) -> None: def build_image(self) -> None:
print(f"Building image '{self.IMAGE}'") print(f"Building image '{self.IMAGE}' with user {self.host_username} ({self.host_uid}:{self.host_gid})")
self._run([ self._run([
"docker", "build", "docker", "build",
"--build-arg", f"USERNAME={self.host_username}",
"--build-arg", f"UID={self.host_uid}",
"--build-arg", f"GID={self.host_gid}",
"-t", self.IMAGE, "-t", self.IMAGE,
str(self.docker_context_dir), str(self.docker_context_dir),
]) ])
@ -66,16 +98,14 @@ class OpenCodeContainer:
# ========================= # =========================
def create_container(self) -> None: def create_container(self) -> None:
uid = os.getuid()
gid = os.getgid()
print(f"Creating container '{self.container_name}'") print(f"Creating container '{self.container_name}'")
self._run([ self._run([
"docker", "create", "docker", "create",
"--name", self.container_name, "--name", self.container_name,
"--user", f"{uid}:{gid}", "--user", f"{self.host_uid}:{self.host_gid}",
"--volume", f"{self.project_path}:{self.project_path}", "--volume", f"{self.project_path}:{self.project_path}",
"--volume", f"{self.container_home_path}:/home/{self.host_username}",
self.IMAGE, self.IMAGE,
"sh", "-c", "sleep infinity", "sh", "-c", "sleep infinity",
]) ])