MoreRSS

site iconLala | 荒岛修改

一个应用分享、教程类的博客,主要是那些需要自部署的。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Lala | 荒岛的 RSS 预览

自建RomM(游戏ROM管理器)畅玩复古游戏

2026-07-14 14:23:06

RomM(一款游戏ROM管理器)界面简洁流畅,方便您扫描、丰富、浏览和畅玩您的游戏收藏。它支持多种平台、多种命名规则和自定义标签,是所有模拟器玩家的必备工具。

本文将介绍如何自建并使用RomM畅玩以前的经典游戏,如:拳皇97、合金弹头、魂斗罗等。

准备工作:

1.一个域名,添加A记录解析:romm.example.com

2.如果你需要刮削游戏的元数据,则最好准备一个IGDB(互联网游戏数据库)的API密钥。IGDB是Twitch旗下的服务,你必须注册一个Twitch账号并启用2FA。然后按照这篇文档创建API密钥。在创建API密钥期间,回调地址直接配置为:http://localhost

3.RomM只是一个管理工具,游戏的ROM资源需要你自己准备,如果你不知道在哪里可以安全获取这些资源,这里我有一些推荐:Reddit的Roms板块r-romsminerva-archiveInternet Archive

其中minerva-archive是以BT种子为主的资源站,所有ROM均以BT种子的形式提供,Internet Archive提供ROM压缩包直链下载,但往往需要你注册账号才能下载。请勿轻易相信搜索引擎提供的ROM网站,通过这些网站下载的ROM很有可能携带病毒、木马等恶意软件。

安装NGINX、CertBot、Docker:

apt update
apt install curl nginx python3-certbot-nginx
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

新建compose文件:

mkdir /opt/romm && cd /opt/romm && nano docker-compose.yml

写入如下内容:

volumes:
  mysql_data:
  romm_resources:
  romm_redis_data:

services:
  romm:
    image: rommapp/romm:latest
    container_name: romm
    restart: unless-stopped
    environment:
      - DB_HOST=romm-db
      - DB_NAME=romm 
      - DB_USER=romm-user
      - DB_PASSWD=
      - ROMM_AUTH_SECRET_KEY=
      - SCREENSCRAPER_USER= # These are the recommended metadata providers
      - SCREENSCRAPER_PASSWORD= # https://docs.romm.app/latest/getting-started/metadata-providers/#screenscraper
      - RETROACHIEVEMENTS_API_KEY= # https://docs.romm.app/latest/getting-started/metadata-providers/#retroachievements
      - STEAMGRIDDB_API_KEY= # https://docs.romm.app/latest/getting-started/metadata-providers/#steamgriddb
      - HASHEOUS_API_ENABLED=true # https://docs.romm.app/latest/getting-started/metadata-providers/#hasheous
      - IGDB_CLIENT_ID=
      - IGDB_CLIENT_SECRET=
    volumes:
      - romm_resources:/romm/resources
      - romm_redis_data:/redis-data
      - ./library:/romm/library
      - ./assets:/romm/assets
      - ./config:/romm/config
    ports:
      - 127.0.0.1:8080:8080
    depends_on:
      romm-db:
        condition: service_healthy
        restart: true

  romm-db:
    image: mariadb:latest
    container_name: romm-db
    restart: unless-stopped
    environment:
      - MARIADB_ROOT_PASSWORD=
      - MARIADB_DATABASE=romm
      - MARIADB_USER=romm-user
      - MARIADB_PASSWORD=
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: [CMD, healthcheck.sh, --connect, --innodb_initialized]
      start_period: 30s
      start_interval: 10s
      interval: 10s
      timeout: 5s
      retries: 5

1.ROMM_AUTH_SECRET_KEY的值使用如下命令生成:

openssl rand -hex 32

2.请注意MARIADB_ROOT_PASSWORD设置的是数据库root用户的密码不是romm-user用户的密码,所以DB_PASSWD的值应匹配MARIADB_PASSWORD而非MARIADB_ROOT_PASSWORD

3.配置IGDB_CLIENT_IDIGDB_CLIENT_SECRET,以体验最佳的刮削效果。

