Compare commits

..
19 Commits
Author SHA1 Message Date
thuanleandClaude cb3737e163 cleanup-server: add AI CLI Tools upgrade step (claude/codex/agy/kiro/paseo)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 16:18:20 +07:00
thuanle f0c9943c2a update 2026-07-25 03:21:20 +07:00
thuanleandClaude 5b61bc37c2 Add ssh-open: open iTerm2 window with 6 SSH tabs
Opens a new iTerm2 window with tabs that ssh into ta, vrc, aws-vanhoa,
toc, oc, tk in order. Tab colors are applied by the ssh() override in
~/.zshrc (per-host); a small delay between tab creations lets iTerm
process the color OSC reliably.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-25 03:16:23 +07:00
thuanleandClaude 3dbed651e5 Arch pkg cache: remove partial download-* files before pacman -Sc
pacman -Sc errors with 'could not open file .../download-XXXX: Error
reading fd 7' on leftover incomplete downloads. Find and remove them
(with confirm) first, then run paccache/pacman -Sc cleanly.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:45:17 +07:00
thuanleandClaude 171e5dc9c0 Show target host in every step header
Each step now prints "--- [Label] on <host> ---". The host is threaded
through the remote self-bootstrap as argv[2] so it shows the real
server name when running over ssh.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:36:56 +07:00
thuanleandClaude 2e126a1743 Fix target alias: aws -> aws-vanhoa (matches ~/.ssh/config)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:35:22 +07:00
thuanleandClaude b64303bb23 Use legible figlet block-art banner (CLEANUP SERVER) with per-line rainbow
Replaces the gradient-bar version with proper figlet-style ASCII art
so the title is actually readable; each line colored in a rainbow
gradient, with a Vietnamese subtitle.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:26:25 +07:00
thuanleandClaude ab9d62fac6 Replace illegible box-art banner with readable gradient-bar banner
The Unicode box-drawing art didn't form legible letters. Now uses
two rainbow gradient bars around a plain, readable title + subtitle.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:24:06 +07:00
thuanleandClaude ce49f1ce5c Harden step_snap: dedupe Set ids (multi-snap sets)
Local flow verified end-to-end (macOS guards). Remote Ubuntu/Arch
verification deferred to user (interactive, destructive on real hosts).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:20:20 +07:00
thuanleandClaude a49f26adc8 Add step_tmp_cache: find >7d dry-run + confirmed rm
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:18:45 +07:00
thuanleandClaude f50579efb2 Add step_snap: list saved + forget per id
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:18:14 +07:00
thuanleandClaude 37ce7f6ca8 Add step_pkg_cache: apt clean / paccache / pacman -Sc
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:17:56 +07:00
thuanleandClaude f4c8f6c9e5 Add step_journal: disk-usage + vacuum-time=7d
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:17:38 +07:00
thuanleandClaude 833b33e042 Add step_docker: system df + prune per type
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:17:12 +07:00
thuanleandClaude 50d51b328f Add step_upgrade (apt / paru-yay / pacman)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:16:45 +07:00
thuanleandClaude e031bd0c6d Add distro detection, confirm helper, remote self-bootstrap
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:16:17 +07:00
thuanleandClaude 6fa2891d41 Add single-select target menu (arrow-key)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:15:23 +07:00
thuanleandClaude f8ddfa48a4 Add cleanup-server skeleton: banner + main dispatch
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:14:45 +07:00
thuanleandClaude 3999760d02 Drop stale 'cc' host from supgrade
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:01:01 +07:00
9 changed files with 670 additions and 255 deletions
-217
View File
@@ -1,217 +0,0 @@
#!/usr/bin/env bash
: <<'DOC'
ask — AI terminal assistant (macOS/Linux)
Setup API key (macOS Keychain):
security add-generic-password -a "$USER" -s TK_AI_API_TOKEN -w "YOUR_API_KEY"
Setup API key (Arch/Linux — libsecret):
echo "YOUR_API_KEY" | secret-tool store --label "AI API Token" service TK_AI_API_TOKEN
Usage:
ask "question"
ask -i # interactive mode
ask -v # verbose (debug raw response)
DOC
# ===== CONFIG =====
SERVER="https://ai.acbpro.com/v1/chat/completions"
MODEL="gpt-5.3-codex"
KEY_NAME="TK_AI_API_TOKEN"
# ===== OS DETECTION =====
if [ -f /etc/arch-release ]; then
OS="arch"
elif [ "$(uname -s)" = "Darwin" ]; then
OS="macos"
else
OS="linux"
fi
VERBOSE=0
INTERACTIVE=0
# ===== COLORS =====
GREEN="\033[1;32m"
CYAN="\033[1;36m"
YELLOW="\033[1;33m"
RED="\033[1;31m"
RESET="\033[0m"
# ===== PARSE FLAGS =====
while [[ "$1" == -* ]]; do
case "$1" in
-v) VERBOSE=1 ;;
-i) INTERACTIVE=1 ;;
esac
shift
done
# ===== LOAD TOKEN =====
if [ -n "${!KEY_NAME}" ]; then
API_TOKEN="${!KEY_NAME}"
else
case "$OS" in
macos)
API_TOKEN=$(security find-generic-password -a "$USER" -s "$KEY_NAME" -w 2>/dev/null | tr -d '\n')
;;
*)
API_TOKEN=$(secret-tool lookup service "$KEY_NAME" 2>/dev/null | tr -d '\n')
;;
esac
fi
if [ -z "$API_TOKEN" ]; then
echo "❌ API token not found ($KEY_NAME)" >&2
exit 1
fi
# ===== SYSTEM PROMPT =====
SYSTEM_PROMPT="You are a terminal assistant for macOS (zsh/bash).
- Default: return ONLY the command, no explanation.
- If user asks for explanation, then explain.
- Commands must be safe and runnable.
- Prefer standard macOS/Linux tools.
- No markdown, no code block.
"
# ===== TERMINAL STATUS =====
supports_status_line() {
[ -t 2 ] && [ "${TERM:-}" != "dumb" ]
}
render_status_line() {
supports_status_line || return 0
printf '\r\033[2K%b' "$1" >&2
}
clear_status_line() {
supports_status_line || return 0
printf '\r\033[2K' >&2
}
print_response() {
if [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then
echo -e "${CYAN}🤖 $1${RESET}"
else
printf '%s\n' "$1"
fi
}
# ===== FUNCTION CALL API =====
call_api() {
local Q="$1"
local CWD GIT_BRANCH LAST_CMD JSON
local START_TIME TMP_FILE CURL_PID
local SPINNER i NOW ELAPSED CHAR COLOR
local RESPONSE CONTENT CURL_STATUS
CWD="$(pwd)"
GIT_BRANCH="$(git branch --show-current 2>/dev/null)"
LAST_CMD="$(fc -ln -1 2>/dev/null)"
JSON=$(jq -n \
--arg model "$MODEL" \
--arg sys "$SYSTEM_PROMPT" \
--arg q "$Q" \
--arg cwd "$CWD" \
--arg branch "$GIT_BRANCH" \
--arg last "$LAST_CMD" \
'{
model: $model,
messages: [
{role: "system", content: $sys},
{role: "user", content:
("Context:
" +
"cwd: " + $cwd + "
" +
"git_branch: " + $branch + "
" +
"last_command: " + $last + "
" +
"Request:
" + $q)
}
]
}'
)
# ===== CALL (async) =====
START_TIME=$(date +%s)
TMP_FILE=$(mktemp)
curl -s "$SERVER" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d "$JSON" > "$TMP_FILE" &
CURL_PID=$!
trap 'clear_status_line; [ -n "${CURL_PID:-}" ] && kill "${CURL_PID}" 2>/dev/null; [ -n "${TMP_FILE:-}" ] && rm -f "${TMP_FILE}"; exit 130' INT TERM
# ===== SPINNER + TIMER =====
SPINNER='|/-\'
i=0
if supports_status_line; then
while kill -0 "$CURL_PID" 2>/dev/null; do
NOW=$(date +%s)
ELAPSED=$((NOW - START_TIME))
CHAR=${SPINNER:i%${#SPINNER}:1}
if [ "$ELAPSED" -lt 10 ]; then COLOR="$GREEN";
elif [ "$ELAPSED" -lt 30 ]; then COLOR="$YELLOW";
else COLOR="$RED"; fi
render_status_line "${COLOR}⏳ calling ${CHAR} ${ELAPSED}s${RESET}"
i=$((i+1))
sleep 0.1
done
fi
wait "$CURL_PID"
CURL_STATUS=$?
clear_status_line
trap - INT TERM
RESPONSE=$(<"$TMP_FILE")
rm -f "$TMP_FILE"
if [ "$CURL_STATUS" -ne 0 ]; then
echo "❌ request failed (curl exit $CURL_STATUS)" >&2
[ -n "$RESPONSE" ] && echo "$RESPONSE" >&2
return "$CURL_STATUS"
fi
if [ "$VERBOSE" -eq 1 ]; then
echo -e "${YELLOW}🔧 RAW RESPONSE:${RESET}" >&2
echo "$RESPONSE" >&2
fi
CONTENT=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // .response // .text // .')
print_response "$CONTENT"
}
# ===== INTERACTIVE MODE =====
if [ "$INTERACTIVE" -eq 1 ]; then
echo -e "${GREEN}💬 Interactive mode (Ctrl+C để thoát)${RESET}"
while true; do
printf "${GREEN}❓ ask> ${RESET}"
read Q
[ -z "$Q" ] && continue
call_api "$Q"
done
exit 0
fi
# ===== NORMAL MODE =====
if [ $# -eq 0 ]; then
echo "Usage: ask [-v] [-i] <question>"
exit 1
fi
call_api "$*"
+2 -2
View File
@@ -39,8 +39,8 @@ fi
export ANTHROPIC_BASE_URL="https://api.z.ai/api/anthropic"
export ANTHROPIC_AUTH_TOKEN="$TOKEN"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="glm-4.5-air"
export ANTHROPIC_DEFAULT_SONNET_MODEL="glm-5.1"
export ANTHROPIC_DEFAULT_OPUS_MODEL="glm-5.1"
export ANTHROPIC_DEFAULT_SONNET_MODEL="glm-5.2"
export ANTHROPIC_DEFAULT_OPUS_MODEL="glm-5.2"
echo "→ Backend: GLM"
Executable
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# claude-gpt — Run Claude Code with GPT backend
#
# Usage:
# claude-gpt [args...]
#
# Setup API key (reuse existing):
# macOS:
# security add-generic-password -a "$USER" -s TK_AI_API_TOKEN -w "YOUR_KEY"
# Linux:
# echo "YOUR_KEY" | secret-tool store --label "AI API Token" service TK_AI_API_TOKEN
KEY_NAME="TK_AI_API_TOKEN"
if [ -n "${!KEY_NAME}" ]; then
TOKEN="${!KEY_NAME}"
fi
get_token() {
if command -v security >/dev/null 2>&1; then
security find-generic-password -s "$KEY_NAME" -w 2>/dev/null && return
fi
if command -v secret-tool >/dev/null 2>&1; then
secret-tool lookup service "$KEY_NAME" 2>/dev/null && return
fi
return 1
}
if [ -z "$TOKEN" ]; then
TOKEN=$(get_token)
fi
[ -z "${TOKEN}" ] && echo "❌ Missing TK_AI_API_TOKEN" && exit 1
export ANTHROPIC_BASE_URL="https://ai.acbpro.com"
#export ANTHROPIC_BASE_URL="https://claudecode-classifier-cliproxyapi-proxier.ldt116.workers.dev"
export ANTHROPIC_AUTH_TOKEN="${TOKEN}"
export ANTHROPIC_DEFAULT_OPUS_MODEL="glm-5.2"
export ANTHROPIC_DEFAULT_SONNET_MODEL="glm-5.2"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="glm-4.7"
export _CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL=1
echo "→ Backend: TK AI GLM"
if [[ "$*" == *"--permission-mode"* ]]; then
exec claude "$@"
fi
exec claude --permission-mode auto "$@"
+3 -3
View File
@@ -38,9 +38,9 @@ fi
export ANTHROPIC_BASE_URL="https://ai.acbpro.com"
export ANTHROPIC_AUTH_TOKEN="$TOKEN"
export ANTHROPIC_DEFAULT_OPUS_MODEL="gpt-5.3-codex(high)"
export ANTHROPIC_DEFAULT_SONNET_MODEL="gpt-5.3-codex(medium)"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="gpt-5.3-codex(medium)"
export ANTHROPIC_DEFAULT_OPUS_MODEL="gpt-5.6-sol(medium)"
export ANTHROPIC_DEFAULT_SONNET_MODEL="gpt-5.6-terra(medium)"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="gpt-5.6-luna(medium)"
echo "→ Backend: GPT"
Executable
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# claude-gpt — Run Claude Code with GPT backend
#
# Usage:
# claude-gpt [args...]
#
# Setup API key (reuse existing):
# macOS:
# security add-generic-password -a "$USER" -s TK_AI_API_TOKEN -w "YOUR_KEY"
# Linux:
# echo "YOUR_KEY" | secret-tool store --label "AI API Token" service TK_AI_API_TOKEN
KEY_NAME="TK_AI_API_TOKEN"
if [ -n "${!KEY_NAME}" ]; then
TOKEN="${!KEY_NAME}"
fi
get_token() {
if command -v security >/dev/null 2>&1; then
security find-generic-password -s "$KEY_NAME" -w 2>/dev/null && return
fi
if command -v secret-tool >/dev/null 2>&1; then
secret-tool lookup service "$KEY_NAME" 2>/dev/null && return
fi
return 1
}
if [ -z "$TOKEN" ]; then
TOKEN=$(get_token)
fi
if [ -z "$TOKEN" ]; then
echo "❌ Missing GPT token ($KEY_NAME)"
exit 1
fi
export ANTHROPIC_BASE_URL="https://ai.acbpro.com"
export ANTHROPIC_AUTH_TOKEN="$TOKEN"
export ANTHROPIC_DEFAULT_OPUS_MODEL="grok-4.5"
export ANTHROPIC_DEFAULT_SONNET_MODEL="grok-4.5"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="grok-3-mini"
echo "→ Backend: GPT"
has_permission_mode=false
for arg in "$@"; do
if [ "$arg" = "--permission-mode" ] || [[ "$arg" == --permission-mode=* ]]; then
has_permission_mode=true
break
fi
done
if [ "$has_permission_mode" = true ]; then
exec claude "$@"
else
exec claude --permission-mode auto "$@"
fi
Executable
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# claude-glm — Run Claude Code with GLM backend
#
# Usage:
# claude-glm [args...]
#
# Setup API key (reuse existing):
# macOS:
# security add-generic-password -a "$USER" -s GLM_API_KEY -w "YOUR_KEY"
# Linux:
# echo "YOUR_KEY" | secret-tool store --label "GLM API Key" service GLM_API_KEY
KEY_NAME="TL-AI-PROXY"
if [ -n "${!KEY_NAME}" ]; then
TOKEN="${!KEY_NAME}"
fi
get_token() {
if command -v security >/dev/null 2>&1; then
security find-generic-password -s "$KEY_NAME" -w 2>/dev/null && return
fi
if command -v secret-tool >/dev/null 2>&1; then
secret-tool lookup service "$KEY_NAME" 2>/dev/null && return
fi
return 1
}
if [ -z "$TOKEN" ]; then
TOKEN=$(get_token)
fi
if [ -z "$TOKEN" ]; then
echo "❌ Missing GLM token ($KEY_NAME)"
exit 1
fi
export ANTHROPIC_BASE_URL="https://ai-proxy.thuanle.me/auto/claude"
export ANTHROPIC_AUTH_TOKEN="$TOKEN"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="tl-auto-low"
export ANTHROPIC_DEFAULT_SONNET_MODEL="tl-auto-high"
export ANTHROPIC_DEFAULT_OPUS_MODEL="tl-auto-high"
echo "→ Backend: GLM"
has_permission_mode=false
for arg in "$@"; do
if [ "$arg" = "--permission-mode" ] || [[ "$arg" == --permission-mode=* ]]; then
has_permission_mode=true
break
fi
done
if [ "$has_permission_mode" = true ]; then
exec claude "$@"
else
exec claude --permission-mode auto "$@"
fi
Executable
+466
View File
@@ -0,0 +1,466 @@
#!/bin/bash
# cleanup-server - Dọn dẹp + nâng cấp server (Ubuntu/Arch), interactive, tuần tự.
# Cách dùng:
# cleanup-server # hiện menu chọn target (local hoặc server alias)
# cleanup-server --run-steps # (nội bộ) chạy thẳng 7 bước trên máy hiện tại
# -----------------------------
# Color helpers (tự tắt khi không phải TTY)
# -----------------------------
if [ -t 1 ]; then
C_RESET=$'\033[0m'
C_TITLE=$'\033[1;36m' # tiêu đề
C_OK=$'\033[1;32m' # thành công
C_DEL=$'\033[1;31m' # sẽ xóa / lỗi
C_WARN=$'\033[1;33m' # cảnh báo
else
C_RESET=""; C_TITLE=""; C_OK=""; C_DEL=""; C_WARN=""
fi
# -----------------------------
# Banner rainbow (ASCII art figlet, per-line gradient)
# -----------------------------
print_banner() {
local -a lines=(
'▗▄▄▄▖▗▖ ▗▄▄▖▗▄▄▄▖▗▄▄▖ ▗▖ ▗▖▗▄▄▄▖▗▄▄▖ ▗▄▄▖▗▖ ▗▄▄▄▖ ▗▄▖ ▗▖ ▗▖▗▄▄▄▖▗▄▖ '
' █ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌▐▛▚▖▐▌▐▌ ▐▌ ▐▌'
' █ ▐▌ ▝▀▚▖▐▛▀▀▘▐▛▀▚▖▐▌ ▐▌▐▛▀▀▘▐▛▀▚▖ ▐▌ ▐▌ ▐▛▀▀▘▐▛▀▜▌▐▌ ▝▜▌▐▛▀▀▘▐▛▀▚▖'
' █ ▐▙▄▄▖ ▗▄▄▞▘▐▙▄▄▖▐▌ ▐▌ ▝▚▞▘ ▐▙▄▄▖▐▌ ▐▌ ▝▚▄▄▖▐▙▄▄▖▐▙▄▄▖▐▌ ▐▌▐▌ ▐▌▐▙▄▄▖▐▌ ▐▌'
)
local -a colors=()
if [ -t 1 ]; then
colors=(
$'\033[1;38;5;51m' # cyan
$'\033[1;38;5;120m' # xanh lá
$'\033[1;38;5;220m' # vàng gold
$'\033[1;38;5;209m' # cam-đỏ
)
fi
echo ""
local i
for ((i = 0; i < ${#lines[@]}; i++)); do
if [ -t 1 ]; then
printf '%s%s%s\n' "${colors[$i]}" "${lines[$i]}" "$C_RESET"
else
printf '%s\n' "${lines[$i]}"
fi
done
printf ' %sDọn dẹp + nâng cấp server (Ubuntu / Arch)%s\n' "$C_WARN" "$C_RESET"
echo ""
}
# -----------------------------
# Host đang chạy (đặt ở main; hiển thị lại trong mỗi header bước)
# -----------------------------
CURRENT_TARGET="local"
# In header bước kèm host: --- [Label] on <host> ---
step_header() {
echo "${C_TITLE}--- [$1] on ${CURRENT_TARGET} ---${C_RESET}"
}
# -----------------------------
# Phát hiện distro qua /etc/os-release → debian | arch | unknown
# -----------------------------
detect_distro() {
[ -r /etc/os-release ] || { echo unknown; return; }
local id id_like
. /etc/os-release 2>/dev/null
id="${ID:-}"; id_like="${ID_LIKE:-}"
case "$id $id_like" in
*arch*) echo arch ;;
*debian*) echo debian ;;
*ubuntu*) echo debian ;;
*) echo unknown ;;
esac
}
# -----------------------------
# Hỏi Y/n (Enter = Yes). Trả về 0 = đồng ý, 1 = hủy.
# -----------------------------
confirm_yn() {
local prompt="$1" confirm
read -r -p " ${prompt} (Y/n): " confirm
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
echo " Đã hủy."
return 1
fi
return 0
}
# -----------------------------
# Danh sách target fix cứng (alias từ ~/.ssh/config)
# -----------------------------
TARGETS=("local" "ta" "toc" "oc" "cse" "vrc" "aws-vanhoa" "tk" "od")
TARGET_CURSOR=0
TARGET=""
# Vẽ menu single-select
draw_target_menu() {
if [ "$1" = "redraw" ]; then
printf '\033[%dA' "$(( ${#TARGETS[@]} + 2 ))"
fi
echo " Chọn target (↑↓: di chuyển | Enter: chọn):"
echo ""
local i
for ((i = 0; i < ${#TARGETS[@]}; i++)); do
local prefix=" "
if [ "$i" -eq "$TARGET_CURSOR" ]; then
prefix="${C_TITLE}▸ ${C_RESET}"
fi
printf '\r\033[K%s%s\n' "$prefix" "${TARGETS[$i]}"
done
}
# Chạy menu, đặt biến toàn cục TARGET
select_target() {
draw_target_menu
while true; do
IFS= read -rsn1 key
case "$key" in
$'\x1b')
read -rsn2 seq
case "$seq" in
'[A') TARGET_CURSOR=$(( (TARGET_CURSOR - 1 + ${#TARGETS[@]}) % ${#TARGETS[@]} ));;
'[B') TARGET_CURSOR=$(( (TARGET_CURSOR + 1) % ${#TARGETS[@]} ));;
esac
;;
'') # Enter
echo ""
TARGET="${TARGETS[$TARGET_CURSOR]}"
return
;;
esac
draw_target_menu "redraw"
done
}
# -----------------------------
# Bước 1: Nâng cấp hệ thống theo distro
# -----------------------------
step_upgrade() {
step_header "System Upgrade"
local distro
distro=$(detect_distro)
case "$distro" in
debian)
echo " Distro: Debian/Ubuntu (apt)."
sudo apt-get update || { echo "${C_DEL} ✗ apt-get update thất bại.${C_RESET}"; echo ""; return; }
local upgradable
upgradable=$(apt list --upgradable 2>/dev/null | grep -c 'upgradable')
echo " ${C_WARN}✗ ${upgradable} package có thể nâng cấp.${C_RESET}"
if confirm_yn "Upgrade + autoremove?"; then
sudo apt-get upgrade -y
sudo apt-get autoremove --purge -y
echo "${C_OK} ✓ Đã nâng cấp xong.${C_RESET}"
fi
;;
arch)
echo " Distro: Arch (pacman)."
local helper=""
command -v paru >/dev/null 2>&1 && helper="paru"
[ -z "$helper" ] && command -v yay >/dev/null 2>&1 && helper="yay"
[ -z "$helper" ] && helper="sudo pacman"
echo " Helper: $helper"
if confirm_yn "Chạy $helper -Syu?"; then
$helper -Syu
echo "${C_OK} ✓ Đã nâng cấp xong.${C_RESET}"
fi
;;
*)
echo "${C_WARN} ⚠ Distro không hỗ trợ upgrade, bỏ qua.${C_RESET}"
;;
esac
echo ""
}
# -----------------------------
# Bước 2: AI CLI Tools — nếu detect thấy tool thì upgrade
# claude → claude update
# codex → codex update
# agy → agy update
# kiro → binary là kiro-cli → kiro-cli update -y
# paseo → cài qua npm (@getpaseo/cli) → npm install -g @getpaseo/cli@latest
# -----------------------------
step_ai_tools() {
step_header "AI CLI Tools"
local found=0
local -a found_names=()
if command -v claude >/dev/null 2>&1; then
found=1; found_names+=("claude")
echo " ${C_OK}✓ claude: $(claude --version 2>/dev/null | head -1)${C_RESET}"
if confirm_yn "claude update?"; then
claude update
fi
echo ""
fi
if command -v codex >/dev/null 2>&1; then
found=1; found_names+=("codex")
echo " ${C_OK}✓ codex: $(codex --version 2>/dev/null | head -1)${C_RESET}"
if confirm_yn "codex update?"; then
codex update
fi
echo ""
fi
if command -v agy >/dev/null 2>&1; then
found=1; found_names+=("agy")
echo " ${C_OK}✓ agy: $(agy --version 2>/dev/null | head -1)${C_RESET}"
if confirm_yn "agy update?"; then
agy update
fi
echo ""
fi
if command -v kiro-cli >/dev/null 2>&1; then
found=1; found_names+=("kiro")
echo " ${C_OK}✓ kiro (kiro-cli): $(kiro-cli --version 2>/dev/null | head -1)${C_RESET}"
if confirm_yn "kiro-cli update?"; then
kiro-cli update -y
fi
echo ""
fi
if command -v paseo >/dev/null 2>&1; then
found=1; found_names+=("paseo")
echo " ${C_OK}✓ paseo: $(paseo --version 2>/dev/null | head -1)${C_RESET}"
if confirm_yn "npm install -g @getpaseo/cli@latest?"; then
npm install -g @getpaseo/cli@latest
fi
echo ""
fi
if [ "$found" -eq 0 ]; then
echo "${C_OK} ✓ Không tìm thấy AI CLI tool nào (claude/codex/agy/kiro/paseo).${C_RESET}"
echo ""
else
echo "${C_OK} ✓ AI CLI Tools xong: ${found_names[*]}.${C_RESET}"
echo ""
fi
}
# -----------------------------
# Chọn 'docker' hoặc 'sudo docker' tùy permission
# -----------------------------
docker_cmd() {
if docker info >/dev/null 2>&1; then
echo "docker"
elif sudo -n docker info >/dev/null 2>&1; then
echo "sudo docker"
else
echo ""
fi
}
# -----------------------------
# Bước 2: Docker — df rồi prune từng loại
# -----------------------------
step_docker() {
step_header "Docker"
if ! command -v docker >/dev/null 2>&1; then
echo "${C_WARN} ⚠ docker command not found.${C_RESET}"
echo ""
return
fi
local DC
DC=$(docker_cmd)
if [ -z "$DC" ]; then
echo "${C_WARN} ⚠ Không có quyền truy cập docker (cần group docker hoặc sudo).${C_RESET}"
echo ""
return
fi
echo " Lệnh: $DC"
$DC system df
echo ""
confirm_yn "Prune images (-a)?" && $DC image prune -a -f
confirm_yn "Prune containers?" && $DC container prune -f
confirm_yn "Prune volumes?" && $DC volume prune -f
confirm_yn "Prune build cache?" && $DC builder prune -f
confirm_yn "Prune networks?" && $DC network prune -f
echo "${C_OK} ✓ Docker cleanup xong.${C_RESET}"
echo ""
}
# -----------------------------
# Bước 3: Journal logs — vacuum log cũ hơn 7 ngày
# -----------------------------
step_journal() {
step_header "Journal Logs"
if ! command -v journalctl >/dev/null 2>&1; then
echo "${C_WARN} ⚠ journalctl not found.${C_RESET}"
echo ""
return
fi
sudo journalctl --disk-usage
if confirm_yn "Vacuum log cũ hơn 7 ngày (--vacuum-time=7d)?"; then
sudo journalctl --vacuum-time=7d
echo "${C_OK} ✓ Đã dọn journal.${C_RESET}"
fi
echo ""
}
# -----------------------------
# Bước 4: Package cache — apt clean+autoremove hoặc paccache/pacman -Sc
# -----------------------------
step_pkg_cache() {
step_header "Package Cache"
local distro
distro=$(detect_distro)
case "$distro" in
debian)
local d="/var/cache/apt/archives"
[ -d "$d" ] && sudo du -sh "$d"
if confirm_yn "apt clean + autoremove?"; then
sudo apt-get clean
sudo apt-get autoremove -y
echo "${C_OK} ✓ Đã dọn apt cache.${C_RESET}"
fi
;;
arch)
local d="/var/cache/pacman/pkg"
[ -d "$d" ] && sudo du -sh "$d"
# Xóa file download tạm (incomplete) — pacman -Sc báo lỗi fd 7 với chúng.
local partial pcount
partial=$(sudo find "$d" -maxdepth 1 -type f -name 'download-*' 2>/dev/null)
if [ -n "$partial" ]; then
pcount=$(printf '%s\n' "$partial" | grep -c .)
echo " ${C_WARN}✗ ${pcount} file download tạm (incomplete).${C_RESET}"
if confirm_yn "Xóa file download-* tạm?"; then
printf '%s\n' "$partial" | xargs sudo rm -f --
echo "${C_OK} ✓ Đã xóa file tạm.${C_RESET}"
fi
fi
if command -v paccache >/dev/null 2>&1; then
sudo paccache -dk2 # dry-run xem trước
if confirm_yn "paccache -rk2 (giữ 2 bản)?"; then
sudo paccache -rk2
echo "${C_OK} ✓ Đã dọn pacman cache.${C_RESET}"
fi
else
if confirm_yn "Không có paccache, chạy pacman -Sc?"; then
sudo pacman -Sc --noconfirm
echo "${C_OK} ✓ Đã dọn pacman cache.${C_RESET}"
fi
fi
;;
*)
echo "${C_WARN} ⚠ Distro không hỗ trợ dọn package cache.${C_RESET}"
;;
esac
echo ""
}
# -----------------------------
# Bước 5: Snap — forget từng snapshot
# -----------------------------
step_snap() {
step_header "Snap"
if ! command -v snap >/dev/null 2>&1; then
echo "${C_WARN} ⚠ snap not found.${C_RESET}"
echo ""
return
fi
local saved
# Cột đầu (Set) là id cần forget; một Set có thể có nhiều Snap → dedupe.
saved=$(sudo snap saved 2>/dev/null | awk 'NR>1{print $1}' | awk '!seen[$0]++')
if [ -z "$saved" ]; then
echo "${C_OK} ✓ Không có snap snapshot nào.${C_RESET}"
echo ""
return
fi
local id
for id in $saved; do
if confirm_yn "Forget snap snapshot (Set $id)?"; then
sudo snap forget "$id"
fi
done
echo "${C_OK} ✓ Snap cleanup xong.${C_RESET}"
echo ""
}
# -----------------------------
# Bước 6: /tmp & cache cũ — liệt kê file >7 ngày, xác nhận rồi xóa
# -----------------------------
step_tmp_cache() {
step_header "/tmp & Cache cũ"
local days=7
echo " Tìm file cũ hơn ${days} ngày trong /tmp /var/tmp:"
local old_files
old_files=$(sudo find /tmp /var/tmp -type f -mtime +"$days" 2>/dev/null)
if [ -z "$old_files" ]; then
echo "${C_OK} ✓ Không có file cũ >${days} ngày.${C_RESET}"
echo ""
return
fi
local count total_kb total_mib
count=$(printf '%s\n' "$old_files" | grep -c .)
total_kb=$(printf '%s\n' "$old_files" | xargs sudo du -ck 2>/dev/null | tail -1 | cut -f1)
total_mib=$(( total_kb / 1024 ))
echo "${C_DEL} ✗ ${count} file, ~${total_mib} MiB.${C_RESET}"
printf '%s\n' "$old_files" | head -20 | sed 's/^/ - /'
[ "$count" -gt 20 ] && echo " ... (và $((count - 20)) file nữa)"
if confirm_yn "Xóa các file cũ >${days} ngày?"; then
printf '%s\n' "$old_files" | xargs sudo rm -f --
echo "${C_OK} ✓ Đã xóa.${C_RESET}"
fi
echo ""
}
# -----------------------------
# Chạy tuần tự 7 bước
# -----------------------------
run_all_steps() {
step_upgrade
step_ai_tools
step_docker
step_journal
step_pkg_cache
step_snap
step_tmp_cache
}
# -----------------------------
# Chạy trên target đã chọn.
# local → chạy tại chỗ.
# alias → self-bootstrap: base64 script qua argv, giữ stdin=terminal
# để read/màu tương tác hoạt động qua PTY của `ssh -t`.
# -----------------------------
run_on_target() {
local target="$1"
if [ "$target" = "local" ]; then
CURRENT_TARGET="local"
run_all_steps
return $?
fi
local b64
b64=$(base64 < "$0" | tr -d '\n') || {
echo "${C_DEL} ✗ Không mã hóa được script.${C_RESET}"
return 1
}
# Giải mã ra file tạm trên remote rồi chạy; stdin không bị redirect → read OK.
# Truyền target làm argv[2] để remote biết host (hiển thị lại trong header).
ssh -t "$target" \
"umask 077; t=\$(mktemp); printf '%s' '$b64' | base64 -d > \"\$t\" && bash \"\$t\" --run-steps \"$target\"; r=\$?; rm -f \"\$t\"; exit \$r" \
|| { echo "${C_DEL} ✗ ssh tới $target thất bại.${C_RESET}"; return 1; }
}
# -----------------------------
# Main (phân nhánh theo argv)
# -----------------------------
main() {
if [ "${1:-}" = "--run-steps" ]; then
CURRENT_TARGET="${2:-local}"
echo "${C_TITLE}===== 🧹 Cleanup-server on ${CURRENT_TARGET} =====${C_RESET}"
echo ""
run_all_steps
echo ""
echo "${C_OK}===== ✅ Cleanup-server hoàn tất =====${C_RESET}"
return
fi
print_banner
echo "${C_TITLE}===== 🧹 Cleanup-server =====${C_RESET}"
echo ""
select_target
echo ""
run_on_target "$TARGET"
}
main "$@"
Executable
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# ssh-open — mở cửa sổ iTerm2 mới với 6 tab, mỗi tab ssh vào 1 host theo thứ tự.
# Màu tab do ~/.zshrc (hàm ssh() override) tự set khi ssh chạy trong tab.
# delay giữa các tab để iTerm kịp xử lý OSC đổi màu (tránh mất màu khi tạo tab liên tục).
set -euo pipefail
# Danh sách host theo thứ tự (alias từ ~/.ssh/config).
HOSTS=(ta vrc aws-vanhoa toc oc tk)
# Sinh AppleScript list: {"ta","vrc",...}
alist="$(printf '"%s",' "${HOSTS[@]}")"
alist="{${alist%,}}"
osascript <<APPLESCRIPT
tell application "iTerm"
activate
set theHosts to $alist
set newWindow to (create window with default profile)
delay 0.5
tell newWindow
-- Tab đầu: dùng tab gốc của cửa sổ mới.
tell current session of current tab
write text ("ssh " & item 1 of theHosts)
end tell
-- Các host còn lại: mỗi host 1 tab mới.
repeat with i from 2 to (count of theHosts)
set t to (create tab with default profile)
delay 0.5
tell current session of t
write text ("ssh " & item i of theHosts)
end tell
end repeat
end tell
end tell
APPLESCRIPT
-33
View File
@@ -1,33 +0,0 @@
#!/bin/bash
# Function to run SSH command and handle errors
run_ssh() {
server=$1
command=$2
echo "===== $server ====="
ssh -T $server "$command" &
# Capture the PID of the last background process
pid=$!
# Wait for the background process to finish
wait $pid
# Check the exit status of the background process
if [ $? -eq 0 ]; then
echo "Successfully upgraded $server."
else
echo "Failed to upgrade $server. Check the connection and try again."
fi
}
# Run SSH commands for each server
run_ssh "oc" 'sudo apt-get upgrade -y'
run_ssh "tk" 'sudo apt-get upgrade -y'
run_ssh "cc" 'sudo apt-get upgrade -y'
run_ssh "rpi3" 'sudo apt-get upgrade -y'
run_ssh "cse-z" 'sudo apt-get upgrade -y'
run_ssh "hp" 'sudo apt-get upgrade -y'
run_ssh "od-xu4" 'yay'
echo "Upgrade process completed for all servers."