Featured image of post Frps + MCSM最详细搭建指北

Frps + MCSM最详细搭建指北

Tutorial of building Frp+MCSM Server

Frps+MCSM最详细搭建指北

写在开头

这是我长期折腾计算机以来,首次围绕数据安全、网络安全、服务需求及稳定性四大维度,系统性从 0 搭建的 Ubuntu Server(GNU/Linux)服务器。整个过程就是 “边搭建边优化” 的迭代:最初采用 “开放所有必要端口 + HTTP 连接” 的基础方案,意识到安全隐患后,又调整为 “指定个别端口 + Nginx 反向代理 + HTTPS 加密” 的加固架构;存储层面也从单盘转向保障数据冗余的 RAID 1 阵列。

为确保实体机部署顺利,我先在虚拟机环境中完成了 2 次全流程演练,后续又在实体机上进行了 2 次(第二次是为了组RAID)实际安装调试,才最终达成预期效果。此外,考虑到计算机行业 “文档化运维” 的传统 —— 便于后期排障、迭代与复用,因此整理此篇文档,记录整个搭建过程与关键决策。


安装Ubuntu Server

安装时组建RAID1

  1. 在存储配置的页面,选择自定义

  2. 进入到此界面

  3. 清除已有分区

    • 可以看到,此 VPS 上有两块虚拟硬盘,大小均为 20GB,其中“/dev/vda”已经设置了几个分区,而“/dev/vdb”还未使用。

    • 选中“/dev/vda”并按回车,然后进入“Reformat”选项。此时会提示你,此操作会清除所有数据,确认即可。如果你的数据盘也有数据,也按此方式清除掉所有分区。

    • 清除完成后,你会看到此界面。

  4. 在硬盘上创建分区

    • 再次选中“/dev/vda”,选择“Use As Boot Device”并确认。此操作是为了在系统盘上创建引导分区。

    • 创建完成后,剩余空间是没法直接使用的,需要创建分区。选择可用空间,然后进入“Add GPT Partition”创建分区,空间留空(默认使用全部剩余空间),“Format”选择“Leave unformatted”。

    • 然后,在数据盘上也创建一个分区,空间留空,“Format”同样选择“Leave unformatted”。

  5. 创建 RAID 并分区

    • 在两个硬盘上创建好分区以后,回到分区界面,选择“Create software RAID (md)”,然后在弹出的界面中选中刚才在两块硬盘上创建的分区,类型选择 RAID 1,名称默认是“md0”,并确认。

    • 此时你会在“AVAILABLE DEVICES”中看到刚才创建的 RAID 磁盘。选中并进入“Add GPT Partition”创建分区,“Format”选择“ext4”,“Mount”设置为“/”,即根目录。

  6. 完成后,可以看到如下界面

启用ufw(Uncomplicated Firewall)

  1. 启动 ufw 防火墙

    1
    
    sudo ufw enable
    
  2. 查看当前 ufw 状态

    1
    
    sudo ufw status
    

