Logo lkw123's Blog
我的 Self-hosting 记录

我的 Self-hosting 记录

September 27, 2024
19 min read
Table of Contents

这篇博文主要用于备份,方便自己在迁移服务时做个参考

前景提要

我的 lkwplus.com 域名最初注册于 Namesilo,后迁移至 Cloudflare,域名解析和 CDN 由 Cloudflare 提供,项目代码托管于 GitHub(Private & Public repo)。

  • VPS:腾讯云 2C2G30M 新加坡 LightHouse 服务器、VKVM 4C8G500M 洛杉矶 9950X 高性能服务器;
    • Containerized;
  • PaaS:Cloudflare (Pages/Workers)、Vercel、fly.io、Zeabur、Netlify、Heroku、Render、Koyeb、Glitch
    • Serverless;
    • Containerized;

云服务器

准备工作

安装 Docker

  1. 卸载旧版本:
for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do sudo apt-get remove $pkg; done
  1. 配置 Docker apt 仓库:
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
 
# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
  1. 安装最新版本 Docker:
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
 
# 验证安装成功
sudo docker run hello-world

配置 Node 环境

  1. 安装 pnpm:
curl -fsSL https://get.pnpm.io/install.sh | sh -
  1. 安装 Node.js:
# LTS 版本
pnpm env use --g lts
 
# Latest 版本:
pnpm env use --g latest
  1. 安装 PM2:
pnpm install -g pm2

Caddy

文档地址,配置文件路径:/etc/caddy/Caddyfile

stable releases 安装方式:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

Caddyfile 配置文件示例:

/etc/caddy/Caddyfile
# The Caddyfile is an easy way to configure your Caddy web server.
#
# Unless the file starts with a global options block, the first
# uncommented line is always the address of your site.
#
# To use your own domain name (with automatic HTTPS), first make
# sure your domain's A/AAAA DNS records are properly pointed to
# this machine's public IP, then replace ":80" below with your
# domain name.
{
	servers {
		metrics
	}
}
 
:2019 {
	metrics /metrics
}
 
:80 {
	# Set this path to your site's directory.
	root * /usr/share/caddy
 
	# Enable the static file server.
	file_server
 
	# Another common task is to set up a reverse proxy:
	# reverse_proxy localhost:8080
 
	# Or serve a PHP site through php-fpm:
	# php_fastcgi localhost:9000
}
 
alist.lkwplus.com {
	reverse_proxy 127.0.0.1:5244
}
# Refer to the Caddy docs for more information:
# https://caddyserver.com/docs/caddyfile

常用命令:

# 启动
systemctl start caddy
caddy start --config /etc/caddy/Caddyfile
# 关闭
systemctl stop caddy
caddy stop
# 重载配置文件
caddy reload --config /etc/caddy/Caddyfile
# 格式化 Caddyfile
caddy fmt --overwrite /etc/caddy/Caddyfile

SABnzbd

Usenet 即新闻组,是一种历史久远的互联网产物,在早期主要用于论坛式的交流讨论,现在已经逐渐转化为文件存档类的服务,如想更多了解这个方向,可以在 r/usenet 等社区论坛上进行探索。

现在 Usenet 的使用体系由三部分构成:

  • Indexer(索引器):负责在 Usenet 站点上搜集并索引内容,存储在索引库中。由于很多文件在上传时会经过加密或混淆处理,没有合适的 Indexer 很难从 Usenet 上获取高质量的资源。互联网上有少数的免费 Indexer 可供选择,但质量参差不齐,往往需要付费订阅多个 Indexer 站点从而获取更高质量的资源、更多的 API Quota;
  • Provider(服务商):提供 Usenet 资源的 Post 和 Download 服务,由于 Usenet 上的资源十分庞大,各大服务商会根据自己的策略选择性地同步和存储部分内容。为了优雅的使用 Usenet 服务,往往需要付费订阅多个 Provider(不同 Backbone 的 Provider 间互相补充)来获取你所需的资源;
  • Downloader(下载器):Downloader 是传入 nzb 文件,将 Usenet 上的资源下载到本地的工具。SABnzbd 是目前使用体验最好的两个 nzb 下载器之一(另一个是 NZBGet)。

可以配合 SonarrRadarrOverseerr 等各类媒体管理工具,从而实现影视资源的自动入库和刮削,这是一个漫长的但有意思的折腾之路。

安装目录 /opt/sabnzbddocker-compose.yml 文件内容如下:

docker-compose.yml
services:
  sabnzbd:
    image: lscr.io/linuxserver/sabnzbd:latest
    container_name: sabnzbd
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - /opt/sabnzbd/config:/config
      - /opt/sabnzbd/downloads:/downloads
      - /opt/sabnzbd/tmp:/incomplete-downloads
    ports:
      - 127.0.0.1:12467:8080
    restart: unless-stopped

Alist

文档地址,安装目录: /opt/alist

# 安装
curl -fsSL "https://alist.nn.ci/v3.sh" | bash -s install
# 更新
curl -fsSL "https://alist.nn.ci/v3.sh" | bash -s update
# 卸载
curl -fsSL "https://alist.nn.ci/v3.sh" | bash -s uninstall

服务状态则通过 systemctl 管理:

# 启动
systemctl start alist
# 关闭
systemctl stop alist
# 状态
systemctl status alist
# 重启
systemctl restart alist

创建管理员密码:

/path/to/alist/alist admin set NEW_PASSWORD

对于个人而言,在配置文件 data/config.json 中修改以下内容:

data/config.json
{
  "site_url": "https://alist.lkwplus.com",
  "token_expires_in": 48,
  "scheme": {
    "address": "127.0.0.1", // 限制本机访问,外网访问通过 Caddy 反向代理
    "http_port": 5244
  }
}

添加如下存储源:

  • 坚果云:协议为 WebDAV;
  • 115 网盘:WebDAV 策略为 302 重定向,使得本地可快速访问,Cookie 则通过 Stream 软件获取自 115 官方微信小程序,有效期可达一年;
  • Cloudflare R2:对象存储,有多个 Bucket 需要添加,统一置于 /cloudflare-r2 顶层目录下;
    • 地区:不可填默认的 auto,需要根据实际情况填入 Bucket 所在区域,如 apac、wnam;
    • 自定义 host:根据个人在 Cloudflare 上为 R2 绑定的域名进行填写,如 https://img.lkwplus.com
  • 本机存储。

Dozzle

文档地址,安装目录 /opt/dozzle

Dozzle 是一个轻量的 Docker 容器日志监视器,可以实时监视 Docker 容器的日志输出,并以可视化的方式呈现,同时还支持 SQL Analytics 等,功能强大,如下图:

Dozzle 界面

首先建立数据目录 /opt/dozzle/data,然后通过 Dozzle 的 generate 命令创建 users.yaml 文件,用于 Web 界面用户鉴权:

docker run amir20/dozzle generate lkw123 --password password --email test@email.net --name "John Doe" > data/users.yml

生成的 users.yml 文件内容中记录了用户信息和经过 hash 后的密码,对于版本在 8.5.1 前的 Dozzle 实例,采用 sha-256 算法进行加密,存在安全隐患,而自 Patched version 8.5.3 起,则采用 bcrypt 算法加密密码。

