Compare commits
2 commits
a56da84b7a
...
582038e009
| Author | SHA1 | Date | |
|---|---|---|---|
| 582038e009 | |||
| 941140581c |
3 changed files with 147 additions and 5 deletions
123
.gitignore
vendored
123
.gitignore
vendored
|
|
@ -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/
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue