From 941140581ce8b347aecef552acf5b9b1ecc7c5db Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 Jan 2026 01:39:26 +0900 Subject: [PATCH 1/2] Update readme with important features --- .gitignore | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 7 ++- 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e69de29..dda707e 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,123 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +Pipfile.lock + +# PEP 582 +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ \ No newline at end of file diff --git a/README.md b/README.md index d1ca75f..f404b2e 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,12 @@ the host. - Arch Linux–based image - Runs as the host user (same username, UID, GID) +- **Per-project isolation**: Each project gets its own container (identified by project path hash) +- **Shared persistent home**: All containers mount the same home directory from XDG_DATA_HOME, allowing tools to persist across projects +- **Sudo access**: OpenCode agent can install project-specific dependencies that persist in the stopped container +- **Hard linking support**: Can hard link files like `~/.gitconfig` to share configurations with containers - Mounts only the current project directory (same absolute path inside container) -- Persists OpenCode state in XDG_DATA_HOME/opencode-container/container-home directory -- No access to SSH keys, passwords, or full `$HOME` +- **Security boundary**: No access to SSH keys, passwords, or full `$HOME` (intentionally prevents remote code pushes) - Simple shell function (`opencode`) to launch interactively ## Install From 582038e009a4649e7dc001eea98c7a37d9389950 Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 Jan 2026 01:39:44 +0900 Subject: [PATCH 2/2] Disallow running more than one instance of opercode We don't want to let people run more than one instance of opencode in one project directory, this will lead to chaos and then they interfear with each other in weird ways like when one stopps it crashes the other, etc. --- opencode-container.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/opencode-container.py b/opencode-container.py index 84a7919..2419caa 100755 --- a/opencode-container.py +++ b/opencode-container.py @@ -1,12 +1,20 @@ #!/usr/bin/env python3 import hashlib +import logging import os import subprocess import sys import time from pathlib import Path +logging.basicConfig( + level=logging.INFO, + format='%(message)s', + stream=sys.stderr +) +logger = logging.getLogger(__name__) + class OpenCodeContainer: IMAGE = "opencode-container:latest" @@ -45,6 +53,14 @@ class OpenCodeContainer: # Ensure container home directory exists self.container_home_path.mkdir(parents=True, exist_ok=True) + # Check if this project already has a running container + if self.container_exists() and self.container_running(): + logger.error(f"Project '{self.project_path.name}' already has a running OpenCode container.") + logger.error(f"Container name: {self.container_name}") + logger.error("Wait for the current instance to finish or manually stop it with:") + logger.error(f" docker stop {self.container_name}") + sys.exit(1) + # Pre-create project directory structure to prevent root-owned directories try: relative_path = self.project_path.relative_to(Path.home()) @@ -60,7 +76,7 @@ class OpenCodeContainer: self.create_container() if not self.start_container(): - print("Recreating container due to failed start") + logger.warning("Recreating container due to failed start") self.remove_container() self.create_container() if not self.start_container(): @@ -83,7 +99,7 @@ class OpenCodeContainer: ).returncode == 0 def build_image(self) -> None: - print(f"Building image '{self.IMAGE}' with user {self.host_username} ({self.host_uid}:{self.host_gid})") + logger.info(f"Building image '{self.IMAGE}' with user {self.host_username} ({self.host_uid}:{self.host_gid})") self._run([ "docker", "build", "--build-arg", f"USERNAME={self.host_username}", @@ -98,7 +114,7 @@ class OpenCodeContainer: # ========================= def create_container(self) -> None: - print(f"Creating container '{self.container_name}'") + logger.info(f"Creating container '{self.container_name}'") self._run([ "docker", "create",