|
#!/usr/bin/env sh |
|
set -eu |
|
|
|
MODE="${1:---bin}" # default to --bin |
|
ZSHRC="${ZDOTDIR:-$HOME}/.zshrc" |
|
|
|
case "$MODE" in |
|
--bin) |
|
BIN_DIR="${DCLEAN_BIN_DIR:-$HOME/.local/bin}" |
|
TARGET="$BIN_DIR/dclean" |
|
echo "β Installing dclean command to: $TARGET" |
|
mkdir -p "$BIN_DIR" |
|
|
|
# write the executable |
|
cat > "$TARGET" <<'ZSH' |
|
#!/usr/bin/env zsh |
|
# dclean: clean, rebuild, and start specific Docker Compose services |
|
|
|
dclean() { |
|
emulate -L zsh -o pipefail |
|
|
|
# Allow running from anywhere via envs |
|
local project_dir="${DCLEAN_PROJECT_DIR:-$PWD}" |
|
|
|
# Change to project directory |
|
cd "$project_dir" || { |
|
echo "[x] Cannot access project directory: $project_dir" |
|
return 1 |
|
} |
|
|
|
# Check if this is a Docker Compose project and determine if profiles are needed |
|
local use_profiles=false |
|
|
|
# Check if there are services without profiles |
|
local services_count=$(docker compose config --services 2>/dev/null | wc -l) |
|
if [[ "$services_count" -eq 0 ]]; then |
|
use_profiles=true |
|
fi |
|
|
|
# Build docker compose command |
|
local dc_cmd="docker compose" |
|
if [[ "$use_profiles" == "true" ]]; then |
|
dc_cmd="docker compose --profile all --profile backend --profile frontend" |
|
fi |
|
|
|
# Get services list |
|
local -a services |
|
if (( $# == 0 )); then |
|
services=(${(f)$(eval "$dc_cmd" config --services)}) |
|
else |
|
services=("$@") |
|
fi |
|
|
|
if [[ ${#services[@]} -eq 0 ]]; then |
|
echo "[x] No services found or specified." |
|
return 1 |
|
fi |
|
|
|
echo "π― Target services:"; printf " - %s\n" "${services[@]}"; echo "ββββββββββββββββββββββββββββββ" |
|
|
|
# Get running containers for these services |
|
local -a cids svc_cids |
|
local s; for s in "${services[@]}"; do |
|
svc_cids=(${(f)$(eval "$dc_cmd" ps -q "$s" 2>/dev/null)}) |
|
(( ${#svc_cids[@]} )) && cids+=("${svc_cids[@]}") |
|
done |
|
|
|
# Get container names |
|
local -a cname; local cid name |
|
for cid in "${cids[@]}"; do |
|
name="$(docker inspect -f '{{.Name}}' "$cid" 2>/dev/null)"; name="${name#/}" |
|
[[ -n "$name" ]] && cname+=("$name") |
|
done |
|
|
|
# Get volumes and images from containers |
|
local -a vols imgs; local img_id v |
|
for cid in "${cids[@]}"; do |
|
for v in ${(f)$(docker inspect -f '{{range .Mounts}}{{if eq .Type "volume"}}{{.Name}}{{"\n"}}{{end}}{{end}}' "$cid" 2>/dev/null)}; do |
|
[[ -n "$v" ]] && vols+=("$v") |
|
done |
|
img_id="$(docker inspect -f '{{.Image}}' "$cid" 2>/dev/null)" |
|
[[ -n "$img_id" ]] && imgs+=("$img_id") |
|
done |
|
|
|
local -a uniq_vols uniq_imgs |
|
uniq_vols=(${(u)vols}); uniq_imgs=(${(u)imgs}) |
|
|
|
echo "ποΈ Removing containers..."; echo "ββββββββββββββββββββββββββββββ" |
|
if (( ${#cname[@]} )); then printf ' π¦ %s\n' "${cname[@]}"; else echo "No containers to remove."; fi |
|
eval "$dc_cmd" rm -sfv "${services[@]}" >/dev/null 2>&1 || true |
|
|
|
echo "ββββββββββββββββββββββββββββββ"; echo "ποΈ Removing volumes..."; echo "ββββββββββββββββββββββββββββββ" |
|
if (( ${#uniq_vols[@]} )); then printf ' ποΈ %s\n' "${uniq_vols[@]}"; docker volume rm -f "${uniq_vols[@]}" >/dev/null 2>&1 || true |
|
else echo "No volumes to remove."; fi |
|
|
|
echo "ββββββββββββββββββββββββββββββ"; echo "ποΈ Removing images..."; echo "ββββββββββββββββββββββββββββββ" |
|
if (( ${#uniq_imgs[@]} )); then |
|
local img ref short |
|
for img in "${uniq_imgs[@]}"; do |
|
short="${${img#sha256:}:0:12}" |
|
ref="$(docker image inspect --format '{{if .RepoTags}}{{index .RepoTags 0}}{{else}}<untagged>{{end}}' "$img" 2>/dev/null)" |
|
echo " πΌοΈ ${short:-$img} ${ref}" |
|
done |
|
docker image rm -f "${uniq_imgs[@]}" >/dev/null 2>&1 || true |
|
else echo "No images to remove."; fi |
|
|
|
echo "π Rebuilding services..."; echo "ββββββββββββββββββββββββββββββ" |
|
echo "π οΈ Building Docker Compose services..."; echo "ββββββββββββββββββββββββββββββ" |
|
if eval "$dc_cmd" build "${services[@]}"; then echo "π All services built successfully!"; else echo "β One or more services failed to build."; fi |
|
|
|
echo "π Starting services..."; echo "ββββββββββββββββββββββββββββββ" |
|
eval "$dc_cmd" up -d "${services[@]}"; echo "[β] Done." |
|
} |
|
|
|
dclean "$@" |
|
ZSH |
|
chmod +x "$TARGET" |
|
|
|
# ensure PATH contains ~/.local/bin |
|
case ":${PATH:-}:" in *":$BIN_DIR:"*) ;; *) |
|
printf '\n# dclean installer: add user bin to PATH\nexport PATH="%s:$PATH"\n' "$BIN_DIR" >> "$ZSHRC" |
|
echo "β Added $BIN_DIR to PATH in $ZSHRC (reload shell to use 'dclean')." |
|
esac |
|
|
|
echo "β Installed. Use: dclean <service> [serviceβ¦]" |
|
;; |
|
|
|
--plugin) |
|
ZSH_DIR="${ZSH:-$HOME/.oh-my-zsh}" |
|
PLUGIN_DIR="$ZSH_DIR/custom/plugins/dclean" |
|
[ -d "$ZSH_DIR" ] || { echo "β Oh My Zsh not found at $ZSH_DIR"; exit 1; } |
|
|
|
echo "β Installing Oh My Zsh plugin to: $PLUGIN_DIR" |
|
mkdir -p "$PLUGIN_DIR" |
|
|
|
cat > "$PLUGIN_DIR/dclean.plugin.zsh" <<'ZPLUG' |
|
# dclean: clean, rebuild, and start specific Docker Compose services |
|
# Usage: dclean [service ...] (no args β all services) |
|
dclean() { |
|
emulate -L zsh |
|
setopt pipefail no_unset |
|
|
|
local dc |
|
if docker compose version >/dev/null 2>&1; then |
|
dc="docker compose" |
|
elif command -v docker-compose >/dev/null 2>&1 && docker-compose version >/dev/null 2>&1; then |
|
dc="docker-compose" |
|
else |
|
echo "[x] docker compose not found." |
|
return 1 |
|
fi |
|
|
|
$dc config >/dev/null 2>&1 || { echo "[x] Not a Docker Compose project directory."; return 1; } |
|
|
|
local -a services |
|
if (( $# == 0 )); then services=(${(f)$($dc config --services)}); else services=("$@"); fi |
|
|
|
echo "π― Target services:"; printf " - %s\n" "${services[@]}"; echo "ββββββββββββββββββββββββββββββ" |
|
|
|
local -a cids svc_cids |
|
local s; for s in "${services[@]}"; do |
|
svc_cids=(${(f)$($dc ps -q "$s" 2>/dev/null)}) |
|
(( ${#svc_cids[@]} )) && cids+=("${svc_cids[@]}") |
|
done |
|
|
|
local -a cname |
|
local cid name; for cid in "${cids[@]}"; do |
|
name="$(docker inspect -f '{{.Name}}' "$cid" 2>/dev/null)"; name="${name#/}" |
|
[[ -n "$name" ]] && cname+=("$name") |
|
done |
|
|
|
local -a vols imgs |
|
local img_id v |
|
for cid in "${cids[@]}"; do |
|
for v in ${(f)$(docker inspect -f '{{range .Mounts}}{{if eq .Type "volume"}}{{.Name}}{{"\n"}}{{end}}{{end}}' "$cid" 2>/dev/null)}; do |
|
[[ -n "$v" ]] && vols+=("$v") |
|
done |
|
img_id="$(docker inspect -f '{{.Image}}' "$cid" 2>/dev/null)" |
|
[[ -n "$img_id" ]] && imgs+=("$img_id") |
|
done |
|
|
|
local -a uniq_vols uniq_imgs |
|
uniq_vols=(${(u)vols}); uniq_imgs=(${(u)imgs}) |
|
|
|
echo "ποΈ Removing containers..."; echo "ββββββββββββββββββββββββββββββ" |
|
if (( ${#cname[@]} )); then printf ' π¦ %s\n' "${cname[@]}"; else echo "No containers to remove."; fi |
|
$dc rm -sfv "${services[@]}" >/dev/null 2>&1 || true |
|
|
|
echo "ββββββββββββββββββββββββββββββ"; echo "ποΈ Removing volumes..."; echo "ββββββββββββββββββββββββββββββ" |
|
if (( ${#uniq_vols[@]} )); then printf ' ποΈ %s\n' "${uniq_vols[@]}"; docker volume rm -f "${uniq_vols[@]}" >/dev/null 2>&1 || true |
|
else echo "No volumes to remove."; fi |
|
|
|
echo "ββββββββββββββββββββββββββββββ"; echo "ποΈ Removing images..."; echo "ββββββββββββββββββββββββββββββ" |
|
if (( ${#uniq_imgs[@]} )); then |
|
local img ref short |
|
for img in "${uniq_imgs[@]}"; do |
|
short="${${img#sha256:}:0:12}" |
|
ref="$(docker image inspect --format '{{if .RepoTags}}{{index .RepoTags 0}}{{else}}<untagged>{{end}}' "$img" 2>/dev/null)" |
|
echo " πΌοΈ ${short:-$img} ${ref}" |
|
done |
|
docker image rm -f "${uniq_imgs[@]}" >/dev/null 2>&1 || true |
|
else echo "No images to remove."; fi |
|
|
|
echo "π Rebuilding services..."; echo "ββββββββββββββββββββββββββββββ" |
|
echo "π οΈ Building Docker Compose services..."; echo "ββββββββββββββββββββββββββββββ" |
|
if $dc build "${services[@]}"; then echo "π All services built successfully!"; else echo "β One or more services failed to build."; fi |
|
|
|
echo "π Starting services..."; echo "ββββββββββββββββββββββββββββββ" |
|
$dc up -d "${services[@]}"; echo "[β] Done." |
|
} |
|
ZPLUG |
|
|
|
echo "β Plugin installed." |
|
if ! grep -qE '^[[:space:]]*plugins=.*\bdclean\b' "$ZSHRC" 2>/dev/null; then |
|
cat <<EOF |
|
|
|
Add the plugin to your ~/.zshrc, then reload: |
|
|
|
1) Edit plugins=(...) and include: dclean |
|
Example: |
|
plugins=(git dclean) |
|
|
|
2) Reload: |
|
source "$ZSHRC" |
|
|
|
Done! Now run: |
|
dclean <service> [serviceβ¦] |
|
EOF |
|
else |
|
echo "It looks like 'dclean' is already in plugins=(...). Reload with: source \"$ZSHRC\"" |
|
fi |
|
;; |
|
|
|
*) |
|
echo "Unknown mode: $MODE"; exit 1;; |
|
esac |