配置RAID 1失败邮件告知脚本

  1. 安装相关软件

    1
    
    sudo apt update && sudo apt install smartmontools msmtp msmtp-mta -y
    
  2. 配置脚本并设置权限

    1
    2
    
    sudo nano /usr/local/bin/raid1_monitor.sh
    sudo chmod +x /usr/local/bin/raid1_monitor.sh
    

    脚本如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    
    #!/bin/bash
    # /usr/local/bin/raid1_monitor.sh
    # mdadm PROGRAM style: $1 = EVENT, $2 = RAID_DEV, $3 = component (may be empty)
    # If run with no args -> TEST mode (send a test email)
    set -eu
    
    # configuration (adjust if needed)
    EMAIL="example@examle.com"
    FROM_ADDR="example-sender@example.com"
    ACCOUNT_NAME="qq"   # msmtp account name (not strictly required if /etc/msmtprc default)
    STATE_DIR="/var/lib/raid_monitor"
    LOCKFILE="/var/run/raid_monitor.lock"
    DEBOUNCE_SEC=10
    DEDUP_SEC=$((15*60))
    PROGRESS_THRESHOLDS=(25 50 75 100)
    PROGRESS_STATE_SUFFIX="_rebuild_progress"
    LOG_PREFIX="RAID 事件"
    
    mkdir -p "$STATE_DIR"
    touch "$STATE_DIR/.dummy" >/dev/null 2>&1 || true
    
    # safe arg handling: if no args -> test mode
    if [ $# -eq 0 ]; then
      echo "No arguments provided. Running in TEST mode: will send a test email."
      EVENT="Test"
      RAID_DEV="${RAID_DEV:-/dev/md0}"
      DRIVE="${DRIVE:-/dev/sda1}"
    else
      EVENT="$1"
      RAID_DEV="${2:-}"
      DRIVE="${3:-}"
    fi
    
    # locking to avoid concurrent runs
    exec 9>"$LOCKFILE"
    if ! flock -n 9; then
      logger -p info "$LOG_PREFIX: another instance is running, exiting"
      exit 0
    fi
    
    log() { logger -p info "$LOG_PREFIX: $*"; }
    
    send_mail() {
      local subject="$1"
      local body="$2"
      {
        echo "From: Zephyr BD <$FROM_ADDR>"
        echo "To: $EMAIL"
        echo "Subject: $subject"
        echo "Content-Type: text/plain; charset=UTF-8"
        echo
        echo -e "$body"
      } | msmtp --from="$FROM_ADDR" -t
    }
    
    # mdadm helpers
    get_failed_count() {
      sudo mdadm --detail "$RAID_DEV" 2>/dev/null | awk -F: '/Failed Devices/ {gsub(/ /,"",$2); print $2; exit}'
    }
    is_degraded() {
      sudo mdadm --detail "$RAID_DEV" 2>/dev/null | grep -qiE 'degraded' && return 0 || return 1
    }
    is_resyncing() {
      grep -q "$(basename "$RAID_DEV")" /proc/mdstat 2>/dev/null && grep -qE 'resync|recovery|rebuild' /proc/mdstat 2>/dev/null && return 0 || return 1
    }
    
    # SMART helper
    check_smart_one() {
      local disk="$1"
      [ -b "$disk" ] || { echo "NOTPRESENT"; return; }
      if sudo smartctl -i "$disk" 2>/dev/null | grep -qi 'SMART support is:.*Unavailable'; then
        echo "NOSMART"; return
      fi
      if sudo smartctl -H "$disk" 2>/dev/null | grep -qi 'PASSED'; then
        echo "PASSED"
      else
        echo "FAILED"
      fi
    }
    
    # dedupe helpers
    state_file="${STATE_DIR}/$(basename "$RAID_DEV")_state"
    last_event_file="${STATE_DIR}/$(basename "$RAID_DEV")_last_event"
    progress_state_file="${STATE_DIR}/$(basename "$RAID_DEV")${PROGRESS_STATE_SUFFIX}"
    now_ts=$(date +%s)
    
    should_send_dedup_ok() {
      local ev="$1"
      if [ -f "$last_event_file" ]; then
        read -r last_ev last_ts < <(awk '{print $1" "$2}' "$last_event_file" 2>/dev/null || true)
        if [ "$last_ev" = "$ev" ]; then
          if [ -n "$last_ts" ] && [ $((now_ts - last_ts)) -lt "$DEDUP_SEC" ]; then
            return 1
          fi
        fi
      fi
      return 0
    }
    record_last_event() {
      local ev="$1"
      printf "%s %s\n" "$ev" "$now_ts" > "$last_event_file"
    }
    
    # If Test mode -> send a sample email and exit
    if [ "$EVENT" = "Test" ]; then
      SUBJECT_FINAL="RAID 监控测试邮件"
      BODY="这是一封来自 RAID 监控脚本的测试邮件。\n主机: $(hostname)\n时间: $(date -u +"%Y-%m-%d %H:%M:%SZ")\n示例事件: Test\n"
      if send_mail "$SUBJECT_FINAL" "$BODY"; then
        log "Test email sent."
        exit 0
      else
        log "Failed to send test email."
        exit 2
      fi
    fi
    
    # handle rebuild progress parsing
    rebuild_progress_percent=""
    if echo "$EVENT" | grep -qE '^Rebuild[0-9]+'; then
      rebuild_progress_percent=$(echo "$EVENT" | sed -E 's/[^0-9]*([0-9]+).*/\1/')
      norm_event="RebuildProgress"
    else
      norm_event="$EVENT"
    fi
    
    SENDMAIL=false
    case "$norm_event" in
      Fail)
        SENDMAIL=true
        ;;
      DegradedArray)
        if should_send_dedup_ok "DegradedArray"; then
          SENDMAIL=true
        else
          log "DegradedArray suppressed by dedupe"
          SENDMAIL=false
        fi
        ;;
      RebuildStarted)
        if should_send_dedup_ok "RebuildStarted"; then
          SENDMAIL=true
        else
          SENDMAIL=false
        fi
        echo "0" > "$progress_state_file" 2>/dev/null || true
        ;;
      RebuildFinished)
        SENDMAIL=true
        echo "100" > "$progress_state_file" 2>/dev/null || true
        ;;
      RebuildProgress)
        # threshold-based progress notifications
        last_notified=0
        if [ -f "$progress_state_file" ]; then
          read -r last_notified < "$progress_state_file" 2>/dev/null || last_notified=0
          last_notified=${last_notified:-0}
        fi
        curr_p="${rebuild_progress_percent:-0}"
        new_notified=$last_notified
        for t in "${PROGRESS_THRESHOLDS[@]}"; do
          if [ "$curr_p" -ge "$t" ] && [ "$t" -gt "$last_notified" ]; then
            new_notified="$t"
          fi
        done
        if [ "$new_notified" -gt "$last_notified" ]; then
          SENDMAIL=true
          progress_to_record="$new_notified"
        else
          SENDMAIL=false
        fi
        ;;
      SpareActive|DeviceDisappeared|NewArray)
        # DeviceDisappeared/NewArray can be transient — debounce
        log "$EVENT on $RAID_DEV detected for $DRIVE — waiting ${DEBOUNCE_SEC}s to confirm"
        sleep "$DEBOUNCE_SEC"
        if is_degraded || [ "$(get_failed_count || echo 0)" -gt 0 ] || is_resyncing; then
          if should_send_dedup_ok "DegradedArray"; then
            SENDMAIL=true
            norm_event="DegradedArray"
          else
            log "After debounce, DegradedArray confirmed but dedupe suppressed mail"
            SENDMAIL=false
          fi
        else
          log "Transient $EVENT on $RAID_DEV resolved; not sending mail"
          SENDMAIL=false
        fi
        ;;
      *)
        SENDMAIL=false
        ;;
    esac
    
    # build message body
    MESSAGE=""
    if [[ "$norm_event" == "Fail" || "$norm_event" == "DegradedArray" || "$norm_event" == "RebuildStarted" || "$norm_event" == "RebuildFinished" ]]; then
      MESSAGE="事件: $EVENT\n阵列: $RAID_DEV\n组件: $DRIVE\n时间: $(date -u +"%Y-%m-%d %H:%M:%SZ")\n"
    fi
    
    if [[ "$norm_event" == "Fail" || "$norm_event" == "DegradedArray" ]]; then
      MESSAGE+="\nSMART 检查结果:\n"
      for disk in /dev/sd[a-z]; do
        res=$(check_smart_one "$disk")
        case "$res" in
          PASSED) MESSAGE+="✅ $disk: SMART PASSED\n";;
          FAILED)
            MESSAGE+="❌ $disk: SMART FAILED (请检查)\n"
            if sudo smartctl -l error "$disk" >/dev/null 2>&1; then
              MESSAGE+="---- 最近 Error Log (前 5 行) ----\n$(sudo smartctl -l error "$disk" 2>/dev/null | sed -n '1,5p')\n"
            fi
            ;;
          NOSMART) MESSAGE+="ℹ️ $disk: 不支持 SMART,已跳过\n";;
          NOTPRESENT) MESSAGE+="ℹ️ $disk: 设备不存在\n";;
        esac
      done
    fi
    
    # progress mail handling
    if [ "$SENDMAIL" = true ] && [ "$norm_event" = "RebuildProgress" ]; then
      SUBJECT_FINAL="RAID 重建进度: ${RAID_DEV} - ${rebuild_progress_percent}%"
      BODY="🔄 阵列 ${RAID_DEV} 重建进度: ${rebuild_progress_percent}%\n组件: ${DRIVE}\n时间: $(date -u +"%Y-%m-%d %H:%M:%SZ")\n\n(已触发阈值通知)"
      if send_mail "$SUBJECT_FINAL" "$BODY"; then
        log "Rebuild progress ${rebuild_progress_percent}% notification sent for ${RAID_DEV}"
        if [ -n "${progress_to_record:-}" ]; then
          echo "$progress_to_record" > "$progress_state_file" 2>/dev/null || true
        else
          echo "$rebuild_progress_percent" > "$progress_state_file" 2>/dev/null || true
        fi
        record_last_event "RebuildProgress_${rebuild_progress_percent}"
      else
        log "Failed to send rebuild progress mail for ${RAID_DEV} ${rebuild_progress_percent}%"
      fi
      exit 0
    fi
    
    # generic send for other events
    if [ "$SENDMAIL" = true ]; then
      SUBJECT_FINAL="服务器 RAID 事件通知: $RAID_DEV - $norm_event"
      BODY="$MESSAGE"
      if send_mail "$SUBJECT_FINAL" "$BODY"; then
        log "邮件已发送: $SUBJECT_FINAL"
        record_last_event "$norm_event"
        if [ "$norm_event" = "RebuildStarted" ]; then
          echo "0" > "$progress_state_file" 2>/dev/null || true
        fi
        if [ "$norm_event" = "RebuildFinished" ]; then
          echo "100" > "$progress_state_file" 2>/dev/null || true
        fi
      else
        log "邮件发送失败: $SUBJECT_FINAL"
      fi
    else
      log "$EVENT detected on $RAID_DEV ($DRIVE). No email (informational or suppressed)."
    fi
    
    exit 0
    
  3. 修改msmtp配置

    1
    2
    
    sudo nano /etc/msmtprc
    sudo chmod 600 /etc/msmtprc
    

    内容如下(QQ邮箱):

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    # /etc/msmtprc
    defaults
    auth           on
    tls            on
    tls_starttls off
    tls_trust_file /etc/ssl/certs/ca-certificates.crt
    logfile        /var/log/msmtp.log
    
    account qq
    host     smtp.qq.com
    port     465
    from     example-sender@example.com
    user     example-sender@example.com
    password example-password
    
    account default : qq
    
  4. 测试脚本

    1
    
    sudo /usr/local/bin/raid1_monitor.sh
    
  5. 编辑 mdadm 配置,添加通知脚本

    1
    2
    
    sudo nano /etc/mdadm/mdadm.conf
    sudo systemctl restart mdmonitor
    

    添加以下内容:

    1
    
    PROGRAM /usr/local/bin/raid1_monitor.sh
    
  6. 最后测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # 假设 RAID 是 /dev/md127,某块盘是 /dev/sdb1
    sudo mdadm --manage /dev/md127 --fail /dev/sdb1
    
    # 移除失败标记
    sudo mdadm --manage /dev/md127 --remove /dev/sdb1
    # 再重新加回去,触发 rebuild
    sudo mdadm --manage /dev/md127 --add /dev/sdb1
    # 查看重建状态
    cat /proc/mdstat
    