data/users.yml 形如:

data/users.yml
users:
  lkw123:
    email: test@example.com
    name: lkw123
    password: $2a$11$jZEMe2xxxxx

docker-compose.yml 文件内容如下:

docker-compose.yml
services:
  dozzle:
    image: amir20/dozzle:latest
    container_name: dozzle
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /opt/dozzle/data:/data
    environment:
      - DOZZLE_AUTH_PROVIDER=simple # 开启 auth 功能
      - DOZZLE_NO_ANALYTICS=true # 关闭向 Dozzle 发送匿名统计数据
    ports:
      - 127.0.0.1:18083:8080
    restart: unless-stopped

可以在多台服务器上运行 Dozzle agent,另一台服务器上通过 DOZZLE_REMOTE_AGENT 环境变量指定 Remote Agent 的地址,即可实现多主机的日志展示,详见文档

Open WebUI

文档地址,安装目录 /opt/open-webui

Docker 安装

docker-compose.yml 文件内容如下:

docker-compose.yml
services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    ports:
      - 127.0.0.1:18081:8080
    environment:
      - ENABLE_OLLAMA_API=false
      - OPENAI_API_KEY={your_secret_key}
      - OPENAI_API_BASE_URL={your_openai_api_base_url}
      - DEFAULT_MODELS=gpt-4o
      - ENABLE_MESSAGE_RATING=false
    volumes:
      - /opt/open-webui/data:/app/backend/data
    restart: unless-stopped

pip 安装

采用 uv 管理 Python 虚拟环境,安装方式如下:

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

创建虚拟环境并安装 Open WebUI:

uv venv
source .venv/bin/activate
uv pip install open-webui

创建 ecosystem.config.js 用于 pm2 进行进程管理:

ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'open-webui',
      script: '.venv/bin/open-webui',
      args: 'serve',
      cwd: '/opt/open-webui',
      interpreter: '/bin/bash',
      interpreter_args: '-c "source .venv/bin/activate && exec $0 $@"',
      error_file: './logs/open-webui-error.log',
      out_file: './logs/open-webui-out.log',
      env: {
        NODE_ENV: 'development',
      },
      env_production: {
        NODE_ENV: 'production',
      },
    },
  ],
}

启动 Open WebUI:

pm2 start ecosystem.config.js
 
# 保存状态
pm2 save
# 设置开机自启
pm2 startup

Glance

文档地址,安装目录 /opt/glance

Glance 是一个定制化程度比较高的、用于各类信息源集中展示的 dashboard。

官方给出了一个 Preconfigured page 对应的配置文件,可以先上手按照搭建出来,后面再根据自己的需求逐步修改面板的布局、功能、信息源等。配置文件路径为:/opt/glance/glance.yml

docker-compose.yml 文件内容如下:

docker-compose.yml
services:
  glance:
    image: glanceapp/glance:latest
    container_name: glance
    volumes:
      - /opt/glance/glance.yml:/app/glance.yml
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - 127.0.0.1:13242:8080
    restart: unless-stopped

n8n

n8n 可用于构建自动化工作流,是 IFTTT 的开源替代,有各类软件集成,通过 Web UI 可以很方便的对自动化流程进行编辑与配置。

文档地址,安装目录 /opt/n8ndocker-compose.yml 文件内容如下:

docker-compose.yml
services:
  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=host.docker.internal
      - DB_POSTGRESDB_PORT=5444
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=<PASSWORD>
    ports:
      - 127.0.0.1:15678:5678
    volumes:
      - /opt/n8n/n8n-storage:/home/node/.n8n
    extra_hosts:
      - host.docker.internal:host-gateway

下面介绍的是一个简单的 Grafana + Prometheus + blabla-exporter + cAdvisor 构建的个人服务器监控体系:

Node Exporter

参考官方的 GitHub README,照抄配置文件即可。

Node Exporter 和下面的 Postgres Exporter 均采用 host 模式,直接使用宿主机的网络,从而便携的对相关资源进行监控。

docker-compose.yml 文件内容如下:

docker-compose.yml
services:
  node_exporter:
    image: quay.io/prometheus/node-exporter:latest
    container_name: node_exporter
    command:
      - '--path.rootfs=/host'
    network_mode: host
    pid: host
    restart: unless-stopped
    volumes:
      - '/:/host:ro,rslave'

Postgres Exporter

docker-compose.yml 文件内容如下:

docker-compose.yml
services:
  postgres-exporter:
    image: quay.io/prometheuscommunity/postgres-exporter:latest
    container_name: postgres_exporter
    environment:
      DATA_SOURCE_URI: 'localhost:5444/postgres?sslmode=disable'
      DATA_SOURCE_USER: '{your_username}'
      DATA_SOURCE_PASS: '{your_password}'
    network_mode: host
    restart: unless-stopped

cAdvisor

监控容器资源可以有多种方案,这里采用 cAdvisor,它是一个运行在 Docker 容器内的、提供容器资源监控的工具。GitHub README

由于 Prometheus 实例和 cAdvisor 实例间通信通过 docker network 内部完成,因此可以省略掉 ports 配置。

docker-compose.yml 文件内容如下:

docker-compose.yml
services:
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    privileged: true
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    devices:
      - /dev/kmsg
    command:
      - '--docker_only=true'
      - '--housekeeping_interval=10s'
      - '--disable_metrics=disk,udp,referenced_memory,cpu_topology,resctrl,tcp,advtcp,sched,process,hugetlb,percpu'
    restart: unless-stopped
    networks:
      - monitoring
 
networks:
  monitoring:
    external: true

Beszel

Beszel 是一个轻量级的服务器监控工具,功能强大、前端界面美观,支持 Docker 监控和告警等。暂时选用 Beszel 作为容器资源监控的方案,替换掉了之前的 cAdvisor。

GitHub README,安装目录 /opt/beszeldocker-compose.yml 文件内容如下:

docker-compose.yml
services:
  beszel:
    image: henrygd/beszel:latest
    container_name: beszel
    restart: unless-stopped
    ports:
      - 127.0.0.1:18096:8090
    volumes:
      - /opt/beszel/beszel_data:/beszel_data
    extra_hosts:
      - host.docker.internal:host-gateway

进入页面后会要求设置初始管理员账户,添加需要进行监控的主机的相关 IP、端口信息,Beszel 会自动生成 agent 部署所需的 docker-compose.yml。在放行相关端口的防火墙策略后,便可在目标主机启动 agent 开启监控。

Prometheus

为了使得 Prometheus 实例和 Grafana、cAdvisor 实例间顺畅通信,可以创建一个 docker network,命名为 monitering,并在相关的 docker-compose 配置文件中做好相关的定义,使其加入到这个 network 中。

同时,为了使得 Promethus 可以向 Node Exporter 和 Postgres Exporter 等采集数据,可以配置 extra_hosts 选项,将宿主机的 IP 地址映射到容器内的 host.docker.internal

docker-compose.yml 文件内容如下:

docker-compose.yml
services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    hostname: prometheus
    restart: unless-stopped
    volumes:
      - /opt/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - /opt/prometheus/data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--web.enable-lifecycle'
    ports:
      - 127.0.0.1:19090:9090
    networks:
      - monitoring
    extra_hosts:
      - host.docker.internal:host-gateway
 
networks:
  monitoring:
    external: true

同时,通过 Prometheus 的配置文件 prometheus.yml,定义各类 job,如下:

prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
 
scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ['localhost:9090']
        labels:
          instance: prometheus
  - job_name: node
    static_configs:
      - targets: ['host.docker.internal:9100']
  - job_name: postgres
    static_configs:
      - targets: ['host.docker.internal:9187']
  - job_name: caddy
    static_configs:
      - targets: ['host.docker.internal:2019']
  - job_name: cadvisor
    scrape_interval: 30s
    static_configs:
      - targets: ['cadvisor:8080']

共定义了五个 job,每个 job 后续在 Grafana 对应一个 Dashboard,展示相关数据:

  • prometheus:Prometheus 自身的监控指标,默认对外提供服务于 http://localhost:9090

  • node:Node Exporter 采集的主机资源数据,由于采用 host 模式,可以通过 http://host.docker.internal:9100 访问宿主机的 9100 端口获取数据;

  • postgres:Postgres Exporter 采集的数据库资源数据,由于采用 host 模式,可以通过 http://host.docker.internal:9187 访问宿主机的 9187 端口获取数据;

  • caddy:Caddy 自身提供 Metrics 获取接口以供 Prometheus 采集,需要在 Caddyfile 中进行配置,开启 Metrics 接口:

    /etc/caddy/Caddyfile
    {
      servers {
        metrics
      }
    }
     
    :2019 {
      metrics /metrics
    }
  • cadvisor:cAdvisor 采集的容器资源数据,由于 Prometheus 和 cAdvisor 在同一个 docker network 中,因此通过 http://cadvisor:8080 可以访问到。

VictoriaMetrics

经过对比,由 Prometheus 切换至单节点部署的 VictoriaMetrics,在存储空间、性能、系统资源占用方面都有明显提升。需要部署两个容器:

  • victoria-metrics:用于存储和查询时间序列数据,采用单机模式部署;
  • vmagent:用于从数据源抓取信息,并将其上报到 VictoriaMetrics。

docker-compose.yml 文件内容如下:

docker-compose.yml
services:
  victoriametrics:
    image: victoriametrics/victoria-metrics:latest
    container_name: victoriametrics
    hostname: victoriametrics
    restart: unless-stopped
    volumes:
      - /opt/victoriametrics/data:/victoria-metrics-data
    command:
      - '--storageDataPath=/victoria-metrics-data'
      - '--httpListenAddr=:8428'
      - '--retentionPeriod=1'
    ports:
      - 127.0.0.1:18428:8428
    networks:
      - monitoring
    extra_hosts:
      - host.docker.internal:host-gateway
 
  vmagent:
    image: victoriametrics/vmagent:latest
    container_name: vmagent
    hostname: vmagent
    restart: unless-stopped
    volumes:
      - /opt/victoriametrics/vmagent.yml:/etc/vmagent/vmagent.yml
    command:
      - '--promscrape.config=/etc/vmagent/vmagent.yml'
      - '--remoteWrite.url=http://victoriametrics:8428/api/v1/write'
    networks:
      - monitoring
    depends_on:
      - victoriametrics
    extra_hosts:
      - host.docker.internal:host-gateway
 
networks:
  monitoring:
    external: true

配置文件 vmagent.yml 可与此前的 prometheus.yml 基本兼容,部分不支持的字段会默认跳过不进行严格校验。

下面简要描述为 vmagentVictoriaMetrics 配置 Basic Auth 认证的过程。

  • 修改 docker-compose.yml,在各 service 的 command 部分添加命令行参数:
    • 对于 VictoriaMetrics,添加 --httpAuth.username={user1}--httpAuth.password={pwd1} 用于自身的身份认证;
    • 对于 vmagent,添加 --remoteWrite.basicAuth.username={user1}--remoteWrite.basicAuth.password={pwd1},用于 remote write 推送数据时进行认证;添加 --httpAuth.username={user2}--httpAuth.password={pwd2} 用于自身的身份认证;
  • 修改 vmagent.yml,为 vmagent 从自身拉取 metrics 指标时添加认证:
    /opt/victoriametrics/vmagent.yml
    scrape_configs:
      - job_name: victoriametrics
        basic_auth:
          username: '{user1}'
          password: '{pwd1}'
        static_configs:
          - targets: ['victoriametrics:8428']
      - job_name: vmagent
        basic_auth:
          username: '{user2}'
          password: '{pwd2}'
        static_configs:
          - targets: ['localhost:8429']
  • 在 Grafana 中配置数据源时选择 Prometheus,添加 VictoriaMetrics 实例,并添加 Basic Auth 认证信息。

认证会在三个阶段进行:

  • 访问 vmagent 和 VictoriaMetrics 的 WebUI;
  • vmagent 向 VictoriaMetrics 进行 remote write 推送数据指标;
  • Grafana 对接 VictoriaMetrics 进行数据查询并展示为图表。

Grafana

安装 Grafana 时会默认将数据存储于 sqlite3。考虑到性能和高可用等因素,可以选用 Postgres 等作为数据库,在常规的 Docker Compose 配置文件中加入如下环境变量即可:

docker-compose.yml
- GF_DATABASE_TYPE=postgres
- GF_DATABASE_HOST=host.docker.internal:5444
- GF_DATABASE_NAME=grafana
- GF_DATABASE_USER=grafana
- GF_DATABASE_PASSWORD={your_password}
- GF_DATABASE_SSL_MODE=disable

docker-compose.yml 文件内容如下:

docker-compose.yml
services:
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    environment:
      - GF_SERVER_ROOT_URL=https://grafana.lkwplus.com/
    extra_hosts:
      - host.docker.internal:host-gateway
    ports:
      - 127.0.0.1:19823:3000
    volumes:
      - /opt/grafana/data:/var/lib/grafana
    networks:
      - monitoring
 
networks:
  monitoring:
    external: true

对应 Prometheus 中的五个 job,在 Grafana 中添加如下 Dashboard,用于不同类别的监控信息展示:

Gitea

一个基于 Go 语言开发的轻量级的 Git 服务器,文档地址,安装目录 /opt/giteadocker-compose.yml 文件内容如下:

docker-compose.yml
services:
  server:
    image: gitea/gitea:latest
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=host.docker.internal:5444
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD={your_password}
    restart: unless-stopped
    extra_hosts:
      - host.docker.internal:host-gateway
    volumes:
      - /opt/gitea/data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - 127.0.0.1:13099:3000
      - 222:22

qBittorrent

安装目录 /opt/qbittorrentdocker-compose.yml 文件内容如下:

docker-compose.yml
services:
  qbittorrent:
    image: linuxserver/qbittorrent:latest
    container_name: qbittorrent
    environment:
      - TZ=Asia/Shanghai
      - UMASK_SET=022
      - WEBUI_PORT=8081
    volumes:
      - /opt/qbittorrent/config:/config
      - /opt/qbittorrent/downloads:/downloads
    ports:
      - 65231:65231
      - 65231:65231/udp
      - 127.0.0.1:8081:8081
    restart: unless-stopped

your_spotify

一个很美观的 Spotify 听歌记录展示页面,项目地址,由三部分组成:

  • your_spotify_server:后端服务,负责提供 API 接口;
  • your_spotify_client:前端客户端,负责展示数据;
  • mongodb:6:数据库,负责存储数据。

为了节省资源,我省去了部署 MongoDB 的步骤,使用的是来自外部的免费 MongoDB 实例,通过 MONGO_ENDPOINT 环境变量进行声明。

值得注意的是,在部署完毕后 Web 端和服务端一直无法进行通信导致无法 Auth,最终解决方案是在 Cloudflare 域名解析中将 API_ENDPOINT 的 Proxy Status 设置为 DNS only(即关闭小黄云)。

在管理面板创建 token 后,可以将自己的 Spotify 数据分析面板分享给访客,示例效果

docker-compose.yml 文件内容如下:

docker-compose.yml
services:
  server:
    image: yooooomi/your_spotify_server:latest
    container_name: your_spotify_server
    restart: unless-stopped
    ports:
      - 127.0.0.1:18777:8080
    environment:
      API_ENDPOINT: https://your-spotify.lkwplus.com
      CLIENT_ENDPOINT: https://spotify.lkwplus.com
      TIMEZONE: Asia/Shanghai
      SPOTIFY_PUBLIC: { your_spotify_client_id }
      SPOTIFY_SECRET: { your_spotify_secret }
      MONGO_ENDPOINT: mongodb+srv://lkw123:{password}@cluster0.kopgi.mongodb.net/your_spotify?retryWrites=true&w=majority&appName=Cluster0
      MONGO_NO_ADMIN_RIGHTS: true
      CORS: i-want-a-security-vulnerability-and-want-to-allow-all-origins
 
  web:
    image: yooooomi/your_spotify_client:latest
    container_name: your_spotify_client
    restart: unless-stopped
    ports:
      - 127.0.0.1:13241:3000
    environment:
      API_ENDPOINT: https://your-spotify.lkwplus.com

Opengist

GitHub Gist 的开源替代,项目地址,安装目录 /opt/opengistdocker-compose.yml 文件内容如下:

docker-compose.yml
services:
  opengist:
    image: ghcr.io/thomiceli/opengist:1
    container_name: opengist
    restart: unless-stopped
    ports:
      - 127.0.0.1:16157:6157 # Web
      - 2222:2222 # SSH
    volumes:
      - /opt/opengist/data:/opengist

Hoarder

此前一直使用 Omnivore 作为稍后读工具,存档一些值得阅读学习的文章、项目链接等,而近期该项目宣布停止服务,于是便开始寻找一款合适的开源自部署的 {书签、网页存档、稍后读} 工具。

在搜寻了一些社区的讨论后,通过筛选 selfh.st 中标签为 Bookmarks 的项目,了解到 LinkwardenReadeckLinkding 等活跃的社区项目替代品。

我比较看重经由接入 OpenAI API 自动根据网页内容生成标签的功能,选择 Hoarder 作为新的稍后读工具。安装部署流程详见 官方文档

Redis

安装目录 /opt/redisdocker-compose.yml 文件内容如下:

docker-compose.yml
services:
  redis:
    image: redis:latest
    container_name: redis
    restart: unless-stopped
    ports:
      - 6479:6379
    volumes:
      - /opt/redis/data:/data
      - /opt/redis/redis.conf:/usr/local/etc/redis/redis.conf
      - /opt/redis/logs:/logs
    command: redis-server /usr/local/etc/redis/redis.conf --appendonly yes --requirepass '{your_password}'

WeWeRSS

文档地址,用于将微信公众号转为 RSS Feed,体验还不错,订阅了一些技术类的公众号。

docker-compose.yml 文件内容如下:

docker-compose.yml
services:
  app:
    image: cooderl/wewe-rss-sqlite:latest
    container_name: wewerss
    ports:
      - 127.0.0.1:4005:4000
    environment:
      # 数据库连接地址
      # - DATABASE_URL=file:../data/wewe-rss.db
      - DATABASE_TYPE=sqlite
      # 服务接口请求授权码
      # - AUTH_CODE=0211
      # 提取全文内容模式
      # - FEED_MODE=fulltext
      # 定时更新订阅源Cron表达式
      # - CRON_EXPRESSION=35 5,17 * * *
      # 服务接口请求限制,每分钟请求次数
      # - MAX_REQUEST_PER_MINUTE=60
      # 外网访问时,需设置为服务器的公网 IP 或者域名地址
      - SERVER_ORIGIN_URL=https://wewerss.lkwplus.com
    restart: unless-stopped
    volumes:
      # 映射数据库文件存储位置,容器重启后不丢失
      - /opt/wewerss/data:/app/data

Moments

极简朋友圈风格的个人站点,用来无聊的时候瞎哔哔几句,项目地址,安装目录 /opt/momentsdocker-compose.yml 文件内容如下:

docker-compose.yml
services:
  moments:
    image: kingwrcy/moments:latest
    container_name: moments
    ports:
      - 127.0.0.1:16237:3000
    volumes:
      - /opt/moments/data:/app/data
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
    restart: unless-stopped

Uptime-Kuma

用于监控各类网站、服务等的可用性,并即时通知,文档地址,安装目录 /opt/uptime-kumadocker-compose.yml 文件内容如下:

docker-compose.yml
services:
  uptime-kuma:
    image: louislam/uptime-kuma:latest
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - 127.0.0.1:13059:3001
    volumes:
      - /opt/uptime-kuma/data:/app/data
      - /var/run/docker.sock:/var/run/docker.sock # 用于监控 Docker 容器状态

Watchtower

文档地址,用于(自动)更新 Docker 镜像。

# 拉取镜像
docker pull containrrr/watchtower
# 执行手动更新
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR {CONTAINER_NAME}

目前使用其自动更新镜像,并使用 Telegram Bot 进行通知,docker-compose.yml 文件内容如下:

docker-compose.yml
services:
  watchtower:
    image: containrrr/watchtower:latest
    container_name: watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: container1 container2 container3 # 可选,仅更新指定容器
    environment:
      - TZ=Asia/Shanghai
      - WATCHTOWER_SCHEDULE=0 0 17 * * *
      - WATCHTOWER_NOTIFICATIONS_HOSTNAME={your_hostname}
      - WATCHTOWER_NOTIFICATIONS=shoutrrr
      - WATCHTOWER_NOTIFICATION_URL=telegram://{HTTP_API_TOKEN}@telegram/?chats={CHAT_ID}&notification=no
      - WATCHTOWER_CLEANUP=true

Diun

Diun (Docker Image Update Notifier) 是一个 Docker 镜像更新通知工具,暂时未使用,由 Watchtower 替代。安装目录 /opt/diundocker-compose.yml 文件内容如下:

docker-compose.yml
services:
  diun:
    image: crazymax/diun:latest
    container_name: diun
    command: serve
    volumes:
      - /opt/diun/data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - 'TZ=Asia/Shanghai'
      - 'DIUN_WATCH_WORKERS=20'
      - 'DIUN_WATCH_SCHEDULE=0 */6 * * *'
      - 'DIUN_WATCH_JITTER=30s'
      - 'DIUN_PROVIDERS_DOCKER=true'
      - 'DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true'
      - 'DIUN_NOTIF_TELEGRAM_TOKEN={your_bot_token}'
      - 'DIUN_NOTIF_TELEGRAM_CHATIDS={your_chat_id}'
    labels:
      - 'diun.enable=true'
    restart: unless-stopped

Restic

增量备份方案,文档地址,类似的项目如 Duplicati (Linux 下运行需要 mono,所以排除)、Borg 等,详见 ArchLinux Wiki

export AWS_DEFAULT_REGION={your_region}
export AWS_ACCESS_KEY_ID={your_access_key}
export AWS_SECRET_ACCESS_KEY={your_secret_key}
export RESTIC_PASSWORD={your_restic_password}
export RESTIC_REPOSITORY=s3:https://{your_endpoint}.r2.cloudflarestorage.com/{your_bucket_name}
restic init

首先 dry-run 测试可以正常进行备份:

restic backup /which/path/to/backup --dry-run -vv

可以将备份任务添加至 crontab,如:

30 4 * * * /usr/bin/restic backup /which/path/to/backup --password-file /path/to/rc-password >> /path/to/backup.log 2>&1

资源使用情况

这个小鸡目前 Docker 使用资源大致如下:

$ free -h
               total        used        free      shared  buff/cache   available
Mem:           1.8Gi       1.1Gi       100Mi        35Mi       800Mi       680Mi
Swap:          2.0Gi       184Mi       1.8Gi
 
$ docker stats --no-stream
CONTAINER ID   NAME                CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
923f971d3b37   dozzle              0.00%     10.34MiB / 1.796GiB   0.56%     1.25MB / 5.29MB   4.95MB / 6.84MB   9
fd237887aecd   new-api             0.00%     58.19MiB / 1.796GiB   3.16%     7.41MB / 2.07MB   27.8MB / 28.5MB   6
8f37d4f379fe   grafana             0.09%     97.36MiB / 1.796GiB   5.29%     86.9MB / 133MB    297MB / 51.9MB    9
fc932e8b8fd5   prometheus          0.21%     172.6MiB / 1.796GiB   9.38%     5.22GB / 142MB    1.07GB / 1.94GB   9
6990c8fa1463   cadvisor            2.79%     68.21MiB / 1.796GiB   3.71%     104MB / 5.96GB    36MB / 12MB       11
4a601308c9e1   postgres_exporter   0.00%     10.92MiB / 1.796GiB   0.59%     0B / 0B           17.2MB / 11.7MB   7
093f763befb5   node_exporter       0.00%     19.05MiB / 1.796GiB   1.04%     0B / 0B           25.6MB / 11.4MB   7
65e3cb4e66c8   sabnzbd             0.03%     50.29MiB / 1.796GiB   2.73%     2.53MB / 12.1MB   68.8MB / 17.8MB   30
b36798faaa29   qbittorrent         0.02%     11.46MiB / 1.796GiB   0.62%     1.48MB / 1.43MB   54.3MB / 9.61MB   17
6c5a7fffe90a   redis               0.32%     8.031MiB / 1.796GiB   0.44%     509kB / 297kB     21MB / 2.34MB     6
3f8854c8002c   postgres            4.10%     38.79MiB / 1.796GiB   2.11%     125MB / 563MB     38.1MB / 30.6MB   9
c7dfa82dcf03   glance              0.00%     18.48MiB / 1.796GiB   1.01%     7.24MB / 1.56MB   12MB / 4.97MB     6
7bddcf9118ce   wewerss             0.00%     58.85MiB / 1.796GiB   3.20%     797kB / 285kB     81.6MB / 3.16MB   15

TODO

  • 重要数据定期备份方案,上传至 Cloudflare R2;—> Restic
  • 容器镜像的自动更新方案,通过 Telegram Bot 通知;—> Watchtower

Cloudflare

可以参考 Awesome Cloudflare,发现更多有意思的基于 Cloudflare 的开源工具。

Cloudflare Pages

ChatGPT-Next-Web

调用各类 LLM api 进行对话的网页端项目,文档地址,配置构建命令和输出目录如下:

  • Build command: npx @cloudflare/next-on-pages --experimental-minify
  • Build output: .vercel/output/static

配置如下环境变量:

CODE = "{your_code}" # 网页端访问密码
BASE_URL = "https://new-api.lkwplus.com"
OPENAI_API_KEY = "{your_openai_api_key}"
NEXT_TELEMETRY_DISABLE = "1"
NODE_VERSION = "20.1"
PHP_VERSION = "7.4"
YARN_VERSION = "1.22.19"

PS: Next.js 项目还是 Vercel 的支持更好,应该优先部署到 Vercel 而非 Cloudflare :)

excalidraw

用于绘制手绘风格的流程图,GitHub REAMDE,构建配置如下:

  • Build command: yarn --cwd ./excalidraw-app build
  • Build output: excalidraw-app/build

BroadcastChannel

用于将 Telegram Channel 转换为微博客,0 JS 的理念很有意思,文档地址,构建配置如下:

  • Build command: pnpm run build
  • Build output: dist

配置如下环境变量:

CHANNEL = "lkw123_channel"
GITHUB = "synthpop123"
LOCALE = "zh-cn"
TELEGRAM = "lkwtg"
TIMEZONE = "Asia/Shanghai"
TWITTER = "whoamamamiii"

IT-Tools

IT 工具箱,功能丰富、UI 美观,文档地址,构建配置如下:

  • Build command: pnpm run build
  • Build output: dist

Web Archive

Web Archive 可以提供类似于使用 Singlefile 插件进行网页存档的功能,服务基于 Cloudflare D1 数据库及 R2 存储桶。参考 官方文档 进行一键部署,从 Release 页下载 Chrome 插件后即可开始使用。

Quartz

将 Obsidian 的笔记内容构建为静态网页,可定制性强,是一个很好的官方 Obsidian Publish 功能的替代方案,文档地址。我仿照一个非常好看 Quartz 实例 Garden’s Gate 进行了一些修改。构建配置如下:

  • Build command: git fetch --unshallow && npx quartz build
  • Build output: public

Slidev

Slidev 是一个为开发者设计的基于 Web 的幻灯片制作工具,文档地址,构建配置如下:

  • Build command: pnpm run build
  • Build output: dist
  • Rewrites: "source": "/(.*)", "destination": "/index.html"

MkDocs

采用 Material for MkDocs 主题的文档站,GitHub Repo。构建配置如下:

  • Build command: mkdocs build
  • Build output: site

R2 Uploader

此前一直苦于没有美观好用的用于 Cloudflare R2 的 S3 兼容上传器,用如 PicGo 之类的软件会感觉太重,体验一般。

jw-12138/r2-uploader 则很好的规避了太重的问题,保留核心功能,还支持传输大文件、自动压缩、自定义域名等,且密钥等均通过浏览器存在本地,保障了安全的问题,部署教程详见 README

除此之外,轻量的命令行工具 pluveto/upgit 也很不错,非常推荐。

Sink

完全运行于 Cloudflare 的短链接生成工具,支持分析功能。部署教程详见 README

Dimension

此前的个人主页,稍稍修改自 HTML5 UP 设计的静态页面。

site-status

基于 UptimeRobot API 的在线状态面板,根据 README 中的部署教程,修改 .env 文件中的 UptimeRobot API Key 即可。

Clouflare Workers

gh-proxy

基于 Cloudflare Workers 对 GitHub Release、Archive 以及项目文件进行反代,从而实现加速访问,提供了一个简单的 Web UI,部署教程

DeepLX

详见项目 deeplx-for-cloudflare,可以给如沉浸式翻译插件或 Bob 等软件提供翻译 API 接口。

Reverse Proxy

基于 Cloudflare Workers 的简单反向代理脚本,且可以处理跨域请求,偶尔用于给无法访问源网站的朋友分享内容时会用到,部署教程详见 README

Pastebin

通过 Cloudflare KV 进行存储的 Pastebin 项目,用于临时分享代码或小文件等使用,部署教程详见 README

ChatGPT-Telegram-Workers

部署教程详见 README。环境变量如下:

API_KEY = "{your_api_key}"
CHAT_WHITE_LIST = "{your_tg_id}"
CUSTOM_COMMAND_35 = "/setenvs {'OPENAI_CHAT_MODEL': 'gpt-3.5-turbo'}"
CUSTOM_COMMAND_4o = "/setenvs {'OPENAI_CHAT_MODEL': 'gpt-4o'}"
CUSTOM_COMMAND_4o-mini = "/setenvs {'OPENAI_CHAT_MODEL': 'gpt-4o-mini'}"
CUSTOM_COMMAND_ds-coder = "/setenvs {'OPENAI_CHAT_MODEL': 'deepseek-coder'}"
CUSTOM_COMMAND_cn2en = "/setenvs {'SYSTEM_INIT_MESSAGE': '你是一个翻译,下面将我说的话都翻译成英文'}"
CUSTOM_COMMAND_en2cn = "/setenvs {'SYSTEM_INIT_MESSAGE': '你是一个翻译,下面将我说的话都翻译成中文'}"
DALL_E_IMAGE_QUALITY = "hd"
DALL_E_IMAGE_SIZE = "1024x1024"
DALL_E_IMAGE_STYLE = "natural"
DALL_E_MODEL = "dall-e-3"
ENABLE_USAGE_STATISTICS = "true"
GROUP_CHAT_BOT_ENABLE = "false"
OPENAI_API_BASE = "https://new-api.lkwplus.com/v1"
OPENAI_CHAT_MODEL = "gpt-4o"
TELEGRAM_AVAILABLE_TOKENS= "{your_bot_token}"

Oaifree Helper

提取多个 ChatGPT 账号的 AccessToken/RefreshToken 后,基于 Cloudflare Workers 反代始皇的 new.oaifree.com 镜像站,从而实现免梯访问 ChatGPT 和多号切换,部署教程详见 README

Fuclaude

依赖于始皇的 fuclaude,基于 Cloudflare Workers 实现号池分享,部署教程可参考 L 站帖子

Letterboxd Diary Embed

基于 Cloudflare Workers 获取任意用户的最新 Letterboxd 影评,并生成 HTML 代码以嵌入个人网站中,项目地址

其逻辑是发出 GET 请求后会首先在 Cloudflare KV 中查询是否有缓存,若有则直接返回缓存内容,若没有则向 Letterboxd API 发出请求获取最新影评,并将结果 HTML 代码缓存到 KV 中。

个人对缓存逻辑和生成的影评 CSS 样式进行了简单修改后重新部署,以添加 ?username=lkw123 参数为例发送请求,获取到的的内容如下:

<style>
  #letterboxd-embed-tc {
    margin-bottom: 1.5rem;
  }
 
  .letterboxd-embed-tc-diary-entry {
    margin-bottom: 1rem;
  }
 
  .letterboxd-embed-tc-content {
    display: flex;
    align-items: flex-start;
    gap: 2rem;
    margin-bottom: 1em;
    margin-top: 1em;
  }
 
  .letterboxd-embed-tc-poster img {
    margin: 0 !important;
    border-radius: 0.375rem;
    width: 70px;
    height: 105px;
    object-fit: cover;
  }
 
  .letterboxd-embed-tc-title {
    font-size: 1.125rem;
    font-weight: 700;
    line-height: 1.25;
  }
 
  .letterboxd-embed-tc-year {
    font-size: 0.875rem;
    font-weight: 400;
    color: #6c757d;
    margin-left: 0.25rem;
  }
 
  .letterboxd-embed-tc-date {
    font-size: 0.875rem;
    color: #6c757d;
    margin-top: 0.25rem;
    margin-bottom: 0.25rem;
  }
 
  .letterboxd-embed-tc-rating {
    font-size: 1.125rem;
    font-weight: 500;
    color: #16a34a;
  }
 
  .letterboxd-embed-tc-review {
    font-size: 0.875rem;
    color: #6c757d;
    margin-top: 0.25rem;
  }
 
  .letterboxd-embed-tc-divider {
    border-bottom: 1px solid #6c757d;
    margin-top: 1rem;
    margin-bottom: 1rem;
  }
 
  .letterboxd-embed-tc-more {
    font-size: 1rem;
    color: #6c757d;
    transition: color 0.2s;
  }
 
  .letterboxd-embed-tc-more:hover {
    color: #000000;
  }
</style>
 
