140 lines
4.8 KiB
Bash
Executable File
140 lines
4.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
|
REPO_DIR="$(pwd)"
|
|
|
|
# Run `./claudaris config` to set your own image/container name (so multiple
|
|
# people can each run their own container from this repo) and other options.
|
|
# It writes them to .env, which is gitignored and sourced here if present.
|
|
NAME=claudaris
|
|
[ -f .env ] && set -a && . .env && set +a
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: ./claudaris <command>
|
|
|
|
Commands:
|
|
config, configure Interactive wizard to write .env with your settings
|
|
build Build the container image (always --no-cache --pull)
|
|
start Start (or create) the container
|
|
connect Start the container if needed, then attach a tmux session
|
|
remove, stop, rm Stop and remove the container
|
|
help Show this message
|
|
EOF
|
|
}
|
|
|
|
cmd_config() {
|
|
echo "claudaris config — press enter to keep the current/default value."
|
|
echo
|
|
|
|
read -r -p "NAME [$NAME]: " input
|
|
NAME="${input:-$NAME}"
|
|
|
|
local data_dir_default="${DATA_DIR:-/data/$NAME}"
|
|
read -r -p "DATA_DIR [$data_dir_default]: " input
|
|
DATA_DIR="${input:-$data_dir_default}"
|
|
|
|
local workspace_dir_default="${WORKSPACE_DIR:-/home/${USER:-$(id -un)}/projects}"
|
|
read -r -p "WORKSPACE_DIR [$workspace_dir_default]: " input
|
|
WORKSPACE_DIR="${input:-$workspace_dir_default}"
|
|
|
|
cat > .env <<EOF
|
|
# Written by \`./claudaris config\` — gitignored, per-user.
|
|
NAME=$NAME
|
|
DATA_DIR=$DATA_DIR
|
|
WORKSPACE_DIR=$WORKSPACE_DIR
|
|
EOF
|
|
|
|
echo
|
|
echo "Wrote .env:"
|
|
cat .env
|
|
}
|
|
|
|
cmd_build() {
|
|
export BUILDX_NO_DEFAULT_ATTESTATIONS=1
|
|
docker build --no-cache --pull -t "$NAME" .
|
|
}
|
|
|
|
cmd_start() {
|
|
# Host directory for volume mount data, separate from the repo checkout.
|
|
DATA_DIR="${DATA_DIR:-/data/$NAME}"
|
|
|
|
# Host directory mounted as /projects (the project workspace) inside the
|
|
# container. Override via `./claudaris config` if it's not right for you.
|
|
WORKSPACE_DIR="${WORKSPACE_DIR:-/home/${USER:-$(id -un)}/projects}"
|
|
|
|
# Seed host copies of .bashrc and bash_aliases, without clobbering any
|
|
# customization already made on this host. Both are bind-mounted as
|
|
# single files below so they can be tweaked per-host (e.g. a coworker
|
|
# importing their own aliases) without rebuilding the image.
|
|
mkdir -p "$DATA_DIR/home"
|
|
[ -f "$DATA_DIR/home/.bashrc" ] || cp "$REPO_DIR/files/bashrc" "$DATA_DIR/home/.bashrc"
|
|
[ -f "$DATA_DIR/home/bash_aliases" ] || cp "$REPO_DIR/files/bash_aliases" "$DATA_DIR/home/bash_aliases"
|
|
|
|
# Two files carry everything needed to stay logged in: .credentials.json
|
|
# (the OAuth token) and claude.json (account/onboarding state — Claude Code
|
|
# checks this too, so persisting the token alone isn't enough to avoid a
|
|
# re-login prompt after a rebuild). Each is bind-mounted individually,
|
|
# same as .bashrc above, seeded empty on first run so Docker doesn't create
|
|
# a directory in its place. Individual file mounts (rather than a directory
|
|
# volume + in-container symlink) matter here: Claude Code likely saves
|
|
# these atomically (write a temp file, then rename() over the target), and
|
|
# rename() onto a symlink replaces the symlink instead of writing through
|
|
# it — silently breaking persistence after the first write. A bind mount
|
|
# doesn't have that failure mode.
|
|
mkdir -p "$DATA_DIR/claude"
|
|
[ -f "$DATA_DIR/claude/credentials.json" ] || touch "$DATA_DIR/claude/credentials.json"
|
|
[ -f "$DATA_DIR/claude/claude.json" ] || touch "$DATA_DIR/claude/claude.json"
|
|
|
|
# Optional: mount ~/.ssh into the container (read-only) for connecting out to
|
|
# other nodes. Opt in by creating $DATA_DIR/ssh and populating it with
|
|
# keys/config before starting the container.
|
|
ssh_mount=()
|
|
if [ -d "$DATA_DIR/ssh" ]; then
|
|
ssh_mount=(-v "$DATA_DIR/ssh:/root/.ssh:ro")
|
|
fi
|
|
|
|
if docker ps -aq -f name="^${NAME}\$" | grep -q .; then
|
|
docker start "$NAME"
|
|
else
|
|
docker run -d \
|
|
--name "$NAME" \
|
|
--hostname "$NAME" \
|
|
-v "$DATA_DIR/home/.bashrc:/root/.bashrc" \
|
|
-v "$DATA_DIR/home/bash_aliases:/opt/dotfiles/bash_aliases" \
|
|
-v "$DATA_DIR/claude/credentials.json:/root/.claude/.credentials.json" \
|
|
-v "$DATA_DIR/claude/claude.json:/root/.claude.json" \
|
|
-v "$WORKSPACE_DIR:/projects" \
|
|
"${ssh_mount[@]}" \
|
|
"$NAME"
|
|
fi
|
|
}
|
|
|
|
cmd_connect() {
|
|
# The container exits on its own once the main tmux session ends (see
|
|
# entrypoint.sh) and isn't auto-restarted, so make sure it's up before
|
|
# exec'ing in — a no-op if it's already running.
|
|
docker start "$NAME" >/dev/null
|
|
docker exec -it "$NAME" tmux
|
|
}
|
|
|
|
cmd_remove() {
|
|
docker stop "$NAME" >/dev/null 2>&1 || true
|
|
docker rm "$NAME"
|
|
}
|
|
|
|
case "${1:-help}" in
|
|
config|configure) cmd_config ;;
|
|
build) cmd_build ;;
|
|
start) cmd_start ;;
|
|
connect) cmd_connect ;;
|
|
remove|stop|rm) cmd_remove ;;
|
|
help|-h|--help) usage ;;
|
|
*)
|
|
echo "Unknown command: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|