配置OpenSSH

公私钥可通过XSheel生成

  1. 上传公钥到服务器

    1
    
    nano ~/.ssh/authorized_keys 
    

    粘贴公钥即可

  2. 打开配置文件

    1
    
    sudo nano /etc/ssh/sshd_config
    

    更改设置如下:

    1
    2
    3
    4
    5
    6
    
    Port 1022
    AddressFamily any
    ListenAddress 0.0.0.0
    PubkeyAuthentication yes
    AuthorizedKeysFile      .ssh/authorized_keys .ssh/authorized_keys2
    PasswordAuthentication no
    

    如果配置没有生效,修改

    1
    
    sudo nano /etc/ssh/sshd_config.d/50-cloud-init.conf
    
  3. 放行1022端口

    1
    
    sudo ufw allow 1022/tcp && sudo ufw reload
    
  4. 重启 SSH 服务

    1
    2
    3
    
    sudo systemctl daemon-reload   # 重新加载 systemd 配置
    sudo systemctl restart ssh.socket  # 重启 SSH socket 单元(负责监听端口)
    sudo systemctl restart sshd       # 重启 SSH 服务
    
  5. 验证是否生效

    1
    
    ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no zephyrbd@192.168.31.159 -p 1022
    

安装实用软件和环境

  1. 安装zsh和neofetch

    1
    
    sudo apt update && sudo apt install zsh neofetch
    

    设置zsh为默认Shell

    1
    
    chsh -s $(which zsh)
    
  2. 安装SMART信息软件(在上面安装过就跳过)

    1
    
    sudo apt install smartmontools -y
    

    确认硬盘设备名并查看 SMART 信息

    1
    2
    
    lsblk
    sudo smartctl -a /dev/sda
    
  3. 安装java

    1
    2
    
    # 安装 OpenJDK 17 和 21
    sudo apt update && sudo apt install openjdk-17-jre openjdk-21-jre -y
    

    交互式切换默认版本

    1
    
    sudo update-alternatives --config java
    

