Uncategorized 2025年11月22日 97 min read

如何从0开始配置一个“好用”的服务器?


Fluent Server

下面内容以ubuntu为例,网络环境为NJU校园网,4H8G服务器(PVE虚拟机)。

SSH Client

推荐使用termius或者Xshell,warp广告打得挺多的,但是感觉有些三不像,其他的ssh客户端要么功能不强大,要么颜值不够。

Terminus

XShell

Xshell、Xftp需要协同使用,如过需要还得加上X11做桌面转发。

网络

校园网认证

  1. NJU校园网认证使用以下命令
curl https://p.nju.edu.cn/api/portal/v1/login -X POST -d '{"username":"学号", "password":"密码"}'

参考 Do1e/p-dot-nju-login:使用命令行登录南京大学校园网(p.nju.edu.cn),统一身份验证方式,相关功能已集成入 NJUlogin

科学上网环境

  1. 配置科学上网环境
git clone --branch master --depth 1 https://gh-proxy.com/https://github.com/nelvko/clash-for-linux-install.git \
 && cd clash-for-linux-install \
 && sudo bash install.sh

参考 nelvko/clash-for-linux-install:😼 优雅地使用基于 clash/mihomo 的代理环境

在按照指示设置好代理之后,如果仍然不能连接网络,请将tun模式打开:

clashctl tun on

注意,每一次开启一个会话都需要用clashctl on启动代理,或者直接将如下环境变量写入zshrc:

export http_proxy="http://127.0.0.1:7890"
export https_proxy="http://127.0.0.1:7890"
export all_proxy="socks5h://127.0.0.1:7890"
export no_proxy="localhost,127.0.0.1,::1"

clash脚本的逻辑在/opt/clash/script/clashctl.sh中。 mihomo的配置文件在/opt/clash/runtime.yaml

[!NOTE] Notice 注意这里的install脚本只会把clash相关的命令写在bashrc里面(因为用的bash!)。具体可以tail一下~/.bashrc

source /opt/clash/script/common.sh && source /opt/clash/script/clashctl.sh && watch_proxy

如果喜欢使用zsh,只需要把这一行加到~/.zshrc就行

如果想要一台其他机器也走这一台机器的clash代理,可以在webui中允许LAN。 image.png 也可以通过配置文件来找,因为mihomo加入了systemd进程,可以查找其.service文件

直接使用mihomo

  • 安装mihomo
  • 配置mihomo的systemd和配置文件
    • 记得找个地方记住配置文件的位置,或者mihomo -t,可以检查配置文件的位置

南大校内镜像

  1. NJU CITE Lab 提供的校内镜像 - Do1e
  2. mirror站PyPI 镜像源使用帮助 — NJU Mirror Help 文档

代理管理脚本

分享一个自用的代理管理脚本:

function proxy() {
    # ------------------------------------------
    # 1. 配置区域 (可以在这里添加更多代理和网址)
    # ------------------------------------------
    
    # 代理池数组
    local PROXY_LIST=(
        "http://yama:yama@172.26.41.148:7890"
        "http://114.212.85.120:3128"
        "http://127.0.0.1:7897"
        # 你可以在这里继续添加,例如: "socks5://127.0.0.1:1080"
    )

    # 测试网站池数组 (建议把访问最频繁的放前面)
    local TEST_SITES=(
        "https://www.baidu.com"           # 国内连通性基准
        "https://www.google.com"          # 翻墙连通性基准
        "https://conda.anaconda.org"      # Conda源
        "https://github.com"              # 代码托管
        "https://huggingface.co"    
)

    # ------------------------------------------
    # 2. 逻辑区域
    # ------------------------------------------

    # 颜色定义
    local GREEN="\033[32m"
    local RED="\033[31m"
    local YELLOW="\033[33m"
    local CYAN="\033[36m"
    local RESET="\033[0m"

    local action="$1"
    local manual_url="$2"

    # 帮助信息
    if [ -z "$action" ]; then
        echo "Usage: proxy {set [url] | unset | show | status}"
        echo "  set [url] : 自动轮询代理池设置。如有 url 则强制使用该 url。"
        echo "  unset     : 取消所有代理设置。"
        echo "  show      : 显示当前生效的代理变量。"
        echo "  status    : 【深度体检】测试所有代理对所有网站的连通性。"
        return
    fi

    case "$action" in
        set)
            # 场景1: 用户手动指定
            if [ -n "$manual_url" ]; then
                export http_proxy="$manual_url"
                export https_proxy="$manual_url"
                export HTTP_PROXY="$manual_url"
                export HTTPS_PROXY="$manual_url"
                echo -e "${GREEN}[OK]${RESET} 已强制设置代理为: $manual_url"
                return
            fi

            # 场景2: 自动轮询
            echo "正在自动寻找可用代理..."
            local found=0
            
            for p in "${PROXY_LIST[@]}"; do
                # 默认只测试第一个网站(百度)来决定该代理是否“活着”
                # 这样速度快。如果需要测试能不能翻墙,可以改测 Google
                local test_url="${TEST_SITES[0]}" 
                
                # echo -n "尝试代理 $p ... "
                if curl --connect-timeout 2 -s -I -x "$p" "$test_url" >/dev/null; then
                    export http_proxy="$p"
                    export https_proxy="$p"
                    export HTTP_PROXY="$p"
                    export HTTPS_PROXY="$p"
                    echo -e "${GREEN}[Success]${RESET} 代理可用: $p"
                    found=1
                    break
                fi
            done

            if [ $found -eq 0 ]; then
                echo -e "${RED}[Failed]${RESET} 代理池中所有代理均无法连接测试网站(${TEST_SITES[0]})。"
                echo "请检查网络,或使用 proxy set <url> 手动指定。"
                return 1
            fi
            ;;

        unset)
            unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY
            echo -e "${GREEN}[OK]${RESET} 已取消所有代理设置。"
            ;;

        show)
            echo -e "${CYAN}当前环境变量:${RESET}"
            echo "http_proxy  = ${http_proxy:-[未设置]}"
            echo "https_proxy = ${https_proxy:-[未设置]}"
            echo "HTTP_PROXY  = ${HTTP_PROXY:-[未设置]}"
            echo "HTTPS_PROXY = ${HTTPS_PROXY:-[未设置]}"
            ;;

        status)
            echo -e "${CYAN}正在进行全网路况体检 (超时设置: 2秒)...${RESET}"
            printf "%-40s | %-15s | %-s\n" "代理地址 (Proxy)" "目标网站 (Site)" "状态 (Status)"
            echo "--------------------------------------------------------------------------------"

            # 1. 遍历每一个代理
            for p in "${PROXY_LIST[@]}"; do
                # 2. 遍历每一个网站
                for s in "${TEST_SITES[@]}"; do
                    # 提取域名方便显示
                    local domain=$(echo "$s" | awk -F/ '{print $3}')
                    
                    # 测试连接
                    local code=$(curl -I -s --connect-timeout 2 -x "$p" -o /dev/null -w "%{http_code}" "$s")
                    
                    if [[ "$code" == "200" || "$code" == "301" || "$code" == "302" ]]; then
                        printf "%-40s | %-15s | ${GREEN}通畅 (Code: $code)${RESET}\n" "$p" "$domain"
                    elif [ "$code" == "000" ]; then
                         printf "%-40s | %-15s | ${RED}连接超时/失败${RESET}\n" "$p" "$domain"
                    else
                        printf "%-40s | %-15s | ${YELLOW}异常 (Code: $code)${RESET}\n" "$p" "$domain"
                    fi
                done
                echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
            done
            
            ;;
        
        *)
            echo "错误: 未知命令 '$action'"
            echo "请尝试: proxy set, proxy unset, proxy show, proxy status"
            ;;
    esac
}

Shell

安装oh-my-zsh并配置

安装 zsh:

sudo apt-get install zsh

安装 oh-my-zsh:

sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

配置插件:

  1. zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
  1. zsh-syntax-highlighting
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
  1. 启用插件,在 ~/.zshrc 中修改 plugins 字段:
plugins=(
    # other plugins...
    git
    zsh-autosuggestions
    zsh-syntax-highlighting
    z
)

参考 zsh 安装与配置:9步打造高效命令行 - 知乎

另:可以直接安装主题,自带许多开箱即用的配置。

Oh-my-bash

在只有bash的环境下,也可以安装ob-my-bash进行美化

自定义命令

简化的systemctl运维脚本

可以自定义zsh函数,或者写一个shell脚本,之后在zshrc中加入配置项。 我的做法:

  1. 创建~/.local/bin/sys,写入sys的脚本
  2. 在zshrc中export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:$PATH
  3. 之后就可以使用sys这个命令了 其中sys文件为:
#!/usr/bin/env bash
# ----------------------------------------
# Color helpers
# ----------------------------------------
# 注意:red 输出重定向到了 stderr (>&2),防止被变量捕获
green() { printf "\033[32m%s\033[0m\n" "$1"; }
yellow() { printf "\033[33m%s\033[0m\n" "$1"; }
red() { printf "\033[31m%s\033[0m\n" "$1" >&2; }

# ----------------------------------------
# Fuzzy match service name
# ----------------------------------------
resolve_service() {
    local query="$1"
    
    # 0. 优先检查:直接看 /etc/systemd/system/ 下有没有这个文件
    # 解决了刚创建文件但 systemd 还没载入导致找不到的问题
    if [[ -f "/etc/systemd/system/${query}.service" ]]; then
        echo "$query"
        return 0
    fi

    # 1. 尝试 systemctl 完整匹配 (包括未激活的 units)
    # 使用 list-unit-files 比 list-units 更全
    if systemctl list-unit-files "${query}.service" &>/dev/null; then
        echo "$query"
        return 0
    fi

    # 2. 模糊匹配
    local match
    # grep -l (list) 可能会更宽泛,这里优化了逻辑
    match=$(systemctl list-unit-files --type=service --all --no-pager --no-legend \
        | awk '{print $1}' \
        | grep -i "${query}" \
        | sed 's/\.service$//' \
        | head -n 1)

    if [[ -n "$match" ]]; then
        echo "$match"
        return 0
    fi

    # 3. 失败
    red "No matching service found for: $query"
    return 1
}

# ----------------------------------------
# Commands
# ----------------------------------------
cmd="$1"
shift

# 如果没有参数,显示帮助
if [[ -z "$cmd" ]]; then
    set -- "help"
    cmd="help"
fi

case "$cmd" in
    status)
        # || exit 1 很重要,如果找不到服务,立即停止脚本
        svc=$(resolve_service "$1") || exit 1
        green "[STATUS] $svc"
        sudo systemctl status "$svc"
        ;;

    restart)
        svc=$(resolve_service "$1") || exit 1
        green "[RESTART] $svc"
        sudo systemctl restart "$svc"
        ;;

    reload)
        # 特殊处理:只输入 sys reload 时重载守护进程
        if [[ -z "$1" ]]; then
            yellow "[daemon-reload] Systemd configuration reloaded."
            sudo systemctl daemon-reload
        else
            # 输入 sys reload xxx 时,重载配置并重启服务
            yellow "[daemon-reload]"
            sudo systemctl daemon-reload
            svc=$(resolve_service "$1") || exit 1
            green "[RESTART] $svc"
            sudo systemctl restart "$svc"
        fi
        ;;

    add)
        if [[ -z "$1" ]]; then
            red "Usage: sys add <name>"
            exit 1
        fi
        svc="${1}.service"
        path="/etc/systemd/system/$svc"
        
        if [[ -f "$path" ]]; then
            yellow "Service file already exists: $path"
        else
            sudo touch "$path"
            green "Created: $path"
        fi
        
        sudo ${EDITOR:-vim} "$path"
        yellow "[daemon-reload] Auto reloading after edit..."
        sudo systemctl daemon-reload
        ;;

    edit)
        svc=$(resolve_service "$1") || exit 1
        sudo ${EDITOR:-vim} "/etc/systemd/system/${svc}.service"
        yellow "[daemon-reload] Auto reloading after edit..."
        sudo systemctl daemon-reload
        ;;

    log)
        svc=$(resolve_service "$1") || exit 1
        green "[LOG] $svc"
        sudo journalctl -u "$svc" -f
        ;;

    start|stop|enable|disable)
        svc=$(resolve_service "$1") || exit 1
        green "[$cmd] $svc"
        sudo systemctl "$cmd" "$svc"
        ;;

    where)
        svc=$(resolve_service "$1") || exit 1
        path="/etc/systemd/system/${svc}.service"
        if [[ -f "$path" ]]; then
            green "$path"
        else
            yellow "Not in /etc/systemd/system/, checking system directories..."
            systemctl show -p FragmentPath "$svc" | cut -d= -f2
        fi
        ;;

    kill)
        svc=$(resolve_service "$1") || exit 1
        pid=$(systemctl show -p MainPID "$svc" | cut -d= -f2)

        if [[ "$pid" == "0" || -z "$pid" ]]; then
            red "Service '$svc' has no running MainPID."
            exit 1
        fi

        yellow "Killing process PID=$pid (from service $svc)"
        sudo kill -9 "$pid"
        ;;

    list)
        echo
        green "Enabled services:"
        systemctl list-unit-files --type=service | grep enabled | awk '{print "  ● "$1}'
        echo
        yellow "Disabled services:"
        systemctl list-unit-files --type=service | grep disabled | awk '{print "  ○ "$1}'
        echo
        ;;

    *)
        yellow "Unknown command: $cmd"
        echo "Available commands:"
        echo "  sys status NAME"
        echo "  sys restart NAME"
        echo "  sys reload [NAME]"
        echo "  sys add NAME"
        echo "  sys edit NAME"
        echo "  sys log NAME"
        echo "  sys start / stop / enable / disable NAME"
        echo "  sys where NAME"
        echo "  sys kill NAME"
        echo "  sys list"
        ;;
esac

Docker

参考:Linux系统配置安装docker(以ubuntu为例) - 知乎

1. 更新包索引并安装必要依赖

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg

2. 添加 Docker 官方 GPG 密钥

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

3. 添加 Docker 官方仓库

echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

4. 安装 Docker 引擎

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

5. 配置非 root 用户访问(推荐)

将当前用户加入 docker 用户组以避免每次使用 sudo

sudo usermod -aG docker $USER
newgrp docker

6. 配置国内镜像源

请参考境内 Docker 镜像状态监控

# 创建或编辑 Docker 配置文件
sudo tee /etc/docker/daemon.json <<-'EOF'
{
    "registry-mirrors": [
        "https://registry.docker-cn.com",
        "https://docker.mirrors.ustc.edu.cn",
        "https://hub-mirror.c.163.com",
        "https://mirror.baidubce.com",
        "https://ccr.ccs.tencentyun.com"
    ]
}
EOF

# 重新加载配置并重启服务
sudo systemctl daemon-reload
sudo systemctl restart docker

7. 验证镜像源生效

sudo docker info | grep -A 1 'Registry Mirrors'

8. 检查 Docker 服务状态

sudo systemctl status docker

Frp内网穿透

待补充

Python

  1. python安装
sudo apt update
sudo apt install python3 python3-pip

可以在shell中设置alias,把python3改为python

  1. uv安装
curl -LsSf https://astral.sh/uv/install.sh | sh

uv集成了pyenv的功能,可以自己下载python。创建任意版本的python环境,可以直接指定python版本:

uv venv --python 3.10

Tailscale组网

在机器上安装tailscale

run:

curl -fsSL https://tailscale.com/install.sh | sh

Node.js

# Download and install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash

# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"

# Download and install Node.js:
nvm install 24

# Verify the Node.js version:
node -v # Should print "v24.11.1".

# Verify npm version:
npm -v # Should print "11.6.2".

Remote Develop

Remote SSH

在VsCode和Cursor中,都有此插件。选择远程登录时,会先在服务器上安装vscode-server或者cursor-server。注意,如果安装失败,请检查是不是磁盘空间不够! 本地通过ssh连接和远程服务器进行交互,无需设置代理。

使用cursor进行远程开发的时候,需要为远程开发设置好proxy(如果使用的是内网服务器) 请在cursor setting->General->editor setting->open中,找到对应的标签页search "proxy",设置好代理。代理服务器的配置请参考上面科学上网的章节。

例子,设置proxy为"http://usr:passwd@ip:port"

使用秘钥登录

在cursor/vscode进行选择主机进行remote登录的时候,可以选择编辑ssh的配置文件 (windows环境在C:\Users\username\.ssh\config) 可以配置秘钥:

Host ip
  HostName ip
  Port 22
  User vqa
  IdentityFile C:\Users\yama\.ssh\id_rsa_lab

注意这里的IdentityFile填写的是windows下的私钥,而非远程服务器的公钥

其他服务

dify

直接按照官方仓库的做法即可

n8n

先配置nvm/nodejs环境。 在踩了多次坑之后,我觉得n8n部署的方式是:

  • 创建一个npm工程,在这个工程中安装n8n,并且在环境变量中指明:export N8N_USER_FOLDER="$PROJECT_DIR/n8n-local-data",这样所有的用户数据都会在这里。
  • 在这个工程下,还可以安装python环境;确保n8n环境不会干扰到其他的地方

值得注意的地方:

  • 如果要通过cli来安装社区节点,请在${N8N_USER_FOLDER}/.n8n/nodes下安装
  • 在code节点里面的python是Pyodide,纯沙盒,不能调用本地的python,只能做一些脚本工作......
  • 但是在code节点中的js是可以调用任何包的,只需要在安装n8n的目录下安装即可,参考Enable modules in Code node | n8n Docs 我用到的脚本:
#!/bin/bash

# ==========================================
# 1. 路径与环境初始化
# ==========================================
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$PROJECT_DIR/.venv/bin/activate"

# ==========================================
# 2. N8N 核心配置
# ==========================================
export N8N_USER_FOLDER="$PROJECT_DIR/n8n-local-data"
export N8N_PYTHON_BINARY="$PROJECT_DIR/.venv/bin/python"
export NODE_FUNCTION_ALLOW_BUILTIN=*
export NODE_FUNCTION_ALLOW_EXTERNAL=*

# ==========================================
# 3. 网络与服务配置
# ==========================================
export WEBHOOK_URL="http://172.26.41.148:5678/"
export PORT=5678
export N8N_SECURE_COOKIE=false


# ==========================================
# 4. [新增/关键] 禁用遥测与外部连接
# ==========================================
# 彻底禁用诊断数据,解决 fetch failed
export N8N_DIAGNOSTICS_ENABLED=false 
# 禁用个人使用情况调查
export N8N_PERSONALIZATION_ENABLED=false 
# 禁用版本更新通知
export N8N_VERSION_NOTIFICATIONS_ENABLED=false 

# ==========================================
# 5. 性能与维护配置
# ==========================================
export EXECUTIONS_DATA_PRUNE=true
export EXECUTIONS_DATA_MAX_AGE=72
export N8N_DEFAULT_LOCALE=zh

# ==========================================
# 6. 启动 N8N
# ==========================================
echo "--------------------------------------------"
echo "Starting n8n from local project: $PROJECT_DIR"
echo "Python env active: $(which python)"
echo "--------------------------------------------"

"$PROJECT_DIR/node_modules/.bin/n8n" start

Fluent Editor

准备再开一个章节写Editor的配置

Zed编辑器

在zed编辑器中,加上自定义支持ACP协议的模型:

  "agent_servers": {
    "Kimi CLI": {
      "type": "custom",
      "command": "kimi",
      "args": ["--acp"],
      "env": {}
    }
  }

overleaf

仓库:Github自己搜 文档:Upgrading TeX Live | On-premises | Overleaf docs 需要注意的是,在overleaf中,需要bin\shell进入容器里,然后下载完整的texlive tlmgr install scheme-full

在安装前,先配置好南大镜像源: tlmgr option repository https://mirrors.nju.edu.cn/CTAN/systems/texlive/tlnet

之后,注意:[Upgrading TeX Live | On-premises | Overleaf docs](https://docs.overleaf.com/on-premises/installation/upgrading-tex-live#saving-your-changes save changes

My server overview

Server name IP(NJU or Public) IP(Tailscale) service+port other info
yamalab 172.26.41.148(NJU) 100.122.147.99 gitea:3000 dify:80 n8n:5678 immich:2283 校内主服务器、内有mihomo
overleaf 172.26.4.134(NJU) 100.124.96.20 overleaf:80 newapi:3000 mc:25565 校内服务器
izj6c2pom9q4lr1zpofa88z 47.76.41.45(Hong Kong) 100.103.80.30 无应用 200M带宽,2H 0.5G
hcss-ecs-99ab 115.175.12.31(China mainland) 100.122.89.125 无应用 小带宽2M,2H2G

记不得了旧点到这里去看Google AI Studio

通过iptable进行端口转发,把流量转到校内,其他的就直接用代理 如果忘记之前是怎么配置的,务必要看nginx!!! ssh -N -D 127.0.0.1:1080 -J yama@172.26.41.148 root@47.76.41.45

More to Explore

Discussion