<div id="letterboxd-embed-tc">
  <div class="letterboxd-embed-tc-diary-entry">
    <div class="letterboxd-embed-tc-content">
      <div class="letterboxd-embed-tc-poster">
        <a
          href="https://letterboxd.com/lkw123/film/the-volunteers-the-battle-of-life-and-death/"
          target="_blank"
        >
          <img
            src="https://a.ltrbxd.com/resized/film-poster/1/1/8/0/9/3/3/1180933-the-volunteers-the-battle-of-life-and-death-0-70-0-105-crop.jpg?v=998f8665cf"
            alt="The Volunteers: The Battle of Life and Death poster"
          />
        </a>
      </div>
 
      <div>
        <div class="letterboxd-embed-tc-title">
          The Volunteers: The Battle of Life and Death
          <span class="letterboxd-embed-tc-year"> 2024 </span>
        </div>
 
        <div class="letterboxd-embed-tc-date">October 3, 2024</div>
 
        <div class="letterboxd-embed-tc-rating">★★½</div>
      </div>
    </div>
  </div>
 
  <div class="letterboxd-embed-tc-divider"></div>
 
  <div class="letterboxd-embed-tc-diary-entry">
    <div class="letterboxd-embed-tc-content">
      <div class="letterboxd-embed-tc-poster">
        <a
          href="https://letterboxd.com/lkw123/film/the-cloud-in-her-room/1/"
          target="_blank"
        >
          <img
            src="https://a.ltrbxd.com/resized/film-poster/5/0/4/5/1/8/504518-the-cloud-in-her-room-0-70-0-105-crop.jpg?v=60cbaf1d17"
            alt="The Cloud in Her Room poster"
          />
        </a>
      </div>
 
      <div>
        <div class="letterboxd-embed-tc-title">
          The Cloud in Her Room
          <span class="letterboxd-embed-tc-year"> 2020 </span>
        </div>
 
        <div class="letterboxd-embed-tc-date">&#9850; September 7, 2024</div>
 
        <div class="letterboxd-embed-tc-rating">★★★½</div>
 
        <div class="letterboxd-embed-tc-review">
          One of my favorite film posters
        </div>
      </div>
    </div>
  </div>
 
  <div class="letterboxd-embed-tc-divider"></div>
 
  <div class="letterboxd-embed-tc-diary-entry">
    <div class="letterboxd-embed-tc-content">
      <div class="letterboxd-embed-tc-poster">
        <a href="https://letterboxd.com/lkw123/film/alien/" target="_blank">
          <img
            src="https://a.ltrbxd.com/resized/sm/upload/8v/f1/qw/aa/bg7K6VtUG7Ew70gQj6SSroD5d4R-0-70-0-105-crop.jpg?v=a932f9e98e"
            alt="Alien poster"
          />
        </a>
      </div>
 
      <div>
        <div class="letterboxd-embed-tc-title">
          Alien
          <span class="letterboxd-embed-tc-year"> 1979 </span>
        </div>
 
        <div class="letterboxd-embed-tc-date">August 25, 2024</div>
 
        <div class="letterboxd-embed-tc-rating">★★★★</div>
      </div>
    </div>
  </div>
 
  <div class="letterboxd-embed-tc-divider"></div>
 
  <div class="letterboxd-embed-tc-diary-entry">
    <div class="letterboxd-embed-tc-content">
      <div class="letterboxd-embed-tc-poster">
        <a
          href="https://letterboxd.com/lkw123/film/alien-romulus/"
          target="_blank"
        >
          <img
            src="https://a.ltrbxd.com/resized/film-poster/8/5/0/4/5/9/850459-alien-romulus-0-70-0-105-crop.jpg?v=acabb7fd83"
            alt="Alien: Romulus poster"
          />
        </a>
      </div>
 
      <div>
        <div class="letterboxd-embed-tc-title">
          Alien: Romulus
          <span class="letterboxd-embed-tc-year"> 2024 </span>
        </div>
 
        <div class="letterboxd-embed-tc-date">August 21, 2024</div>
 
        <div class="letterboxd-embed-tc-rating">★★★★</div>
      </div>
    </div>
  </div>
 
  <div class="letterboxd-embed-tc-divider"></div>
 
  <div class="letterboxd-embed-tc-diary-entry">
    <div class="letterboxd-embed-tc-content">
      <div class="letterboxd-embed-tc-poster">
        <a
          href="https://letterboxd.com/lkw123/film/despicable-me-4/"
          target="_blank"
        >
          <img
            src="https://a.ltrbxd.com/resized/film-poster/4/4/8/5/0/6/448506-despicable-me-4-0-70-0-105-crop.jpg?v=3391582e43"
            alt="Despicable Me 4 poster"
          />
        </a>
      </div>
 
      <div>
        <div class="letterboxd-embed-tc-title">
          Despicable Me 4
          <span class="letterboxd-embed-tc-year"> 2024 </span>
        </div>
 
        <div class="letterboxd-embed-tc-date">August 13, 2024</div>
 
        <div class="letterboxd-embed-tc-rating">★★</div>
      </div>
    </div>
  </div>
 
  <div class="letterboxd-embed-tc-divider"></div>
 
  <div class="letterboxd-embed-tc-more">
    <a href="https://letterboxd.com/lkw123" target="_blank"
      >...more on Letterboxd</a
    >
  </div>
</div>

可以通过如下方式将其嵌入到博客中。具体效果详见我的博客的 Misc 页面 的 Movie Stats 部分。

<div id="letterboxd-embed-wrapper-tc">Loading...</div>
 
<script>
  fetch('https://letterboxd-embed.lkwplus.com?username=lkw123')
    .then((response) => response.text())
    .then((data) => {
      const element = document.getElementById('letterboxd-embed-wrapper-tc')
      if (element) {
        element.innerHTML = data
      }
    })
</script>

PT Gen

根据豆瓣、IMDb、Bangumi、Steam 链接自动生成简介,主要用于 PT(自动化)发种,文档地址

Vercel

此前在 Vercel 上部署的项目很多,但自决定全面转向 Cloudflare 的 CDN 和各类服务后,逐步对项目进行迁移。

RSSHub

文档地址

RSSHub 是一个开源的、用于将各种网站的内容聚合到一个统一的接口中生成 RSS Feed 的项目,主要用于订阅未提供 RSS Feed 的网站或社交媒体。在这个碎片化信息爆炸的时代,做一些试图逃脱信息茧房的挣扎。

根据 GitHub Issue #14622 的讨论,在今年上半年起 Vercel/Cloudflare Workers 无法部署 RSSHub 的最新代码,测试可用的最新 Commit 截至 29276d8。这个问题的修复目前卡在 got 和 NextJS 不兼容,需要等待 got 依赖从 RSSHub 项目移除。

暂时性的解决方案为:首先将 DIYgod/RSSHub 完整的 Fork 至自己账号下,可以在 Vercel 的 Create Deployment 处填入相应的 Commit 链接 https://github.com/{your-username}/RSSHub/commit/29276d8,或是在 GitHub 将默认分支由 master 切换至 legacy 分支后再部署。

Koodo Reader

跨平台的电子书阅读器,部署教程详见 README

Umami

Google Analytics 的开源替代,项目地址

采用 Vercel 免费提供的 Postgres 作为后端数据库,和项目进行关联后,在部署页面配置数据库连接信息即可。同时,可以修改 TRACKER_SCRIPT_NAME 环境变量为不包含 umami、analytics 等关键字的名称,避免目标网站中引入的 js 脚本被 Adblock 等插件拦截。

Nobelium

项目地址,基于 Notion 的 Next.js 静态博客,类似的开源解决方案如 NotionNext,闭源解决方案如 Super,或是直接选择使用官方的 Notion Website 功能即可。

Homer

项目地址,极简风格的静态面板,比较适合用于服务器、Homelab、NAS 等,可以方便的集中整理自己的各类自部署服务或书签。

DailyHot

项目后端 imsyy/DailyHotApi-Vercel,项目前端 imsyy/DailyHot。后端部署完毕后,更改前端项目的环境变量 VITE_GLOBAL_API 即可,如 “https://dailyhot-api.lkwplus.com”。

Last.fm Recently Played

项目地址,用于生成个人的最新 Last.fm 听歌记录,可美观的展示于 GitHub Profile 或其他地方,类似的还可以选择同开发者的 JeffreyCA/spotify-recently-played-readme 项目。