安装NVIDIA GPU驱动

  1. 查看推荐版本

    1
    
    ubuntu-drivers devices
    
  2. 自动安装推荐版本

    1
    
    sudo ubuntu-drivers autoinstall
    
  3. 重启并验证

    1
    2
    
    sudo reboot
    nvidia-smi
    

安装并配置 Frp 服务端(Frps)

  1. 打开 FRP 官方 Releases 页面,找到最新版本(如 v0.52.3),复制对应架构的下载链接(例如 frp_0.52.3_linux_amd64.tar.gz)。

    在服务器上用 wget 下载(替换链接为实际版本):

    1
    
    wget https://github.com/fatedier/frp/releases/download/v0.52.3/frp_0.52.3_linux_amd64.tar.gz
    
  2. 解压压缩包并进入文件夹

    1
    2
    
    tar -zxvf frp_0.52.3_linux_amd64.tar.gz
    cd frp_0.52.3_linux_amd64
    
  3. 查看核心文件

    • frps:服务端程序(我们需要的)
    • frps.toml:服务端配置文件
    • frpc/frpc.toml:客户端程序和配置(暂时不用)
  4. 移动到系统目录

    1
    2
    
    sudo mkdir -p /etc/frp
    sudo cp frps frps.toml /etc/frp/
    
  5. 编辑配置文件

    1
    
    sudo nano /etc/frp/frps.toml 
    

    具体如下:

    1
    2
    3
    4
    5
    6
    7
    8
    
    bindPort = 7000
    auth.method = "token"
    auth.token = "example-token"
    
    webServer.addr = "0.0.0.0"
    webServer.port = 7001
    webServer.user = "admin"
    webServer.password = "example"
    
  6. 创建系统服务

    1
    
    sudo nano /etc/systemd/system/frps.service
    

    服务配置如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    [Unit]
    Description=FRP Server Service
    After=network.target
    
    [Service]
    Type=simple
    ExecStart=/etc/frp/frps -c /etc/frp/frps.toml
    Restart=always
    User=root
    
    [Install]
    WantedBy=multi-user.target
    
  7. 重载系统服务、启动 FRP 服务、设置开机自启

    1
    2
    3
    
    sudo systemctl daemon-reload
    sudo systemctl start frps
    sudo systemctl enable frps
    
  8. 放行7000端口

    1
    
    sudo ufw allow 7000
    

