Compare commits
No commits in common. "582038e009a4649e7dc001eea98c7a37d9389950" and "a56da84b7a3d58cc0587cd567eb10cb3191a3f33" have entirely different histories.
582038e009
...
a56da84b7a
3 changed files with 5 additions and 147 deletions
123
.gitignore
vendored
123
.gitignore
vendored
|
|
@ -1,123 +0,0 @@
|
||||||
# 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/
|
|
||||||
|
|
@ -8,12 +8,9 @@ the host.
|
||||||
|
|
||||||
- Arch Linux–based image
|
- Arch Linux–based image
|
||||||
- Runs as the host user (same username, UID, GID)
|
- 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)
|
- Mounts only the current project directory (same absolute path inside container)
|
||||||
- **Security boundary**: No access to SSH keys, passwords, or full `$HOME` (intentionally prevents remote code pushes)
|
- Persists OpenCode state in XDG_DATA_HOME/opencode-container/container-home directory
|
||||||
|
- No access to SSH keys, passwords, or full `$HOME`
|
||||||
- Simple shell function (`opencode`) to launch interactively
|
- Simple shell function (`opencode`) to launch interactively
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,12 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format='%(message)s',
|
|
||||||
stream=sys.stderr
|
|
||||||
)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class OpenCodeContainer:
|
class OpenCodeContainer:
|
||||||
IMAGE = "opencode-container:latest"
|
IMAGE = "opencode-container:latest"
|
||||||
|
|
@ -53,14 +45,6 @@ class OpenCodeContainer:
|
||||||
# Ensure container home directory exists
|
# Ensure container home directory exists
|
||||||
self.container_home_path.mkdir(parents=True, exist_ok=True)
|
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
|
# Pre-create project directory structure to prevent root-owned directories
|
||||||
try:
|
try:
|
||||||
relative_path = self.project_path.relative_to(Path.home())
|
relative_path = self.project_path.relative_to(Path.home())
|
||||||
|
|
@ -76,7 +60,7 @@ class OpenCodeContainer:
|
||||||
self.create_container()
|
self.create_container()
|
||||||
|
|
||||||
if not self.start_container():
|
if not self.start_container():
|
||||||
logger.warning("Recreating container due to failed start")
|
print("Recreating container due to failed start")
|
||||||
self.remove_container()
|
self.remove_container()
|
||||||
self.create_container()
|
self.create_container()
|
||||||
if not self.start_container():
|
if not self.start_container():
|
||||||
|
|
@ -99,7 +83,7 @@ class OpenCodeContainer:
|
||||||
).returncode == 0
|
).returncode == 0
|
||||||
|
|
||||||
def build_image(self) -> None:
|
def build_image(self) -> None:
|
||||||
logger.info(f"Building image '{self.IMAGE}' with user {self.host_username} ({self.host_uid}:{self.host_gid})")
|
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"USERNAME={self.host_username}",
|
||||||
|
|
@ -114,7 +98,7 @@ class OpenCodeContainer:
|
||||||
# =========================
|
# =========================
|
||||||
|
|
||||||
def create_container(self) -> None:
|
def create_container(self) -> None:
|
||||||
logger.info(f"Creating container '{self.container_name}'")
|
print(f"Creating container '{self.container_name}'")
|
||||||
|
|
||||||
self._run([
|
self._run([
|
||||||
"docker", "create",
|
"docker", "create",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue