chore: local dev orchestration, CI for every subproject, manual release dispatch - #13
Conversation
Add scripts/dev.sh to run console, web and website in a single tmux session with start/stop/restart/status/logs/creds subcommands. Every subcommand returns immediately; logs are tee'd to scripts/.dev/. Port reclamation after stop only targets PIDs that both listen on the port and belong to the window process tree, so manually started instances are never killed. Service configuration goes through scripts/dev.env because the tmux server does not inherit the caller's environment. Workers are intentionally out of scope: their launch parameters differ per implementation and some scenarios need several instances at once.
package-release now triggers via workflow_dispatch with a version input instead of a tag push. The tag filter used to enforce the version shape, so an explicit regex check replaces it; the tag itself is created from the selected branch by the draft release step. Add deploy-website for deploying the site without cutting a release, with a mode input selecting between a full deploy and a version upload that does not shift traffic.
Nothing ran on pull requests before. Add six jobs covering console, the Go and Rust workers, web and website, gated by dorny/paths-filter so a frontend change does not trigger a Rust build. Changes under api/ fan out to every backend: the Go modules pull it in through a replace directive and worker-boxlite compiles api/proto in build.rs. The ci-ok job aggregates results and treats skipped jobs as success, so it can be the single required check in branch protection. web gains format:check, since format writes files in place and cannot be used as a gate.
The boxlite-shared build script shells out to protoc; our own build.rs uses protoc-bin-vendored, so this only shows up on a machine without it.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7a8a0d39ae
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| console: | ||
| - *shared | ||
| - 'console/**' |
There was a problem hiding this comment.
Flatten the shared path rules before passing them to paths-filter
The *shared alias is inserted as a single sequence item, so console expands to a nested array such as [['api/**', '.github/workflows/ci.yml'], 'console/**'], rather than a list of rules. dorny/paths-filter expects each item to be a glob string or predicate object according to its filter syntax, and rejects this nested sequence while parsing the filters. Consequently, the changes job fails on every PR/push and none of the six checks can run; repeat the shared strings in each filter or use YAML merge-compatible mappings instead.
Useful? React with 👍 / 👎.
| # 从扁平 JSON 日志行里取一个字符串字段 | ||
| json_field() { | ||
| printf '%s' "$1" | sed -n "s/.*\"$2\":\"\([^\"]*\)\".*/\1/p" | ||
| } |
There was a problem hiding this comment.
Parse credentials when console uses text logging
When a developer enables the recommended CONSOLE_LOG_FORMAT=text setting from dev.env.example, the matching slog record contains fields like username=... rather than JSON fields like "username":"...". This JSON-only extraction therefore returns empty values, after which cmd_creds caches that unusable record even though the plaintext password is emitted only once; creds will keep printing blank credentials until the cache and database are manually removed. Support both configured log formats or capture the credentials independently of formatted log output.
AGENTS.md reference: AGENTS.md:L8-L8
Useful? React with 👍 / 👎.
| svc_port() { | ||
| case "$1" in | ||
| console) echo 8089 ;; | ||
| web) echo 5178 ;; | ||
| website) echo 5173 ;; |
There was a problem hiding this comment.
Derive managed ports from the development configuration
When dev.env overrides CONSOLE_HTTP_ADDR or CONSOLE_GRPC_ADDR, as the supplied example explicitly supports, the console listens on the configured ports while this script continues checking only 8089/50051. status consequently reports the managed console as not listening, and stop cannot perform its documented fallback cleanup if a go run child remains on either custom port. Load the configured addresses for status and reclamation, with these values only as defaults.
AGENTS.md reference: AGENTS.md:L8-L8
Useful? React with 👍 / 👎.
| permissions: | ||
| contents: read |
There was a problem hiding this comment.
Grant paths-filter permission to inspect pull requests
On pull_request runs, dorny/paths-filter obtains the changed-file list through the pull-request REST API, for which its permissions documentation requires pull-requests: read. Declaring only contents: read makes every unspecified GITHUB_TOKEN permission unavailable, so the changes job receives an API authorization failure on PRs even after the filter syntax is corrected; add the required read permission at workflow or job scope.
Useful? React with 👍 / 👎.
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: 'Release version, e.g. 0.7.2 or 0.8.0-rc.1 (tag is created from the selected branch)' |
There was a problem hiding this comment.
Create the release tag from the selected ref
When this workflow is dispatched from a non-default release branch, all checkout and build jobs use that selected ref, but softprops/action-gh-release is not given target_commitish. For a tag that does not yet exist, GitHub's create-release API defaults the tag target to the repository's default branch, so the published tag can point at different source code from the binaries and website built by this run despite the input description promising otherwise. Pass the selected commit SHA as target_commitish when creating the draft.
Useful? React with 👍 / 👎.
| # 服务命令包一层 tee:面板显示与日志落盘同源。 | ||
| # 前置 source dev.env:tmux server 环境与调用方隔离,配置只能在窗口内加载。 | ||
| wrapped_cmd() { | ||
| echo "set -a; [ -f '$ENV_FILE' ] && . '$ENV_FILE'; set +a; $(svc_cmd "$1") 2>&1 | tee '$LOG_DIR/$1.log'" |
There was a problem hiding this comment.
Execute tmux service commands through Bash
When a developer's tmux default-shell is a non-POSIX shell such as fish, tmux evaluates this command with that shell rather than the Bash interpreter from this script's shebang. The set -a, . file, and set +a sequence is therefore unsupported or has different semantics, so dev.env is not exported correctly and configured services can immediately fail or start with defaults. Invoke an explicit Bash command for each window instead of relying on the user's tmux shell.
AGENTS.md reference: AGENTS.md:L8-L8
Useful? React with 👍 / 👎.
| concurrency: | ||
| group: deploy-onlyboxes-website | ||
| cancel-in-progress: false |
There was a problem hiding this comment.
Serialize website deployment with release deployment
When a website-only production dispatch overlaps a package release, this concurrency group serializes only runs of deploy-website.yml; the deploy-cloudflare job in package-release.yml has no matching group. Both workflows execute wrangler deploy, so whichever finishes last takes production traffic even if it builds an older or unrelated branch. GitHub concurrency only arbitrates jobs or runs assigned to the same concurrency group, so both deployment paths need a shared group.
Useful? React with 👍 / 👎.
- package-release: pass target_commitish so a tag created from a non-default branch points at the built commit instead of the repository default branch - package-release: add a latest input, since a manual dispatch makes it easy to patch an older line, which must not steal the Latest label - package-release/deploy-website: share one concurrency group so the two deploy paths cannot race for production traffic - ci: grant pull-requests: read for paths-filter's API fallback - dev.sh: derive console ports from dev.env instead of hardcoding them, so status and port reclamation follow a configured listen address - dev.sh: parse credentials from both json and text log formats; only cache a line the fields can actually be read from - dev.sh: run window commands through bash, since tmux would otherwise use the developer's default-shell
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 941e40ac76
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| latest: | ||
| description: 'Mark as Latest. Turn off when patching an older line so the newest release keeps the label. Ignored for prereleases.' | ||
| required: false | ||
| default: true | ||
| type: boolean |
There was a problem hiding this comment.
Respect
latest when publishing mutable release aliases
When dispatching an older-line patch with latest=false, which is the stated use case for this input, the value only affects the final gh release edit: Resolve Docker tags still publishes onlyboxes:latest at lines 653-655, the runtime job still overwrites default, default-cn, lobehub, and latest at lines 731-736, and deploy-cloudflare still sends the older website to production at lines 788-796. This can silently roll production aliases and the website back to the older branch even though the GitHub Latest label is preserved; gate these mutable targets on this input as well.
Useful? React with 👍 / 👎.
| # tmux server 与调用方 shell 的环境是隔离的,直接 `FOO=bar scripts/dev.sh start` | ||
| # 不会生效,配置必须写在这里。修改后需要 restart 对应服务。 | ||
|
|
||
| # ── console ──────────────────────────────────────────────────────────── |
There was a problem hiding this comment.
Provide the required console hash key
On a fresh checkout without an existing console/config.toml or tmux environment, following this example leaves CONSOLE_HASH_KEY unset, while persistence.NewHasher rejects an empty key in console/internal/persistence/hash.go:17-20; consequently scripts/dev.sh start console exits before binding either port. Add instructions and a persistent development value-generation step for this required setting so the mandated local launcher works from the documented configuration.
AGENTS.md reference: AGENTS.md:L8-L8
Useful? React with 👍 / 👎.
| if [ -f "$CREDS_FILE" ]; then | ||
| line="$(cat "$CREDS_FILE")" | ||
| else |
There was a problem hiding this comment.
Invalidate cached credentials when the database changes
When CONSOLE_DB_PATH is changed to a fresh database, or the current database is deleted after credentials have previously been cached, this branch always selects the old cache and never inspects the new one-time initialization record in console.log. creds therefore prints credentials that cannot authenticate to the active database; associate the cache with the configured database or invalidate/replace it when a newer initialization record exists.
Useful? React with 👍 / 👎.
| ```bash | ||
| scripts/dev.sh stop console | ||
| rm console/db/onlyboxes-console.db | ||
| scripts/dev.sh start console && scripts/dev.sh creds |
There was a problem hiding this comment.
Wait before reading credentials after asynchronous startup
When this documented reset command is run, start console returns immediately after spawning the tmux window, so the following creds commonly checks the log before go run has compiled and initialized the account and reports that no credentials were found. Poll for the initialization record/window failure or document a separate delayed lookup rather than chaining the commands synchronously.
AGENTS.md reference: AGENTS.md:L8-L8
Useful? React with 👍 / 👎.
| mkdir -p "$LOG_DIR" | ||
| : >"$log" |
There was a problem hiding this comment.
Restrict permissions on logs containing admin credentials
On a multi-user development machine with the normal 0022 umask, this redirection creates scripts/.dev/console.log as mode 0644 inside a mode 0755 directory. The console's first-start record includes the plaintext administrator password and optional API key, so other local users can read those secrets even though the extracted cache is later protected with mode 0600; create the directory and all service logs with owner-only permissions before launching the pane.
Useful? React with 👍 / 👎.
|
|
||
| # The tag push trigger used to enforce the '*.*.*' shape; with manual | ||
| # dispatch nothing does, so validate the input here. | ||
| if [[ ! "${version_raw}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-(rc|beta|alpha)[.-][0-9A-Za-z.]+)?$ ]]; then |
There was a problem hiding this comment.
Reject prerelease versions that are invalid Git refs
When a typo such as 1.2.3-rc..1 is entered, this regex accepts it because the suffix permits arbitrary periods, but the subsequent draft-release step cannot create that tag: Git ref names may not contain .. according to git-check-ref-format. Reject consecutive periods and other invalid-ref forms during this validation step so malformed input fails with the intended version error rather than later during release creation.
Useful? React with 👍 / 👎.
| LOG_DIR="$SCRIPT_DIR/.dev" | ||
| CREDS_FILE="$LOG_DIR/console-creds.json" | ||
| # tmux server 不继承调用方的自定义环境变量,因此额外配置统一走这个文件 | ||
| ENV_FILE="${ONLYBOXES_DEV_ENV:-$SCRIPT_DIR/dev.env}" |
There was a problem hiding this comment.
Resolve custom env paths before changing service directories
When ONLYBOXES_DEV_ENV is a relative path such as scripts/team.env, env_value reads it relative to the caller's directory, but each tmux window runs under console/, web/, or website/ and therefore attempts to source that same relative path from a different directory. The service silently starts without the requested configuration while status can still report ports parsed from the original file; canonicalize the custom path to an absolute path before constructing window commands.
Useful? React with 👍 / 👎.
| require_tmux() { | ||
| command -v tmux >/dev/null 2>&1 || die "未找到 tmux,请先安装(brew install tmux)" | ||
| } |
There was a problem hiding this comment.
Check every process-management dependency up front
On a minimal Linux installation where the developer installs tmux as instructed but lacks lsof or pgrep, startup still succeeds because only tmux is checked. Missing lsof is then silently treated as every port being free and not listening, disabling collision detection, status, and residual-process reclamation, while missing pgrep prevents descendant processes from entering the ownership snapshot; require these commands explicitly or provide functional fallbacks.
Useful? React with 👍 / 👎.
| web) echo "NO_COLOR=1 yarn dev" ;; | ||
| website) echo "NO_COLOR=1 yarn dev" ;; |
There was a problem hiding this comment.
Force Vite to stay on the managed ports
When port 5178 or 5173 is already occupied, Vite does not fail by default; it selects the next available port unless --strictPort is enabled, as documented by the --strictPort CLI option. The script consequently leaves the frontend running on an untracked port while status sees the unrelated listener on the expected port plus a live pane and reports the service as healthy; launch both Vite services with strict-port behavior.
Useful? React with 👍 / 👎.
Both nav-active snapshots were pinned opaque, and the new one paints on top, so the highlight showed the incoming label from the very first frame — while the box still sat on the item being left. That item read as blinking into a black box carrying another section's name. Fade the incoming label in over the outgoing one instead. The outgoing snapshot stays opaque underneath, which also keeps the black fill solid for the whole slide.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 09cb6e8dec
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # the tag does not exist yet and target_commitish otherwise defaults | ||
| # to the repository default branch, which would point the tag at code | ||
| # other than what this run built | ||
| target_commitish: ${{ github.sha }} |
There was a problem hiding this comment.
Verify an existing release tag before reusing the version
When the entered version already exists—such as after a failed draft run or an accidental reuse from another branch—target_commitish cannot retarget that existing tag. The jobs still build the newly selected branch, and later gh release upload --clobber calls can replace the tagged release's assets with binaries from a different commit. Before proceeding, verify that any existing tag points to ${{ github.sha }} and that its release is safely resumable, or reject the dispatch.
Useful? React with 👍 / 👎.
| fi | ||
|
|
||
| mkdir -p "$LOG_DIR" | ||
| : >"$log" |
There was a problem hiding this comment.
Preserve uncached credentials before truncating the log
When console has initialized its database but the developer has not yet run creds, stopping and starting it reaches this truncation with no console-creds.json cache. That deletes the only log record containing the generated password, while the existing database prevents later starts from emitting it again, so creds can recover only by deleting the database. Preserve or rotate the console log, or extract valid credentials into the protected cache before truncating it.
AGENTS.md reference: AGENTS.md:L8-L8
Useful? React with 👍 / 👎.
A nav item animates its own background over 200ms when it stops being active, and the new snapshot is taken about 30ms in — with the tween barely started, so the item being left is captured still dark. Both sidebar snapshots are static for the transition's whole 320ms, so that dark background sat there until the live DOM took over and dropped it in a single frame: the black flash reported on the item being left. Freeze transitions inside the sidebar for the duration, so the item leaves the active state at once and the highlight hand-off is left to the transition.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ffd7d8bfea
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # json 的 "key":"value",text 的 key=value 与 key="value" | ||
| log_field() { | ||
| local line="$1" key="$2" value | ||
| value="$(printf '%s' "$line" | sed -n "s/.*\"$key\":\"\([^\"]*\)\".*/\1/p")" |
There was a problem hiding this comment.
Decode escaped credential values before caching
When CONSOLE_DASHBOARD_PASSWORD contains a quote or backslash, both slog JSON and text output escape that character, but these regexes stop at the escaped quote and return only a corrupted prefix. Because the result is still nonempty, cmd_creds accepts and caches the record, so subsequent creds calls report a password that cannot authenticate; parse the structured log with escape-aware decoding or capture the credentials before log formatting.
AGENTS.md reference: AGENTS.md:L8-L8
Useful? React with 👍 / 👎.
| - 此文件夹为项目根目录。使用 monorepo 管理多个工程,前后端分离。核心服务以 控制节点-执行节点 的形式部署。 | ||
| - 所有子工程都有各自的`README`文件夹,每个 md 文件代表某个方面的说明,如果工作内容涉及对应方面,应当阅读对应 md 文件。 | ||
| - 根目录的`README`文件夹用于记录跨工程的项目说明,其中`README/API.md`与`README/API.zh-CN.md`为统一 API 参考,`README/release-defaults.md`为发版默认值检查清单。发版或调整默认版本时,应阅读`README/release-defaults.md`。 | ||
| - 本地启动服务统一使用`scripts/dev.sh`(tmux 编排 console / web / website,所有子命令立即返回,不阻塞终端),不要自行拼装`go run`或`yarn dev`。用法见`scripts/README.md`。worker 不在编排范围内,需手动启动。 |
There was a problem hiding this comment.
Route root startup examples through dev.sh
The new unified-launcher rule conflicts with the primary onboarding instructions: README.md:207-220 and README.zh-CN.md:207-220 still tell developers to run go run and yarn dev directly. A developer following either root README therefore bypasses the required tmux orchestration and its environment, logging, and credential behavior; replace those examples with scripts/dev.sh commands and link to scripts/README.md.
AGENTS.md reference: AGENTS.md:L8-L8
Useful? React with 👍 / 👎.
背景
仓库此前没有任何 CI:
.github/workflows/只有package-release.yml,PR 与 push main 时不跑任何检查。本地开发也没有统一入口,起一套完整环境要手动开三个终端。这个 PR 补上这两块,并把发版改成手动触发。改动
1.
scripts/dev.sh— 本地开发进程编排用一个 tmux 会话托管 console / web / website,六个子命令全部立即返回,不阻塞终端;日志 tee 到
scripts/.dev/<svc>.log。几个实现上的要点:
stop先采集窗口进程树快照,只回收「既占该端口、又在树内」的 PID。手动启动的实例不会被误杀,脚本会明确提示端口被外部占用并跳过。scripts/dev.env:tmux server 不继承调用方环境,FOO=bar scripts/dev.sh start是无效的,配置在窗口内 source(示例见scripts/dev.env.example)。creds:console 只在首次初始化数据库时打印一次管理员账号,而 start 会清空日志,因此首次抓到后固化到scripts/.dev/console-creds.json(权限 600)。worker 不在编排范围内——各实现启动参数差异较大,部分场景需要同时跑多个实例。
2.
ci.yml— 六个子项目的 PR 检查go vet、go testcargo fmt --check、cargo clippy、cargo testformat:check、type-check、单测、build-onlybuild用
dorny/paths-filter按路径分流,改前端不会触发 Rust 编译。api/**是共同上游会 fan out 到全部四个后端:三个 Go module 通过 replace 引用它,worker-boxlite的build.rs也编译api/proto。ci-ok汇总 job 把 skipped 视为通过,适合作为分支保护里唯一的必需检查。web 新增
format:check(format是写入式的,不能当关卡)。3. 发版改为手动触发 + 独立的官网部署
package-release.yml:workflow_dispatch+version入参取代 tag push。原先 tag 过滤器'*.*.*'隐式保证了版本号格式,去掉后补了显式正则校验。tag 由 draft release 步骤基于所选分支创建,不需要再本地打 tag。deploy-website.yml:新增,手动部署官网而不必走完整发版。mode可选production(切 100% 流量)或preview(versions upload,不切流量)。package-release.yml里原有的deploy-cloudflare保留,两条路并存。4. 删除
scripts/e2e-session-concurrency.sh一并清理了两个 worker README 中的引用。
验证
dev.sh 的所有子命令与分支路径都在本地真实配置下跑过:三服务启动均 LISTEN、
restart console换 PID 无address already in use、stop后四个端口全部释放、creds的命中与未命中两条路径、参数与未知命令校验。CI 里的每条命令也都本地实跑过:console / worker-docker / worker-sys 的 vet + test 全绿,web 的 format:check / type-check / 69 项单测 / build-only 全绿,website 7 项单测绿,worker-boxlite 的
cargo fmt --check与测试编译 exit 0。版本号校验逐个验过:
0.7.2、0.8.0-rc.1、1.0.0-beta-2接受且 prerelease 判定正确;v0.7.2、0.72、1.2.3-foo.1、空值拒绝。因为这个 PR 改到了
ci.yml本身,而它在 paths-filter 里是所有 job 的触发条件,所以本次 PR 会把六个 job 全跑一遍。