安装并配置MCSM(MCSMANAGER)面板服务

  1. 执行自动安装脚本(只支持 Ubuntu/Centos/Debian/Arch 等主流 x86_64/ARM 架构的操作系统)

    1
    
    sudo su -c "wget -qO- https://script.mcsmanager.com/setup_cn.sh | bash"
    
  2. 修改Web和Daemon的端口(可选)

    1
    2
    
    sudo nano /opt/mcsmanager/web/data/SystemConfig/config.json
    sudo nano /opt/mcsmanager/daemon/data/Config/global.json 
    

    这里我的Web改成了22223,Daemon改成了23334

  3. 放行2556519132端口

    1
    2
    
    sudo ufw allow 25565/tcp
    sudo ufw allow 19132/udp
    

修改时区和设置时间同步

  1. 设置为北京时间

    1
    
    sudo timedatectl set-timezone Asia/Shanghai
    
  2. 验证设置

    1
    
    date
    

    此时应显示类似 Wed Aug 20 12:30:00 CST 2025 的时间(CST 即中国标准时间)。

  3. 使用 chrony同步系统时间

    1
    
    sudo apt install chrony
    
  4. 设置开机启动

    1
    
    sudo systemctl enable chrony.service
    

设置定时关机(Option One)

  1. 编辑 crontab 配置

    1
    
    sudo crontab -e
    
  2. 添加定时关机任务

    1
    
    30 0 * * * /sbin/shutdown -h now
    
    • 含义解释:30 0 * * * 表示每天 0 点 30 分
    • /sbin/shutdown -h now 是立即关机的命令
  3. 验证任务是否添加成功

    1
    
    sudo crontab -l
    

    列出所有定时任务,确认刚才添加的关机任务是否存在。

配置阿里云备份存档(Option Two)

特别感谢tickstep/aliyunpan项目

  1. 安装

    aliyunpan:

    1
    
    sudo curl -fsSL http://file.tickstep.com/apt/pgp | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/tickstep-packages-archive-keyring.gpg > /dev/null && echo "deb [signed-by=/etc/apt/trusted.gpg.d/tickstep-packages-archive-keyring.gpg arch=amd64,arm64] http://file.tickstep.com/apt aliyunpan main" | sudo tee /etc/apt/sources.list.d/tickstep-aliyunpan.list > /dev/null && sudo apt-get update && sudo apt-get install -y aliyunpan
    

    zip包:

    1
    
    sudo apt update && sudo apt install zip
    
  2. 登录

    1
    
    aliyunpan login
    
  3. 配置脚本

    1
    
    nano ~/mc_backup.sh
    
    • 不带清除多余备份和关机版

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      
      #!/bin/bash
      
      SOURCE_DIR="/opt/mcsmanager/daemon/data/InstanceData/bd4fab342c5b4d1aaed443c7f7c1a081"
      TMP_DIR="/tmp/mc_backup"
      PAN_DIR="/Buckups/minecraft_backups"
      
      mkdir -p "$TMP_DIR"
      aliyunpan mkdir -p "$PAN_DIR"
      
      TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
      ZIP_NAME="mc_worlds_$TIMESTAMP.zip"
      
      # 打包
      zip -r "$TMP_DIR/$ZIP_NAME" \
          "$SOURCE_DIR/world" \
          "$SOURCE_DIR/world_nether" \
          "$SOURCE_DIR/world_the_end"
      
      # 上传
      aliyunpan upload "$TMP_DIR/$ZIP_NAME" "$PAN_DIR"
      
      # 删除临时文件
      rm -f "$TMP_DIR/$ZIP_NAME"
      
    • 带清除多余备份和关机版

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      
      #!/bin/bash
      
      SOURCE_DIR="/opt/mcsmanager/daemon/data/InstanceData/bd4fab342c5b4d1aaed443c7f7c1a081"
      PAN_DIR="/Buckups/minecraft_backups"
      
      mkdir -p "$TMP_DIR"
      aliyunpan mkdir -p "$PAN_DIR"
      
      TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
      ZIP_NAME="mc_worlds_$TIMESTAMP.zip"
      
      # 打包上传
      zip -r "$TMP_DIR/$ZIP_NAME" \
          "$SOURCE_DIR/world" \
          "$SOURCE_DIR/world_nether" \
          "$SOURCE_DIR/world_the_end"
      
      aliyunpan upload "$TMP_DIR/$ZIP_NAME" "$PAN_DIR"
      rm -f "$TMP_DIR/$ZIP_NAME"
      
      # 清理云盘1天前的备份
      DATE_THRESHOLD=$(date -d "1 day ago" +"%Y%m%d")
      aliyunpan ls "$PAN_DIR" | tail -n +5 | awk '{print $NF}' | while read file_info; do
          if [[ "$file_info" =~ ^mc_worlds_([0-9]{8})_.*\.zip$ ]]; then
              FILE_DATE="${BASH_REMATCH[1]}"
              if [[ "$FILE_DATE" -lt "$DATE_THRESHOLD" ]]; then
                  aliyunpan rm "$PAN_DIR/$file_info"
              fi
          fi
      done
      
      # 关机
      /sbin/shutdown -h now
      
  4. 赋予执行权限

    1
    2
    
    chmod +x ~/mc_backup_1505.sh
    chmod +x ~/mc_backup_0025.sh
    
  5. 配置定时任务

    1
    
    crontab -e
    

    填入:

    1
    2
    
    5 15 * * * /home/zephyrbd/mc_backup_1505.sh >> /home/zephyrbd/mc_backup.log 2>&1
    25 0  * * * /home/zephyrbd/mc_backup_0025.sh >> /home/zephyrbd/mc_backup.log 2>&1
    
  6. 测试

    1
    
    ~/mc_backup_1505.sh
    