由于该仓库自身提供服务使用的 *.vercel.app 子域名被墙,因此部署后绑定自定义域名以规避这个问题。具体效果详见我的博客的 Misc 页面 的 Music Stats 部分。

NetEase Music API

网易云音乐 Node.js API service,原仓库由于版权问题现已删库,如有需求可以考虑部署 PHP 实现 kilingzhang/NeteaseCloudMusicApi。自己此前 fork 的 仓库 尚且幸存,部署的 API 一直可以能正常使用。此前主要用于配合 qier222/YesPlayMusic 在 Vercel 搭建在线音乐 Web 端,但很少使用。

fly.io

我的 fly.io 账户幸运的停留在了 Legacy Hobby plan,可以免费部署三个 cpu-1x mem-256M 的实例,相当的大方且慷慨,感激 :)

Reader

阅读的服务器端项目,可以方便的在网页端摸鱼看小说。文档地址fly.toml 文件内容如下:

fly.toml
app = 'lkw123-reader'
primary_region = 'iad'
 
[build]
  image = "hectorqin/reader:openj9-latest"
 
[env]
  SPRING_PROFILES_ACTIVE = "prod"
  READER_APP_USERLIMIT = "1"
  READER_APP_USERBOOKLIMIT = "200"
  READER_APP_CACHECHAPTERCONTENT = "true"
  READER_APP_SECURE = "true"
  READER_APP_SECUREKEY = "{your_secret_key}"
 
[http_service]
  internal_port = 8080
  force_https = true
  auto_stop_machines = 'off'
  auto_start_machines = false
  min_machines_running = 0
  processes = ['app']
 
[mounts]
  source="reader_data"
  destination="/storage"
 
[[vm]]
  size = 'shared-cpu-1x'

FastAPI

源码存放于我的 GitHub 仓库 synthpop123/fly-fastapifly.toml 文件内容如下:

fly.toml
app = 'lkw123-fastapi'
primary_region = 'iad'
 
[build]
  builder = 'paketobuildpacks/builder:base'
 
[env]
  PORT = '8000'
 
[http_service]
  internal_port = 8000
  force_https = true
  auto_stop_machines = 'off'
  auto_start_machines = false
  min_machines_running = 0
  processes = ['app']
 
[[vm]]
  size = 'shared-cpu-1x'

Vaultwarden

使用 Rust 构建的开源密码管理器 Bitwarden 的服务端实现,且和上游的 Bitwarden 客户端兼容,fly.toml 文件内容如下:

fly.toml
app = 'lkw123-vault'
primary_region = 'iad'
 
[http_service]
  internal_port = 8080
  force_https = true
  auto_stop_machines = 'off'
  auto_start_machines = false
  min_machines_running = 0
  processes = ['app']
 
[env]
  SIGNUPS_ALLOWED = "false"
 
[build]
  image = "vaultwarden/server:latest"
 
[mounts]
  source = "vw_data"
  destination = "/data"
 
[[services]]
  http_checks = []
  internal_port = 80
  [[services.ports]]
    force_https = true
    handlers = ["http"]
    port = 80
 
  [[services.ports]]
    handlers = ["tls", "http"]
    port = 443
 
  [[services.tcp_checks]]
    grace_period = "1s"
    interval = "15s"
    restart_limit = 0
    timeout = "2s"
 
[[vm]]
  size = 'shared-cpu-1x'

Memos

参考 hu3rror/memos-on-fly 的文档进行部署,除了保有项目的原功能外,还支持通过 Litestream 自动备份数据库到个人的 Backblaze B2

为了接入 Telegram Bot,需要采用标签为 stable-memogram 的镜像,并需要手动添加 Bot Token 至环境变量:

flyctl secrets set BOT_TOKEN="{your_bot_token}"

fly.toml 文件内容如下:

fly.toml
app = 'lkw123-memos'
primary_region = 'iad'
 
[build]
  image = 'ghcr.io/hu3rror/memos-litestream:stable-memogram'
 
[env]
  LITESTREAM_REPLICA_BUCKET = '{lkw123-memos}'
  LITESTREAM_REPLICA_ENDPOINT = '{s3.us-west-004.backblazeb2.com}'
  LITESTREAM_REPLICA_PATH = 'memos_prod.db'
 
[[mounts]]
  source = 'memos_data'
  destination = '/var/opt/memos'
 
[http_service]
  internal_port = 5230
  force_https = true
  auto_stop_machines = false
  auto_start_machines = true
  min_machines_running = 0
 
[[vm]]
  size = 'shared-cpu-1x'

顺手记录一下自己的 Memos 自定义 CSS 样式:

custom.css
/* 设置 Memos 标签样式 */
span.inline-block.w-auto.text-blue-600.dark\:text-blue-400 {
  color: #f3f3f3;
  background-color: #40b76b;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  border-radius: 2px;
  padding: 2px 6px;
  font-size: 15px;
  margin-bottom: 4px;
}
/* 设置同级下不同的标签显示不同的颜色 */
/* 第2个标签 */
span.inline-block.w-auto.text-blue-600.dark\:text-blue-400:nth-child(n + 2) {
  background-color: #157cf5;
}
/* 第3个标签 */
span.inline-block.w-auto.text-blue-600.dark\:text-blue-400:nth-child(n + 4) {
  background-color: #f298a6;
}
/* 第4个标签 */
span.inline-block.w-auto.text-blue-600.dark\:text-blue-400:nth-child(n + 6) {
  background-color: #fdb15d;
}
/* 第5个标签 */
span.inline-block.w-auto.text-blue-600.dark\:text-blue-400:nth-child(n + 8) {
  background-color: #67d6ca;
}
/* 第6个标签 */
span.inline-block.w-auto.text-blue-600.dark\:text-blue-400:nth-child(n + 10) {
  background-color: #7445e0;
}

Zeabur

Zeabur 是一个比较新的云端部署服务,作为国人团队开发的产品,它会有更多的本地化支持和对大陆用户的优化。

在通常情况下,我们使用如 Zeabur 这类 PaaS 服务时,主要是将项目部署至其多区域的共享集群 (Shared Cluster),利用其 Edge 节点提供的高速网络连接,同时降低运维成本。

Zeabur 最近推出了“独立服务器”的功能,用户可以将自己的服务器注册到 Zeabur 上,从而无需支付硬件资源的使用费用,又能享受到平台为部署流程带来的便利。我用 VKVM 的洛杉矶 9950X 4C8G VPS 测试了下,效果还不错,但是占用系统资源比较多,如果有闲置的大内存机器可以尝试。

Blog

基于 Astro 构建,修改自 astro-erudite,并使用了 astro-theme-pure 中的部分组件样式。

Homepage

个人很喜欢博客主题 astro-erudite 的样式,就根据博客 clone 了一份,去除无用的依赖,保留 index.astro 单页面中的 Card 组建,并稍微调整样式,便成了新的个人主页。

Blog Legacy

基于 Astro 构建,修改自 AstroPaper,部分修改过程记录在了博文 AstroPaper 博客自定义 中,代码存放于我的 GitHub 仓库 synthpop123/astro-blog。自迁移至新的主题后,该旧博客暂时保留。


参考: