fix: 前端类型修复(@types/node + vite-env.d.ts + ufw SSHD_PORT)

- frontend: 加 @types/node / vite/client 类型声明
- frontend: tsconfig 加 types: [node, vite/client]
- scripts: deploy_remote.sh 用 sg docker + dc() 函数避免引号问题
- scripts: deploy_remote.sh ufw 改用 \ 变量
This commit is contained in:
Mavis
2026-06-07 23:04:06 +08:00
parent 60b062daf2
commit 427e1f5cf2
9 changed files with 370 additions and 0 deletions

View File

@@ -18,6 +18,7 @@
"vfonts": "^0.0.3" "vfonts": "^0.0.3"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.9.0",
"@vitejs/plugin-vue": "^5.1.4", "@vitejs/plugin-vue": "^5.1.4",
"typescript": "^5.6.3", "typescript": "^5.6.3",
"vite": "^5.4.10", "vite": "^5.4.10",

1
frontend/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -4,6 +4,7 @@
"useDefineForClassFields": true, "useDefineForClassFields": true,
"module": "ESNext", "module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"], "lib": ["ES2020", "DOM", "DOM.Iterable"],
"types": ["node", "vite/client"],
"skipLibCheck": true, "skipLibCheck": true,
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,

16
scripts/_check_sshd.py Normal file
View File

@@ -0,0 +1,16 @@
import os, sys, paramiko
PW = os.environ.get("REMOTE_PASS", "")
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect("207.57.129.228", port=19717, username="root", password=PW, timeout=15, allow_agent=False, look_for_keys=False)
def run(cmd):
si, so, se = c.exec_command(cmd, timeout=15)
out = so.read().decode("utf-8", "replace")
err = se.read().decode("utf-8", "replace")
print(f"$ {cmd}")
if out: print(out, end="")
if err: print("[err]", err, end="", file=sys.stderr)
run("ls -la /root/.ssh/ && echo --- && cat /root/.ssh/authorized_keys | head -1 | cut -c1-100")
run("sshd -T 2>/dev/null | grep -iE 'pubkeyauth|permitroot|authentic' | head -20")
run("grep -E 'PubkeyAuthentication|PermitRootLogin|PasswordAuthentication|AuthorizedKeysFile' /etc/ssh/sshd_config 2>/dev/null; echo --- && ls -la /etc/ssh/sshd_config.d/ 2>/dev/null")
c.close()

37
scripts/_enable_pubkey.py Normal file
View File

@@ -0,0 +1,37 @@
import os, sys, paramiko
PW = os.environ.get("REMOTE_PASS", "")
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect("207.57.129.228", port=19717, username="root", password=PW, timeout=15, allow_agent=False, look_for_keys=False)
def run(cmd, allow_fail=False):
print(f"$ {cmd}")
si, so, se = c.exec_command(cmd, timeout=20)
out = so.read().decode("utf-8", "replace")
err = se.read().decode("utf-8", "replace")
rc = so.channel.recv_exit_status()
if out: print(out, end="")
if err: print("[err]", err, end="", file=sys.stderr)
print(f" -> rc={rc}")
if rc != 0 and not allow_fail:
raise SystemExit(f"failed: {cmd}")
return out, err, rc
# 1) 备份
run("cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%s)")
# 2) 改 PubkeyAuthentication
run("sed -i -E 's/^#?\\s*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config")
# 3) 确认
run("grep -n '^[^#]*PubkeyAuthentication' /etc/ssh/sshd_config")
# 4) 语法检查
run("sshd -t && echo 'sshd config OK'")
# 5) 重启(用 service 或 systemctl,Ubuntu 24 用 systemd)
# 先试 systemctl,失败回退 service
out, _, _ = run("systemctl is-active ssh 2>/dev/null || systemctl is-active sshd 2>/dev/null || echo NONE", allow_fail=True)
if "active" in out:
run("systemctl restart ssh || systemctl restart sshd")
else:
run("service ssh restart || service sshd restart")
# 6) 再确认 sshd 配置生效
run("sshd -T 2>/dev/null | grep -i pubkeyauth")
c.close()
print("DONE")

54
scripts/_push_key.py Normal file
View File

@@ -0,0 +1,54 @@
import os, sys, paramiko
HOST = "207.57.129.228"
PORT = 19717
USER = "root"
PW = os.environ.get("REMOTE_PASS", "")
PUB = os.path.expanduser("~/.ssh/id_rsa.pub")
if not PW:
print("REMOTE_PASS not set", file=sys.stderr); sys.exit(2)
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
c.connect(HOST, port=PORT, username=USER, password=PW, timeout=15, allow_agent=False, look_for_keys=False)
except Exception as e:
print("CONNECT FAIL:", e, file=sys.stderr); sys.exit(1)
def run(cmd, check=False):
si, so, se = c.exec_command(cmd, timeout=15)
out = so.read().decode("utf-8", "replace")
err = se.read().decode("utf-8", "replace")
if out: print(out, end="")
if err: print("[err]", err, end="", file=sys.stderr)
if check and (so.channel.recv_exit_status() != 0):
raise SystemExit(f"cmd failed: {cmd}")
run("mkdir -p /root/.ssh && chmod 700 /root/.ssh")
run("touch /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys")
pub = open(PUB, encoding="utf-8").read().strip()
marker = "news-deploy-key"
if marker not in pub:
pub = pub + " " + marker
# 用 sftp 写文件(避免 shell 转义)
sftp = c.open_sftp()
ak_path = "/root/.ssh/authorized_keys"
existing = ""
try:
with sftp.open(ak_path, "r") as f:
existing = f.read().decode("utf-8", "replace")
except IOError:
pass
if marker in existing:
print("[ok] public key already present, skip")
else:
with sftp.open(ak_path, "a") as f:
f.write(pub + "\n")
print("[ok] appended public key to", ak_path)
sftp.close()
run("ls -la /root/.ssh/ && echo '---' && wc -l /root/.ssh/authorized_keys")
c.close()
print("DONE")

23
scripts/_run_deploy.py Normal file
View File

@@ -0,0 +1,23 @@
import os, paramiko
HOST, PORT, USER = "207.57.129.228", 19717, "root"
PW = os.environ["REMOTE_PASS"]
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(HOST, port=PORT, username=USER, password=PW, timeout=15, allow_agent=False, look_for_keys=False)
# 1) 推更新后的部署脚本
sftp = c.open_sftp()
sftp.put("D:/selftools/diary-news/scripts/deploy_remote.sh", "/root/deploy_news.sh")
sftp.chmod("/root/deploy_news.sh", 0o755)
sftp.close()
print("[ok] script pushed")
# 2) 杀掉旧进程(若有)
si, so, se = c.exec_command("pkill -f deploy_news.sh 2>/dev/null; sleep 2; echo done")
print(so.read().decode().strip())
# 3) 后台启动,设 SSHD_PORT=19717
si, so, se = c.exec_command("nohup env SSHD_PORT=19717 bash /root/deploy_news.sh > /root/deploy_news.log 2>&1 & echo $!", timeout=10)
pid = so.read().decode().strip()
print(f"[ok] deploy started, PID={pid}")
c.close()

158
scripts/deploy_remote.sh Normal file
View File

@@ -0,0 +1,158 @@
#!/usr/bin/env bash
# 部署脚本:在远程服务器上跑一次
# 用法: bash /root/deploy_news.sh
set -euo pipefail
# === 配置 ===
GITEA_URL="http://124.223.26.33:3000/xiaji/diary-news.git"
APP_DIR="/srv/news"
DOMAIN="" # 留空走 IP
log() { echo "[$(date +'%H:%M:%S')] $*"; }
fail() { echo "[FAIL] $*" >&2; exit 1; }
# === 1. 系统初始化 ===
log "1/8 系统更新 + 基础包"
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get install -y --no-install-recommends curl git ufw fail2ban openssl ca-certificates
# === 2. 创建非 root 用户(后续切过去) ===
if ! id news &>/dev/null; then
log "2/8 创建 news 用户"
adduser --disabled-password --gecos "" news
echo "news ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/news
fi
# === 3. Docker ===
if ! command -v docker &>/dev/null; then
log "3/8 安装 Docker"
curl -fsSL https://get.docker.com -o /tmp/get-docker.sh
sh /tmp/get-docker.sh
usermod -aG docker news
fi
docker --version
# === 4. 防火墙 ===
log "4/8 防火墙"
SSHD_PORT="${SSHD_PORT:-22}" # 自定义 SSH 端口(默认 22)
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow "${SSHD_PORT}/tcp" comment 'ssh'
ufw allow 80/tcp comment 'http'
ufw allow 443/tcp comment 'https'
ufw --force enable
ufw status verbose
# === 5. 拉代码 ===
log "5/8 拉代码到 $APP_DIR"
mkdir -p "$APP_DIR"
if [ ! -d "$APP_DIR/.git" ]; then
sudo -u news git clone "$GITEA_URL" "$APP_DIR"
else
cd "$APP_DIR" && sudo -u news git pull --rebase
fi
chown -R news:news "$APP_DIR"
# === 6. 写 .env(自动生成密码) ===
log "6/8 生成 .env"
if [ ! -f "$APP_DIR/.env" ]; then
cat > "$APP_DIR/.env" <<EOF
TZ=Asia/Hong_Kong
LOG_LEVEL=INFO
POSTGRES_USER=news
POSTGRES_PASSWORD=$(openssl rand -hex 24)
POSTGRES_DB=news
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=$(openssl rand -hex 24)
REDIS_DB=0
JWT_SECRET=$(openssl rand -hex 64)
JWT_ALGORITHM=HS256
ACCESS_TOKEN_TTL_MIN=60
REFRESH_TOKEN_TTL_DAY=14
TENCENTCLOUD_SECRET_ID=__FILL_ME__
TENCENTCLOUD_SECRET_KEY=__FILL_ME__
TENCENTCLOUD_REGION=ap-hongkong
TENCENT_TMT_ENDPOINT=tmt.tencentcloudapi.com
TENCENT_TMT_QUOTA_MONTH=5000000
TENCENT_TMT_QUOTA_BUFFER=0.05
TENCENT_TMT_MAX_CHARS_PER_REQ=4500
LOCAL_TRANSLATE_ENABLED=false
LOCAL_TRANSLATE_MODEL=nllb-200-distilled-600M
LOCAL_TRANSLATE_DEVICE=cpu
FETCH_GLOBAL_QPS=4
FETCH_TIMEOUT=20
FETCH_FAIL_PAUSE_THRESHOLD=3
FETCH_MAX_RETRIES=2
DOMAIN=$DOMAIN
ACME_EMAIL=
EOF
chown news:news "$APP_DIR/.env"
chmod 600 "$APP_DIR/.env"
log "已生成 .env(随机密码已写入,TENCENTCLOUD_SECRET 仍待填)"
fi
# === 7. 启动 docker compose ===
log "7/8 docker compose up"
cd "$APP_DIR"
# news 用户需要 docker 组权限,用 sg 临时切换(写成函数避免引号问题)
dc() { sg docker -c "docker compose $*"; }
dc up -d --build
# 等健康
log " 等待 postgres / redis healthy..."
for i in {1..30}; do
if dc exec -T postgres pg_isready -U news -d news &>/dev/null; then
break
fi
sleep 2
done
for i in {1..30}; do
if dc exec -T redis redis-cli -a "$(grep ^REDIS_PASSWORD $APP_DIR/.env | cut -d= -f2)" ping 2>/dev/null | grep -q PONG; then
break
fi
sleep 2
done
# === 8. 初始化 ===
log "8/8 数据库迁移 + 用户 + 源"
dc exec -T api alembic upgrade head
# 创建 owner(从 env 或自动生成,避免后台跑卡在 read)
if [ -n "${OWNER_PASS:-}" ]; then
log " 使用环境变量 OWNER_PASS"
else
OWNER_PASS="$(openssl rand -hex 12)"
log " 自动生成 owner 密码(写入 /root/.owner_pass): $OWNER_PASS"
fi
dc exec -T api python -m app.scripts.create_user --username owner --password "$OWNER_PASS" || true
echo "$OWNER_PASS" > /root/.owner_pass
chmod 600 /root/.owner_pass
# 种子
dc exec -T api python -m app.scripts.seed_sources
# 健康检查
log " 健康检查"
sleep 3
curl -s http://localhost/api/v1/healthz && echo
echo
echo "================================================"
echo " 部署完成!"
echo " 访问: http://$(curl -s ifconfig.me)/"
echo " 账号: owner"
echo " 密码: 写入到 /root/.owner_pass (chmod 600)"
echo " 后续: docker compose -f $APP_DIR/docker-compose.yml logs -f"
echo "================================================"

