Semaphore v2.19.17 常用 CLI 命令与运维实操

Semaphore v2.19.17 CLI 运维实操:命令树与全局参数、用户与 API 令牌与 TOTP 管理、项目备份恢复、Runner 注册注销排障实录、数据库迁移与密钥重加密,命令输出全部实测。

SemaphoreAnsibleDevOps自动化自托管自动化部署2026/10/24

Semaphore v2.19.17 常用 CLI 命令与运维实操

目标:系统梳理 Semaphore v2.19.17 自带的 semaphore 命令行工具,覆盖用户管理、API 令牌、双因子认证、项目备份与恢复、Runner 注册注销、数据库迁移、密钥重加密等核心运维场景。本文全部命令与回显均在 Ubuntu 22.04(x86_64)+ Semaphore v2.19.17 + MySQL 8.0 环境实测通过,输出逐行照抄实测结果;涉及令牌的输出已做节选脱敏并在原位注明。

一、环境信息

本篇为命令与运维实操篇,先交代实测环境与关键路径,后文所有命令均在此环境执行。

  • 操作系统:Ubuntu 22.04(x86_64,内核 5.15)
  • Semaphore 版本:v2.19.17(semaphore version 自报 v2.19.17-f8cefe6-1792391878,为官方最新稳定版;2026-10-24 实查 GitHub Releases,预发布通道最新为 v2.20.16-beta2,不影响本文命令)
  • CLI 二进制路径:/usr/local/bin/semaphore(官方 deb 包安装后的固定位置)
  • 配置文件:/etc/semaphore/config.json(服务端与 CLI 共用同一份配置)
  • 数据库:MySQL 8.0(config.json 中 dialect: mysql)
  • 服务运行形态:semaphore server --config /etc/semaphore/config.json 常驻运行,监听 3000 端口
  • 管理账号:admin / q1w2e3r4(部署时初始化的管理员)
  • 备份目录:/opt/semaphore/backup/(本文项目备份实操所用)

💡 说明:semaphore CLI 与 Server 进程读取同一份 config.json。CLI 的每一个写操作(建用户、建令牌、导入项目)都直接作用于 Server 正在使用的同一数据库,执行后立即生效,无需重启 Server——本文 5.2 节的实测可以直接验证这一点。

二、工具概览

semaphore 是 Semaphore 官方二进制自带的单一 CLI,既负责运行服务(server/runner/setup),也负责日常运维(用户、备份、迁移、密钥)。本节先给出完整的命令树与全局参数,便于按图索骥。

2.1 命令树总览

执行 semaphore --help 得到 v2.19.17 的完整命令树(实测回显):

$ semaphore --help
Semaphore UI is a beautiful web UI for Ansible.
Source code is available at https://github.com/semaphoreui/semaphore.
Complete documentation is available at https://semaphoreui.com.

