我的VPS服务部署记录
本文记录了在 VPS 服务器上部署各种服务的详细步骤,包括服务器初始化、Docker 环境配置、以及多个自托管服务的部署过程。
我的 VPS 使用的是 AlmaLinux 9 操作系统,所以以下操作都是基于 AlmaLinux 系统。
服务器设置
以下为初次登录 VPS 后的基础配置。AlmaLinux 9 默认开启 SELinux,使用 Nginx 反向代理时需在安装 Nginx 后执行后文中的 setsebool 命令。
设置主机名(可选):
hostnamectl set-hostname vps配置 AlmaLinux 9 源(可选):先清理默认多份 .repo,再创建一份精简源文件(仅 BaseOS + AppStream)。
第一步:清理默认 yum 源文件
mkdir -p /etc/yum.repos.d/bakmv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak/第二步:新建精简 repo 文件
按机房位置选用下面一段,将 REPO_BASE 替换为对应域名后,整段复制执行(默认官方源、美国 AWS、国内阿里云三选一):
- 默认官方源(适合美国等,Fastly CDN 加速):
REPO_BASE=https://repo.almalinux.org - 美国 AWS:
REPO_BASE=https://bos.aws.repo.almalinux.org(丹佛用den替换bos) - 国内阿里云:
REPO_BASE=https://mirrors.aliyun.com
REPO_BASE=https://repo.almalinux.org
cat > /etc/yum.repos.d/almalinux.repo << EOF[baseos]name=AlmaLinux 9 - BaseOSbaseurl=$REPO_BASE/almalinux/9/BaseOS/\$basearch/os/gpgcheck=1enabled=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
[appstream]name=AlmaLinux 9 - AppStreambaseurl=$REPO_BASE/almalinux/9/AppStream/\$basearch/os/gpgcheck=1enabled=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9EOF清理缓存并重建缓存:
dnf clean alldnf makecache更新系统:
dnf update -y安装常用软件:
dnf install git vim jq chrony -y设置时钟同步(AlmaLinux 9 默认使用 chrony):
systemctl enable chronydsystemctl start chronydchronyc makestep设置时区为上海时区:
# 查看当前时区timedatectl
# 设置时区为上海(Asia/Shanghai)timedatectl set-timezone Asia/Shanghai
# 验证时区设置timedatectldate配置 git:
git config --global user.name chensoulgit config --global user.email ichensoul@gmail.com[可选] 使普通用户拥有 root 级 sudo 权限
将用户加入 wheel 组即可获得等同 root 的 sudo 权限(AlmaLinux 9 默认配置为 %wheel ALL=(ALL) ALL):
# 新建用户(若已有用户可只执行 usermod 与 passwd)useradd -m -s /bin/bash chensoulusermod -aG wheel chensoulpasswd chensoul
# 验证:切换为该用户后执行,应看到 (ALL) ALL 且 sudo whoami 输出 rootsu - chensoulsudo -lsudo whoami # 应输出 root之后可用 su - chensoul 或 SSH 登录该用户,建议配置好 SSH 密钥后再考虑禁用 root 密码登录。
[可选] 设置系统 Swap 交换分区
因为 vps 服务器的运行内存很小,所以这里先设置下 Swap 空间为 4G。
sudo fallocate -l 4G /swapfile && \sudo dd if=/dev/zero of=/swapfile bs=1024 count=4194304 && \sudo chmod 600 /swapfile && \sudo mkswap /swapfile && \sudo swapon /swapfile && \echo "/swapfile swap swap defaults 0 0" | sudo tee -a /etc/fstab && \sudo swapon --show && \sudo free -h[可选] 安全加固
在确认已有可用的 SSH 密钥登录或普通用户可 sudo 之前,不要禁用密码登录或 root 登录,以免被锁在机器外。
- SSH 密钥生成与上传(本地执行):
在本机生成密钥对,并将公钥上传到 VPS,以便后续使用密钥登录。
# 生成 Ed25519 密钥对(推荐,比 RSA 更短更安全)ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519_vps
# 提示输入 passphrase 可留空,或设置后每次使用密钥时需输入上传公钥到 VPS(任选一种方式):
# 方式一:ssh-copy-id(需当前能密码登录 VPS)ssh-copy-id -i ~/.ssh/id_ed25519_vps.pub root@your_vps_ip# 若登录用户为普通用户:ssh-copy-id -i ~/.ssh/id_ed25519_vps.pub user@your_vps_ip# 方式二:手动追加(将本机公钥内容追加到 VPS 的 authorized_keys)# 本机查看公钥:cat ~/.ssh/id_ed25519_vps.pub# 在 VPS 上执行:mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo "粘贴公钥内容" >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys上传后务必先测试密钥登录是否成功(新开一个终端窗口,密码登录不要关),再在服务端禁用密码登录:
ssh -i ~/.ssh/id_ed25519_vps root@your_vps_ip# 若使用非默认密钥路径,可在 ~/.ssh/config 中配置 Host 与 IdentityFile- SSH 仅允许密钥登录:确认密钥登录无误后,在服务端用命令修改
/etc/ssh/sshd_config并重启:
# 备份并设置:禁用密码登录、启用公钥登录sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.baksudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/; s/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_configgrep -q '^PasswordAuthentication ' /etc/ssh/sshd_config || echo 'PasswordAuthentication no' | sudo tee -a /etc/ssh/sshd_configgrep -q '^PubkeyAuthentication ' /etc/ssh/sshd_config || echo 'PubkeyAuthentication yes' | sudo tee -a /etc/ssh/sshd_configsudo systemctl restart sshd- 禁用 root 密码登录(仅允许 root 密钥或改用普通用户):确认密钥或普通用户可用后,用命令二选一:
# 仅允许 root 使用密钥登录(推荐)sudo sed -i 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_configgrep -q '^PermitRootLogin ' /etc/ssh/sshd_config || echo 'PermitRootLogin prohibit-password' | sudo tee -a /etc/ssh/sshd_configsudo systemctl restart sshd# 禁止 root 登录,仅用普通用户 + sudosudo sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_configgrep -q '^PermitRootLogin ' /etc/ssh/sshd_config || echo 'PermitRootLogin no' | sudo tee -a /etc/ssh/sshd_configsudo systemctl restart sshd- 安装 fail2ban 防暴力破解:
dnf install epel-release -ydnf install fail2ban fail2ban-firewalld -ysystemctl enable fail2ban --now# 默认会保护 sshd,可查看状态:fail2ban-client status sshd安装 Nginx
AlmaLinux 9 默认仓库不含 Nginx 或版本较旧,需要额外添加 Nginx 官方 yum 源,再用 dnf install nginx 安装(本文采用包管理安装,不从源码编译)。
# 添加 Nginx 官方 yum 源(适用于 RHEL 9 / AlmaLinux 9)cat > /etc/yum.repos.d/nginx.repo << 'EOF'[nginx-stable]name=nginx stable repobaseurl=https://nginx.org/packages/rhel/9/$basearch/gpgcheck=1enabled=1gpgkey=https://nginx.org/keys/nginx_signing.keymodule_hotfixes=trueEOF
# 若系统已装 AlmaLinux 自带的 nginx-core,先卸载以免与官方 nginx 包冲突dnf remove -y nginx-core nginx-filesystem nginx-mod-* 2>/dev/null || true
dnf install nginx -y
systemctl enable nginxsystemctl start nginx打开防火墙端口:
dnf install firewalld -y
firewall-cmd --zone=public --permanent --add-service=httpfirewall-cmd --zone=public --permanent --add-service=httpsfirewall-cmd --reload使用反向代理需要打开 SELinux 网络访问权限:
setsebool -P httpd_can_network_connect on安装并生成证书
域名托管在 CloudFlare,使用 acme.sh 通过 DNS API 自动申请和续期 SSL 证书。参考 acme.sh DNS API 文档。
准备工作:
- 在 CloudFlare 获取 API Token(推荐)或 Global API Key;使用 Token 时建议同时设置 Account ID
- 创建 SSL 证书存储目录
# 邮箱统一使用变量(Git、acme.sh 注册与 CloudFlare 共用)export GIT_EMAIL=ichensoul@gmail.com
# 创建 SSL 证书目录mkdir -p /etc/nginx/ssl
# 安装 acme.shcurl https://get.acme.sh | sh -s email=$GIT_EMAIL
# 使用 API Token(推荐)+ Account IDexport CF_Token="your_api_token"export CF_Account_ID="your_account_id"# 或使用 Global API Key:export CF_Key=xxx(邮箱会使用上面的 GIT_EMAIL)
# 申请证书(支持通配符域名)~/.acme.sh/acme.sh --issue --server letsencrypt --dns dns_cf -d chensoul.cc -d '*.chensoul.cc'
# 复制证书到 Nginx 目录cp ~/.acme.sh/chensoul.cc_ecc/{chensoul.cc.cer,chensoul.cc.key,fullchain.cer,ca.cer} /etc/nginx/ssl/
# 安装证书并设置自动续期~/.acme.sh/acme.sh --installcert -d chensoul.cc -d *.chensoul.cc \ --cert-file /etc/nginx/ssl/chensoul.cc.cer \ --key-file /etc/nginx/ssl/chensoul.cc.key \ --fullchain-file /etc/nginx/ssl/fullchain.cer \ --ca-file /etc/nginx/ssl/ca.cer \ --reloadcmd "nginx -s reload"注意:证书会自动续期,续期命令已添加到 crontab 中。
Docker 安装和配置
一键安装docker和docker compose:
bash <(curl -sSL https://linuxmirrors.cn/docker.sh)启动 Docker 服务:
systemctl enable dockersystemctl start docker
# 验证安装docker --versiondocker ps查看docker配置文件,默认会给我们配置刚才选择的镜像加速地址
cat /etc/docker/daemon.json设置 iptables 允许流量转发:
iptables -P FORWARD ACCEPT参考 Best Practice: Use a Docker network,创建一个自定义的网络:
docker network create vps查看 docker 网络:
docker network lsNETWORK ID NAME DRIVER SCOPE68f4aeaa57bd bridge bridge local6a96b9d8617e vps bridge local4a8679e35f4d host host localba21bef23b04 none null local注意:bridge、host、none 是内部预先创建的网络。
是否需要暴露端口:需要。Nginx 装在宿主机上,通过 proxy_pass http://127.0.0.1:端口 反代到容器,因此各服务的 compose 里要写 ports 映射到宿主机。为减少误暴露,建议只绑定本机:写成 127.0.0.1:3003:3000 而不是 3003:3000,这样外网无法直连该端口;防火墙只放行 80/443 即可,其余端口仅本机访问。
服务部署
Postgres
PostgreSQL 是一个功能强大的开源关系型数据库管理系统,作为其他服务的数据存储后端。
创建 postgres.yaml 文件:
services: postgres: image: postgres:18 restart: always ports: - "127.0.0.1:5435:5432" environment: POSTGRES_DB: postgres POSTGRES_USER: postgres POSTGRES_PASSWORD: vps@027! TZ: Asia/Shanghai healthcheck: test: ["CMD", "pg_isready", "-U", "postgres", "-d", "postgres"] interval: 5s timeout: 10s retries: 5 networks: - vps
networks: vps: external: true启动服务:
docker compose -f postgres.yaml up -d
# 查看日志docker logs -f postgres
# 验证数据库连接docker exec -it postgres psql -U postgres -d postgresUmami
Umami 是一个简单、快速、注重隐私的网站分析工具,可以作为 Google Analytics 的开源替代品。
- 在 postgres 容器创建 umami 数据库:
docker exec -it postgres psql -U postgresCREATE DATABASE umami OWNER postgres;- 通过 docker compose 安装,创建
umami.yaml:
services: umami: image: ghcr.io/umami-software/umami:postgresql-latest container_name: umami ports: - "127.0.0.1:3003:3000" environment: DATABASE_URL: postgresql://postgres:vps@027!@postgres:5432/umami DATABASE_TYPE: postgresql HASH_SALT: vps@2025 TRACKER_SCRIPT_NAME: random-string.js TZ: "Asia/Shanghai" GENERIC_TIMEZONE: "Asia/Shanghai" networks: - vps restart: always
networks: vps: external: true参考 https://eallion.com/umami/,Umami 的默认跟踪代码是被大多数的广告插件屏蔽的,被屏蔽了你就统计不到访客信息了。如果需要反屏蔽,需要在 docker compose.yml 文件中添加环境变量:TRACKER_SCRIPT_NAME,如:
environment: TRACKER_SCRIPT_NAME: random-string.js然后获取到的跟踪代码的 src 会变成:
srcipt.js => random-string.js启动:
docker compose -f umami.yaml up -ddocker logs -f umami设置自定义域名:
umami.chensoul.cc配置 nginx 配置文件
/etc/nginx/conf.d/umami.conf:
server { listen 80; listen [::]:80; server_name umami.chensoul.cc;
return 301 https://$host$request_uri;}
server { listen 443 ssl; server_name umami.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer; ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on;
location / { proxy_pass http://127.0.0.1:3003;
proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
add_header X-Cache $upstream_cache_status; add_header Cache-Control no-cache; expires 12h; }}- 重新加载 Nginx 配置并访问服务:
nginx -tnginx -s reload访问 https://umami.chensoul.cc/,默认用户名和密码为 admin/umami。登录之后,请立即修改密码,并添加要统计的网站。
Memos
Memos 是一个开源的、自托管的备忘录和知识管理系统,支持 Markdown、标签、搜索等功能。
- 在 postgres 容器创建 memos 数据库:
docker exec -it postgres psql -U postgresCREATE DATABASE memos OWNER postgres;- 通过 docker compose 安装,创建
memos.yaml:
services: memos: image: neosmemo/memos:0.18.2 container_name: memos environment: - MEMOS_DRIVER=postgres - MEMOS_DSN=postgres://postgres:vps@027!@postgres:5432/memos?sslmode=disable volumes: - ~/.memos/:/var/opt/memos ports: - '127.0.0.1:5230:5230' networks: - vps
networks: vps: external: true版本说明:
- 0.21.0 版本:支持 Mermaid、分享功能
- 0.22.0 版本:去掉了分享功能
- 0.24.0 版本:支持 Shortcuts
- 0.24.1 版本:改变了布局
- 启动服务:
docker compose -f memos.yaml up -ddocker logs -f memos- 配置 nginx 配置文件
/etc/nginx/conf.d/memos.conf:
server { listen 80; listen [::]:80; server_name memos.chensoul.cc;
return 301 https://$host$request_uri;}
server { listen 443 ssl; server_name memos.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer; ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on;
location / { proxy_pass http://127.0.0.1:5230; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
}}重新加载 Nginx 配置并访问服务:
nginx -s reload访问 https://memos.chensoul.cc/ ,默认用户名和密码为 admin/memos。登录之后,请立即修改密码。
N8n
N8n 是一个开源的工作流自动化工具,可以通过可视化界面创建自动化工作流,连接各种服务和 API。
- 在 postgres 容器创建 n8n 数据库:
docker exec -it postgres psql -U postgresCREATE DATABASE n8n OWNER postgres;- 通过 docker compose 安装,创建
n8n.yaml:
services: n8n: image: n8nio/n8n container_name: n8n restart: always environment: - NODE_ENV=production - DB_TYPE=postgresdb - DB_POSTGRESDB_HOST=postgres - DB_POSTGRESDB_PORT=5432 - DB_POSTGRESDB_DATABASE=n8n - DB_POSTGRESDB_USER=postgres - DB_POSTGRESDB_PASSWORD=vps@027! - TZ="Asia/Shanghai" - GENERIC_TIMEZONE="Asia/Shanghai" - N8N_DEFAULT_LOCALE=zh-CN
# 是否启用诊断和个性化推荐 - N8N_DIAGNOSTICS_ENABLED=false - N8N_PERSONALIZATION_ENABLED=false # 是否在启动时重新安装缺失的包 - N8N_REINSTALL_MISSING_PACKAGES=true
- WEBHOOK_URL=https://n8n.chensoul.cc/ - N8N_HOST=n8n.chensoul.cc - N8N_ENCRYPTION_KEY=2cc5b5f9e31b43ff3817c1d04e5fa735 ports: - '127.0.0.1:5678:5678' networks: - vps
networks: vps: external: true- 启动服务:
docker compose -f n8n.yaml up -ddocker logs -f n8n- 配置 nginx 配置文件
/etc/nginx/conf.d/n8n.conf:
server { listen 80; listen [::]:80; server_name n8n.chensoul.cc;
return 301 https://$host$request_uri;}
server { listen 443 ssl; server_name n8n.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer; ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on;
location / { proxy_pass http://127.0.0.1:5678; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1; proxy_set_header Connection 'upgrade'; proxy_set_header Upgrade $http_upgrade; proxy_set_header Origin 'https://n8n.chensoul.cc'; access_log /var/log/nginx/n8n.log combined buffer=128k flush=5s; }}故障排查:如果出现
Origin header does NOT match the expected origin. (Origin: "n8n.chensoul.cc", Expected: "127.0.0.1:5678")错误,需要在 nginx 配置里添加proxy_set_header Origin 'https://n8n.chensoul.cc';
- 重新加载 Nginx 配置并访问服务:
nginx -tnginx -s reload- 创建工作流(Workflows):
参考这篇文章 http://stiles.cc/archives/237/ ,目前我配置了以下 workflows,实现了 github、douban、rss、memos 同步到 Telegram。
workflows 参考:
Linkding
一款自托管的书签管理器,设计简洁、快速且易于设置。
创建数据库
docker exec -it postgres psql -U postgresCREATE DATABASE linkding OWNER postgres;创建 docker-compose 文件 linkding.yaml
services: linkding: #image: woohoodai/linkding-cn:latest image: sissbruecker/linkding container_name: linkding restart: unless-stopped environment: - LD_SUPERUSER_NAME=admin - LD_SUPERUSER_PASSWORD=E2KWxxxx - LD_DB_ENGINE=postgres - LD_DB_DATABASE=linkding - LD_DB_HOST=postgres - LD_DB_USER=postgres - LD_DB_PASSWORD=vps@027! ports: - "127.0.0.1:9090:9090" networks: - vps
networks: vps: external: true启动服务:
docker compose -f linkding.yaml up -ddocker logs -f linkding配置 nginx 配置文件 /etc/nginx/conf.d/linkding.conf:
server { listen 80; listen [::]:80; server_name linkding.chensoul.cc;
return 301 https://$host$request_uri;}
server { listen 443 ssl; server_name linkding.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer; ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on;
location / { proxy_pass http://127.0.0.1:9090; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1; proxy_set_header Connection 'upgrade'; proxy_set_header Upgrade $http_upgrade;
access_log /var/log/nginx/linkding.log combined buffer=128k flush=5s; }}重新加载 Nginx 配置并访问服务:
nginx -tnginx -s reload登录之后进行设置 Auto Tagging:
youtube.com youtube videogithub.com githubbilibili.com bilibili video完整的备份:
docker exec -it linkding python manage.py full_backup /etc/linkding/data/backup.zipdocker cp linkding:/etc/linkding/data/backup.zip backup.zip使用:
- 查询上周书签列表,返回 markdown 地址
start=$(date -v-1w -v-mon +%Y-%m-%dT00:00:00Z)end=$(date -v-mon +%Y-%m-%dT00:00:00Z)
curl -sS -H "Authorization: Token ${LINKDING_TOKEN}" \ "https://linkding.chensoul.cc/api/bookmarks/?limit=100" \ | jq -r --arg start "$start" --arg end "$end" ' .results[]? | select(.date_added >= $start and .date_added < $end) | "- [" + (.title // "无标题") + "](" + .url + ")" 'Bark
Bark 是一个 iOS 应用,允许你向 iPhone 推送自定义通知。通过自建服务器,可以实现消息推送的完全控制。
项目地址:https://github.com/Finb/Bark
- 服务端使用 Docker 安装:
docker run -dt --name bark -p 127.0.0.1:3020:8080 -v ~/.bark/data://data finab/bark-server- 配置 nginx 配置文件
/etc/nginx/conf.d/bark.conf:
server { listen 80; listen [::]:80; server_name bark.chensoul.cc;
return 301 https://$host$request_uri;}
server { listen 443 ssl; server_name bark.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer; ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on;
location / { proxy_pass http://127.0.0.1:3020;
proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }}- 重新加载 Nginx 配置:
nginx -tnginx -s reload在 iOS 上安装 Bark 应用,添加私有服务器:`http://bark.chensoul.cc
测试推送功能:
# 替换 <device_key> 为你的设备密钥curl -X "POST" "https://bark.chensoul.cc/<device_key>" \ -H 'Content-Type: application/json; charset=utf-8' \ -d '{ "body": "这是一个测试", "title": "Test Title", "badge": 1, "category": "myNotificationCategory", "icon": "https://www.usememos.com/logo-rounded.png", "group": "test"}'Uptime Kuma
Uptime Kuma 是一个易于使用的自托管监控工具,可以监控网站和服务的可用性。
项目地址:https://github.com/louislam/uptime-kuma
使用 docker compose 部署,创建 uptime.yaml:
services: uptime-kuma: image: louislam/uptime-kuma:1 container_name: uptime-kuma volumes: - ~/.uptime-kuma://app/data ports: - "127.0.0.1:3001:3001" restart: always networks: - vps
networks: vps: external: true启动服务:
docker compose -f uptime.yaml up -ddocker logs -f uptime-kuma配置 nginx 配置文件 /etc/nginx/conf.d/uptime.conf:
server { listen 80; listen [::]:80; server_name uptime.chensoul.cc;
return 301 https://$host$request_uri;}
server { listen 443 ssl; server_name uptime.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer; ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on;
location / { proxy_pass http://127.0.0.1:3001; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }}重新加载 Nginx 配置并访问服务:
nginx -tnginx -s reload访问 https://uptime.chensoul.cc/,首次访问需要设置管理员账号。
升级服务:
docker compose -f uptime.yaml downdocker pull louislam/uptime-kuma:1docker compose -f uptime.yaml up -dArtalk
在 postgres 容器创建 artalk 数据库:
docker exec -it postgres psql -U postgresCREATE DATABASE artalk OWNER postgres;通过 docker compose 安装,创建 artalk.yaml:
services: artalk: container_name: artalk image: artalk/artalk-go restart: unless-stopped ports: - "127.0.0.1:23366:23366" environment: - TZ=Asia/Shanghai - ATK_LOCALE=zh-CN - ATK_SITE_DEFAULT=Chensoul Blog - ATK_SITE_URL=https://blog.chensoul.cc - ATK_TRUSTED_DOMAINS=https://blog.chensoul.cc http://localhost:1313 - ATK_DB_TYPE=pgsql - ATK_DB_HOST=postgres - ATK_DB_PORT=5432 - ATK_DB_NAME=artalk - ATK_DB_USER=postgres - ATK_DB_PASSWORD=vps@027! - ATK_ADMIN_NOTIFY_BARK_ENABLED=true - ATK_ADMIN_NOTIFY_BARK_SERVER=https://bark.chensoul.cc/AXWM5ZKnKa9Lp3rijBsVDm - ATK_ADMIN_NOTIFY_TELEGRAM_ENABLED=true - ATK_ADMIN_NOTIFY_TELEGRAM_API_TOKEN=8286091453:AAE2pucQXfliz_QGac1zVZEFIrrxVOLe938 - ATK_ADMIN_NOTIFY_TELEGRAM_RECEIVERS=[-1001632154815] networks: - vps
networks: vps: external: true启动服务:
docker compose -f artalk.yaml up -ddocker logs -f artalk配置 nginx 配置文件 /etc/nginx/conf.d/artalk.conf:
server { listen 80; listen [::]:80; server_name artalk.chensoul.cc;
return 301 https://$host$request_uri;}
server { listen 443 ssl; server_name artalk.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer; ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on;
location / { proxy_pass http://127.0.0.1:23366;
proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }}重新加载 Nginx 配置:
nginx -tnginx -s reload执行命令创建管理员账户:
docker exec -it artalk artalk admin浏览器输入 http://artalk.chensoul.cc 进入 Artalk 后台登录界面。
服务升级
使用 Watchtower 自动更新 Docker 镜像与容器。Watchtower 会定期检查容器镜像更新,并自动重启容器使用新镜像。
# 每天凌晨3点05分更新docker run -d \ --name watchtower \ --restart always \ -e TZ=Asia/Shanghai \ -v /var/run/docker.sock:/var/run/docker.sock \ containrrr/watchtower \ --cleanup \ -s "0 5 3 * * *"注意:Watchtower 会自动更新所有容器。如果只想更新特定容器,可以使用标签过滤功能。
数据库备份和恢复
定期备份数据库是保障数据安全的重要措施。以下脚本实现了自动备份 PostgreSQL 数据库并推送到 Git 仓库。
PostgreSQL 数据库备份
备份脚本:/opt/vps-backup/scripts/backup_database.sh
#!/bin/bashset -e
CONTAINER_NAME="postgres"BACKUP_BASE="/opt/vps-backup"BACKUP_DIR="$BACKUP_BASE/postgres"BACKUP_POSTFIX=$(date +%Y%m%d%H) # 如 2026030316# n8n 数据量太大了,只需要部署 workflow 文件和凭证即可DATABASES=(memos umami linkding artalk)
mkdir -p "$BACKUP_DIR"
for DB_NAME in "${DATABASES[@]}"; do BACKUP_FILE="${DB_NAME}_${BACKUP_POSTFIX}.sql" docker exec "$CONTAINER_NAME" pg_dump -U postgres "$DB_NAME" > "$BACKUP_DIR/$BACKUP_FILE" echo "DB $DB_NAME backup success: $BACKUP_FILE"done
# 删除超过1天的备份文件find "$BACKUP_DIR" -name "*.sql" -type f -mtime +1 -delete
cd "$BACKUP_BASE"git pullgit add .git commit -m "backup postgres for $BACKUP_POSTFIX"git push origin main查询某库内各表记录数
在容器内用 psql 查指定库中每个表的行数(以下为估算值,来自 pg_stat_user_tables,大表足够参考):
CONTAINER_NAME="postgres"DB_NAME="n8n" # 改成要查的库名
docker exec "$CONTAINER_NAME" psql -U postgres -d "$DB_NAME" -c "SELECT schemaname AS schema, relname AS table_name, n_live_tup AS row_estimateFROM pg_stat_user_tablesORDER BY n_live_tup DESC;"若要精确行数(大表较慢),可先生成再执行:
docker exec "$CONTAINER_NAME" psql -U postgres -d "$DB_NAME" -t -A -c "SELECT 'SELECT count(*) AS \"' || relname || '\" FROM ' || quote_ident(schemaname) || '.' || quote_ident(relname) || ';'FROM pg_stat_user_tables;"把输出在 psql -d "$DB_NAME" 里执行即可。
清理表数据
在确认无需保留数据的前提下,在目标库中执行:
- 清空单张表(保留表结构,重置自增等): docker exec "$CONTAINER_NAME" psql -U postgres -d "$DB_NAME" -c "TRUNCATE TABLE 表名;"
- 清空单表并连同依赖该表外键的表一起清空: docker exec "$CONTAINER_NAME" psql -U postgres -d "$DB_NAME" -c "TRUNCATE TABLE 表名 CASCADE;"
- 清空当前库下 public schema 中所有表(慎用): docker exec "$CONTAINER_NAME" psql -U postgres -d "$DB_NAME" -c "DO \$\$DECLARE r RECORD;BEGINFOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public')LOOPEXECUTE 'TRUNCATE TABLE ' || quote_ident(r.tablename) || ' CASCADE;';END LOOP;END \$\$;"
执行前建议先备份或确认库名、表名无误。
数据库恢复
按备份时间戳恢复(备份目录 postgres/,文件命名:<库名>_<YYYYMMDDHH>.sql)。将 BACKUP_POSTFIX 改为要恢复的那次备份时间(如 2026030316),在备份仓库根或指定目录下执行。
若目标库已存在:会先禁止新连接、断开已有连接、删库、建空库再导入,当前库数据会被覆盖,请确认后再执行。若仍报 database "xxx" is being accessed by other users,请先停止使用该库的容器(如 docker stop memos)再执行恢复。
CONTAINER_NAME="postgres"BACKUP_DIR="/opt/vps-backup/postgres"BACKUP_POSTFIX="2026030316" # 改为实际备份时间DATABASES=(memos umami n8n linkding artalk)
for DB_NAME in "${DATABASES[@]}"; do F="${DB_NAME}_${BACKUP_POSTFIX}.sql" [ -f "$BACKUP_DIR/$F" ] || { echo "跳过(文件不存在): $F"; continue; } docker cp "$BACKUP_DIR/$F" "$CONTAINER_NAME:/tmp/$F" # 禁止新连接并断开已有连接后再删库 docker exec "$CONTAINER_NAME" psql -U postgres -d postgres -c "ALTER DATABASE $DB_NAME CONNECT LIMIT 0;" 2>/dev/null || true docker exec "$CONTAINER_NAME" psql -U postgres -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$DB_NAME' AND pid <> pg_backend_pid();" 2>/dev/null || true sleep 1 docker exec "$CONTAINER_NAME" psql -U postgres -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME;" docker exec "$CONTAINER_NAME" psql -U postgres -d postgres -c "CREATE DATABASE $DB_NAME OWNER postgres;" docker exec "$CONTAINER_NAME" psql -U postgres -d "$DB_NAME" -f "/tmp/$F" docker exec "$CONTAINER_NAME" rm -f "/tmp/$F" echo "已恢复: $DB_NAME"done只恢复单个库时(同样会先断连接、删库再建库再导入):
BACKUP_POSTFIX="2026030316"DB_NAME=memosBACKUP_DIR="/opt/vps-backup/postgres"
docker cp "$BACKUP_DIR/${DB_NAME}_${BACKUP_POSTFIX}.sql" postgres:/tmp/docker exec postgres psql -U postgres -d postgres -c "ALTER DATABASE $DB_NAME CONNECT LIMIT 0;" 2>/dev/null || truedocker exec postgres psql -U postgres -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$DB_NAME' AND pid <> pg_backend_pid();" 2>/dev/null || truesleep 1docker exec postgres psql -U postgres -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME;"docker exec postgres psql -U postgres -d postgres -c "CREATE DATABASE $DB_NAME OWNER postgres;"docker exec postgres psql -U postgres -d "$DB_NAME" -f /tmp/${DB_NAME}_${BACKUP_POSTFIX}.sqldocker exec postgres rm -f /tmp/${DB_NAME}_${BACKUP_POSTFIX}.sqlN8n 工作流备份和恢复
备份脚本:/opt/vps-backup/scripts/backup_n8n.sh
#!/bin/bashset -e
BACKUP_BASE="/opt/vps-backup"BACKUP_DIR="$BACKUP_BASE/n8n"WORKFLOWS_DIR="$BACKUP_DIR/workflows"mkdir -p "$WORKFLOWS_DIR"
# 导出工作流到容器临时目录,再拷出并按名称重命名docker exec -u node n8n n8n export:workflow --backup --output=./backup_workflowsdocker cp n8n:/home/node/backup_workflows/. "$WORKFLOWS_DIR/"for f in "$WORKFLOWS_DIR"/*; do [ -f "$f" ] || continue name=$(jq -r '.name' "$f" 2>/dev/null) [ -n "$name" ] && [ "$name" != "null" ] && mv "$f" "$WORKFLOWS_DIR/${name}.json"done
# 导出凭证到当日目录docker exec -u node n8n n8n export:credentials --all --output=./credentials.jsondocker cp n8n:/home/node/credentials.json "$BACKUP_DIR/credentials.json"
# 删除超过1天的备份日期目录find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d -mtime +1 -exec rm -rf {} \;
cd "$BACKUP_BASE"git pullgit add .git commit -m "backup n8n"git push origin main恢复工作流:
BACKUP_DIR="/opt/vps-backup/n8n"
docker cp "$BACKUP_DIR/workflows" n8n:/home/node/restore_workflowsdocker exec -u node n8n n8n import:workflow --separate --input=/home/node/restore_workflowsdocker exec -u node n8n rm -rf /home/node/restore_workflows恢复凭证(需与备份时使用同一 N8N_ENCRYPTION_KEY,否则无法解密):
docker cp "$BACKUP_DIR/$DATE/credentials.json" n8n:/home/node/docker exec -u node n8n n8n import:credentials --input=/home/node/credentials.jsondocker exec -u node n8n rm -f /home/node/credentials.json说明:若当前库中已有同 ID 的工作流或凭证,导入时会覆盖。恢复后若 n8n 已在运行,工作流/凭证变更会生效,必要时可重启容器。
定时任务
使用 crontab 设置定时任务,实现自动化运维。可手动 crontab -e 添加,或用下面命令一次性写入(不重复添加):
# 写入 crontab(若已有同类任务会重复,建议先 crontab -l 查看后选用其一方式)( crontab -l 2>/dev/null | grep -v 'acme.sh --cron' | grep -v 'backup_database.sh' | grep -v 'backup_n8n.sh'; \ echo '58 1 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null'; \ echo '0 */3 * * * sh /opt/vps-backup/scripts/backup_database.sh'; \ echo '0 2 * * * sh /opt/vps-backup/scripts/backup_n8n.sh'; \) | crontab -
# 查看当前任务crontab -l说明:SSL 证书续期(每天 1:58)、数据库备份(每 3 小时)、N8n 工作流备份(每天 2:00)。时钟同步由 chronyd 常驻,无需 crontab。
附录
AlmaLinux 9 一键初始化脚本
将下文脚本保存为 almalinux9-setup.sh,按需修改顶部变量或通过环境变量传入,以 root 执行 bash almalinux9-setup.sh。涵盖系统设置、安全加固、Nginx、SSL(acme.sh)、Docker;可选步骤均由变量控制。安全加固(SSH_KEY_ONLY 等)请确认密钥或普通用户可用后再开启;SSL 申请需 CloudFlare 凭证与 DOMAIN,未配置则只安装 acme.sh 与证书目录。
#!/usr/bin/env bash# AlmaLinux 9 一键初始化:系统设置 + Nginx + SSL(acme.sh)+ Docker# 用法:修改变量或环境变量后执行 bash almalinux9-setup.sh(需 root)
set -e
# ---------- 系统设置 ----------HOSTNAME="${HOSTNAME:-vps}"MIRROR="${MIRROR:-}"GIT_USER="${GIT_USER:-chensoul}"GIT_EMAIL="${GIT_EMAIL:-ichensoul@gmail.com}"CREATE_USER="${CREATE_USER:-}"SWAP_GB="${SWAP_GB:-0}"SSH_KEY_ONLY="${SSH_KEY_ONLY:-no}"PERMIT_ROOT="${PERMIT_ROOT:-prohibit-password}"INSTALL_FAIL2BAN="${INSTALL_FAIL2BAN:-no}"# ---------- Nginx / SSL / Docker ----------INSTALL_NGINX="${INSTALL_NGINX:-yes}"INSTALL_SSL="${INSTALL_SSL:-yes}"INSTALL_DOCKER="${INSTALL_DOCKER:-yes}"DOMAIN="${DOMAIN:-}"CF_Token="${CF_Token:-}"CF_Account_ID="${CF_Account_ID:-}"CF_Key="${CF_Key:-}"CF_Email="${CF_Email:-$GIT_EMAIL}" # 不设则与 GIT_EMAIL 一致
[[ $(id -u) -eq 0 ]] || { echo "请使用 root 运行"; exit 1; }
echo "[1/11] 设置主机名 ..."[[ -z "$HOSTNAME" ]] || hostnamectl set-hostname "$HOSTNAME"
echo "[2/11] 配置 dnf 源(清理默认并创建精简 repo) ..."case "$MIRROR" in "") REPO_BASE="https://repo.almalinux.org" ;; aliyun) REPO_BASE="https://mirrors.aliyun.com" ;; aws-bos) REPO_BASE="https://bos.aws.repo.almalinux.org" ;; aws-den) REPO_BASE="https://den.aws.repo.almalinux.org" ;; *) echo "未知 MIRROR: $MIRROR"; exit 1 ;;esacmkdir -p /etc/yum.repos.d/bakmv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak/ 2>/dev/null || truecat > /etc/yum.repos.d/almalinux.repo << REPOEOF[baseos]name=AlmaLinux 9 - BaseOSbaseurl=$REPO_BASE/almalinux/9/BaseOS/\$basearch/os/gpgcheck=1enabled=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
[appstream]name=AlmaLinux 9 - AppStreambaseurl=$REPO_BASE/almalinux/9/AppStream/\$basearch/os/gpgcheck=1enabled=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9REPOEOFdnf clean all && dnf makecache
echo "[3/11] 更新系统并安装常用软件 ..."dnf update -ydnf install -y git vim jq chrony
echo "[4/11] 时钟同步与时区 ..."systemctl enable chronyd && systemctl start chronydchronyc -a makestep || truetimedatectl set-timezone Asia/Shanghai
echo "[5/11] Git 全局配置 ..."git config --global user.name "$GIT_USER"git config --global user.email "$GIT_EMAIL"
echo "[6/11] 可选:创建 sudo 用户 ..."if [[ -n "$CREATE_USER" ]]; then if ! id "$CREATE_USER" &>/dev/null; then useradd -m -s /bin/bash "$CREATE_USER" usermod -aG wheel "$CREATE_USER" echo "请为 $CREATE_USER 设置密码:" passwd "$CREATE_USER" else usermod -aG wheel "$CREATE_USER" echo "用户 $CREATE_USER 已存在,已加入 wheel。" fifi
echo "[7/11] 可选:Swap ..."if [[ "$SWAP_GB" -gt 0 ]] && [[ ! -f /swapfile ]]; then fallocate -l "${SWAP_GB}G" /swapfile chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile echo '/swapfile swap swap defaults 0 0' >> /etc/fstab swapon --showfi
echo "[8/11] 可选:SSH 加固与 fail2ban ..."if [[ "$SSH_KEY_ONLY" == "yes" || "$PERMIT_ROOT" != "yes" ]]; then cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.bak if [[ "$SSH_KEY_ONLY" == "yes" ]]; then sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/; s/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config grep -q '^PasswordAuthentication ' /etc/ssh/sshd_config || echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config grep -q '^PubkeyAuthentication ' /etc/ssh/sshd_config || echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config fi if [[ "$PERMIT_ROOT" != "yes" ]]; then sed -i "s/^#*PermitRootLogin.*/PermitRootLogin $PERMIT_ROOT/" /etc/ssh/sshd_config grep -q '^PermitRootLogin ' /etc/ssh/sshd_config || echo "PermitRootLogin $PERMIT_ROOT" >> /etc/ssh/sshd_config fi systemctl restart sshdfiif [[ "$INSTALL_FAIL2BAN" == "yes" ]]; then dnf install -y epel-release dnf install -y fail2ban fail2ban-firewalld systemctl enable fail2ban --nowfi
echo "[9/11] Nginx ..."if [[ "$INSTALL_NGINX" == "yes" ]]; then if [[ ! -f /etc/yum.repos.d/nginx.repo ]]; then cat > /etc/yum.repos.d/nginx.repo << 'NGINXREPO'[nginx-stable]name=nginx stable repobaseurl=https://nginx.org/packages/rhel/9/$basearch/gpgcheck=1enabled=1gpgkey=https://nginx.org/keys/nginx_signing.keymodule_hotfixes=trueNGINXREPO fi dnf remove -y nginx-core nginx-filesystem nginx-mod-* 2>/dev/null || true dnf install -y nginx systemctl enable nginx && systemctl start nginx dnf install -y firewalld 2>/dev/null || true firewall-cmd --zone=public --permanent --add-service=http 2>/dev/null || true firewall-cmd --zone=public --permanent --add-service=https 2>/dev/null || true firewall-cmd --reload 2>/dev/null || true setsebool -P httpd_can_network_connect on 2>/dev/null || true echo "Nginx 安装完成。"fi
echo "[10/11] SSL(acme.sh)..."if [[ "$INSTALL_SSL" == "yes" ]]; then mkdir -p /etc/nginx/ssl if [[ ! -x /root/.acme.sh/acme.sh ]]; then curl -sSL https://get.acme.sh | sh -s "email=$GIT_EMAIL" fi export CF_Token CF_Account_ID CF_Key CF_Email if [[ -n "$DOMAIN" ]] && { [[ -n "$CF_Token" ]] || [[ -n "$CF_Key" ]]; }; then echo "申请证书:$DOMAIN 与 *.$DOMAIN ..." /root/.acme.sh/acme.sh --issue --server letsencrypt --dns dns_cf -d "$DOMAIN" -d "*.$DOMAIN" --ecc cp /root/.acme.sh/"${DOMAIN}"_ecc/{"$DOMAIN".cer,"$DOMAIN".key,fullchain.cer,ca.cer} /etc/nginx/ssl/ /root/.acme.sh/acme.sh --installcert -d "$DOMAIN" -d "*.$DOMAIN" --ecc \ --cert-file /etc/nginx/ssl/"$DOMAIN".cer \ --key-file /etc/nginx/ssl/"$DOMAIN".key \ --fullchain-file /etc/nginx/ssl/fullchain.cer \ --ca-file /etc/nginx/ssl/ca.cer \ --reloadcmd "nginx -s reload" echo "证书已安装并配置自动续期。" else echo "未设置 DOMAIN 或 CloudFlare 凭证,仅完成 acme.sh 与 /etc/nginx/ssl。" fifi
echo "[11/11] Docker ..."if [[ "$INSTALL_DOCKER" == "yes" ]]; then if ! command -v docker &>/dev/null; then curl -fsSL https://get.docker.com | sh fi systemctl enable docker && systemctl start docker iptables -P FORWARD ACCEPT 2>/dev/null || true docker network create vps 2>/dev/null || true echo "Docker 安装完成,已创建网络 vps(若不存在)。"fi
echo "全部完成。"变量说明:
| 变量 | 说明 | 示例 |
|---|---|---|
HOSTNAME | 主机名,空则不设置 | vps |
MIRROR | 软件源:空=默认,aliyun,aws-bos,aws-den | aliyun |
GIT_USER / GIT_EMAIL | Git 全局配置;邮箱同时用于 acme.sh 与 CloudFlare(CF_Email 未设时) | - |
CREATE_USER | 创建并加入 wheel 的用户名,空则不创建 | chensoul |
SWAP_GB | Swap 大小(GB),0 不配置 | 2 |
SSH_KEY_ONLY | yes=仅密钥登录,请先确认密钥可用 | no |
PERMIT_ROOT | prohibit-password / no / yes | prohibit-password |
INSTALL_FAIL2BAN | yes 安装 fail2ban | no |
INSTALL_NGINX | yes 安装 Nginx + 防火墙 + SELinux | yes |
INSTALL_SSL | yes 安装 acme.sh;有 DOMAIN+CF 凭证则申请证书 | yes |
INSTALL_DOCKER | yes 安装 Docker + 网络 vps | yes |
DOMAIN | 主域名(通配符 *.$DOMAIN),空则不申请证书 | chensoul.cc |
CF_Token | CloudFlare API Token(推荐) | - |
CF_Account_ID | CloudFlare Account ID,与 CF_Token 配合使用 | - |
CF_Key | 或使用 Global API Key(邮箱同 GIT_EMAIL,亦可设 CF_Email 覆盖) | - |
示例:
cd /optgit clone git@github.com:chensoul/vps-backup.gitcd vps-backup/scripts
# 仅系统设置 + Nginx + Docker,不申请证书INSTALL_SSL=no bash almalinux9-setup.sh
# 系统设置 + 全部(含证书,需 CloudFlare 凭证)MIRROR=aws-bos CREATE_USER=chensoul DOMAIN=chensoul.cc CF_Token="tEk7EtRfMHTC7kA9b_WwbJG-R4torcVzYhhBim3D" CF_Account_ID="2e1980a0ef43f57b920ea28cdf9d38d1" bash almalinux9-setup.shDocker 使用官方 get.docker.com;如需国内镜像或交互选源,可手动执行 bash <(curl -sSL https://linuxmirrors.cn/docker.sh) 后再运行本脚本(跳过 Docker 安装:INSTALL_DOCKER=no)
服务部署脚本
前置:已将各服务的 postgres.yaml、umami.yaml、memos.yaml、n8n.yaml、linkding.yaml、artalk.yaml 及(可选)uptime.yaml 放在同一目录,且 Nginx 配置已准备好(如放在该目录下 nginx/)。在该目录下执行:
set -eDIR="${DIR:-$(pwd)}" # compose 文件所在目录,默认当前目录cd "$DIR"
docker network create vps 2>/dev/null || truedocker compose -f postgres.yaml up -d
# 等 Postgres 就绪后创建数据库(非交互)sleep 5for db in umami memos n8n linkding artalk; do docker exec postgres psql -U postgres -c "CREATE DATABASE $db OWNER postgres;" 2>/dev/null || truedone
docker compose -f umami.yaml up -ddocker compose -f memos.yaml up -ddocker compose -f n8n.yaml up -ddocker compose -f linkding.yaml up -ddocker compose -f artalk.yaml up -d# 可选:docker compose -f uptime.yaml up -d
[ -d nginx ] && cp nginx/*.conf /etc/nginx/conf.d/ && nginx -t && nginx -s reload定时任务与证书续期、备份等仍按上文「定时任务」一节单独配置即可。