#!/usr/bin/env bash set -euo pipefail CONFIG_FILE="${JETBOARD_CONFIG:-/etc/jetboard.conf}" APP_DIR="${JBOARD_APP_DIR:-${APP_DIR:-}}" COMPOSE="${COMPOSE:-docker compose}" if [ -z "${BACKUP_ROOT:-}" ]; then if [ "$(id -u)" -eq 0 ]; then BACKUP_ROOT="/root" else BACKUP_ROOT="${HOME:-/tmp}" fi fi UNINSTALL_CONFIRM="${UNINSTALL_CONFIRM:-}" if [ -z "$APP_DIR" ] && [ -r "$CONFIG_FILE" ]; then # shellcheck disable=SC1090 . "$CONFIG_FILE" APP_DIR="${JBOARD_APP_DIR:-${APP_DIR:-}}" COMPOSE="${COMPOSE:-docker compose}" fi APP_DIR="${APP_DIR:-/opt/jboard}" is_interactive() { [ -r /dev/tty ] && [ -w /dev/tty ] } prompt_print() { if is_interactive; then printf '%b' "$*" > /dev/tty else printf '%b' "$*" >&2 fi } prompt_read() { local __var="$1" if is_interactive; then IFS= read -r "$__var" < /dev/tty || true else return 1 fi } run_as_root() { if [ "$(id -u)" -eq 0 ]; then "$@" elif command -v sudo >/dev/null 2>&1; then sudo "$@" else echo "需要 root 权限。请使用 root 用户运行,或先安装 sudo。" >&2 exit 1 fi } compose() { if [ "$(id -u)" -eq 0 ]; then $COMPOSE "$@" else run_as_root $COMPOSE "$@" fi } line() { printf '%s\n' "------------------------------------------------------------" } section() { echo line printf '%s\n' "$1" line } confirm_uninstall() { if [ "$UNINSTALL_CONFIRM" = "YES" ]; then return fi if ! is_interactive; then echo "非交互卸载请设置 UNINSTALL_CONFIRM=YES。" >&2 exit 1 fi section "确认卸载 J-Board Lite Docker 部署" printf '将停止并删除 Docker Compose 服务和 SQLite volume。\n' printf '将删除安装目录:%s\n' "$APP_DIR" printf '将删除命令:/usr/local/bin/jetboard、/usr/local/bin/JetBoard\n' printf '将删除配置:%s\n' "$CONFIG_FILE" echo "卸载前会尝试备份 .env、backups 和 SQLite 数据库到 ${BACKUP_ROOT}。" echo prompt_print "请输入 YES 确认卸载: " local answer="" prompt_read answer || true if [ "$answer" != "YES" ]; then echo "已取消卸载。" exit 0 fi } backup_before_remove() { if [ ! -d "$APP_DIR" ]; then echo "安装目录不存在,跳过备份:$APP_DIR" return fi local backup_dir backup_file sqlite_backup backup_dir="${BACKUP_ROOT%/}/jboard-docker-uninstall-backup-$(date +%F-%H%M%S)" backup_file="${backup_dir}.tar.gz" sqlite_backup="$backup_dir/sqlite-storage.tar.gz" run_as_root mkdir -p "$backup_dir" if [ "$(id -u)" -ne 0 ]; then run_as_root chown "$(id -u):$(id -g)" "$backup_dir" fi if [ -f "$APP_DIR/.env" ]; then run_as_root cp "$APP_DIR/.env" "$backup_dir/.env" fi if [ -d "$APP_DIR/backups" ]; then run_as_root cp -R "$APP_DIR/backups" "$backup_dir/backups" fi if [ -f "$APP_DIR/docker-compose.yml" ]; then local backup_cmd='cd /app/storage 2>/dev/null && if [ -f jboard.db ]; then set -- jboard.db; [ -f jboard.db-wal ] && set -- "$@" jboard.db-wal; [ -f jboard.db-shm ] && set -- "$@" jboard.db-shm; tar -czf - "$@"; fi' ( cd "$APP_DIR" if compose ps --services --filter status=running 2>/dev/null | grep -qx app; then compose exec -T app sh -lc "$backup_cmd" > "$sqlite_backup" || true fi if [ ! -s "$sqlite_backup" ]; then compose --profile setup run --rm -T --entrypoint sh init -lc "$backup_cmd" > "$sqlite_backup" || true fi ) if [ ! -s "$sqlite_backup" ]; then rm -f "$sqlite_backup" echo "未找到可备份的 SQLite volume,跳过数据库备份。" fi fi run_as_root tar -C "$(dirname "$backup_dir")" -czf "$backup_file" "$(basename "$backup_dir")" run_as_root rm -rf "$backup_dir" echo "卸载备份已保存:$backup_file" } remove_compose_stack() { section "删除 Docker Compose 服务" if [ -f "$APP_DIR/docker-compose.yml" ]; then ( cd "$APP_DIR" compose down --volumes --remove-orphans || true ) fi } remove_files() { section "删除文件" cd / run_as_root rm -f /usr/local/bin/jetboard /usr/local/bin/JetBoard run_as_root rm -f "$CONFIG_FILE" run_as_root rm -rf "$APP_DIR" } main() { confirm_uninstall backup_before_remove remove_compose_stack remove_files echo echo "J-Board Lite Docker 部署已卸载。Docker 本身不会被删除。" } main "$@"