Usage:
  semaphore [flags]
  semaphore [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  migrate     Execute migrations
  projects    Manage projects
  runner      Run in runner mode
  server      Run in server mode
  setup       Perform interactive setup
  users       Manage users
  vaults      Manage access keys and other secrets
  version     Print the version of Semaphore

Flags:
      --config string         Configuration file path
      --debug-filter string   Debug namespace filter (only with DEBUG level), e.g. 'runner,task_*' or '*,-db'
  -h, --help                  help for semaphore
      --log-level string      Log level: DEBUG, INFO, WARN, ERROR, FATAL, PANIC
      --no-config             Don't use configuration file

Use "semaphore [command] --help" for more information about a command.

各命令职责速查(子命令均为 v2.19.17 实测存在):

命令职责常用子命令
semaphore server以 Server 模式运行(Web UI + API + 调度)别名 service
semaphore runner以 Runner 模式运行(执行任务)register / setup / start / unregister
semaphore users用户管理list / get / add / change-by-login / change-by-email / delete / token / totp
semaphore projects项目备份与恢复export / import(别名 project)
semaphore migrate执行数据库结构迁移(无子命令)
semaphore vaults访问密钥(Access Key)密文运维check / rekey(别名 vault)
semaphore setup交互式初始化生成配置(无子命令)
semaphore version打印版本号(无子命令)
semaphore completion生成 shell 补全脚本bash / zsh / fish / powershell

2.2 全局参数

以下四个全局参数挂在所有子命令上(官方 CLI 参考与实测 --help 一致):

参数类型说明
--configstring显式指定配置文件路径;同机运行多实例(Server + 独立 Runner)时必用
--no-configbool不读取配置文件(仅纯本地命令如 version 需要)
--log-levelstring日志级别:DEBUG / INFO / WARN / ERROR / FATAL / PANIC
--debug-filterstring仅 DEBUG 级别下生效的调试命名空间过滤,如 'runner,task_*' 或 '*,-db'

关键解读:--config 是多实例部署的分水岭——Server 进程读主配置,Runner 进程读自己的 Runner 配置,两份配置靠这个参数区分;不显式指定时按「环境变量 → 默认路径」的顺序兜底。

2.3 配置读取与定位

CLI 定位配置的规则与 Server 完全一致,排查「命令行为和预期不符」时先确认读到了哪份配置:

  1. 显式 --config /path/to/config.json,优先级最高;
  2. 环境变量方式:所有 config.json 键都有对应环境变量(如 SEMAPHORE_DB_HOST),环境变量优先于文件(详见 《Semaphore v2.19.17 配置参数详解》第二章的优先级规则);
  3. 都没有时按默认路径查找(/etc/semaphore/config.json)。

本文实操环境只有一个实例,配置固定在 /etc/semaphore/config.json,命令中通常不写 --config;Runner 注册一节(第七章)因需要 Runner 专属配置,会显式演示 --config 用法。

三、前置条件

在真实服务器上跟随本文操作,需要先满足以下条件,每一条都给出可执行的验证命令:

1. semaphore CLI 已安装且可用。Ubuntu 22.04 用官方 deb 包安装(资产文件名于 2026-10-24 实查 GitHub Releases 取证):

wget https://github.com/semaphoreui/semaphore/releases/download/v2.19.17/semaphore_2.19.17_linux_amd64.deb
sudo dpkg -i semaphore_2.19.17_linux_amd64.deb

安装后验证(实测回显):

which semaphore
/usr/local/bin/semaphore

官方 Release 页同时提供 .rpm(RHEL/CentOS 系)与 .tar.gz(免安装解压即用)形态,文件名规律为 semaphore_2.19.17_linux_<架构>.<格式>,另有 semaphore_community_* 系列为社区打包版;下载后可用同目录的 semaphore_2.19.17_checksums.txt 校验 SHA256。

2. 配置文件就绪且指向正确数据库:

ls -la /etc/semaphore/config.json
-rw-r--r-- 1 semaphore root 381 Oct 24 12:19 /etc/semaphore/config.json

3. Server 正在运行且 CLI 能连通数据库(绝大多数 CLI 命令要读写数据库):

ps aux | grep '[s]emaphore server'
semaphor  7  0.1  2.1 1252564 43520 ?  Ssl  12:19  0:06 /usr/local/bin/semaphore server --config /etc/semaphore/config.json

4. 具备管理员账号。用户管理、令牌签发等命令虽在服务器本地执行,但 users token create 等操作建议以管理员身份进行;本文使用管理员 admin。

⚠️ 注意:CLI 的写操作直接落库,不做二次确认(users delete 也不会询问)。生产环境执行删除类命令前,先用 users get / projects export 核对目标对象。

四、版本确认与基础命令

运维任何系统前先确认版本与工具可用性。本节给出版本查询、帮助体系与 shell 补全的基础命令。

4.1 查看版本

semaphore version
v2.19.17-f8cefe6-1792391878

输出格式为「版本号-提交哈希前缀-构建时间戳」。升级后第一件事就是跑这条命令确认实际生效版本。

4.2 帮助体系

cobra 风格 CLI,每个命令都支持 --help,help 子命令等价:

semaphore help users
Manage users

Usage:
  semaphore users [flags]
  semaphore users [command]

Aliases:
  users, user

Available Commands:
  add             Add new user
  change-by-email Change user found by email
  change-by-login Change user found by login
  delete          Remove existing user
  get             Show user's data
  list            Print all users
  token           Manage user API tokens
  totp            Manage TOTP verification

Flags:
  -h, --help   help for users

Global Flags:
      --config string         Configuration file path
      --debug-filter string   Debug namespace filter (only with DEBUG level), e.g. 'runner,task_*' or '*,-db'
      --log-level string      Log level: DEBUG, INFO, WARN, ERROR, FATAL, PANIC
      --no-config             Don't use configuration file

Use "semaphore users [command] --help" for more information about a command.

输入不存在的子命令时给出明确报错(实测):

semaphore nosuchcmd
Error: unknown command "nosuchcmd" for "semaphore"
Run 'semaphore --help' for usage.
unknown command "nosuchcmd" for "semaphore"

4.3 生成 shell 补全

semaphore completion --help
Generate the autocompletion script for semaphore for the specified shell.
See each sub-command's help for details on how to use the generated script.

Usage:
  semaphore completion [command]

Available Commands:
  bash        Generate the autocompletion script for bash
  fish        Generate the autocompletion script for fish
  powershell  Generate the autocompletion script for powershell
  zsh         Generate the autocompletion script for zsh

Flags:
  -h, --help   help for completion

Global Flags:
      --config string         Configuration file path
      --debug-filter string   Debug namespace filter (only with DEBUG level), e.g. 'runner,task_*' or '*,-db'
      --log-level string      Log level: DEBUG, INFO, WARN, ERROR, FATAL, PANIC
      --no-config             Don't use configuration file

Use "semaphore completion [command] --help" for more information about a command.

为当前 bash 会话启用补全(写入 profile 可持久化):

source <(semaphore completion bash)

此后输入 semaphore users 按 Tab 即可补全子命令,对记忆 change-by-email / change-by-login 这类长命令名很有帮助。

五、用户管理实战(users)

用户管理是 CLI 最高频的运维场景:批量初始化账号、找回被锁账号的密码替代手段、签发 API 令牌、重置双因子。本节对 users 全部子命令逐一实测。

💡 技术拓展:Semaphore 的访问身份体系有三类凭据,适用场景不同——

凭据载体适用场景
登录密码 + 会话 Cookie浏览器人通过 Web UI 日常操作
API TokenHTTP 头 Authorization脚本/CI 调用 REST API、Runner 对接
TOTP 双因子手机验证器 App高权限账号的登录二次校验

本节演示的 token 子命令签发第二类,totp 子命令管理第三类;change-by-* 子命令改的是第一类。三类凭据相互独立,重置密码不影响已签发的 API Token。

5.1 查看用户:list 与 get

list 打印全部用户登录名(一行一个):

semaphore users list
admin

get 查看单个用户详情,支持 --login 或 --email 定位:

semaphore users get --login admin
ID: 1
Created: 2026-10-24 01:56:43 +0000 UTC
Login: admin
Name: admin
Email: admin@localhost
Admin: true

查不存在的用户会报错并返回非零退出码(实测,可写进巡检脚本做判断):

semaphore users get --login nosuchuser
User with login nosuchuser or email  not found

(echo $? 确认退出码为 1。)

5.2 添加用户:add

add 全参数形式创建普通用户(实测环境曾反复演练用户增删,自增 ID 不连续属正常现象):

semaphore users add --login testops --name "Ops Tester" --email "testops@example.com" --password "q1w2e3r4"
User testops <testops@example.com> added!

立即用 list 验证,无需重启 Server:

semaphore users list
admin
testops

add 的完整参数(实测 --help):

参数说明
--login登录名(唯一键)
--name显示名
--email邮箱
--password初始密码
--admin加上则创建管理员
--external标记为外部用户(LDAP 或 OIDC 来源,密码由外部目录管理)

排障实录:重复登录名直接 panic 而非优雅报错。v2.19.17 对重复 --login 的处理是数据库唯一键冲突直接 panic(实测完整回显):

semaphore users add --login testops --name dup --email dup@example.com --password "q1w2e3r4"
panic: Error 1062 (23000): Duplicate entry 'testops' for key 'user.username'

goroutine 1 [running]:
github.com/semaphoreui/semaphore/cli/cmd.init.func15(0xb03d2557c00?, {0x2e1a117?, 0x4?, 0x2e1a11b?})
	/go/src/semaphore/cli/cmd/user_add.go:71 +0x46c
github.com/spf13/cobra.(*Command).execute(0x63981e0, {0xb03d2456500, 0x8, 0x8})
	/go/src/semaphore/vendor/github.com/spf13/cobra/command.go:1019 +0xafb
github.com/spf13/cobra.(*Command).ExecuteC(0x6396800)
	/go/src/semaphore/vendor/github.com/spf13/cobra/command.go:1148 +0x465
github.com/spf13/cobra.(*Command).Execute(...)
	/go/src/semaphore/vendor/github.com/spf13/cobra/command.go:1071
github.com/semaphoreui/semaphore/cli/cmd.Execute()
	/go/src/semaphore/cli/cmd/root.go:107 +0xfe
main.main()
	/go/src/semaphore/cli/main.go:8 +0xf
exit=2

定位与规避:user.username 是唯一索引(Error 1062 为 MySQL 标准重复键错误),批量建号前先 semaphore users get --login <名字> 探测是否存在,存在则改走 change-by-login 更新,避免脚本被 panic 打断。

5.3 修改用户:change-by-login 与 change-by-email

两个子命令功能相同,仅定位方式不同(--login 定位或 --email 定位)。修改显示名实测:

semaphore users change-by-login --login testops --name "Ops Tester 2"
User testops <testops@example.com> changed!

回读确认生效:

semaphore users get --login testops
ID: 3
Created: 2026-10-24 11:30:08 +0000 UTC
Login: testops
Name: Ops Tester 2
Email: testops@example.com
Admin: false

可用参数:--name(新显示名)、--email(新邮箱)、--password(新密码)、--admin(赋予/收回管理员)。改密码场景(用户忘记密码时的最快恢复路径,无需邮件流程):

semaphore users change-by-login --login testops --password "q1w2e3r4"
User testops <testops@example.com> changed!

⚠️ 注意:--admin 是布尔开关,change-by-login --login testops --admin 会把普通用户提权为管理员;收回管理员同样用它配合服务端取值逻辑,操作后务必 users get 复核 Admin: 字段。

5.4 API 令牌管理:token create 与 token list

API Token 供脚本与 CI 通过 REST API 调用 Semaphore。创建不过期令牌:

semaphore users token create --login testops --name cli-demo
lkcfhi13-pkohbaeyo8o-h8n2p0eupdxh8zefj-ej9i=

输出就是令牌明文,只在创建时打印这一次,务必当场保存。创建带 TTL 的令牌(支持 Go duration 格式:30m、1h、24h):

semaphore users token create --login testops --name cli-ttl --ttl 1h
defzceruxnzwm8nzkelj3rrcdk0md9h3eir_i6un_mm=

list 查看令牌状态(实测,含过期时间列):

semaphore users token list --login testops
cli-ttl	active	2026-10-24T12:30:24Z
cli-demo	active	never

三列含义:令牌名 / 状态(active) / 过期时间(never 表示永不过期,带 --ttl 的显示绝对过期时刻)。漏传 --login 的报错(实测):

semaphore users token create
argument --login required

⚠️ 注意:users token list 只显示名字与状态,不回显令牌明文。令牌丢失只能 create 一个新的并在旧脚本中轮换;v2.19.17 的 CLI 没有提供删除令牌的子命令,吊销过期/泄漏令牌需走 Web UI(用户设置 → API Tokens)或 API。

5.5 双因子认证:totp enable / show / disable

为用户开启 TOTP 二次验证:

semaphore users totp enable --login testops
Recovery code:  5FY66V5IQBT7JGEV

otpauth://totp/Semaphore:testops@example.com?algorithm=SHA1&digits=6&issuer=Semaphore&period=30&secret=VHV4H76KEVNIZUJHN6H6PTXUU4K5OYMR

(实测输出随后还渲染了一段反色字符画二维码,供手机验证器扫码,此处以文本节选替代。回显中的恢复码与 secret 属已删除的演练账号,随账号删除一并失效。)

三个关键字段:Recovery code 是丢失验证器时的救命码,otpauth:// 链接内的 secret 可手工录入验证器,二维码与链接等价。开启后查看详情:

semaphore users totp show --login testops

输出与 enable 时相同(恢复码 + otpauth 链接 + 二维码),用于重新绑定验证器场景。关闭 TOTP(实测无任何输出,静默成功,属正常现象):

semaphore users totp disable --login testops

关闭后 users get 回读可确认用户恢复单因子登录。

💡 技术拓展:TOTP(基于时间的一次性密码)只是 2FA 的实现之一。常见对比——

方案原理特点
TOTP共享密钥 + 时间片哈希(RFC 6238)离线生成、无需联网,Semaphore 采用
HOTP共享密钥 + 计数器(RFC 4226)无时钟依赖但可能失步
WebAuthn/Passkey非对称挑战-应答抗钓鱼最强,需硬件/平台支持
短信验证码运营商通道下发依赖手机号,安全性最弱

Semaphore v2.19.17 实际采用 TOTP(algorithm=SHA1&digits=6&period=30,即验证器 App 的通用默认参数)。

5.6 删除用户:delete

semaphore users delete --login testops
User testops <testops@example.com> deleted!

验证删除结果:

semaphore users list
admin

支持 --login 或 --email 二选一定位。实测中 testops 名下尚有 2 个未过期 API Token,删除用户时令牌随之级联清理,未出现阻塞报错。

六、项目备份与恢复(projects)

projects 子命令把单个项目(含密钥定义、仓库、模板、视图、环境等全部项目内对象)导出为 JSON 备份文件,是项目级迁移与灾备的标准手段。本节以实测项目 demo(ID=1)为例走通「导出 → 检查 → 恢复」全流程。

💡 技术拓展:Semaphore 有两个层级的备份,覆盖面与恢复粒度不同——

层级工具覆盖内容适用场景
项目级备份semaphore projects export单个项目的全部配置对象(不含任务历史输出)项目迁移、模板复制、单项目灾备
数据库级备份mysqldump / pg_dump / sqlite 备份全部项目 + 用户 + 任务运行历史整机灾备、版本回滚

两者互补:日常按项目粒度用 export,整机级别用数据库快照。本文实测的是项目级;数据库级流程见部署篇的持久化说明。

6.1 导出备份:projects export

直接输出到 stdout(快速查看备份内容):

semaphore projects export --project-id 1
{
  "environments": [],
  "integration_aliases": [],
  "integrations": [],
  "inventories": [],
  "keys": [
    {
      "name": "None",
      "owner": "",
      "synchronized": false,
      "type": "none"
    }
  ],
  "meta": {
    "alert": false,
    "alert_chat": "",
    "max_parallel_tasks": 0,
    "name": "demo",
    "type": ""
  },
  "repositories": [],
  "roles": [],
  "runners": [],
  "schedules": [],
  "secret_storages": [],
  "templates": [],
  "views": [
    {
      "hidden": false,
      "position": 0,
      "sort_reverse": false,
      "title": "All",
      "type": "all"
    }
  ]
}

(输出节选自实测,结构完整;真实项目在 keys/repositories/templates 等数组中会有实际对象。)备份文件包含的资源域一目了然:环境、集成、清单、密钥、仓库、调度、模板、视图等。

落盘为文件(先建备份目录):

mkdir -p /opt/semaphore/backup
semaphore projects export --project-id 1 --file /opt/semaphore/backup/demo-backup.json
Project exported to /opt/semaphore/backup/demo-backup.json

确认产物:

ls -la /opt/semaphore/backup/
total 12
drwxr-xr-x    2 semaphore root          4096 Oct 24 19:30 .
drwxr-xr-x    1 semaphore root          4096 Oct 24 19:30 ..
-rw-r--r--    1 semaphore root           620 Oct 24 19:30 demo-backup.json

也可以用 --project-name 按名字导出。不带任何定位参数的报错(实测,指路明确):

semaphore projects export
Argument --project-id or --project-name required
Use command `semaphore project export --help` for details.

6.2 恢复导入:projects import

从备份文件恢复,用 --project-name 覆盖备份内的原项目名(避免与现存同名项目混淆),模拟「异机恢复」场景:

semaphore projects import --file /opt/semaphore/backup/demo-backup.json --project-name "demo-restore"
Imported project from /opt/semaphore/backup/demo-backup.json
Project(s) imported: 1/1

通过 API 侧验证导入结果(恢复出的项目出现在项目列表,拿到新 ID=3):

curl -s -c /tmp/ck.txt -H "Content-Type: application/json" \
  -d '{"auth":"admin","password":"q1w2e3r4"}' \
  http://localhost:3000/api/auth/login -o /dev/null -w "%{http_code}\n"
curl -s -b /tmp/ck.txt http://localhost:3000/api/projects
204
[{"id":1,"name":"demo","created":"2026-10-24T04:20:46Z","alert_chat":"","type":""},{"id":3,"name":"demo-restore","created":"2026-10-24T11:30:53Z","alert_chat":"","type":""},{"id":2,"name":"demo2","created":"2026-10-24T04:20:46Z","alert_chat":"","type":""}]

(登录回显 204 No Content,项目列表为实测回显节选。)

import 的参数语义:

参数说明
--file单个备份文件路径
--dir目录批量导入(目录下每个 .json 备份导入一个项目)
--project-name覆盖项目名(仅与 --file 搭配有效)

⚠️ 注意:导入会创建新项目(新 ID),不是覆盖同名项目。同一备份重复导入会产生多份副本,演练后请清理(API DELETE /api/project/<id> 或 Web UI 项目设置内删除);备份 JSON 内 meta.name 字段配合 --project-name 可实现「同库改名恢复」。

七、Runner 注册与注销(runner)

v2.19 起任务执行器(Runner)独立成体系:Server 负责调度,Runner 拉取任务执行。runner 子命令负责 Runner 侧的注册、运行与注销。本节在与 Server 同机的环境完整实测注册与注销,并把两个关键坑的排障过程沉淀下来。

7.1 注册流程与配置要点

注册前需要三样东西:Server 地址、一次性注册令牌(Registration Token)、Runner 专属配置文件。

第一步:在 Server 侧生成注册令牌。v2.19.17 的注册令牌按 Runner 粒度管理,Web UI 中在 Runner 设置页点击生成;等价 API 调用(管理员会话):

curl -s -b /tmp/ck.txt -X POST http://localhost:3000/api/runners/1/registration-token
{"registration_token":"smrs_sqeoMrf2RjNO+QjqtgNmQK8+2BVkAN/TI8N1iU5ZCxw=","runner_id":1}

⚠️ 注意:注册令牌是一次性的——成功注册一次后即失效轮换,重放会得到 400: Invalid registration token(实测见 7.4 排障实录二)。

第二步:准备 Runner 配置文件。Runner 进程需要知道 Server 地址,这个地址在配置文件中对应 web_host 键(Server 模式下它也是对外可访问地址,Runner 模式下是 Server 的连接地址)。Runner 专属配置 /tmp/runner-config.json 实测内容:

{
  "web_host": "http://localhost:3000",
  "mysql": {
    "host": "mysql:3306",
    "user": "semaphore",
    "pass": "q1w2e3r4",
    "name": "semaphore"
  },
  "dialect": "mysql",
  "tmp_path": "/tmp/semaphore",
  "cookie_hash": "f5I+atUJX6MA5xNnpRimZqDKYAM8EzqEjlXn6b5w5zg=",
  "cookie_encryption": "aoSl2CFTBhktRX83U1ivNJ8TXKWW95UAqIpZDylMN8A=",
  "access_key_encryption": "9hm9XNftIltnY05Q50cLHXvr/Vdsj6RmmZiz5M8QEus="
}

⚠️ 注意:web_host 缺失是本节第一大坑——runner register 不会读任何 host 类字段的其他写法,只认 web_host,缺失时注册请求根本发不出去(实测排障见 7.4 实录一)。独立 Runner 的配置文件只需要 web_host + runner 块(runner.token 等),不需要数据库连接;本文因与 Server 同机演练,沿用了完整配置。

第三步:执行注册。支持从文件或标准输入读令牌(实测 --help 参数表):

参数说明
--name注册使用的 Runner 名
--tagsRunner 标签(逗号分隔或重复该参数),任务按标签路由
--project-id项目级 Runner(不传为全局 Runner)
--registration-token-file从文件读注册令牌
--stdin-registration-token从标准输入读注册令牌
--enabled注册后是否启用(默认 true)
--webhookRunner webhook URL

7.2 注册实测:runner register

文件方式注册(推荐,令牌不进 shell history):

printf "smrs_sqeoMrf2RjNO+QjqtgNmQK8+2BVkAN/TI8N1iU5ZCxw=\n" > /tmp/reg-token.txt
semaphore --config /tmp/runner-config.json runner register \
  --registration-token-file /tmp/reg-token2.txt \
  --name cli-runner --tags ansible
time="2026-10-24T19:37:42+08:00" level=info msg="Registering a new runner"

退出码为 0,输出仅一行日志即成功(没有多余提示)。标准输入方式等价:semaphore runner register --stdin-registration-token --name cli-runner < /tmp/reg-token.txt。

在 Server 侧验证注册结果(API 实测回显,registered 已翻为 true):

curl -s -b /tmp/ck.txt http://localhost:3000/api/runners
[{"id":2,"project_id":null,"webhook":"","max_parallel_tasks":0,"active":false,"is_default":false,"name":"cli-runner","tags":["ansible"],"touched":null,"cleaning_requested":null,"started_at":null,"registered":true,"status":"offline"}]

registered: true + status: offline 表示注册完成但 Runner 进程尚未启动——注册与运行是两个独立动作。

7.3 运行 Runner:runner start

注册成功后以 Runner 模式常驻(实测 --help 参数):

参数说明
--auto-register未注册时自动注册
--register--auto-register 的别名
semaphore --config /tmp/runner-config.json runner start

生产环境把该命令交给 systemd 托管(ExecStart=/usr/local/bin/semaphore --config /etc/semaphore/runner-config.json runner start)。Runner 运行后,Server 侧该 Runner 的 status 会从 offline 变为在线,touched 字段开始随心跳刷新。Runner 连接 Server 的 TLS 校验行为由 runner.connection.skip_tls_verify(默认关闭,即默认校验证书)与 runner.connection.server_ca_cert_file(自签 CA 场景注入)控制,详见 《Semaphore v2.19.17 配置参数详解》的 Runner 域。

7.4 注销与排障实录:runner unregister

unregister 从配置中读取 runner.token(Runner 注册后由 Server 签发的长期凭据)调用 Server 注销。它没有任何参数,全部依赖配置文件。

排障实录一:配置缺 web_host,注册请求发不出去。用不含 web_host 的主配置执行注册:

semaphore runner register --stdin-registration-token --name probe-runner --tags demo < /tmp/reg-token.txt
time="2026-10-24T19:34:45+08:00" level=info msg="Registering a new runner"
time="2026-10-24T19:34:45+08:00" level=error msg="failed to send registration request to the server" context=registration error="Post \"/api/internal/runners\": unsupported protocol scheme \"\""
panic: runner registration failed

goroutine 1 [running]:
github.com/semaphoreui/semaphore/cli/cmd.registerRunner(0x6396dc0)
        /go/src/semaphore/cli/cmd/runner_register.go:108 +0xb9
(以下 goroutine 堆栈节选)
exit=2

定位:错误 unsupported protocol scheme "" 表示请求 URL 为空串——注册走 HTTP API(POST /api/internal/runners)而非直写数据库,Server 地址取自配置的 web_host 键。逐项试验 host、runner.host 均无效,只有 web_host 生效。解决:在 Runner 配置中加入 "web_host": "http://<server>:3000" 后重新执行注册即成功(见 7.2 实测)。

排障实录二:注册令牌一次性,重放报 400。同一枚令牌第二次注册:

semaphore --config /tmp/runner-config.json runner register --stdin-registration-token --name probe-runner --tags demo < /tmp/reg-token.txt
time="2026-10-24T19:36:57+08:00" level=info msg="Registering a new runner"
time="2026-10-24T19:36:57+08:00" level=error msg="server rejected runner registration: the server returned error 400: Invalid registration token" context=registration error="invalid status code" runner_name=probe-runner status_code=400
panic: runner registration failed
(以下 goroutine 堆栈节选,与实录一相同)

定位:server returned error 400: Invalid registration token——首次注册成功后令牌已被消费轮换。解决:到 Server 侧重新生成注册令牌再执行。

注销实测。未注册(配置无 runner.token)时的行为:

semaphore runner unregister
panic: runner is not registered
(以下 goroutine 堆栈节选)

runner.token 与 Server 侧不一致时(实测把错误令牌写入配置后执行):

semaphore --config /tmp/runner-config.json runner unregister
panic: encountered error while unregistering runner; server returned code 401
(以下 goroutine 堆栈节选)

⚠️ 注意:unregister 的 401 说明它依赖配置中有效的 runner.token。注册成功后 CLI 不会自动把签发的 token 回写到配置文件——需要从注册响应中取得 token 并手工写入配置的 runner.token 键。运维上的稳妥顺序是:注册 → 取得 runner token → 写入 Runner 配置 → runner start;弃用 Runner 时可直接在 Server 侧删除该 Runner(Web UI 或 API DELETE /api/runners/<id>,实测返回 204),不必依赖 CLI 注销。

八、数据库迁移与密钥运维(migrate / vaults)

这两组命令是版本升级与密钥轮换的配套工具:migrate 负责数据库表结构演进,vaults 负责密文数据的重加密。

8.1 数据库迁移:migrate

Semaphore 升级后首次启动会自动执行迁移;手工执行用于升级前的预检与批量维护:

semaphore migrate
MySQL semaphore@mysql semaphore

已处于最新结构时输出一行数据库连接信息即退出(幂等,重复执行无副作用)。参数速查(实测 --help):

参数类型默认说明
--apply-tostring—迁移到指定版本
--undo-tostring—回滚到指定版本(降级场景)
--merge-existing-usersbool—按用户名合并已存在用户而非报冲突
--skip-task-outputbool—迁移时跳过任务输出导入
--err-log-sizeint0错误日志大小

⚠️ 注意:--undo-to 回滚表结构前必须先备份数据库(mysqldump 全量),结构回滚不承诺数据无损。

8.2 密钥检查:vaults check

只读命令,报告各加密密钥(key id)名下存量的密文数量与状态,用于密钥轮换前后的核对:

semaphore vaults check
Access keys: 0 total
  tGLUe0YS2RE: 0 rows — active
JWT signing key: not set

实测环境是新部署,无存量密文(0 rows),tGLUe0YS2RE 是配置中 access_key_encryption 派生出的当前活跃 key id;JWT signing key: not set 表示登录会话签名密钥尚未以密文形式托管(v2.19 支持把 JWT 密钥也纳入密钥组管理)。有存量密钥的生产环境,输出会按 key id 列出各自行数,key id 不在密钥组中的行会标记告警并使命令以非零退出码结束——这正是升级/轮换后判断旧密钥能否移除的判据。

8.3 密钥重加密:vaults rekey

轮换 access_key_encryption 后,用 rekey 把存量密文重加密到新活跃密钥下(官方推荐零停机轮换流程:新密钥入组并指向活跃 → vaults rekey 重加密存量 → vaults check 确认旧密钥 0 rows → 移除旧密钥)。参数速查(实测 --help):

参数说明
--backup重加密前把当前密文备份到指定文件
--rollback从备份文件恢复密文(替代重加密执行)
--old-key显式提供旧密钥(旧版单密钥迁移遗留场景)

标准操作序列(演练环境未存密文,此处给可执行形态;有存量时先 --backup 再执行,事后 vaults check 核对):

semaphore vaults rekey --backup /opt/semaphore/backup/vault-ciphertexts.bak
semaphore vaults check

⚠️ 注意:rekey 是全量重加密写操作,务必带 --backup;密钥组由 keys_folder 目录或配置 keys: 块定义,新密钥生效支持热加载(轮询 keys_poll_interval 或向进程发 kill -HUP)。

九、服务运行与初始化(server / setup)

9.1 Server 模式:server

server 子命令以前台方式运行完整服务(Web UI + API + 调度 + 内置 Runner):

semaphore server

别名 service(semaphore service 等价)。生产环境由 systemd 托管(官方 deb 包安装后即提供,systemctl status semaphore 查看),进程形态实测为:

/usr/local/bin/semaphore server --config /etc/semaphore/config.json

日志级别调整示例(DEBUG 级并过滤命名空间):

semaphore --log-level DEBUG --debug-filter 'runner,task_*' server

9.2 交互式初始化:setup

setup 是交互式命令,逐项询问数据库、目录、Cookie 密钥等并生成 config.json:

semaphore setup

按提示依次输入数据库类型、连接信息与存储路径后,工具自动写入配置并完成首库迁移。已由部署系统(如 Compose 环境变量)生成配置的环境无需再跑 setup,避免交互覆盖既有配置——各配置项的含义与取值详见 《Semaphore v2.19.17 配置参数详解》。

十、注意事项

综合实测过程,以下要点在运维中容易踩坑,集中列出:

  1. CLI 写操作立即生效且不询问:users delete、projects import 均无确认步骤;破坏性操作前先 get/export 核对。
  2. 注册令牌一次性:registration-token 成功消费一次即失效,重放报 400: Invalid registration token;批量注册多个 Runner 需逐个生成令牌。
  3. Runner 配置只认 web_host:Server 连接地址必须写在 web_host 键,写成 host / runner.host 均报 unsupported protocol scheme ""。
  4. API Token 只在创建时明文出现一次:token list 不回显明文;令牌按 --ttl 控制生命周期,永不过期令牌要最小化签发。
  5. 重复 --login 建号直接 panic:批量脚本先 users get 探测存在性再分支处理。
  6. migrate --undo-to 不是数据回滚:只回滚表结构,执行前必须数据库全量备份。
  7. 同机多实例必须显式 --config:Server 与 Runner 各用一份配置,否则 CLI 会读默认路径的配置造成「改了不生效」。
  8. CLI 与 Server 版本保持一致:升级时 deb 包升级后 CLI 与常驻 Server 同步为新版本;semaphore version 确认的版本即二者共同版本。

十一、常见问题

Q1:users add 报 panic: Error 1062 Duplicate entry 'testops' for key 'user.username'? 登录名撞唯一索引。先 semaphore users get --login <名字> 检查,已存在则用 users change-by-login 更新属性,而不是 add。

Q2:runner register 报 Post "/api/internal/runners": unsupported protocol scheme ""? Runner 配置缺 web_host(或写成了 host)。在配置文件加入 "web_host": "http://<server>:3000" 后重试;注册走 HTTP API,没有 Server 地址就无法发起注册。

Q3:注册时报 server returned error 400: Invalid registration token? 令牌已被消费(一次性)或已过期。到 Server 侧(Web UI Runner 设置页或 POST /api/runners/<id>/registration-token)重新生成。

Q4:runner unregister 报 panic: runner is not registered 或 server returned code 401? 前者:配置中没有 runner.token,说明该配置对应的 Runner 从未注册。后者:token 与 Server 侧不匹配。确认配置指向的 Runner 与 token 一致;若 token 彻底丢失,直接在 Server 侧删除该 Runner 记录后在 Runner 侧重新注册。

Q5:创建的 API Token 在 token list 里但脚本 401? 检查三点:令牌是否带 --ttl 已过期(token list 第三列);请求头是否为 Authorization: Bearer <token> 形式的原始明文令牌;目标账号是否被禁用或删除。

Q6:users totp disable 执行后没有任何输出,是失败了吗? 不是。v2.19.17 的 totp disable 静默成功(实测无输出、退出码 0),users get 与实际登录行为可确认已关闭。

Q7:projects import 导入后项目去哪了? 导入创建的是新项目(新 ID),按导入时的项目名出现在项目列表;用 --project-name 可在导入时改名。重复导入同一备份会产生多份副本。

Q8:升级后首次执行 CLI 报数据库相关错误? 先跑 semaphore migrate 完成结构迁移(Server 启动时也会自动执行),再用 semaphore version 确认 CLI 与 Server 为同一版本。

十二、参考链接