diff --git a/README.md b/README.md index 3bba44d..977e1a5 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,14 @@ source ~/Projects/agent-container/agent.aliases This makes the `opencode`, `claude`, `pi`, and `agent-container` commands available in new sessions, plus the `opencode-local`, `claude-local`, and `pi-local` variants that run the tools directly on the host. +Optionally run the setup script to share agent state (`.claude`, `.pi`, `.agents`, OpenCode state, ...) between the container and the host: + +```sh +~/Projects/agent-container/setup.sh +``` + +It moves existing state into the container home and symlinks it back into `$HOME`, so containerized tools and the `*-local` variants share logins, settings, and history. See "Sharing agent state between host and container via symlinks" below. The script is safe to re-run and skips items it cannot handle safely. + The container home directory at `$XDG_DATA_HOME/agent-container/container-home/` serves as a central `$HOME` inside every container, independent of which project directory you start in. Everything written to `$HOME` inside the container persists there. ## Environment Variables @@ -85,6 +93,8 @@ To remove all containers, the image, and the persistent home directory: agent-container purge ``` +Note that this also deletes the shared agent state that lives in the container home (`.claude`, `.pi`, ...). The symlinks in your `$HOME` will dangle afterwards. + ## Sharing host config files via hard links The container home at `~/.local/share/agent-container/container-home/` is mounted as `/home/` inside every container. You can hard link files from your real `$HOME` into this directory so the container sees them without copying or syncing. @@ -102,6 +112,10 @@ Avoid linking sensitive files such as `~/.ssh/id_*` or `~/.gnupg/` -- keeping th ## Sharing agent state between host and container via symlinks +The repository ships a `setup.sh` script that automates the following for `.claude`, +`.claude.json`, `.pi`, `.agents`, and the OpenCode state directories. The manual +recipe for any other directory looks like this: + State directories such as `.claude/`, `.pi/`, or `.agents/` can be shared between the containerized tools (`claude`, `pi`, `opencode`) and their local variants (`claude-local`, `pi-local`, `opencode-local`). Keep the real data in the container home diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..bbc2ac3 --- /dev/null +++ b/setup.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# +# Set up shared agent state for agent-container. +# +# Moves agent state (Claude Code, Pi, OpenCode) from the real $HOME into the +# container home and symlinks it back, so the containerized tools (claude, pi, +# opencode) and the local variants (claude-local, pi-local, opencode-local) +# share logins, settings, and history. See README.md, section "Sharing agent +# state between host and container via symlinks". +# +# Safe to re-run: already shared items are detected and left alone. + +set -euo pipefail + +XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" +CONTAINER_HOME="$XDG_DATA_HOME/agent-container/container-home" + +# State directories to share. Created in the container home (and linked into +# $HOME) even if they don't exist yet, so both sides share from the start. +DIRS=( + .claude + .pi + .agents + .config/opencode + .local/share/opencode +) + +# Single files to share. Only linked when they exist on one side, tools create +# them on first use. +FILES=( + .claude.json +) + +info() { printf ' %s\n' "$*"; } +warn() { printf ' warning: %s\n' "$*" >&2; } + +# Print the action taken for an item, shared by both helpers below. +_report() { + local action="$1" item="$2" + case "$action" in + moved) info "moved $item into the container home, symlinked from \$HOME" ;; + linked) info "linked $item (container home already had it)" ;; + created) info "created $item in the container home, symlinked from \$HOME" ;; + ok) info "ok $item (already shared)" ;; + skip) info "skip $item (does not exist yet, the tool will create it)" ;; + esac +} + +share_item() { + local item="$1" must_exist="$2" + local host="$HOME/$item" target="$CONTAINER_HOME/$item" + + if [ -L "$host" ]; then + case "$(readlink -f "$host")" in + "$CONTAINER_HOME"/*) + _report ok "$item" + return + ;; + *) + warn "$item is a symlink to $(readlink "$host"), not touching it" + return + ;; + esac + fi + + if [ -e "$host" ] && [ -e "$target" ]; then + warn "$item exists in both \$HOME and the container home" + warn "reconcile the two copies manually, then re-run this script" + return + fi + + if [ ! -e "$host" ] && [ ! -e "$target" ]; then + if [ "$must_exist" = "yes" ]; then + _report skip "$item" + return + fi + mkdir -p "$target" + _report created "$item" + elif [ -e "$host" ]; then + mkdir -p "$(dirname "$target")" + mv "$host" "$target" + _report moved "$item" + else + _report linked "$item" + fi + + mkdir -p "$(dirname "$host")" + ln -s "$target" "$host" +} + +main() { + printf 'Sharing agent state via %s\n\n' "$CONTAINER_HOME" + + local item + for item in "${DIRS[@]}"; do share_item "$item" no; done + for item in "${FILES[@]}"; do share_item "$item" yes; done + + if [ -f "$HOME/.gitconfig" ] && [ ! -e "$CONTAINER_HOME/.gitconfig" ]; then + printf '\n' + info "Tip: share your git config too (see README):" + info " ln $HOME/.gitconfig $CONTAINER_HOME/.gitconfig" + fi +} + +main "$@" \ No newline at end of file