Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0c9943c2a | ||
|
|
5b61bc37c2 | ||
|
|
3dbed651e5 | ||
|
|
171e5dc9c0 | ||
|
|
2e126a1743 | ||
|
|
b64303bb23 | ||
|
|
ab9d62fac6 | ||
|
|
ce49f1ce5c | ||
|
|
a49f26adc8 | ||
|
|
f50579efb2 | ||
|
|
37ce7f6ca8 | ||
|
|
f4c8f6c9e5 |
@@ -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
@@ -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
@@ -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 "$@"
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
+162
-22
@@ -18,39 +18,47 @@ else
|
||||
fi
|
||||
|
||||
# -----------------------------
|
||||
# Banner rainbow (giống style `cleanup`)
|
||||
# Banner rainbow (ASCII art figlet, per-line gradient)
|
||||
# -----------------------------
|
||||
print_banner() {
|
||||
local use_color=0 reset="" i=0 line
|
||||
local -a lines=(
|
||||
' ██████╗██╗ ██╗ ██████╗██╗ ██╗██╗ ██╗██████╗ ███████╗███╗ ██╗ ██████╗██╗ ██╗'
|
||||
' ██╔════╝██║ ██║██╔════╝██║ ██║██║ ██║██╔══██╗ ██╔════╝████╗ ██║██╔═══██╗╚██╗██╔╝'
|
||||
' ██║ ██║ ██║██║ ███████║██║ ██║██║ ██║ █████╗ ██╔██╗ ██║██║ ██║ ╚███╔╝ '
|
||||
' ██║ ██║ ██║██║ ██╔══██║██║ ██║██║ ██║ ██╔══╝ ██║╚██╗██║██║ ██║ ██╔██╗ '
|
||||
' ╚██████╗╚██████╔╝╚██████╗██║ ██║╚██████╔╝██████╔╝ ███████╗██║ ╚████║╚██████╔╝██╔╝ ██╗'
|
||||
' ╚═════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝'
|
||||
'▗▄▄▄▖▗▖ ▗▄▄▖▗▄▄▄▖▗▄▄▖ ▗▖ ▗▖▗▄▄▄▖▗▄▄▖ ▗▄▄▖▗▖ ▗▄▄▄▖ ▗▄▖ ▗▖ ▗▖▗▄▄▄▖▗▄▖ '
|
||||
' █ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌ ▐▌▐▛▚▖▐▌▐▌ ▐▌ ▐▌'
|
||||
' █ ▐▌ ▝▀▚▖▐▛▀▀▘▐▛▀▚▖▐▌ ▐▌▐▛▀▀▘▐▛▀▚▖ ▐▌ ▐▌ ▐▛▀▀▘▐▛▀▜▌▐▌ ▝▜▌▐▛▀▀▘▐▛▀▚▖'
|
||||
' █ ▐▙▄▄▖ ▗▄▄▞▘▐▙▄▄▖▐▌ ▐▌ ▝▚▞▘ ▐▙▄▄▖▐▌ ▐▌ ▝▚▄▄▖▐▙▄▄▖▐▙▄▄▖▐▌ ▐▌▐▌ ▐▌▐▙▄▄▖▐▌ ▐▌'
|
||||
)
|
||||
local -a colors=()
|
||||
if [ -t 1 ]; then
|
||||
use_color=1
|
||||
reset=$'\033[0m'
|
||||
colors=(
|
||||
$'\033[1;38;5;51m' $'\033[1;38;5;87m' $'\033[1;38;5;123m'
|
||||
$'\033[1;38;5;159m' $'\033[1;38;5;220m' $'\033[1;38;5;214m'
|
||||
$'\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 ""
|
||||
for line in "${lines[@]}"; do
|
||||
if [ "$use_color" -eq 1 ]; then
|
||||
printf '%b%s%b\n' "${colors[$i]}" "$line" "$reset"
|
||||
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' "$line"
|
||||
printf '%s\n' "${lines[$i]}"
|
||||
fi
|
||||
i=$((i + 1))
|
||||
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
|
||||
# -----------------------------
|
||||
@@ -83,7 +91,7 @@ confirm_yn() {
|
||||
# -----------------------------
|
||||
# Danh sách target fix cứng (alias từ ~/.ssh/config)
|
||||
# -----------------------------
|
||||
TARGETS=("local" "ta" "toc" "oc" "cse" "vrc" "aws" "tk" "od")
|
||||
TARGETS=("local" "ta" "toc" "oc" "cse" "vrc" "aws-vanhoa" "tk" "od")
|
||||
TARGET_CURSOR=0
|
||||
TARGET=""
|
||||
|
||||
@@ -131,7 +139,7 @@ select_target() {
|
||||
# Bước 1: Nâng cấp hệ thống theo distro
|
||||
# -----------------------------
|
||||
step_upgrade() {
|
||||
echo "${C_TITLE}--- [System Upgrade] ---${C_RESET}"
|
||||
step_header "System Upgrade"
|
||||
local distro
|
||||
distro=$(detect_distro)
|
||||
case "$distro" in
|
||||
@@ -184,7 +192,7 @@ docker_cmd() {
|
||||
# Bước 2: Docker — df rồi prune từng loại
|
||||
# -----------------------------
|
||||
step_docker() {
|
||||
echo "${C_TITLE}--- [Docker] ---${C_RESET}"
|
||||
step_header "Docker"
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "${C_WARN} ⚠ docker command not found.${C_RESET}"
|
||||
echo ""
|
||||
@@ -209,12 +217,141 @@ step_docker() {
|
||||
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ự 6 bước (stub — các bước thêm ở Task 4-9)
|
||||
# -----------------------------
|
||||
run_all_steps() {
|
||||
step_upgrade
|
||||
step_docker
|
||||
step_journal
|
||||
step_pkg_cache
|
||||
step_snap
|
||||
step_tmp_cache
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
@@ -226,6 +363,7 @@ run_all_steps() {
|
||||
run_on_target() {
|
||||
local target="$1"
|
||||
if [ "$target" = "local" ]; then
|
||||
CURRENT_TARGET="local"
|
||||
run_all_steps
|
||||
return $?
|
||||
fi
|
||||
@@ -235,8 +373,9 @@ run_on_target() {
|
||||
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; r=\$?; rm -f \"\$t\"; exit \$r" \
|
||||
"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; }
|
||||
}
|
||||
|
||||
@@ -245,7 +384,8 @@ run_on_target() {
|
||||
# -----------------------------
|
||||
main() {
|
||||
if [ "${1:-}" = "--run-steps" ]; then
|
||||
echo "${C_TITLE}===== 🧹 Cleanup-server (--run-steps) =====${C_RESET}"
|
||||
CURRENT_TARGET="${2:-local}"
|
||||
echo "${C_TITLE}===== 🧹 Cleanup-server on ${CURRENT_TARGET} =====${C_RESET}"
|
||||
echo ""
|
||||
run_all_steps
|
||||
echo ""
|
||||
|
||||
@@ -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
|
||||
@@ -1,32 +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 "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."
|
||||
Reference in New Issue
Block a user