79
scripts/push_ssh_key.py Normal file
View File

@@ -0,0 +1,79 @@
"""推公钥到远程服务器 /root/.ssh/authorized_keys。
用法:python _push_key.py
依赖:paramiko
"""
import os
import sys
import paramiko
HOST = "207.57.129.228"
PORT = 19717
USER = "root"
PASSWORD = os.environ["REMOTE_PASS"] # 由调用方设置
PUB_KEY_PATH = os.path.expanduser("~/.ssh/id_rsa.pub")
def main() -> int:
pub = open(PUB_KEY_PATH, encoding="utf-8").read().strip()
print(f"公钥({PUB_KEY_PATH})前 60 字符: {pub[:60]}...")
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
client.connect(
HOST, port=PORT, username=USER, password=PASSWORD,
timeout=15, allow_agent=False, look_for_keys=False,
)
except Exception as e:
print(f"连接失败: {e}", file=sys.stderr)
return 1
# 1) 检查 .ssh 目录
cmds = [
"mkdir -p /root/.ssh && chmod 700 /root/.ssh",
f"touch /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys",
]
for c in cmds:
_exec(client, c)
# 2) 检查是否已经存在(去重)
stdin, stdout, stderr = client.exec_command("grep -F -c 'news-deploy-key' /root/.ssh/authorized_keys || true")
# 用一个独特注释,后续可识别
marker = "news-deploy-key"
if marker not in pub:
pub_with_marker = f"{pub} {marker}"
else:
pub_with_marker = pub
stdin, stdout, stderr = client.exec_command("cat /root/.ssh/authorized_keys | grep -F '" + marker + "' || true")
existing = stdout.read().decode().strip()
if existing:
print(f"已存在,跳过: {existing[:60]}...")
else:
# 写入(用 heredoc 避免转义)
quoted = pub_with_marker.replace("'", "'\\''")
cmd = f"echo '{quoted}' >> /root/.ssh/authorized_keys"
_exec(client, cmd)
print(f"已追加公钥到 /root/.ssh/authorized_keys")
# 3) 验证权限
_exec(client, "ls -la /root/.ssh/ /root/.ssh/authorized_keys")
# 4) 关闭密码登录(可选项,MVP 保留)
client.close()
return 0
def _exec(client: paramiko.SSHClient, cmd: str) -> None:
print(f"$ {cmd}")
stdin, stdout, stderr = client.exec_command(cmd, timeout=15)
out = stdout.read().decode().strip()
err = stderr.read().decode().strip()
if out:
print(out)
if err:
print(f"[stderr] {err}", file=sys.stderr)
if __name__ == "__main__":
sys.exit(main())