forked from beenuar/AiSOC
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuninstall.sh
More file actions
executable file
·315 lines (280 loc) · 12.4 KB
/
Copy pathuninstall.sh
File metadata and controls
executable file
·315 lines (280 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
#!/usr/bin/env bash
###############################################################################
# AiSOC — Uninstaller (Linux + macOS)
#
# Tears down whatever ./install.sh + `pnpm aisoc:demo` brought up, in
# decreasing levels of aggressiveness:
#
# default Stop the demo stack and delete its named volumes.
# (Equivalent to: pnpm aisoc:demo:down)
# --images Also remove the ghcr.io/beenuar/aisoc-* container images
# pulled by the demo (saves ~ 2-3 GB of disk).
# --node-modules Also delete node_modules trees inside the repo (~ 1 GB).
# --repo Also delete the AiSOC repo clone itself.
# --all Equivalent to --images --node-modules --repo.
#
# What this script DOES NOT do:
# - Uninstall Docker, Node, pnpm, or git. Those are general-purpose tools
# you almost certainly want for other projects. If you really want to
# remove them, use your distro's package manager directly:
# Ubuntu/Debian: sudo apt-get remove docker-ce nodejs git
# Fedora: sudo dnf remove docker-ce nodejs git
# Arch: sudo pacman -R docker nodejs git
# macOS: brew uninstall --cask docker; brew uninstall node git
# - Remove the user from the docker group. Your call.
# - Touch any other Docker containers, images, or volumes outside the
# aisoc-demo project. We're surgical here.
#
# Usage:
# ./uninstall.sh # stop stack + drop volumes
# ./uninstall.sh --images # also remove pulled images
# ./uninstall.sh --all # everything except shared deps
# ./uninstall.sh --help
#
# Exit codes:
# 0 success
# 1 invalid arguments / unexpected error
# 2 not run from inside an AiSOC clone (and --repo wasn't given)
# 3 refused to delete a path for safety reasons (system dir, not an AiSOC
# clone, etc.) — visible separately so CI scripts don't silently treat
# a refusal as "all done"
###############################################################################
set -euo pipefail
# Match install.sh's logging style so the two feel like a pair.
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_DIM=$'\033[2m'
C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'
C_BLUE=$'\033[34m'; C_CYAN=$'\033[36m'
else
C_RESET=""; C_BOLD=""; C_DIM=""; C_RED=""; C_GREEN=""; C_YELLOW=""; C_BLUE=""; C_CYAN=""
fi
log() { printf '%s[aisoc]%s %s\n' "$C_DIM" "$C_RESET" "$*"; }
info() { printf '%s[aisoc]%s %s\n' "$C_BLUE" "$C_RESET" "$*"; }
ok() { printf '%s[aisoc]%s %s\n' "$C_GREEN" "$C_RESET" "$*"; }
warn() { printf '%s[aisoc]%s %s\n' "$C_YELLOW" "$C_RESET" "$*" >&2; }
err() { printf '%s[aisoc]%s %s\n' "$C_RED" "$C_RESET" "$*" >&2; }
section() { printf '\n%s%s━━━ %s ━━━%s\n\n' "$C_BOLD" "$C_CYAN" "$*" "$C_RESET"; }
die() { err "$*"; exit 1; }
usage() {
sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'
exit 0
}
# ─── Flags ───────────────────────────────────────────────────────────────────
REMOVE_IMAGES=0
REMOVE_NODE_MODULES=0
REMOVE_REPO=0
YES=0 # skip confirmation prompts (for CI / scripted use)
# Tracks whether we refused a delete for safety reasons. We don't fail-fast
# on refusals (the user might still want volumes/images cleaned), but we exit
# non-zero at the end so CI scripts notice.
SAFETY_REFUSED=0
while [ $# -gt 0 ]; do
case "$1" in
--images) REMOVE_IMAGES=1 ;;
--node-modules) REMOVE_NODE_MODULES=1 ;;
--repo) REMOVE_REPO=1 ;;
--all) REMOVE_IMAGES=1; REMOVE_NODE_MODULES=1; REMOVE_REPO=1 ;;
--yes|-y) YES=1 ;;
--help|-h) usage ;;
*) die "unknown flag: $1 (try --help)" ;;
esac
shift
done
# ─── Locate the repo ─────────────────────────────────────────────────────────
# We must be run from inside an AiSOC clone OR have access to one, since
# `docker compose` needs the compose file to resolve project resources.
REPO_ROOT=""
find_repo() {
# The uninstaller lives at the repo root, alongside install.sh.
local self_dir
self_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "$self_dir/docker-compose.demo.yml" ] && grep -q '"name": "aisoc"' "$self_dir/package.json" 2>/dev/null; then
REPO_ROOT="$self_dir"
return 0
fi
# Fallback: maybe the user ran us from PATH or a copy. Try $HOME/aisoc.
if [ -d "$HOME/aisoc" ] && [ -f "$HOME/aisoc/docker-compose.demo.yml" ]; then
REPO_ROOT="$HOME/aisoc"
return 0
fi
# Fallback 2: maybe we're run from the cwd of an aisoc clone.
if [ -f "$PWD/docker-compose.demo.yml" ] && grep -q '"name": "aisoc"' "$PWD/package.json" 2>/dev/null; then
REPO_ROOT="$PWD"
return 0
fi
return 1
}
if ! find_repo; then
if [ "$REMOVE_IMAGES" = "1" ] || [ "$REMOVE_REPO" = "1" ]; then
# We can still nuke images + repo dir without the compose file. We just
# can't do a clean `compose down`, so the user might have orphan
# containers. Warn but proceed.
warn "Couldn't locate AiSOC clone. Skipping 'compose down' (orphan containers may remain)."
else
err "Run this from inside an AiSOC clone, or pass --repo to clean up the cloned repo at \$HOME/aisoc."
exit 2
fi
fi
confirm() {
# Skip confirmation in non-interactive shells or when --yes was passed.
if [ "$YES" = "1" ] || [ ! -t 0 ]; then return 0; fi
printf '%s%s%s [y/N]: ' "$C_YELLOW" "$1" "$C_RESET" >&2
local ans
read -r ans
case "$ans" in
[yY]|[yY][eE][sS]) return 0 ;;
*) info "Skipped."; return 1 ;;
esac
}
# ─── Step 1: bring the demo stack down ───────────────────────────────────────
stop_demo_stack() {
if ! command -v docker >/dev/null 2>&1; then
warn "docker is not on PATH; skipping compose down."
return 0
fi
if ! docker info >/dev/null 2>&1; then
warn "docker daemon not reachable; skipping compose down."
return 0
fi
if [ -z "$REPO_ROOT" ]; then return 0; fi
info "Stopping AiSOC demo stack and removing its volumes..."
( cd "$REPO_ROOT" && docker compose -f docker-compose.demo.yml down -v --remove-orphans ) \
|| warn "compose down exited non-zero; some resources may not have been cleaned up."
ok "Demo stack stopped, named volumes deleted."
}
# ─── Step 2: remove pulled images ────────────────────────────────────────────
# We only target images we know belong to AiSOC, namely ghcr.io/beenuar/aisoc-*.
# We deliberately leave alpine/postgres/redis/kafka/zookeeper images alone —
# they're widely shared with other projects and re-pulling them is cheap.
remove_aisoc_images() {
if [ "$REMOVE_IMAGES" = "0" ]; then return 0; fi
if ! command -v docker >/dev/null 2>&1 || ! docker info >/dev/null 2>&1; then
warn "docker unreachable; skipping image removal."
return 0
fi
section "Removing AiSOC container images"
local images
images=$(docker images --format '{{.Repository}}:{{.Tag}}' | grep '^ghcr.io/beenuar/aisoc-' || true)
if [ -z "$images" ]; then
info "No ghcr.io/beenuar/aisoc-* images found."
return 0
fi
printf '%s\n' "$images" | sed 's/^/ /'
if confirm "Remove the images above?"; then
printf '%s\n' "$images" | xargs -r docker rmi -f || warn "some images couldn't be removed (in use?)"
ok "Images removed."
fi
}
# ─── Step 3: node_modules cleanup ────────────────────────────────────────────
remove_node_modules() {
if [ "$REMOVE_NODE_MODULES" = "0" ]; then return 0; fi
if [ -z "$REPO_ROOT" ]; then return 0; fi
section "Removing node_modules"
# The monorepo has node_modules at the root and also inside each app/package.
# `pnpm` symlinks aggressively, so a plain `rm -rf node_modules` at the root
# leaves stragglers in apps/* and packages/*.
local count
count=$(find "$REPO_ROOT" -type d -name node_modules -prune 2>/dev/null | wc -l | tr -d ' ')
info "Found $count node_modules directories under $REPO_ROOT"
if confirm "Remove them all?"; then
find "$REPO_ROOT" -type d -name node_modules -prune -exec rm -rf {} + 2>/dev/null || true
ok "node_modules removed."
fi
}
# ─── Step 4: blow away the repo clone ────────────────────────────────────────
is_dangerous_path() {
# Refuse rm -rf on anything that looks like a system directory or the
# user's home root. This is the "did Steam delete my home directory"
# safety check — better paranoid than apologetic. We canonicalise via
# realpath when available so symlinks can't dodge the blacklist.
local p="$1"
case "$p" in
""|/|/.|/..) return 0 ;;
esac
# Block the user's home root, common system dirs, and the FS root.
local abs="$p"
if command -v realpath >/dev/null 2>&1; then
abs="$(realpath "$p" 2>/dev/null || printf '%s' "$p")"
fi
case "$abs" in
/|/bin|/sbin|/usr|/usr/*|/etc|/etc/*|/var|/var/*|/opt|/opt/*) return 0 ;;
/lib|/lib/*|/lib64|/lib64/*|/boot|/boot/*|/dev|/dev/*|/proc|/proc/*|/sys|/sys/*) return 0 ;;
/tmp|/private/tmp|"$HOME"|"$HOME/") return 0 ;;
/Users|/Users/*|/home|/home/*)
# Allow /home/<user>/something but not /home or /home/<user> itself.
# Same for /Users.
local depth
depth=$(printf '%s' "$abs" | tr -cd '/' | wc -c | tr -d ' ')
[ "$depth" -le 2 ] && return 0
;;
esac
return 1
}
remove_repo_clone() {
if [ "$REMOVE_REPO" = "0" ]; then return 0; fi
# Two candidate locations: $REPO_ROOT (whatever we found) and the canonical
# $HOME/aisoc. We prefer $REPO_ROOT but warn if it's not the canonical
# location (the user may have meant something else).
local target="${REPO_ROOT:-$HOME/aisoc}"
if [ ! -d "$target" ]; then
warn "No repo found at $target; nothing to remove."
return 0
fi
# Final sanity check: the path must look like an AiSOC clone (has the
# compose file AND a package.json claiming the project name). This stops
# us from rm -rf'ing some unrelated directory the user happened to put
# in $HOME/aisoc.
if [ ! -f "$target/docker-compose.demo.yml" ] || ! grep -q '"name": "aisoc"' "$target/package.json" 2>/dev/null; then
warn "$target doesn't look like an AiSOC clone (missing compose file or package.json marker)."
warn "Refusing to delete it — clean it up manually if you really want to."
SAFETY_REFUSED=1
return 0
fi
if is_dangerous_path "$target"; then
err "Refusing to delete $target — looks like a system or home root directory."
err "If you really meant to delete this path, do it by hand."
SAFETY_REFUSED=1
return 0
fi
section "Removing AiSOC repo"
warn "About to recursively delete: $target"
if confirm "Are you absolutely sure?"; then
# cd somewhere safe before rm -rf'ing — if $REPO_ROOT is the cwd of
# this shell, deleting it can confuse macOS's filesystem and leave
# the script unable to print the final banner.
cd /tmp 2>/dev/null || cd / || true
rm -rf "$target"
ok "Repo deleted."
fi
}
# ─── Main ────────────────────────────────────────────────────────────────────
main() {
section "AiSOC Uninstaller"
stop_demo_stack
remove_aisoc_images
remove_node_modules
remove_repo_clone
if [ "$SAFETY_REFUSED" = "1" ]; then
cat <<EOF
${C_YELLOW}AiSOC uninstall finished — but at least one delete was refused for safety.${C_RESET}
${C_YELLOW}See messages above. Exiting with code 3 so CI/scripts notice.${C_RESET}
EOF
else
cat <<EOF
${C_GREEN}AiSOC uninstall complete.${C_RESET}
EOF
fi
cat <<EOF
${C_DIM}Things this script intentionally did NOT remove:${C_RESET}
- Docker, Node, pnpm, git (shared dev tools)
- Your membership in the 'docker' group
- Other Docker images (postgres, redis, kafka, zookeeper, alpine)
- Cached pnpm store at ~/.local/share/pnpm/store
${C_DIM}To remove the leftover infrastructure images too:${C_RESET}
docker image prune -a # removes ALL dangling+unused images, not just AiSOC's
EOF
if [ "$SAFETY_REFUSED" = "1" ]; then
exit 3
fi
}
main "$@"