4.如果你配置了IGDB,则这些涉及刮削的环境变量可不配置,用处不大。大多数情况下IGDB + HASHEOUS足够了:

SCREENSCRAPER_USER=
SCREENSCRAPER_PASSWORD=
RETROACHIEVEMENTS_API_KEY=
STEAMGRIDDB_API_KEY=

启动:

docker compose up -d

新建NGINX站点配置文件:

nano /etc/nginx/sites-available/romm

写入如下内容:

server {
    listen 80;
    server_name romm.example.com;
    client_max_body_size 0;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        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_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

启用站点:

ln -s /etc/nginx/sites-available/romm /etc/nginx/sites-enabled/romm

签发证书:

certbot --nginx

首次访问的时候会提示你是否要创建选中的游戏平台目录,我建议直接全选创建,这样可以省去你自己整理文件夹结构的时间,后续你可以通过Web UI直接将游戏ROM上传到对应的平台下,开箱即玩。

详情见官方的这两篇文档:Folder StructureSupported Platforms

首次访问RomM请在“元数据源”页面查看IGDB配置是否正常:

现在我们来下载ROM资源,我通过Internet Archive下载了:

合金弹头1(mslug.zip)
合金弹头2(mslug2.zip)
合金弹头4(mslug4.zip)
合金弹头5(mslug5.zip)
合金弹头X (mslugx.zip)
拳皇97(kof97.zip)

下载地址:https://archive.org/download/fbnarcade-fullnonmerged/arcade/

将这些压缩包通过Web UI上传到RomM内,我刚下载的这些游戏全部都是街机游戏(Arcade)所以平台就选择Arcade:

上传完成后,RomM会自动开始扫描,扫描完成后,你应该看到游戏库有更新,此时会显示游戏的海报、简介等信息:

游戏启动的时候需要你自己选择一个核心,对于我刚才下载的这些游戏而言,核心一律选择fbneo:

可以玩了:

现在说一下魂斗罗这款游戏如何添加到RomM,首先你得通过r-roms找到对应的平台,这里魂斗罗对应的平台是Nintendo Entertainment System:

然后你会发现这里有Headered和Headerless两个分类,这里一定要下载Headered分类的压缩包,Headerless是无法启动的。跳转到minerva-archive后搜索Contra:

后面的步骤就和之前一样了,找到对应的平台Nintendo Entertainment System,上传压缩包:

启动游戏的时候选择核心,这里我个人推荐nestopia:

可以玩了:

总结向RomM添加游戏的步骤:

1.通过r-roms找到游戏对应的平台、分类。

2.通过r-roms提供的minerva-archive或Internet Archive网页地址下载正确的游戏ROM压缩包。

3.在RomM选择正确的游戏平台后将压缩包上传到RomM,这一步非常重要,因为关系到RomM在运行时选择哪个核心来运行游戏。

如果你不知道游戏属于哪个平台,游戏ROM压缩包的名字叫什么,游戏运行时该使用哪个核心,可以问AI。

Chatto自托管聊天服务部署指南

2026-07-11 17:44:19

Chatto是一款功能齐全的基于Web的聊天应用程序,您可以使用自己的服务器来托管它。

Chatto主要特点:

  • 功能齐全:除了普通的文本消息外,还支持文件共享、视频嵌入以及带屏幕共享的语音/视频通话。
  • 部署简单:Chatto只是一个单独的二进制文件,无需单独的数据库。或者您可以使用Docker Compose
  • 灵活的权限和角色:服务器管理员可以精细控制谁可以做什么以及在哪里可以做。
  • 高性能:后端和前端都非常精简,资源占用低,对性能进行了极致的优化。
  • 注重数据保护和隐私:Chatto使用每个用户的密钥加密消息文本和选定的持久帐户字段

部署前需要准备:

  • 一个域名,添加A解析记录:chatto.example.com / livekit.chatto.example.com
  • 一个SMTP账户,供Chatto发送用户注册邮件

本文不使用官方文档的Docker Compose方式来部署,原因是官方文档使用了Caddy容器,这会把服务器的80 / 443端口独占。本文改为使用主机的NGINX反向代理。

安装NGINX、CertBot、Docker:

apt update
apt install curl nginx python3-certbot-nginx
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

新建compose文件:

mkdir /opt/chatto && cd /opt/chatto && nano docker-compose.yml

写入如下内容:

services:
  nats:
    image: nats:latest
    command: ["--jetstream", "--store_dir=/data", "--auth=${NATS_TOKEN}"]
    volumes:
      - nats_data:/data
    healthcheck:
      test: ["CMD", "nats-server", "--help"]
      interval: 5s
      timeout: 3s
      retries: 3

  livekit:
    image: livekit/livekit-server:latest
    command:
      - --config
      - /etc/livekit.yaml
    ports:
      - "50000-50200:50000-50200/udp"  # WebRTC media (UDP, must be direct)
      - "3478:3478/udp"  # TURN/STUN relay for NAT traversal
      - "127.0.0.1:7880:7880"
    volumes:
      - ./livekit.yaml:/etc/livekit.yaml:ro
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:7880"]
      interval: 5s
      timeout: 3s
      retries: 3

  chatto:
    image: ghcr.io/chattocorp/chatto:latest
    env_file: .env
    environment:
      PUID: "${PUID:-1000}"
      PGID: "${PGID:-1000}"
    depends_on:
      nats:
        condition: service_healthy
      livekit:
        condition: service_healthy
    ports:
      - "127.0.0.1:4000:4000"

volumes:
  nats_data:

新建livekit配置文件:

nano livekit.yaml

写入如下内容:

port: 7880

rtc:
  port_range_start: 50000
  port_range_end: 50200
  use_external_ip: true

turn:
  enabled: true
  udp_port: 3478

keys:
  chatto: 729594219c7e...

webhook:
  urls:
    - https://chatto.example.com/webhooks/livekit
  api_key: chatto

logging:
  level: info

1.keys使用如下命令生成:

openssl rand -hex 32

2.webhook.urls改为你自己的域名:

https://chatto.example.com/webhooks/livekit

新建.env文件:

nano .env

写入如下内容:

PUID=1000
PGID=1000

NATS_TOKEN=

[email protected]
CHATTO_NATS_EMBEDDED_ENABLED=false
CHATTO_NATS_CLIENT_URL=nats://nats:4222
CHATTO_NATS_CLIENT_AUTH_METHOD=token
CHATTO_NATS_CLIENT_TOKEN=
CHATTO_WEBSERVER_URL=https://chatto.example.com
CHATTO_WEBSERVER_PORT=4000
CHATTO_WEBSERVER_COOKIE_SIGNING_SECRET=
CHATTO_WEBSERVER_COOKIE_ENCRYPTION_SECRET=
CHATTO_CORE_SECRET_KEY=
CHATTO_CORE_ASSETS_SIGNING_SECRET=
CHATTO_LOG_LEVEL=info
CHATTO_LOG_FORMAT=json
CHATTO_OPERATOR_API_ENABLED=true
CHATTO_OPERATOR_API_SOCKET_PATH=/tmp/chatto/operator.sock

# SMTP is required for direct email/password registration, email verification,
# and password reset. Replace these placeholders with your provider settings.
CHATTO_SMTP_ENABLED=true
CHATTO_SMTP_HOST=mail.example.com
CHATTO_SMTP_PORT=587
CHATTO_SMTP_TLS=mandatory
# Use when CHATTO_SMTP_HOST is an IP address or internal alias but the certificate is issued for a DNS name.
# CHATTO_SMTP_TLS_SERVER_NAME=mail.example.com
# Insecure; use only for trusted internal SMTP servers with self-signed or mismatched certificates.
# CHATTO_SMTP_TLS_SKIP_VERIFY=false
CHATTO_SMTP_USERNAME=smtp
CHATTO_SMTP_PASSWORD=passwd
[email protected]

CHATTO_PUSH_ENABLED=true
CHATTO_PUSH_VAPID_PUBLIC_KEY=
CHATTO_PUSH_VAPID_PRIVATE_KEY=
CHATTO_PUSH_VAPID_SUBJECT=mailto:[email protected]

CHATTO_LIVEKIT_ENABLED=true
CHATTO_LIVEKIT_URL=wss://livekit.chatto.example.com
CHATTO_LIVEKIT_API_KEY=chatto
CHATTO_LIVEKIT_API_SECRET=

1.以下这些变量的值:

NATS_TOKEN=
CHATTO_NATS_CLIENT_TOKEN=
CHATTO_WEBSERVER_COOKIE_SIGNING_SECRET=
CHATTO_WEBSERVER_COOKIE_ENCRYPTION_SECRET=
CHATTO_CORE_SECRET_KEY=
CHATTO_CORE_ASSETS_SIGNING_SECRET=

全部使用如下命令生成:

openssl rand -hex 32

请注意NATS_TOKENCHATTO_NATS_CLIENT_TOKEN的值应保持一致,NATS_TOKEN用作NATS容器,而CHATTO_NATS_CLIENT_TOKEN是Chatto用于连接NATS容器的。

2.CHATTO_PUSH_VAPID_PUBLIC_KEYCHATTO_PUSH_VAPID_PRIVATE_KEY用作Web通知,如果您需要启用消息通知,则需要使用如下命令生成:

docker run --rm node:24-alpine npx --yes web-push generate-vapid-keys --json

3.CHATTO_LIVEKIT_API_KEYCHATTO_LIVEKIT_API_SECRET的值应与livekit.yaml内的值保持一致,这是Chatto与Livekit通信的关键。

4.CHATTO_WEBSERVER_URLCHATTO_LIVEKIT_URL请确保改为您自己的域名。

5.CHATTO_OWNERS_EMAILS请改为您的邮箱,且稍后您在注册账户的时候请使用这个邮箱,因为只有使用这个邮箱注册的账户才拥有管理员权限。

6.配置与SMTP相关的环境变量,因为Chatto注册的时候需要验证您的邮箱。

全部可配置的环境变量见官方文档:https://docs.chatto.run/reference/environment-variables/

启动:

docker compose up -d

新建NGINX站点配置文件:

nano /etc/nginx/sites-available/chatto

写入如下内容:

upstream chat_backend {
    server 127.0.0.1:4000;
}

upstream livekit_backend {
    server 127.0.0.1:7880;
}

server {
    listen 80;
    listen [::]:80;
    server_name chatto.example.com;

    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    location / {
        proxy_pass http://chat_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    listen [::]:80;
    server_name livekit.chatto.example.com;

    location / {
        proxy_pass http://livekit_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

启用站点:

ln -s /etc/nginx/sites-available/chatto /etc/nginx/sites-enabled/chatto

签发证书:

certbot --nginx

简单测试了下,各项功能全部正常,群聊:

私聊:

这个项目的布局很明显是仿Discord的,但是目前仅实现了单群组,意思就是你部署的实例只能创建一个群组,不能创建多个。不知道后续作者会不会实现多群组的功能。整体使用下来没发现什么明显的问题,功能也非常完善了,硬要说我感觉差了点什么的话,就是不支持自定义表情包。

我还知道另外一个聊天程序是目前开源当中对Discord还原度最高的,但是这个程序目前BUG有点多,且架构非常臃肿,需要启动18个容器,你没看错整整18个容器。。。我有点纠结要不要水一篇文章= =

Ignis:在浏览器中运行Obsidian

2026-07-10 15:00:18

Ignis介绍:

Ignis通过Obsidian官方的 Electron API 实现,使Obsidian能够在标准浏览器中运行,同时将您的保险库保留在服务器上。Obsidian本身不包含在本项目中,也不随本项目一起分发,Docker容器会在首次运行时直接从其官方仓库下载Obsidian。

虽然Obsidian的本地优先策略对大多数用户来说效果不错,但如果用户想远程访问自己的Obsidian却只能通过基于VNC的解决方案来实现,用户体验不佳。Ignis为希望通过浏览器以接近原生方式访问Obsidian的用户提供了一种替代方案。

请注意,Ignis不是远程桌面,不是类似VNC这样的东西,它是一个真正的Web APP。且Ignis只能工作在HTTPS下,为此你必须配置反向代理。

Ignis在Obsidian的基础功能上还增加了一些功能,比较实用的有:

保险库(Vaults)

  • 为Obsidian的多保险库支持定制的用户界面,允许创建、打开、切换、重命名和删除。
  • 不同的保险库可以在不同的浏览器标签页中加载。

文件(Files)

  • 通过功能区图标从本地计算机上传文件,右键单击文件夹->上传文件,或将文件拖放到用户界面中。
  • 通过右键单击任何笔记->下载,或通过右键单击任何文件夹->下载为ZIP来下载文件和文件夹。

服务器端同步

  • Obsidian Headless 以服务器端插件的形式实现,无需活动浏览器标签页即可执行持续同步。

安装NGINX、CertBot、Docker:

apt update
apt install curl nginx python3-certbot-nginx apache2-utils
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

新建compose文件:

mkdir /opt/ignis && cd /opt/ignis && nano docker-compose.yml

写入如下内容:

services:
  ignis:
    image: nobbe/ignis:latest
    ports:
      - "127.0.0.1:18080:8080"
    environment:
      - OBSIDIAN_VERSION=1.12.7
      - PUID=1000
      - PGID=1000
    volumes:
      - ./vaults:/vaults
      - ./data:/app/data
      - obsidian-app:/app/obsidian-app
    restart: unless-stopped

volumes:
  obsidian-app:

离线/受限网络安装,例如你的机子无法通过Github下载Obsidian安装包,可以事先准备好deb软件包:

wget https://github.com/obsidianmd/obsidian-releases/releases/download/v1.12.7/obsidian_1.12.7_amd64.deb

修改compose文件使用如下配置:

services:
  ignis:
    image: nobbe/ignis:latest
    ports:
      - "127.0.0.1:18080:8080"
    environment:
      - OBSIDIAN_VERSION=1.12.7
      - OBSIDIAN_PACKAGE=/packages/obsidian.deb
      - PUID=1000
      - PGID=1000
    volumes:
      - ./obsidian_1.12.7_amd64.deb:/packages/obsidian.deb:ro
      - ./vaults:/vaults
      - ./data:/app/data
      - obsidian-app:/app/obsidian-app
    restart: unless-stopped

volumes:
  obsidian-app:

启动:

docker compose up -d

1.全部可配置的环境变量,见Ignis官方文档

2.Ignis目前没有原生身份验证能力,如果你将其暴露在公网运行,务必配置一个身份验证,你可以使用OIDC / SSO或者Basic Auth。

这里我配置一个NGINX Basic Auth,创建htpasswd文件:

htpasswd -c /etc/nginx/.htpasswd imlala

新建NGINX站点配置文件,请注意Ignis需要WebSocket支持:

nano /etc/nginx/sites-available/ignis

写入如下内容:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name ignis.example.com;
    client_max_body_size 5G;

    auth_basic "auth";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location / {
        proxy_pass http://127.0.0.1:18080;
        proxy_http_version 1.1;
        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_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

启用站点:

ln -s /etc/nginx/sites-available/ignis /etc/nginx/sites-enabled/ignis

签发证书:

certbot --nginx

首次使用,需要创建一个保险库(Vault)

效果:

Transmute:免费、开源、可自托管的文件转换和压缩工具

2026-07-10 14:05:22

Transmute介绍:

Transmute是一款免费、开源、可自行托管的文件转换和压缩工具,专为保护隐私和自动化而设计。它支持在本地转换图像、视频、音频、文档、电子表格、字幕和字体,或者压缩支持的文件以减小文件大小,而无需将任何内容发送给第三方。使用 Docker 即可在几秒钟内完成部署,没有文件大小限制,没有水印。

Transmute特点:

  • 隐私至上:文件在您自己的服务器上处理,绝不会发送给第三方。
  • OIDC/SSO 支持:通过 Authentik 等 OIDC 提供商进行登录和创建帐户,请参阅文档。
  • 文件大小无限制:转换的文件大小不受存储空间限制。
  • 支持100多种格式:图像、视频、音频、文档、电子表格、字幕和字体
  • 内置身份验证:开箱即用,支持用户帐户、基于角色的访问控制和 API 密钥。
  • 支持 Docker:只需一条命令即可部署,无需复杂的设置
  • REST API:通过内置的、符合 OpenAPI 文档规范的 API 自动执行和集成文件转换
  • 多种主题:内置七种浅色和深色用户界面主题

安装NGINX、CertBot、Docker:

apt update
apt install curl nginx python3-certbot-nginx
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

新建compose文件:

mkdir /opt/transmute && cd /opt/transmute && nano docker-compose.yml

写入如下内容:

services:
  transmute:
    image: ghcr.io/transmute-app/transmute:latest
    container_name: transmute
    restart: unless-stopped
    environment:
      - APP_URL=https://transmute.example.com
    ports:
        - 127.0.0.1:3313:3313
    volumes:
      - transmute_data:/app/data
    healthcheck:
      test:
        - "CMD"
        - "wget"
        - "-q"
        - "-O"
        - "/dev/null"
        - "--tries=1"
        - "http://localhost:3313/api/health/ready"
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

volumes:
  transmute_data:

1.所有可配置的环境变量:https://transmute.sh/docs/environment-variables/

2.OIDC/SSO集成:https://transmute.sh/docs/oidc/

启动:

docker compose up -d

新建NGINX站点配置文件:

nano /etc/nginx/sites-available/transmute

写入如下内容:

server {
    listen 80;
    server_name transmute.example.com;
    client_max_body_size 0;

    location / {
        proxy_pass http://127.0.0.1:3313;
        proxy_http_version 1.1;
        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_set_header X-Forwarded-Proto $scheme;
    }
}

启用站点:

ln -s /etc/nginx/sites-available/transmute /etc/nginx/sites-enabled/transmute

签发证书:

certbot --nginx

效果:

Debian搭建Nowhere节点

2026-07-08 06:30:30

Nowhere是一个很新的协议,目前只有Anywhere这个iOS客户端支持,想尝鲜的可以试试。

本文仅记录适用于生产的tls=2模式。tls=2需要你自行准备可信证书,这里我就用certbot + dns01挑战来完成了。

安装certbot和cloudflare dns挑战插件:

apt update
apt install certbot python3-certbot-dns-cloudflare

新建一个目录:

mkdir -p ~/.secrets/certbot

新建cloudflare.ini:

nano ~/.secrets/certbot/cloudflare.ini

填写如下内容,cfut_hidden替换为你自己的API密钥:

dns_cloudflare_api_token = cfut_hidden

设置这个文件的权限,只允许当前用户读写,其它用户无权访问,主要是为了解决certbot的警告,以免不让你继续申请证书:

chmod 600 ~/.secrets/certbot/cloudflare.ini

申请证书:

certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials ~/.secrets/certbot/cloudflare.ini \
  -d nowhere.example.com

申请好的证书保存在如下路径:

/etc/letsencrypt/live/nowhere.example.com/fullchain.pem
/etc/letsencrypt/live/nowhere.example.com/privkey.pem

下载Nowhere二进制文件:

wget https://github.com/NodePassProject/Nowhere/releases/download/v1.3.1/nowhere-x86_64-unknown-linux-gnu.tar.gz
tar -xzvf nowhere-x86_64-unknown-linux-gnu.tar.gz
mv nowhere /usr/local/bin/

现在可以先手动运行测试一下:

nowhere 'portal://yourpasswd@:443?log=info&tls=2&crt=/etc/letsencrypt/live/nowhere.example.com/fullchain.pem&key=/etc/letsencrypt/live/nowhere.example.com/privkey.pem'

客户端能成功连接的话就ctrl+c退出来。新建systemd单元配置文件:

nano /etc/systemd/system/nowhere.service

写入如下内容:

[Unit]
Description=Nowhere Proxy Server
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/nowhere 'portal://yourpasswd@:443?log=info&tls=2&crt=/etc/letsencrypt/live/nowhere.example.com/fullchain.pem&key=/etc/letsencrypt/live/nowhere.example.com/privkey.pem'
Restart=on-failure
RestartSec=30
LimitNOFILE=1048576
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

启动并设置开机自启:

systemctl enable --now nowhere.service

客户端添加节点的配置:

地址:nowhere.example.com
端口:443
Key:yourpasswd
Spec: 留空
网络:TCP / UDP 任选其一
TLS SNI:nowhere.example.com
ALPN:留空

我看了下官方的文档,好像这个协议支持类似xhttp那样的上下行分离,比如你可以选择上行走udp,下行走tcp。我没有实际测试过,有兴趣的可以折腾看看。

Debian使用sing-box搭建Naive/AnyTLS + ECH节点

2026-07-07 15:19:37

准备工作:

  • 一个域名,托管到CloudFlare,添加A记录解析,本文示例:naive.example.com / anytls.example.com
  • 创建CloudFlare的API令牌,令牌权限至少拥有“编辑区域 DNS”的能力

安装sing-box的alpha版本:

mkdir -p /etc/apt/keyrings &&
    curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&
    chmod a+r /etc/apt/keyrings/sagernet.asc &&
    echo '
Types: deb
URIs: https://deb.sagernet.org/
Suites: *
Components: *
Enabled: yes
Signed-By: /etc/apt/keyrings/sagernet.asc
' | tee /etc/apt/sources.list.d/sagernet.sources &&
    apt update &&
    apt install sing-box-testing

这里安装的软件包是sing-box-testing,而非官方文档指出的sing-box-beta,因为我搜索发现sing-box-beta的版本似乎很旧?

serenity/unknown 1.1.0~beta.3 amd64
  The configuration generator for sing-box.

sing-box/unknown,now 1.13.14 amd64 [residual-config]
  The universal proxy platform.

sing-box-beta/unknown 1.14.0~alpha.4 amd64
  The universal proxy platform.

sing-box-testing/unknown,now 1.14.0~alpha.38 amd64 [installed]
  The universal proxy platform.

编辑配置文件:

nano /etc/sing-box/config.json

我的配置如下:

{
  "log": {
    "level": "info"
  },
  "dns": {
    "servers": [
      {
        "type": "tls",
        "tag": "google",
        "server": "8.8.8.8"
      }
    ]
  },
  "certificate_providers": [
    {
      "type": "acme",
      "tag": "acme-dns01-cert",
      "domain": [
        "naive.example.com",
        "anytls.example.com"
      ],
      "email": "[email protected]",
      "dns01_challenge": {
        "resolvers": [
          "1.1.1.1",
          "1.0.0.1"
        ],
        "provider": "cloudflare",
        "api_token": "cfut_hidden"
      }
    }
  ],
  "inbounds": [
    {
      "type": "naive",
      "listen": "::",
      "listen_port": 8443,
      "users": [
        {
          "username": "imlala",
          "password": "89641937"
        }
      ],
      "quic_congestion_control": "bbr",
      "tls": {
        "enabled": true,
        "server_name": "naive.example.com",
        "min_version": "1.3",
        "ech": {
          "enabled": true,
          "key_path": "/etc/sing-box/ech-naive.pem"
        },
        "certificate_provider": "acme-dns01-cert"
      }
    },
    {
      "type": "anytls",
      "listen": "::",
      "listen_port": 9443,
      "users": [
        {
          "name": "imlala",
          "password": "89641937"
        }
      ],
      "padding_scheme": [
        "stop=8",
        "0=30-30",
        "1=100-400",
        "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
        "3=9-9,500-1000",
        "4=500-1000",
        "5=500-1000",
        "6=500-1000",
        "7=500-1000"
      ],
      "tls": {
        "enabled": true,
        "server_name": "anytls.example.com",
        "min_version": "1.3",
        "ech": {
          "enabled": true,
          "key_path": "/etc/sing-box/ech-anytls.pem"
        },
        "certificate_provider": "acme-dns01-cert"
      }
    }
  ],
  "outbounds": [
    {
      "type": "direct"
    }
  ],
  "route": {
    "rules": [
      {
        "action": "sniff"
      },
      {
        "protocol": "dns",
        "action": "hijack-dns"
      }
    ]
  }
}

简单说下为什么这么配置。

1.ECH只能工作在TLS1.3之上,所以服务端这边指定最低版本就是TLS1.3。

2.之前使用acme申请证书的配置已经弃用了,在即将到来的1.14版本改为使用certificate_provider。

3.我不确定这是不是BUG,本来我是只打算部署一个Naive节点的,然后想着通过DNS自动发现ECH配置,但是我抓包反复测试了无数遍,SNI始终是真实的,不是我期望看到的SNI。。所以后来我又搭建了一个AnyTLS用来测试,这个就完全正常,服务端和客户端都是一模一样的配置,AnyTLS就可以通过DNS自动发现ECH配置,Naive始终不行,我换过sing-box版本,试过各种组合的配置,就是不行,就很迷= =所以我暂且得出一个结论:“通过DNS的HTTPS记录自动发现ECH配置”这个功能目前不能用在Naive节点上,如果要使用Naive + ECH则必须在客户端提供ECHConfig。

然后还需要生成ECH配置:

sing-box generate ech-keypair fuckccp.gov
sing-box generate ech-keypair www.imlala-ech.com

这里的fuckccp.gov和www.imlala-ech.com就是外层的SNI,你不需要实际拥有这些域名,使用任意域名均可,所以请发挥你的想象力。这里我生成了两个ECH配置,分别对应我的两个节点:

fuckccp.gov --> naive.example.com
www.imlala-ech.com --> anytls.example.com

执行这个命令后输出的内容大致是这样的:

-----BEGIN ECH CONFIGS-----
AEb+DQBCAAAgACDVOVL...
-----END ECH CONFIGS-----

-----BEGIN ECH KEYS-----
ACDN3derSCnTPZAOGp...
-----END ECH KEYS-----

你需要把ECH KEYS这部分的内容保存到如下pem格式的文件内:

/etc/sing-box/ech-naive.pem
/etc/sing-box/ech-anytls.pem

重启sing-box使新的配置生效:

systemctl restart sing-box

如果你使用DNS自动发现ECH配置,则需要添加一条HTTPS解析记录:

类型:HTTPS
名称:anytls
优先级:1
目标:. (一个点,代表当前的意思)
值:ech="" (双引号内填写之前生成的ECH CONFIGS内容)

那么服务端的配置到这里就全部完成了。接下来是客户端的配置,客户端我使用的是GUI.for.SingBox,所以我不需要完整的sing-box客户端配置,只把出站配置贴上来就好:

AnyTLS:

{
  "type": "anytls",
  "tag": "anytls-ech-jp",
  "server": "anytls.example.com",
  "server_port": 9443,
  "password": "89641937",
  "idle_session_check_interval": "30s",
  "idle_session_timeout": "30s",
  "min_idle_session": 5,
  "tls": {
    "enabled": true,
    "server_name": "anytls.example.com",
    "ech": {
      "enabled": true
    }
  }
}

Naive:

{
  "type": "naive",
  "tag": "naive-ech-jp",
  "server": "naive.example.com",
  "server_port": 8443,
  "username": "imlala",
  "password": "89641937",
  "insecure_concurrency": 0,
  "quic": false,
  "quic_congestion_control": "bbr",
  "tls": {
    "enabled": true,
    "server_name": "naive.example.com",
    "ech": {
      "enabled": true,
      "config_path": "ech.pem"
    }
  }
}

注意事项:

1.通过DNS的HTTPS记录自动发现ECH配置不能在Naive出站正常工作,Naive出站必须手动指定ECHConfig。

2.要让Naive出站能够正常在GUI.for.SingBox(Windows)下运行,还需要下载libcronet.dll到sing-box的目录内

3.DNS自动发现ECH的配置,默认情况下sing-box查询的是tls.server_name的HTTPS记录,你可以使用query_server_name覆盖。

抓包测试,可以看到SNI是www.imlala-ech.com而非真正的anytls.example.com:

我这里使用Naive的话,开QUIC并配合"quic_congestion_control": "bbr",效果比不使用QUIC好很多。我之前没有启用QUIC是为了方便抓包测试ECH,因为QUIC的数据包都是加密的。当然这个东西我这里好用不代表你那里就好使,毕竟QUIC是基于UDP的。。

另外我在折腾的时候遇到服务器这边的域名解析记录一直不更新的问题,看了下这破B VPS还整个systemd-resolved,也不知道是它商家模板没做好还是咋的,反正我执行这两条命令把DNS缓存清了就好了:

resolvectl statistics
resolvectl flush-caches