使用Nginx进行Https代理

生成自签名证书(适合个人使用)

1
2
3
4
5
6
7
8
9
# 安装 openssl(若未安装)
sudo apt install openssl -y

# 生成证书和私钥(保存到 /etc/ssl/private/ 目录)
sudo mkdir -p /etc/ssl/private/myserver
sudo openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout /etc/ssl/private/myserver/server.key \
  -out /etc/ssl/private/myserver/server.crt \
  -days 3650  # 有效期 10 年

获取ZeroSSL证书(通过CA机构认证)

  1. 创建DuckDNS自动更新ip脚本

    1
    2
    
    sudo nano /usr/local/bin/duckdns-update.sh
    sudo chmod +x /usr/local/bin/duckdns-update.sh
    

    填入以下内容

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    
    #!/usr/bin/env bash
    set -euo pipefail
    
    # ====== 必填 ======
    TOKEN="example-token"
    # 支持多个子域,用逗号分隔,如 "foo,bar,baz"
    DOMAINS="example"
    
    # ====== 可选:配置 ======
    # 留空表示“让 DuckDNS 自动检测本机公网 IP”
    # 你也可以手动指定:IPV4="1.2.3.4"  IPV6="2001:db8::1"
    IPV4=""
    IPV6=""
    
    # 日志与状态缓存
    STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/duckdns"
    LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/duckdns"
    mkdir -p "$STATE_DIR" "$LOG_DIR"
    LAST_FILE="$STATE_DIR/last.txt"
    LOG_FILE="$LOG_DIR/update.log"
    
    # 重试设置
    MAX_RETRY=3
    SLEEP_SECS=5
    
    timestamp() { date +"%Y-%m-%d %H:%M:%S%z"; }
    
    # 构造请求 URL(DuckDNS: https://www.duckdns.org/update?domains=...&token=...&ip=...&ipv6=...)
    build_url() {
      local base="https://www.duckdns.org/update?domains=${DOMAINS}&token=${TOKEN}"
      # 只有当设置了IP时才带上参数;否则让 DuckDNS 自行判断
      [[ -n "${IPV4}" ]] && base="${base}&ip=${IPV4}"
      [[ -n "${IPV6}" ]] && base="${base}&ipv6=${IPV6}"
      printf '%s' "$base"
    }
    
    # 简单状态去重:如果上次请求URL相同,就不重复调用
    need_update() {
      local url="$1"
      if [[ -f "$LAST_FILE" ]]; then
        local last
        last="$(cat "$LAST_FILE" || true)"
        [[ "$last" == "$url" ]] && return 1
      fi
      return 0
    }
    
    do_update() {
      local url="$1"
      local attempt=1
      while (( attempt <= MAX_RETRY )); do
        # -s 静默,--connect-timeout 防止挂起,-f 失败即非0退出
        if resp="$(curl -s      echo "$(timestamp) domains=${DOMAINS} resp=${resp}" | tee -a "$LOG_FILE"
          # DuckDNS 约定返回 "OK" 或 "KO"
          if [[ "$resp" =~ ^OK ]]; then
            printf '%s' "$url" > "$LAST_FILE"
            return 0
          fi
          # 其他视为失败,进入重试
        fi
        echo "$(timestamp) update failed (attempt ${attempt}/${MAX_RETRY}), retrying in ${SLEEP_SECS}s..." | tee -a "$LOG_FILE"
        sleep "$SLEEP_SECS"
        ((attempt++))
      done
      echo "$(timestamp) update failed after ${MAX_RETRY} attempts." | tee -a "$LOG_FILE"
      return 1
    }
    
    main() {
      # 基于“自动检测公网IP”的默认行为,通常无需自己探测 IP。
      local url
      url="$(build_url)"
    
      if need_update "$url"; then
        do_update "$url"
      else
        echo "$(timestamp) skip: no change in request (likely IP unchanged)" | tee -a "$LOG_FILE"
      fi
    }
    
    main "$@"
    
  2. 创建ZeroSSL证书更新脚本

    1
    2
    
    sudo nano /usr/local/bin/duckdns-cert.sh
    sudo chmod +x /usr/local/bin/duckdns-cert.sh
    

    填入以下内容

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    #!/usr/bin/env bash
    set -euo pipefail
    
    DOMAIN="example-ddns.com"
    ACME_HOME="/home/zephyrbd/.acme.sh"
    export DuckDNS_Token="example-token"
    
    CERT_DIR="/home/zephyrbd/mycerts/$DOMAIN"
    mkdir -p "$CERT_DIR"
    
    # 申请或更新证书(忽略“Domains not changed”退出码)
    "$ACME_HOME"/acme.sh --issue --dns dns_duckdns -d "$DOMAIN" || true
    "$ACME_HOME"/acme.sh --install-cert -d "$DOMAIN" \
      --key-file       "$CERT_DIR/privkey.pem" \
      --fullchain-file "$CERT_DIR/fullchain.pem" \
      --reloadcmd "systemctl reload nginx" || true
    
    # 强制 systemd 返回成功
    exit 0
    
  3. 配置服务和定时器

    1. DuckDNS更新服务和Timer

      1
      2
      
      sudo nano /etc/systemd/system/duckdns.service
      sudo nano /etc/systemd/system/duckdns.timer 
      

      Service

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      
      [Unit]
      Description=DuckDNS IP updater
      After=network-online.target
      Wants=network-online.target
      
      [Service]
      Type=oneshot
      Environment="HOME=/root"
      ExecStart=/usr/local/bin/duckdns-update.sh
      StandardOutput=journal
      StandardError=journal
      

      Timer

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      
      [Unit]
      Description=Run DuckDNS updater every 5 minutes
      
      [Timer]
      OnBootSec=30s
      OnUnitActiveSec=5min
      AccuracySec=30s
      Unit=duckdns.service
      
      [Install]
      WantedBy=timers.target
      
    2. SLL证书更新服务和Timer

      • 安装 acme.sh

        1
        
        curl https://get.acme.sh | sh
        
      • 注册账户

        1
        
        ~/.acme.sh/acme.sh --register-account -m example@examle.com
        
      • 创建服务和定时器

        1
        2
        
        sudo nano /etc/systemd/system/duckdns-cert.service
        sudo nano /etc/systemd/system/duckdns-cert.timer  
        

      Service:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      
      [Unit]
      Description=DuckDNS ZeroSSL Certificate Updater
      After=network-online.target
      Wants=network-online.target
      
      [Service]
      Type=oneshot
      User=root
      Environment="HOME=/home/zephyrbd"
      ExecStart=/usr/local/bin/duckdns-cert.sh
      StandardOutput=journal
      StandardError=journal
      

      Timer:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      
      [Unit]
      Description=Run DuckDNS Certificate Updater Daily
      
      [Timer]
      OnBootSec=2min
      OnUnitActiveSec=1d
      AccuracySec=1h
      Unit=duckdns-cert.service
      
      [Install]
      WantedBy=timers.target
      
    3. 刷新服务

    1
    2
    3
    4
    5
    
    sudo systemctl daemon-reload
    sudo systemctl enable --now duckdns.timer
    sudo systemctl start duckdns.service
    sudo systemctl enable --now duckdns-cert.timer
    sudo systemctl start duckdns-cert.service
    

