setup: Show a plan and ask for confirmation before changing anything
This commit is contained in:
parent
d110942710
commit
70699026c3
2 changed files with 128 additions and 45 deletions
|
|
@ -37,7 +37,7 @@ Optionally run the setup script to share agent state (`.claude`, `.pi`, `.agents
|
|||
~/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.
|
||||
It shows a plan first and asks for confirmation before moving anything (`--dry-run` previews without changes, `--yes` skips the question). Existing state is moved into the container home and symlinked 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.
|
||||
|
||||
|
|
|
|||
171
setup.sh
171
setup.sh
|
|
@ -8,7 +8,9 @@
|
|||
# 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.
|
||||
# Shows a plan and asks for confirmation before changing anything:
|
||||
# --dry-run (-n) show the plan without changing anything
|
||||
# --yes (-y) apply the plan without asking
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -34,8 +36,57 @@ FILES=(
|
|||
info() { printf ' %s\n' "$*"; }
|
||||
warn() { printf ' warning: %s\n' "$*" >&2; }
|
||||
|
||||
# Print the action taken for an item, shared by both helpers below.
|
||||
_report() {
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: setup.sh [--dry-run] [--yes]
|
||||
|
||||
Shares agent state (.claude, .pi, .agents, OpenCode state, ...) between the
|
||||
host \$HOME and the agent-container container home by moving it into the
|
||||
container home and symlinking back from \$HOME. Shows a plan and asks for
|
||||
confirmation before changing anything.
|
||||
|
||||
--dry-run, -n show the plan without changing anything
|
||||
--yes, -y apply the plan without asking
|
||||
EOF
|
||||
}
|
||||
|
||||
# Decide what would be done with an item and print the action name.
|
||||
# Pure: changes nothing, so the plan can be computed up front.
|
||||
decide() {
|
||||
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"/*) echo ok ;;
|
||||
*) echo foreign ;;
|
||||
esac
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -e "$host" ] && [ -e "$target" ]; then
|
||||
echo conflict
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -e "$host" ] && [ ! -e "$target" ]; then
|
||||
if [ "$must_exist" = "yes" ]; then
|
||||
echo skip
|
||||
else
|
||||
echo created
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -e "$host" ]; then
|
||||
echo moved
|
||||
else
|
||||
echo linked
|
||||
fi
|
||||
}
|
||||
|
||||
# Explain an action; used for both the plan and the applied result.
|
||||
describe() {
|
||||
local action="$1" item="$2"
|
||||
case "$action" in
|
||||
moved) info "moved $item into the container home, symlinked from \$HOME" ;;
|
||||
|
|
@ -43,57 +94,89 @@ _report() {
|
|||
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)" ;;
|
||||
foreign) warn "$item is a symlink to $(readlink "$HOME/$item"), not touching it" ;;
|
||||
conflict)
|
||||
warn "$item exists in both \$HOME and the container home"
|
||||
warn "reconcile the two copies manually, then re-run this script"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
share_item() {
|
||||
local item="$1" must_exist="$2"
|
||||
_link() {
|
||||
mkdir -p "$(dirname "$1")"
|
||||
ln -s "$2" "$1"
|
||||
}
|
||||
|
||||
# Apply one planned action, then report it.
|
||||
apply() {
|
||||
local item="$1" action="$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"
|
||||
case "$action" in
|
||||
created) mkdir -p "$target" ;;
|
||||
moved)
|
||||
mkdir -p "$(dirname "$target")"
|
||||
mv "$host" "$target"
|
||||
;;
|
||||
esac
|
||||
case "$action" in
|
||||
created|moved|linked) _link "$host" "$target" ;;
|
||||
esac
|
||||
describe "$action" "$item"
|
||||
}
|
||||
|
||||
main() {
|
||||
local dry_run=no ask=yes
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--dry-run|-n) dry_run=yes; ask=no ;;
|
||||
--yes|-y) ask=no ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) usage >&2; exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
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
|
||||
# Plan everything up front; decide() changes nothing, so applying the
|
||||
# stored actions afterwards is exactly what the plan describes.
|
||||
local -a items=() actions=()
|
||||
local item action
|
||||
for item in "${DIRS[@]}"; do items+=("$item"); actions+=("$(decide "$item" no)"); done
|
||||
for item in "${FILES[@]}"; do items+=("$item"); actions+=("$(decide "$item" yes)"); done
|
||||
|
||||
printf 'Plan:\n'
|
||||
local i
|
||||
for i in "${!items[@]}"; do
|
||||
describe "${actions[$i]}" "${items[$i]}"
|
||||
done
|
||||
|
||||
if [ "$dry_run" = "yes" ]; then
|
||||
printf '\nDry run: nothing changed.\n'
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$ask" = "yes" ]; then
|
||||
if [ -t 0 ]; then
|
||||
local reply
|
||||
printf '\nApply these changes? [y/N] '
|
||||
read -r reply
|
||||
case "$reply" in
|
||||
y|Y|yes|Yes) ;;
|
||||
*) printf 'Aborted, nothing changed.\n'; return ;;
|
||||
esac
|
||||
else
|
||||
printf '\nNot running interactively; pass --yes to apply the plan.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
for i in "${!items[@]}"; do
|
||||
apply "${items[$i]}" "${actions[$i]}"
|
||||
done
|
||||
|
||||
if [ -f "$HOME/.gitconfig" ] && [ ! -e "$CONTAINER_HOME/.gitconfig" ]; then
|
||||
printf '\n'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue