用户指南
一份实用的 Town OS 上手指南——从写入 U 盘一直讲到报告缺陷。 无论你是第一次使用,还是正在开发软件包的开发者, 这份指南都涵盖了你入门所需的全部内容。
快速上手
几分钟内就能让 Town OS 在一台闲置电脑上跑起来。你只需要一个 U 盘 (USB-C,4 GB 或更大)和一台用来写入的 Linux 电脑。
1. 写入 U 盘
在任意一台装有 podman、curl、bzip2、
dd、lsblk 和 tar 的 Linux 机器上运行安装脚本——
Podman 是必需的,因为脚本要用它来拉取安装器镜像
(而且必须能正常工作,光装上还不够)。脚本会下载最新的 Town OS 镜像
并直接写入 U 盘。
curl -sSLO https://town-os.github.io/install.sh && bash install.sh 脚本会扫描已连接的 USB 设备,列出设备名称和容量供你选择。确认之后, 它会一次性地把压缩镜像流式解压并写入该设备。整个过程视网速快慢, 大约需要几分钟。
树莓派(4 / 400 / CM4、5 / CM5):传入 RPI=1,
即可把原生启动的树莓派镜像写入 SD 卡、U 盘或 NVMe,而不是标准的 PC 镜像:
curl -sSLO https://town-os.github.io/install.sh && RPI=1 bash install.sh
树莓派镜像始终是 64 位 Arm,并且不经过 UEFI 启动,因此即便你在 x86_64 机器上
运行安装脚本也没问题。树莓派会直接从插入的卡或驱动器启动,没有启动菜单;
如果要用 Pi 5 的 NVMe 启动,请确认引导程序 EEPROM 的启动顺序中包含 NVMe
(用 rpi-eeprom-config 设置)。
Town OS 会格式化并使用所有检测到的本地存储设备 (NVMe、SATA、SAS、SD 卡)。在一台机器上启动 Town OS,将会 永久销毁每一块内置硬盘上的全部现有数据。
请只在一台没有任何你想保留的数据的专用机器上启动 Town OS;或者先按
虚拟机说明安全地试用——包括
make qemu-usb,它会在 QEMU 中以只读方式
启动你刚写好的 U 盘,完全不碰任何真实硬盘。
2. 从 U 盘启动
把 U 盘插到目标电脑上并从它启动。你可能需要在开机时按某个键才能进入启动菜单—— 常见的按键有 F12、F2、Esc 或 Del,具体取决于你的硬件。
3. 首次启动:使用 sledgehammer
如果目标机器此前用作他用,请在第一次启动时从启动菜单中 选择 sledgehammer 启动项。sledgehammer 会擦除所有检测到的存储设备, 确保 Town OS 从一块完全干净的白板开始。这样可以避免先前操作系统遗留的 分区表、文件系统或 RAID 元数据带来的问题。
sledgehammer 完成后,机器会自动重启,Town OS 随即进行全新的存储配置。 完整说明请见 Sledgehammer 启动项。
4. 启动时会发生什么
Town OS 会从 U 盘完整加载进内存,然后启动 ttyforce—— 一个交互式的文本界面安装程序,直接在这台机器的控制台上引导你完成配置。
- 网络配置——ttyforce 会检测可用的网络接口。如果有已连接的有线网络,它会自动继续;否则会列出 WiFi 网络供你选择,显示信号强度、加密方式,并支持输入 WPA2/WPA3 密码。
- 磁盘配置——ttyforce 按类型和容量对检测到的磁盘分组,然后自动选择合适的 RAID 级别:1 块盘用单盘模式,2 块盘用 RAID1(镜像),3 块及以上用 RAID5(带校验的条带)。所有存储都使用 btrfs。
- 导入 SSH 公钥——你可以输入 GitHub 用户名来导入公钥,以便安全地远程访问。
配置完成后系统会重启,ttyforce 切换到 getty 模式——在控制台上实时显示服务健康状况、 系统指标和日志输出。从这里你可以登录、重新配置网络,或触发一次 sledgehammer 擦除。
5. 创建账户,开始使用
在同一网络上的任意设备上打开浏览器,访问
http://town-os.local。如果打不开,请在路由器的 DHCP 客户端列表里
找到名为「town-os」的设备,直接使用它的 IP 地址。系统会提示你创建一个管理员账户。
登录之后,你就可以在仪表盘上安装软件包、管理存储和配置服务了。
6. 把路由器的 DNS 指向 Town OS
Town OS 自带 rolodex, 它是一个 DNS 服务器,负责解析软件包的主机名,并把其他查询转发给上游。 要让网络中的每台设备都自动使用它,请给 Town OS 机器分配一个静态 IP (或 DHCP 保留地址),并把路由器的 首选 DNS 服务器 设为该地址。 各品牌的具体操作和验证步骤见 在路由器上设置 DNS。
从源码构建 U 盘镜像
Town OS 的 U 盘镜像由 install 仓库构建。构建过程会生成一个可启动镜像,其根文件系统为 squashfs,分区表为 GPT。
先决条件
你需要一台 Linux 主机和一个 U 盘(4 GB 或更大)。构建使用 Arch Linux 的工具链
(pacstrap、mkinitcpio、arch-chroot),
但你不需要运行 Arch——在其他发行版上,make image
会自动在同架构的 Arch 容器中执行构建。请用对应你发行版的目标安装主机依赖:
make deps——Arch(及其衍生版)或 Fedora/RHELmake deps-debian——Debian 或 Ubuntu
它们会安装构建和虚拟机工具所需的一切——make、
podman、arch-install-scripts、squashfs-tools、
parted、e2fsprogs、dosfstools、
qemu 和 libvirt。
克隆与构建
git clone https://gitea.com/town-os/install.git
cd install
make image
运行 make 或 make image 即可构建完整的 U 盘镜像。
该过程会下载基础系统、安装 Town OS 组件、把所有内容压缩成 squashfs 文件系统,
并组装出带 GPT 分区表的最终磁盘镜像。
分区与 squashfs
生成的镜像采用 GPT 布局,包含:
- 一个 BIOS 引导分区(1 MiB),用于传统 BIOS 启动
- 一个 EFI 系统分区(64 MiB,FAT32),用于 UEFI 启动
- 一个数据分区(ext4),存放 squashfs 根文件系统和 GRUB
启动时,Town OS 会把 squashfs 挂载为只读的下层,并在其上叠加一层 tmpfs。 这意味着操作系统完全在内存中运行——U 盘本身只在启动时被读取。 所有运行期的改动都发生在内存中,重启后即被丢弃,每次都还你一块干净的白板。
写入镜像
最简单的方式是 make flash:镜像过期时会自动重新构建,然后写入 USB 设备。
如果要手动操作,请注意构建产物带有日期和架构后缀(例如
town-os-2026-07-25-x86_64.img)——用 dd 写入即可:
# 找到你的 USB 设备(例如 /dev/sdb)
lsblk
# 写入镜像(把文件名和 /dev/sdX 替换成你自己的设备)
sudo dd if=town-os-YYYY-MM-DD-x86_64.img of=/dev/sdX bs=4M status=progress conv=fsync
请再三确认目标设备——dd 会不加确认地覆盖你所指定的任何设备。
Sledgehammer 启动项
Town OS 的 U 盘中包含一个 sledgehammer 启动项,它会擦除所有检测到的 存储设备,把系统重置为干净状态。当你想从头开始时,它很有用——例如测试完毕之后、 重新部署到新硬件之前,或者存储已经损坏的时候。
它会做什么
当你在启动时选择 sledgehammer 选项,Town OS 会:
- 检测所有本地存储设备(NVMe、SATA/SAS、SD 卡)——与正常启动时使用的检测逻辑相同
- 擦除每个检测到的设备上的分区表和文件系统
- 重启系统,随后像首次启动那样重新完成存储配置
sledgehammer 会销毁所有检测到的存储设备上的全部数据。U 盘本身不受影响—— 只影响内置磁盘。使用此选项前,请确认你需要的数据都已备份。
如何使用
- 把 Town OS 的 U 盘插入目标机器并从它启动
- 在启动菜单中选择 sledgehammer 条目,而不是默认启动项
- 系统会擦除所有检测到的存储并自动重启
- 下次启动时,Town OS 会依据你的
town-os.yaml配置从零开始设置存储
什么时候用
- 恢复出厂状态——让系统回到刚刚部署时那样的干净状态
- 更换存储后端——从 btrfs 切换到 ZFS(或反向切换)需要先清空现有存储
- 存储损坏——如果文件系统已经损坏到无法修复,sledgehammer 能给你一个干净的起点
- 重新部署——把硬件改用于另一套 Town OS 配置
在路由器上设置 DNS
Town OS 内置了 rolodex, 这个 DNS 服务器为你的软件包管理权威区域,并转发上游查询。要把它用作整个网络的 DNS 服务器,你需要让路由器把 Town OS 机器的 IP 地址作为 DNS 服务器下发给网络中的设备。 这样每台设备都会自动使用 rolodex 解析 DNS——无需逐台配置。
找到 Town OS 的 IP 地址
你需要知道 Town OS 机器的局域网 IP 地址。可以通过以下方式找到:
- 首次启动配置完成后,登录
http://town-os.local的仪表盘,取其中显示的内部 IP——这个地址就是要用作 DNS 服务器的地址 - 如果你用的是虚拟机,运行
make vm-ip - 在路由器的 DHCP 客户端列表里查找名为「town-os」的设备
要让 DNS 稳定工作,Town OS 机器应当拥有静态 IP 地址,或在路由器上设置 DHCP 保留。 如果 IP 变了,整个网络的 DNS 都会失效。
分配静态 IP 或 DHCP 保留
大多数路由器都允许你根据 MAC 地址为特定设备保留 IP。这项设置通常位于 局域网设置、DHCP 或 地址保留 中。 在客户端列表里找到 Town OS 机器,把它当前的 IP 保留下来即可。
如果你的路由器不支持 DHCP 保留,也可以在 town-os.yaml 中添加配置,
直接在 Town OS 机器上设置静态 IP。
在路由器中修改 DNS 服务器
不同品牌的路由器操作细节各异,但大致流程是一样的:
- 登录路由器的管理界面(通常是
192.168.1.1或192.168.0.1) - 找到 DHCP 设置、局域网设置 或 DNS 设置 部分
- 把 首选 DNS 服务器 改成 Town OS 机器的 IP 地址
- 可以再设置一个 备用 DNS 服务器 作为兜底(例如
1.1.1.1或8.8.8.8)——当 Town OS 无法访问时会用它 - 保存并应用设置
保存之后,网络中的设备会在下一次续租 DHCP 时获得新的 DNS 服务器。 你也可以断开并重新连接网络,或者重启设备来立即生效。
常见路由器界面
| 路由器品牌 | DNS 设置所在位置 |
|---|---|
| ASUS(华硕) | 内部网络 → DHCP 服务器 → DNS 服务器 |
| TP-Link | DHCP → DHCP 设置 → 首选 DNS |
| Netgear(网件) | Internet → 域名服务器(DNS)地址 |
| Linksys | 连接 → 本地网络 → DHCP 服务器 → 静态 DNS |
| UniFi | 设置 → 网络 →(你的网络)→ DHCP Name Server |
| pfSense / OPNsense | Services → DHCP Server → DNS Servers |
| OpenWrt | 网络 → 接口 → LAN → DHCP 服务器 → 高级设置 → DHCP-Options:6,<town-os-ip> |
关闭浏览器自带的 DNS
大多数浏览器都自带 DNS 解析器,会完全绕过你的路由器。 Firefox 的 DNS over HTTPS(DoH),以及 Chrome 和 Edge 中对应的「安全 DNS」功能, 都会把查询直接发给 Cloudflare、NextDNS 之类的公共服务商,因此 Town OS 根本看不到 这些查询,你的软件包域名就会显示「找不到服务器」,哪怕网络中其他设备都解析正常。 请在每一台需要按名称访问你服务的设备上关掉它:
- Firefox——设置 → 隐私与安全 → DNS over HTTPS,选择关闭。(默认保护也可能悄悄启用 DoH,所以请明确选「关闭」,不要保留默认值。)
- Chrome——设置 → 隐私和安全 → 安全,关闭使用安全 DNS。
- Edge——设置 → 隐私、搜索和服务 → 安全性,关闭使用安全 DNS 指定如何查找网站的网络地址。
- Brave、Vivaldi、Opera——与 Chrome 相同,位于隐私和安全 → 安全下。
- Safari——没有内置 DoH,使用系统解析器。请到系统设置 → 通用 → VPN 与设备管理确认没有安装 DNS 描述文件。
你可能配置过的任何系统级加密 DNS 也是同理——Android 的私人 DNS、
iOS/macOS 的 DNS 描述文件,或本地的 systemd-resolved/dnscrypt
配置。这些都会覆盖路由器下发的 DNS 服务器。如果你想要加密的上游 DNS,交给 Town OS 就好:
rolodex 自己就能对上游使用 DoH/DoT,同时仍然解析你的本地名称
(见下文选择解析模式)。
验证是否生效
当设备获取到新的 DNS 设置后,验证一下 rolodex 是否真的在处理你的 DNS 查询:
# 查看你的机器正在使用哪个 DNS 服务器
nslookup example.com
# 或者直接向 Town OS 查询
dig @<town-os-ip> example.com
# 查询软件包域名(前提是你已经安装了软件包)。
# 名称形如 <name>.<repo>.<tld> —— .home 是默认网络的 TLD
dig @<town-os-ip> gitea.default.home
如果 example.com 能正确解析,说明 rolodex 已经在工作,
并在为你的网络处理 DNS。
选择解析模式
不属于你任何一个网络的名称,会按照解析模式来处理; 该模式在仪表盘的 设置 → DNS 解析 中设定。共有三种模式:
- 自动(推荐)——默认模式。先尝试从根服务器开始迭代解析,失败后依次回退到 DoH/DoT、本地转发器,最后是公共解析器,并沿用最近一次成功的层级。这样在网络条件允许时保留递归解析的隐私性,在过滤出站 DNS 的网络中也能优雅降级。
- 仅递归——全部从根服务器迭代解析,不做任何回退。可以保证查询绝不会交给第三方解析器,但在封锁或劫持出站 53 端口的网络(酒店、门户认证网络、某些 ISP)中,所有外部名称都会解析失败。
- 转发——始终把未匹配的查询发给上游解析器(默认是 Google Public DNS)。这就是传统的转发行为。
请注意,这项设置说的是本机如何访问互联网。而另一个方向上的加密 DNS—— 也就是你自己的设备用来访问本机的方式——将在下面介绍。
从你的设备使用加密 DNS
Town OS 不只为自己使用加密 DNS,也把它提供给你自己的设备。 共有三个可用端点,它们解析的名称与 53 端口上的普通 DNS 完全相同:
- DNS over HTTPS(DoH)——
https://dns.<tld>/dns-query。它经由 ingress 走标准 HTTPS 端口,因此在限制严格的网络中最有可能可用。 - DNS over TLS(DoT)——
dns.<tld>,853 端口。 - DNS over QUIC(DoQ)——
dns.<tld>,853 端口,基于 UDP。
这三者出示的证书都由本机自己的证书颁发机构签发,所用的名称和地址与系统其余部分一致。 如果你已经在设备上安装了根 CA(参见 信任证书颁发机构),它们无需任何额外配置即可通过校验。
从未信任过你的 CA 的设备同样可以验证这些端点。
Town OS 会在它作为权威的区域中发布 DANE 记录来锚定该证书,分别位于
_853._tcp.dns.<tld> 与 _853._udp.dns.<tld>——
每种传输各一条,因为支持 DANE 的客户端如果找不到自己所选传输对应的记录,就会以失败告终。
由于该设备本就能访问这台解析器,它就能取到这份锚定信息,从而在不预先安装任何东西的情况下,
校验自己收到的证书。
支持 DDR 的客户端会自行发现这一切。Town OS 会在
_dns.resolver.arpa(RFC 9462)发布指派记录,按优先级依次给出 DoH 的 URL 以及
DoT 和 DoQ 的端口——DoH 排在最前,因为 443 端口能够穿过那些封锁 853 的过滤。
只要把支持 DDR 的设备指向本机做普通 DNS,它就会自己发现并升级到加密端点。
这三个监听器是在启动时由安装镜像的配置打开的,因此与绝大多数 DNS 设置不同,它们无法从控制面板开启。 其背后的证书会在本机仅仅处于运行状态时于后台续期——无需重启—— 并且新的 DANE 锚定会在旧的撤下之前先行发布,因此不存在任何会让校验型客户端拒绝连接的时间窗口。
拦截恶意域名
rolodex 可以用公共黑名单筛查每一次查询,让你无需另装 Pi-hole 就能获得全网范围的威胁拦截。相关设置在 DNS → 黑名单 中管理。 黑名单提供方分为两类,作用对象各不相同:
- DNSBL(域名黑名单)——匹配被查询的名称。真正影响日常上网的是这一侧。
- RBL(实时黑洞列表)——匹配 IP 地址,而且只在反向 DNS 查询时生效。
两者都默认关闭,且提供方列表为空,并且都是按需查询的: Town OS 从不下载、解析或预缓存任何黑名单数据源。在你启用某一类并至少添加一个区域之前, 什么都不会被检查。
DNSBL 查询是如何工作的
被解析的名称会被拼接到提供方的区域之前,然后作为一次普通的 DNS 查询发出。
在启用 dbl.spamhaus.org 的情况下解析 badsite.example,
实际会发出对 badsite.example.dbl.spamhaus.org 的查询。
- 有应答即代表已列入——对于被列入的名称,提供方会返回一条地址记录(通常是
127.0.0.x)。rolodex 随后向发起查询的设备返回NXDOMAIN。 - NXDOMAIN 即代表干净——解析照常继续。出错或超时的提供方一律视为未列入,因此一个无法访问的黑名单绝不会让你的网络断线。
- 黑名单胜过外部世界,但绝不胜过你自己的记录——检查发生在本地记录和软件包记录之后(所以
gitea.default.home始终能解析),但在上游缓存和转发器之前,因此即便转发得到的应答已被缓存,被列入的名称仍会被拒绝。 - 按名称匹配,而非按后缀——
doubleclick.net被列入并不会连带拦截stats.g.doubleclick.net,除非提供方也列入了那个名称。要不要因为一个主机而封掉整个域名,是你的决定,不该由数据源替你做主。 - 结果会短暂缓存——命中按提供方的 TTL 缓存,未命中缓存五分钟。
DNSBL 提供方
以下是仪表盘中可一键添加的选项。它们都仍在运营、免费,并且会应答自行递归的解析器、 无需注册——而 Town OS 正是这样一台设备。你也可以手动输入任何其他区域。
| 提供方 | 区域 | 针对的目标 |
|---|---|---|
| Spamhaus DBL | dbl.spamhaus.org | 使用最广泛的域名列表。收录在垃圾邮件中出现的域名,以及钓鱼、托管恶意软件和僵尸网络指挥控制的域名。五者之中覆盖面最广。 |
| SURBL | multi.surbl.org | 汇总出现在垃圾信息正文中的域名——钓鱼站点、恶意软件、被入侵的网站,以及被滥用的跳转服务和短链接。 |
| URIBL | black.uribl.com | 在垃圾邮件正文中出现的 URI。black 区域偏保守:只收录确实活跃于垃圾邮件中的域名,对误报的容忍度很低。 |
| NordSpam DBL | dbl.nordspam.com | 在垃圾邮件中出现的域名,数据来源独立于上述列表——主要适合作为第二意见,而不是主力列表。 |
| Spam Eating Monkey | uribl.spameatingmonkey.net | 从垃圾邮件 URI 中提取的域名,包括新注册的以及用完即弃的短命域名。 |
RBL 查询是如何工作的
IP 地址会被反转,然后拼接到提供方的区域之前。
用 zen.spamhaus.org 检查 192.168.1.100,实际发出的查询是
100.1.168.192.zen.spamhaus.org。IPv6 地址会展开为半字节并以同样方式反转。
有应答即代表已列入,NXDOMAIN 代表干净,缓存方式与 DNSBL 完全一致。
但要注意:RBL 区域只在反向 DNS 查询(in-addr.arpa 和
ip6.arpa)中查到的 IP 上生效,而普通的网页浏览几乎不会产生这类查询。
这些列表的存在意义,是让邮件服务器拒绝某个发起连接的发件方,那才是它们真正发挥价值的地方。
在家用路由器上,它们几乎等同于无操作——真正影响上网的是上面的 DNSBL 那一侧。
如果你在 Town OS 后面运行邮件服务,那就启用它们;否则值得你关注的是域名列表。
RBL 提供方
| 提供方 | 区域 | 针对的目标 |
|---|---|---|
| Spamhaus ZEN | zen.spamhaus.org | 一次查询涵盖四个 Spamhaus 列表:SBL(已核实的垃圾邮件源)、CSS(雪鞋式垃圾邮件团伙)、XBL(被入侵和感染的机器、开放代理)与 PBL(本就不该直接发信的动态和终端用户地址段)。 |
| SpamCop | bl.spamcop.net | 由 SpamCop 用户举报网络上报的 IP。举报停止后条目会自动过期,因此反应快、遗忘也快。 |
| PSBL | psbl.surriel.com | Passive Spam Block List:被垃圾邮件诱捕地址抓到的 IP,条目会自动过期。刻意保持保守,收录量不大。 |
刻意不提供的列表
有三个知名区域被刻意排除在一键添加列表之外,因为它们都会无声地失效—— 你会看到已配置的提供方,于是误以为自己受到了保护。如果你清楚自己在做什么,仍然可以手动添加。
- SORBS(
dnsbl.sorbs.net)——已于 2024 年 6 月 5 日停止服务,区域也被清空。它照样应答,只是永远不会列出任何东西:一个看起来像保护的永久空操作。 - Barracuda(
b.barracudacentral.org)——免费,但需要先注册发起查询的 IP。未注册的机器可能先能用一阵子,然后被无预警地切断。 - UCEPROTECT 第 2、3 级——按整个网段和 ASN 收录,因此你的 ISP 上出了一个坏邻居,整个 ISP 都会被拦下。
本地条目与白名单
- 本地条目(DNS → 黑名单)——手动拦截某个特定域名或 IP,并在旁边记录理由。域名条目会让正向查询返回
NXDOMAIN,且立即生效。这项检查排在所有外部提供方之前,也正是你在不订阅整个数据源的情况下、拦掉某一台特定主机的方式。 - 白名单(DNS → 白名单)——误报时的逃生出口。被列入白名单的名称会直接跳过整个基于名称的检查:既不与 DNSBL 提供方比对,也不与你的本地条目比对,更不会为它发出任何提供方查询。与拦截不同,放行是按后缀匹配的——放行
vendor.example也会一并放行cdn.vendor.example。条目只能是名称,绝不能是 IP,因此反向 IP 的 RBL 路径不受影响。
该抱什么预期
这些数据源是为电子邮件而生的,不是为广告拦截。它们在钓鱼、恶意软件和 垃圾邮件载荷域名上表现出色,在广告和追踪器上则平平——那是可下载的 hosts 式数据源的活儿, 而 Town OS 刻意不去摄取它们。没有任何定时抓取、没有任何解析、也没有任何在你背后进行的缓存。 如果你想让某个广告或追踪域名消失,把它加为本地条目即可。
在 DNS 中发布服务
默认情况下,每个已安装的软件包服务都会在其
网络的 TLD 下发布到 DNS 中,这样你就能按名称访问它。
在 DNS → 服务 中,你可以逐个切换服务是否发布——
未发布的服务照常运行,只是不能再按名称解析。每个服务的完全限定名称遵循
<name>.<repo>.<tld> 的形式(对默认网络而言,该 TLD 为
.home)。
信任证书颁发机构
Town OS 通过 rolodex 内置的证书颁发机构,为你的内部服务签发 TLS 证书。要让浏览器显示锁形图标而不是警告, 每台设备都需要先信任一次 Rolodex 根 CA。 获取它有三种方式 — 挑一个适合该设备的即可。
方式一:通过 DNS 获取(DNS 能用的地方都能用)
Rolodex 会把 CA 证书链发布在 DNS 里,因此任何能解析你区域的设备都可以取到
CA — 不需要访问注册门户。根证书和各区域的中间证书以
CERT 记录(RFC 4398)形式发布在 _ca.<zone>,
并在 _rolodex-ca.<zone> 提供分块的 TXT 回退:
# 查看已发布的 CA 记录
dig @<town-os-ip> CERT _ca.example.home
# 把根 CA 提取为 PEM 文件(根证书就是自签名的那张)
dig @<town-os-ip> +short CERT _ca.example.home
在浏览器中使用这些记录,最简单的办法是
Rolodex 浏览器扩展(位于 rolodex 仓库的
extension/ 目录,可通过 chrome://extensions 或 Firefox 的
about:debugging 以解压方式加载)。打开弹出窗口的
CA via DNS 部分,填入你的 DoH 地址
(https://<town-os-ip>/dns-query)和区域,它就会通过 DNS-over-HTTPS
取回证书链,优先使用 CERT 记录并在必要时自动回退到 TXT;如果你提供主机名,
它还会用已发布的 DANE TLSA 记录进行校验,并提供根证书、中间证书和完整链的 PEM 下载。
方式二:通过注册门户获取
在受信任的网络上,访问注册门户
(默认为 https://<town-os-ip>:8500)并点击
Download root CA (PEM)。上面提到的扩展以及
rolodex-ca-ui 本地控制台也能完成同样的事。
方式三:通过命令行获取
# 通过管理 CLI(打印根证书 + 中间证书的 PEM)
rolodex-dns-cli ensure-zone-ca --zone example.home
# 或者直接抓取门户的下载
curl -k https://<town-os-ip>:8500/api/ca -o rolodex-root-ca.pem 在设备上安装根 CA
拿到 rolodex-root-ca.pem 之后,把它加入信任存储:
| 平台 | 操作方式 |
|---|---|
| Firefox | 设置 → 隐私与安全 → 证书 → 查看证书 → 证书颁发机构 → 导入(勾选“信任由此 CA 来标识网站”) |
| Chrome / Edge(桌面版) | 使用操作系统的信任存储 — 按下面的系统条目安装,然后重启浏览器 |
| macOS | 双击 PEM 文件加入「钥匙串访问」,然后在 SSL 项下设为“始终信任” |
| Linux(Fedora/RHEL) | sudo cp rolodex-root-ca.pem /etc/pki/ca-trust/source/anchors/ && sudo update-ca-trust |
| Linux(Debian/Ubuntu) | sudo cp rolodex-root-ca.pem /usr/local/share/ca-certificates/rolodex.crt && sudo update-ca-certificates |
| Windows | 双击 → 安装证书 → 本地计算机 → “受信任的根证书颁发机构” |
| Android | 设置 → 安全 → 加密与凭据 → 安装证书 → CA 证书 |
| iOS | 打开 PEM 文件(隔空投送或邮件),安装描述文件,然后在设置 → 通用 → 关于本机 → 证书信任设置中启用 |
通过 Rolodex 的 ACME 端点签发的服务器会提供
叶证书 + 中间证书 的证书链,可用这张根证书验证。支持 DANE 的客户端
还可以用 rolodex 在签发时自动发布的 TLSA 记录来额外验证中间证书。
网络与远程访问
Town OS 把你的服务按网络分组。每台设备开箱就带有一个名为
home 的内置网络——它仅限局域网,解析 .home 下的名称,
并且没有隧道。要从家门之外安全地访问你的服务,请再创建别的网络:
每一个都是一层 WireGuard 覆盖网络,配有自己的 DNS 顶级域,
在 仪表盘 → 网络 中管理(仅限管理员)。
默认网络
- 始终存在,无法删除。
home网络会自动创建,除非你另选其他网络,否则软件包都会落在这里。 - 仅限本地。它没有 WireGuard 传输层——
.home名称只在你的局域网内解析,并且刻意不向远程对端开放。 - 使用
.home顶级域,该值来自dns_tld设置。
创建用于远程访问的网络
点击创建网络,给它起个名字(例如 office),
并可选地指定一个 TLD(默认与网络名相同)。随后 Town OS 会:
- 生成一个 WireGuard 接口,其覆盖网子网由
10.64.0.0/10范围内确定性地推导得出,本机占用.1地址。 - 占用该网络的 TLD(TLD 在每台设备上唯一——若创建的网络使用了别的网络已占用的 TLD,会被拒绝)。
- 在覆盖网上运行一个该网络专属的 DNS 解析器,使加入的设备能解析该网络的名称。
每一行上的远程访问开关用于启用或关闭该 WireGuard 接口。 关闭它会切断远程访问,但容器仍在运行,本地依然可以访问。
为设备办理登记
打开某个网络的对端对话框,输入设备名称,点击 添加对端。Town OS 会返回一份可直接导入的 WireGuard 配置—— 把它粘贴到手机或笔记本上的 WireGuard 应用中即可。
请立刻复制这份配置。它包含一把刚生成、从不留存的私钥—— 之后无法再取回,弄丢了就只能重新登记该设备。
生成的配置会把设备的 DNS 指向本机的覆盖网地址,因此隧道一旦建立, 你网络中的名称就会自动解析。普通设备请让运行 rolodex DNS 开关保持关闭;只有当某个对端自身运行着你希望转发过去的 rolodex DNS 服务器时才启用它。
仅限 WireGuard 的账户
如果你想让别人自行登记他们的设备,又不想把整个仪表盘交给他们,可以创建一个 仅限 WireGuard 的账户(创建用户界面上的一个复选框)。这类账户:
- 被限定在一个或多个特定网络上——只能在这些网络上登记对端,且永远不能用于
home网络。 - 默认拒绝:它可以认证、登记和续期自己的对端、获取 CA,但控制面上的其他事情一概不能做。
- 登记的是会过期的对端。每次登记都带有一个有效期(默认 2 小时,可在 设置 → WireGuard 对端有效期 中调整),必须续期才能保持连接;被弃用的设备会自动过期。由管理员添加的对端则是永久的。
查看与断开对端
网络页面上的已连接对端面板会列出所有网络中已登记的每一个对端, 并显示实时握手状态、覆盖网 IP、已传输数据量和到期时间。要强制切断某台设备, 使用断开连接——它会移除该对端、立即拆除隧道并吊销其密钥, 因此该设备在重新登记之前无法再连上来。
在指定网络上安装软件包
安装对话框(以及 页面 对话框)里有一个 网络 选择器。
你选择的网络决定了该服务的 DNS 名称和
TLS 证书——安装在 office 上的软件包会在
.office 下解析,证书也按该名称签发。非默认网络上的服务是双栖的:
对隧道对端解析为覆盖网地址,对本地客户端解析为局域网地址。把软件包重装到另一个网络上,
会把它的 DNS 和证书一并迁到那个网络的 TLD 下。
举个例子,把 Jitsi 安装在 TLD 为 fart 的网络上,它会发布在
jitsi.default.fart。运行起来之后,该服务会以可点击链接的形式出现在你的
仪表盘上,网络中的任何设备都能直接打开。
Android 上的 Town OS
Town OS Android 客户端会通过 WireGuard 把你的手机接入其中一个 网络,并配置好 DNS,让你的服务在任何地方都能按名称解析。 它是一个完整的 WireGuard 客户端,会替你把手机登记为对端;不需要手动复制任何配置文件。
安装应用
该应用以 APK 形式在项目的 发布页面 分发——它不在 Play 商店,也不在 F-Droid 上。
- 请下载 debug 版 APK(
town-os-client-<version>-debug.apk)。同一发行版中的-unsigned.apk无法直接安装——项目没有提供签名密钥,所以要拿的是 debug 版。 - 需要 Android 8.0(Oreo)或更高版本。
- 更愿意自己构建?克隆仓库,在手机通过 USB 连接并开启 USB 调试的情况下运行
make deps && make debug && make install。make help会列出所有目标。
安装方式有两种:直接在手机上装,或者从电脑通过 USB 装。前者除了手机什么都不需要。
直接在手机上安装
不用电脑、不用数据线,也不用开发者模式——开发者选项和 USB 调试只跟下面
adb 那条路有关。Android 唯一要的,就是允许安装一个并非来自应用商店的应用。
- 用手机浏览器下载 APK。打开发布页面,点击
town-os-client-<version>-debug.apk这个文件。浏览器会警告这类文件可能会损害你的设备——任何 APK 都会有这个提示,选择仍要下载。 - 从下载完成的通知、浏览器的下载列表,或者「文件」应用里打开它。
- 授权来源。第一次时,Android 会提示打开它的那个应用没有安装未知应用的权限,并给出一个设置按钮——点进去,为该应用(你的浏览器或文件管理器)打开允许来自此来源,然后返回。同一个开关也在设置 → 应用 → 特殊权限 → 安装未知应用里。
- 点击安装。Play 保护机制可能会提出扫描该应用,或提示它来自未知开发者——对于商店之外安装的应用,这是意料之中的。选择仍要安装。
- 安装完成后,从应用抽屉中打开 Town OS。
先从电脑把 APK 传过去——用 USB 文件传输、adb push,或任意一款同步应用——
再用文件管理器打开它,效果完全一样。
开启开发者模式
只有走 adb 这条路才需要它——直接从手机自己的下载列表安装 APK 并不需要。
adb install 和 make install 都要通过 USB 调试与手机通信,
而 USB 调试藏在 Android 隐藏的开发者选项菜单里。
- 打开设置 → 关于手机。三星手机上是设置 → 关于手机 → 软件信息。
- 连续点击版本号七次。Android 会倒数提示(「再点击 3 次即可成为开发者」),并在完成前要求输入你的 PIN、图案或密码。
- 屏幕会提示你现在已是开发者。此后开发者选项会出现在设置 → 系统里——有些手机把它放在设置的第一层,如果不在你预期的位置,就在设置里搜索一下。
- 打开开发者选项,启用 USB 调试。
- 把手机连到电脑上。Android 会弹出允许 USB 调试吗?对话框,并显示该电脑的密钥指纹——勾选始终允许使用这台电脑进行调试并确认。运行
adb devices,确认手机显示为device而不是unauthorized。
用完之后请把 USB 调试关掉。它会让你授权过的每一台电脑都能通过调试桥完全访问这台手机, 日常一直开着是没有必要的风险。
不用仓库,只用 adb 安装
通过 USB 安装并不需要客户端的源码树——make install 只是一层便利封装。
走 adb 这条路只需要发布出来的 APK,以及 adb 这个可执行文件本身,
它来自 Google 的 Android SDK Platform Tools。
- Ubuntu / Debian——
sudo apt install adb android-sdk-platform-tools-common。后一个软件包带的是 udev 规则,让你的普通用户账户不用 root 就能访问手机;装完之后把手机拔下来重新插一次。 - Arch / Manjaro——
sudo pacman -S android-tools android-udev,然后用sudo usermod -aG adbusers $USER把自己加进adbusers组,再注销并重新登录。 - macOS——
brew install --cask android-platform-tools。没有别的要配置的;macOS 不需要驱动就能跟手机通信。 - Windows——在 PowerShell 里运行
winget install Google.PlatformTools,或者下载下面的压缩包。大多数手机用 Windows 自动安装的驱动就能工作;少数厂商(三星、小米)要装它们自己的 USB 驱动,adb才看得到设备。 - 其他任何系统——从 Google 下载
platform-tools 压缩包,
解压到任意位置即可。没有什么需要安装的——直接在那个目录里运行
adb(Windows 上是.\adb.exe)。
用 adb version 确认装好了。然后从发布页面下载
debug 版 APK,按上面的说明开启 USB 调试,把手机连上,然后:
# 确认手机已连接并已授权
adb devices
# 安装 APK
adb install town-os-client-<version>-debug.apk 有几处容易卡住的地方:
no devices/emulators found——线缆只能充电,或者还没在手机上授权这台电脑。重新插拔线缆,然后留意手机屏幕上的允许 USB 调试吗?提示。- Linux 上出现
no permissions——上面那个 udev 规则软件包没装,或者手机在装它之前就已经插着了。装上它,重新插一次手机,然后运行adb kill-server && adb devices。 INSTALL_FAILED_UPDATE_INCOMPATIBLE——手机上已经装有一份用不同密钥签名的副本,如果你之前自己构建过就会这样。先卸载旧应用(在应用抽屉里长按它,或用adb shell pm list packages town查到包名后adb uninstall),再重新安装。- 原地升级——
adb install -r town-os-client-<version>-debug.apk会替换已有的安装并保留其数据,前提是两者都来自发布页面。 - 这一整套都不会往手机存储里留东西——
adb install一步完成传输和安装,事后不必再到文件管理器里去找那个文件。
连接之前
- 先创建一个网络。应用只能加入带有 WireGuard 覆盖层的网络——内置的
home网络仅限局域网,因此不会出现在列表中。参见创建用于远程访问的网络。 - 准备好一个管理员账户。登记设备属于管理员操作,所以应用需要用管理员凭据登录。
连接到网络
- 登录你的设备。输入它的地址——纯 IP、
IP:端口,或完整 URL(默认端口为5309)——以及你的管理员用户名和密码。 - 加入一个网络。应用会列出你可以加入的网络,并显示各自的 TLD、子网和对端数量。给设备起个名字(默认使用你手机的型号),然后点击加入。WireGuard 密钥对在手机上生成,只有公钥会发送给设备,因此你的私钥绝不离开手机。
- 连接。点击连接,并同意 Android 的连接请求(VPN)提示。应用会建立隧道,并把该网络的 TLD 装为搜索域,因此
gitea和gitea.default.<tld>都能解析。 - 现在你可以从任何地方按名称访问这个网络了。用断开连接关闭隧道,或用忘记此网络删除已保存的登记信息。
疑难排解
- 隧道已连上,但名称解析不了。通常的罪魁祸首是 Android 的严格私人 DNS。如果它被设为某个指定的服务商主机名,Android 会把所有查询都发到那里并忽略隧道,于是 Town OS 的名称就会「找不到」。请把设置 → 网络和互联网 → 私人 DNS 改为自动或关闭。应用会检测到这一情况,并显示带有直达该设置快捷方式的警告。(自动模式没有问题,不会触发警告。)
- 还是不行?在应用的 DNS 卡片中,把解析器覆盖设为该设备的局域网地址——应用会把它经由隧道路由,因此分离视图 DNS 依然有效。
- 设备重启后被登出。Town OS 会在重启时清除所有会话;重新登录即可。
用 U 盘镜像构建虚拟机
install
仓库中包含用于在虚拟机里启动 Town OS 的脚本,这在测试和开发时很有用——
既可以用你从源码构建的镜像,也可以直接用
已经写好的实体 U 盘。
make help 会列出所有目标和变量。
QEMU
make qemu-fg
镜像过期时会先重新构建,然后在前台启动一台 QEMU 虚拟机,
附带串口控制台、KVM 加速,以及四块用于存储测试的虚拟数据盘。
这是你首选的目标:你可以在终端里直接看着启动过程和安装程序,按 Ctrl-C 即可停止虚拟机。
如果你希望它在后台运行,请改用 make qemu
(稍后用 make serial 连接),
而 make rebuild-qemu 则一步完成停止、清理、重建和重新启动。
虚拟机接入 libvirt 的 default NAT 网络(virbr0),
并固定到 VM_IP(默认 192.168.122.50),
因此同时运行多台虚拟机时,请给每台分配各自的地址。
由于客户机处于 NAT 之后,VM_LAN=1(默认值)会把控制 API
(5309)、界面(80/443)、ssh
(2222)和 WireGuard 的 UDP 端口,从宿主机的局域网地址中继进客户机——
正是这一点让运行 Android 客户端的手机能够访问虚拟机。
设置 VM_LAN=0 可关闭这些中继。
从实体 U 盘启动(qemu-usb)
make qemu-usb USB_DEV=/dev/sdX
直接从已写好的实体 U 盘(而非构建出的镜像)在前台启动 QEMU——
很适合用来确认刚写好的 U 盘确实能启动。设备以只读方式打开(快照模式),
因此客户机的写入都会被丢弃,真实 U 盘绝不会被修改;四块虚拟数据盘仍会挂载以便测试存储。
这个目标不构建任何东西——请先克隆 install 仓库,用 install.sh 或
make flash 写一个 U 盘,然后把 USB_DEV 指向它。
在 x86_64 宿主机上,TARGET=aarch64(或 rpi)会以全系统模拟
方式启动该 U 盘,因此没有对应硬件也能测试异架构镜像。
停止与清理
# 停止虚拟机
make stop
# 停止虚拟机并删除镜像和虚拟磁盘
make clean 环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
IMAGE_SIZE | 12G | U 盘镜像构建时的稀疏大小——镜像随后会被收缩 |
VM_DISK_SIZE | 50G | 四块虚拟数据盘各自的大小(取自 town-os.yaml 中的 vm_disk_size) |
VM_MEMORY | 4G | 虚拟机内存 |
VM_CPUS | 4 | 虚拟机的 vCPU 数量——QEMU 默认的 1 个会让 rolodex 的工作线程池吃不饱 |
VM_BRIDGE | virbr0 | 用于联网的宿主机桥接接口 |
VM_NAME | town-os | 虚拟机名称,也用于查找和停止它 |
VM_IP | 192.168.122.50 | libvirt 的 DHCP 保留地址——同时运行多台虚拟机时请各自分配 |
VM_LAN | 1 | 把 NAT 客户机的端口中继到宿主机的局域网地址;0 表示关闭 |
USB_DEV | — | 用于 make flash(写入)和 make qemu-usb(只读启动)的实体块设备 |
串口控制台
# 连接到虚拟机的串口控制台
make serial
# 或者手动通过 socat 连接
socat -,rawer,escape=0x1d unix-connect:/tmp/town-os-serial.sock
# 用 Ctrl-] 断开 找到虚拟机
# 获取虚拟机的 IP 地址
make vm-ip
# 或者从宿主机通过 mDNS 连接
ssh root@town-os.local RAID 安装
磁盘配置由 ttyforce 在首次启动时交互式地完成。所有存储都使用 btrfs。
自动选择 RAID 级别
ttyforce 会检测可用磁盘,按传输类型(NVMe、SATA 等)和相近容量分组, 然后根据磁盘数量自动选择 RAID 级别:
- 1 块盘——single 模式(无冗余)
- 2 块盘——RAID 1(btrfs 镜像)
- 3 块及以上——RAID 5(btrfs 带校验的条带)
默认挂载点是 /town-os。ttyforce 会创建
@etc 和 @var 子卷,以配合 overlayfs 实现持久化。
磁盘检测
ttyforce 会自动检测可用的存储设备。它能识别 NVMe 硬盘、SATA/SAS 硬盘和 SD 卡, 同时排除启动用的 USB 设备以及任何可移动介质。这套检测逻辑确保 Town OS 绝不会碰它自己启动所用的那块盘。
叠加文件系统
无论使用哪种存储后端,Town OS 都通过 overlay 挂载来持久化
/var 和 /etc。下层来自 squashfs 根文件系统,
上层则位于 RAID/ZFS 存储上。这样一来,系统配置和服务数据能跨重启保留,
而基础操作系统始终保持不可变。
使用用户界面
Town OS 提供了一个简洁的网页仪表盘来管理你的服务器。启动完成后,
打开浏览器访问 Town OS 机器的 IP 地址,或者
http://town-os.local。
首次启动:创建账户
首次启动时,系统会提示你创建一个管理员账户。选好用户名和密码—— 这个账户对系统拥有完全控制权。
仪表盘
仪表盘展示系统总览——已安装的软件包以服务卡片形式呈现,附带状态指示、 快捷操作,以及一目了然的系统健康状况。
浏览与安装软件包
「软件包」视图让你可以搜索可用的软件包、查看详情,并在引导式提问的辅助下安装它们。 每个软件包的提问都会呈现为一张表单——填好主机名、端口和其他配置,然后点击安装。
管理服务
已安装的服务可以在「服务」视图中启动、停止和重启。 状态指示会显示每个服务是正在运行、已停止,还是处于错误状态。
查看日志
「日志」视图提供实时的 journal 输出,并支持过滤和 grep。 你可以按服务、优先级筛选,也可以搜索特定文本。
存储管理
查看和管理 btrfs 子卷、为每个软件包配置配额, 并监控整个存储池的磁盘使用情况。
监控
Town OS 内置了基于 Prometheus 和 Node Exporter 的监控, 可以持续跟踪系统指标、服务健康状况和资源使用。默认使用轻量的内置仪表盘, 也可以选择升级到 Grafana。
设置与审计日志
「设置」页面用于配置全系统的选项。「审计日志」会记录每一项管理操作—— 安装、卸载、服务状态变更和配置修改——因此你随时都清楚什么被改动过、何时改的。
托管静态网站
除了容器化的软件包,Town OS 还内置了静态网站托管, 它始终在线,并在 仪表盘 → 页面 中管理。每个页面都由入口后的 一个共享 Web 服务器提供服务,并像软件包一样,在其 网络的 TLD 下获得 DNS 名称和 TLS 证书。
内容来源
创建页面时,你需要选定一个名称、一个域名
(默认与名称相同)、一个网络(默认为 home),
以及三种内容来源之一:
- 上传归档——上传你网站的 tar 包。在你上传归档之前,页面会保持待处理状态。
- Git 仓库——从仓库 URL 克隆。你可以指定分支(默认为
main),这对发布在gh-pages上的站点很方便。 - 容器镜像——从 OCI 镜像中提取某个目录。
git 和容器类型的页面是异步准备的——表格中会显示 准备中… 标记,最终变为「活动」或「错误」。对 git 和容器页面, 用重新构建拉取最新内容;对归档页面,则改为上传新的 tar 包。
页面是如何提供服务的
每个页面都位于自己的存储子卷上,并直接通过 80 端口以 HTTP 提供服务, 而软件包服务则会把 80 端口重定向到 HTTPS。由于页面同样归属某个网络, 它的名称会在该网络的 TLD 下解析,可从你的局域网访问;如果该网络是 WireGuard 覆盖网,你已登记的远程设备也能访问。
更新 Town OS
Town OS 可以在仪表盘上就地更新核心服务。在 系统管理页面,刷新核心服务按钮会拉取最新的容器镜像 并重启每一个核心服务——其中也包括系统控制器本身,这正是这台设备自我更新的方式。
刷新过程中会发生什么
- 镜像会按依赖顺序拉取并重启服务:先是系统控制器(这样在它重启自己之前,新镜像已经就绪),然后是 DNS,最后是其余部分。
- 控制器重启时你的登录会话会被有意重置,因此刷新进行期间,仪表盘会暂缓平时的会话检查,而不是把你弹回登录页。
- 进度以五阶段步骤条呈现:启动系统控制器 → 启动 DNS → 启动系统服务 → 重启软件包(每个已安装的软件包一行)→ 就绪。
- 刷新完成后,仪表盘会显示一个重新加载按钮,而不是在你操作时自作主张地刷新页面。
正常启动时的配置界面上也会出现同样的五个阶段,因此你可以逐个软件包地观察启动进度。
制作软件包
Town OS 的软件包是描述如何运行容器化服务的 YAML 文件。 完整规范请参阅打包格式参考文档。 本节讲的是实际的工作流程。
仓库结构
软件包仓库就是一个带有 packages/ 目录的 git 仓库。
每个软件包各占一个子目录,其中存放带版本号的 YAML 定义:
my-packages/
packages/
my-app/
1.0.yaml
2.0.yaml 编写软件包定义
一个最小的软件包只需要 image 字段。典型的软件包还会包含描述、
网络、卷和面向用户的提问:
image: myapp:latest
description: My custom application
supplies: ["http"]
network:
external:
"@port@": "8080"
volumes:
data:
mountpoint: /app/data
quota: 5gb
questions:
port:
query: "What external port should this app use?"
type: port
default: "9000"
notes:
URL:
value: "http://@LOCAL_EXTERNAL_HOST@:@port@"
type: url
除了自由文本和 port,提问还支持几种类型:secret
(留空时自动生成)、boolean(渲染为复选框)、
oauth(一个连接按钮,会走服务商的设备流来获取令牌),
而且任何提问都可以标记为 optional: true 以允许留空。
完整列表见打包格式参考中的提问一节。
安装时,操作者还会选择该软件包在哪个网络上提供服务。
模板系统
使用 @variable@ 语法来引用提问的回答和内置变量
(@LOCAL_EXTERNAL_HOST@、@LOCAL_INTERNAL_HOST@)。
模板可用于环境变量、端口映射、配额和说明项的取值中。
本地测试
用开发环境来测试你的软件包。
把仓库放在磁盘上,通过界面或 repositories.json 添加它,
然后安装你的软件包。开发环境提供了完整的 Town OS 栈以供测试。
添加仓库
可以通过界面(软件包 → 仓库 → 添加仓库)把你的软件包仓库
添加到 Town OS,也可以直接编辑 repositories.json:
[
{"name": "default", "url": "https://github.com/town-os/default-packages"},
{"name": "my-packages", "url": "https://github.com/myuser/my-packages"}
] 可以自托管的东西
Town OS 天生就能运行任何以容器镜像形式发布的软件。下面是一些可以让你上手的点子—— 其中不少已经收录在 默认软件包仓库中, 而任何容器镜像都可以用一份简单的 YAML 定义打包进来。
影音娱乐
- Plex / Jellyfin——把你的电影和剧集库串流到任何设备
- Navidrome——个人音乐流媒体服务器
- Calibre-web——管理和阅读你的电子书收藏
代码与协作
- Gitea / Forgejo——轻量的自托管 Git
- GitLab——完整的 DevOps 平台
- Nextcloud——文件、日历、通讯录等等
- Wiki.js / BookStack——文档与知识库
沟通
- Jitsi Meet——私有视频会议
- Matrix / Synapse——联邦式加密聊天
- Mattermost / Rocket.Chat——团队沟通
游戏服务器
- Valheim、Minecraft、Terraria、Satisfactory 专用服务器
- 通过 GloriousEggroll 容器支持 Proton/Steam
- 任何以 Linux 可执行文件或容器形式发布的游戏服务器
家庭自动化
- Home Assistant——智能家居控制中枢
- Node-RED——可视化自动化流程
- Mosquitto——面向物联网设备的 MQTT 代理
隐私与安全
- Pi-hole / AdGuard——全网范围的广告拦截
- WireGuard / OpenVPN——用于远程访问的 VPN 服务器
- Vaultwarden——自托管的密码管理器
效率工具
- Paperless-ngx——文档管理与 OCR
- Immich——自托管的照片和视频管理
- Planka / Wekan——看板与项目管理
开发环境与测试套件
Town OS 的开发环境使用 Podman 容器在本地运行完整的技术栈。 这是测试改动、开发软件包和运行测试套件最快的方式。
先决条件
- Linux——btrfs 和 Podman 的 rootful 容器需要它
- Podman——容器运行时(rootful 模式,需要 sudo)
- Go 1.25+——用于后端 API 服务器
- Bun——用于前端构建和开发服务器
- btrfs-progs——用于存储管理
- QEMU——qemu-system-x86_64 与 qemu-img,用于支持虚拟机软件包
- libsystemd——用于 systemd 集成的开发头文件
- golangci-lint——用于 Go 代码检查
- Python 3——用于构建和测试脚本
启动开发环境
git clone https://gitea.com/town-os/town-os.git
cd town-os
make dev 这会启动带热重载的完整开发栈。就绪之后, 打开终端里输出的网址即可访问 Town OS 仪表盘。
开发环境命令
| 命令 | 说明 |
|---|---|
make dev | 启动完整的开发环境 |
make dev-stop | 停止所有开发容器 |
make dev-logs | 跟踪开发容器的日志 |
make dev-clean | 删除开发容器和卷 |
运行测试
| 命令 | 说明 |
|---|---|
make test | 运行单元测试 |
make test-integration | 运行集成测试(需要特权 Podman) |
make test-ui-integration | 运行界面集成测试 |
make test-full | 运行全部测试(单元 + 集成 + 界面) |
make auto-test | 监听改动并自动重跑测试 |
集成测试
集成测试在一个特权 Podman 容器中运行,该容器提供真实的 btrfs 文件系统、 systemd,以及 Podman-in-Podman。这确保测试走的是与生产环境相同的代码路径。 测试容器是一次性的——每次测试都会新建,结束后清理掉。
报告缺陷
发现哪里坏了?一份好的缺陷报告能让问题更快被修好。 下面介绍如何收集所需信息,并提交一份有效的报告。
用 API 收集日志
Town OS 通过它的 REST API 对外提供 journal 日志。 你可以用 Claude Code 连接该 API,并生成一份近期错误的摘要:
# 从 Town OS 获取 error 优先级的 journal 条目
curl -s http://town-os.local:5309/api/systemd/logs/tail?priority=err | jq . 或者用 Claude Code 交互式地总结错误:
# Claude Code 提示词示例:
"Connect to the Town OS API at http://town-os.local:5309
and fetch the last 100 error-priority journal entries from
/api/systemd/logs/tail. Summarize the errors, group them
by service, and suggest likely causes." 提交 issue
请在 Town OS 的 Gitea 实例上提交 issue: gitea.com/town-os/town-os/issues。
应当包含哪些内容
- 日志摘要——错误日志输出,或 Claude Code 生成的近期错误摘要
- 复现步骤——你是怎么一步步触发这个问题的
- Town OS 版本——启动界面上显示的构建日期或提交哈希
- 存储后端——btrfs、btrfs-mdadm 还是 ZFS,以及硬盘数量
- 运行环境——实体硬件还是 QEMU;内存和磁盘容量