安装并配置 Nginx 反向代理

  1. 安装 Nginx

    1
    2
    
    sudo apt install nginx -y
    sudo systemctl enable --now nginx  # 启动并设置开机自启启
    
  2. 创建 Nginx 配置文件

    1
    
    sudo nano /etc/nginx/conf.d/https-proxy.conf
    

    填入以下配置(根据实际服务端口修改,包括证书):

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    
    # WebSocket 协议映射
    map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
    }
    
    server {
        listen 4444 ssl;  # HTTPS端口
        server_name _;    # 匹配所有IP访问
    
        # SSL证书
        # ssl_certificate /etc/ssl/private/myserver/server.crt;
        # ssl_certificate_key /etc/ssl/private/myserver/server.key;
        ssl_certificate /home/zephyrbd/mycerts/mczs.duckdns.org/fullchain.pem;
        ssl_certificate_key /home/zephyrbd/mycerts/mczs.duckdns.org/privkey.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;
        ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 1d;
    
        # 拒绝直接用 IP 访问
        if ($host ~* "^[0-9\.]+$") {
            return 403 "禁止直接使用 IP 访问,请使用域名";
        }
    
        # --------------------------
        # 1. FRP 面板代理
        # --------------------------
        location /frp/ {
            proxy_pass http://127.0.0.1:7001/;
            proxy_redirect http://127.0.0.1:7001/ /frp/;
            proxy_redirect / /frp/;
    
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
        }
    
        # --------------------------
        # 2. MCS Web面板代理
        # --------------------------
        location /mcs/ {
            proxy_pass http://127.0.0.1:22223/;
            proxy_redirect http://127.0.0.1:22223/ /mcs/;
    
            proxy_set_header X-Forwarded-Prefix /mcs;
            proxy_set_header Host $host;
            proxy_set_header X-Real-Ip $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
    
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
    
            client_max_body_size 0;
            proxy_request_buffering off;
            proxy_buffering off;
            proxy_cookie_flags ~ secure;
        }
    
        # --------------------------
        # 3. 多服务 API 分流
        # --------------------------
        location /api/ {
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-Proto $scheme;
    
            # 来自 MCS 页面
            if ($http_referer ~* /mcs/) {
                    proxy_pass http://127.0.0.1:22223;
                    break;
            }
    
            # 来自 Other 服务
            if ($http_referer ~* /other/) {
                    proxy_pass http://127.0.0.1:4444;
                    break;
            }
    
            # 其它来源返回 404
            return 404;
        }
    
        # --------------------------
        # 4. MCS 守护进程 WebSocket
        # --------------------------
        location /mcs-ws/ {
            proxy_pass http://127.0.0.1:23334/;
    
            proxy_set_header Host $host;
            proxy_set_header X-Real-Ip $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
    
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
    
            client_max_body_size 0;
            proxy_request_buffering off;
            proxy_buffering off;
        }
    
        # --------------------------
        # 5. 根路径静态页面
        # --------------------------
        location / {
            root /var/www/html;
            index index.html;
        }
    
        # --------------------------
        # 6. 404 页面
        # --------------------------
        error_page 404 /404.html;
        location = /404.html {
            root /var/www/html;
        }
    
    }
    
  3. 放行4444端口

    1
    
    sudo ufw allow 4444/tcp
    

修改mcsm-web的config

  1. "reverseProxyMode"改为true

    1
    
    sudo nano /opt/mcsmanager/web/data/SystemConfig/config.json 
    
  2. 重启mcsm-web服务

    1
    
    sudo systemctl restart mcsm-web.service
    

修改MCS面板中的节点设置(针对本地)

  1. 远程节点 IP 地址:wss://example-ddns.com(即浏览器现连接服务器需要的地址)。

  2. 端口填写Nginx监听端口,这里是4444

  3. 路径前缀填写为MCS守护进程的反代名称,这里是/mcs-ws/

网络安全进阶

fail2ban

  1. 安装

    1
    
    sudo apt update && sudo apt install fail2ban -y
    
  2. 配置sshd封禁

    1
    
    sudo nano /etc/fail2ban/jail.local
    

    输入以下内容:

    1
    2
    3
    4
    5
    6
    7
    
    [sshd]
    enabled = true
    port    = 1022  # 改为你的SSH端口
    filter  = sshd
    logpath = /var/log/auth.log
    maxretry = 5  # 5次失败尝试后封禁
    bantime = 68400  # 封禁24小时(单位:秒)
    
  3. 启动并设置开机自启

    1
    2
    
    sudo systemctl start fail2ban
    sudo systemctl enable fail2ban
    
  4. 配置Frps+MCSM面板封禁

    1.创建过滤规则(filter)

    1
    
    sudo nano /etc/fail2ban/filter.d/nginx-frp-mcsm.conf
    

    2.填入以下内容:

    1
    2
    3
    4
    
    [Definition]
    failregex = ^<HOST> .* "GET /frp/ HTTP/.*" 401 .*$
                ^<HOST> .* "POST /mcs/api/auth/login HTTP/.*" 500 .*$
    ignoreregex =
    
  5. 创建 jail 配置(启用监控)

    1
    
    sudo nano /etc/fail2ban/jail.d/nginx-frp-mcsm.conf
    

    输入以下内容:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    [nginx-frp-mcsm]
    enabled = true
    port = 4444
    filter = nginx-frp-mcsm
    logpath = /var/log/nginx/access.log
    maxretry = 5
    bantime = 86400
    findtime = 600
    action = ufw[actiontype=deny]
    backend = auto
    
  6. 重启 fail2ban 使配置生效

    1
    
    sudo systemctl restart fail2ban
    
  7. 查看规则是否已加载

    1
    2
    
    sudo fail2ban-client status nginx-frp-mcsm
    sudo fail2ban-client status sshd
    
  8. 尝试后解封IP的命令

    1
    
    sudo fail2ban-client set nginx-frp-mcsm unbanip 192.168.XX.XXX
    

Basic Auth

  1. 安装apache2-utils

    1
    
    sudo apt update && sudo apt install apache2-utils -y
    
  2. 新建密码文件

    首次

    1
    
    sudo htpasswd -c -B /etc/nginx/.htpasswd admin
    

    追加

    1
    
    sudo htpasswd -B /etc/nginx/.htpasswd user2
    

    执行后按提示输入密码(输入时不显示,确认密码后回车)

  3. 修改Nginx配置

    添加:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    server {
    
        ···
        # Basic Auth 核心配置(与加密方式无关)
        auth_basic "Admin Area - 请输入用户名密码";  # 登录弹窗提示语
        auth_basic_user_file /etc/nginx/.htpasswd;  # 指向 bcrypt 加密的密码文件
    
        # 站点其他配置
        root /var/www/html;
        index index.html;
        ···
    }
    
  4. 取消MCS Demon和api的密码认证

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    server {
        ···
        location /api/ {
            auth_basic off;# 覆盖父级认证配置
            ···
        }
        location /mcs-ws/ {
            auth_basic off;# 覆盖父级认证配置
            ···
        }
        ···
    }
    
  5. 验证并重启Nginx

    1
    2
    
    sudo nginx -t
    sudo systemctl restart nginx
    
本作品采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可(CC BY-NC-SA 4.0)
文章浏览量:Loading
Powered By MC ZBD Studio
发表了43篇文章 · 总计69.19k字
载入天数...载入时分秒...
总浏览量Loading | 访客总数Loading

主题 StackJimmy 设计
由ZephyrBD修改