Laminar 1.4 安装与首个流水线

Laminar 1.4 在 Ubuntu 24.04 上的两条真实安装路径:官方 deb 发行包与源码编译,含 systemd 托管、安装后目录语义,以及从 job 脚本到日志、归档、徽章的首个流水线完整实测。

LaminarLaminar持续集成CIC++自动化运维2026/10/27

Laminar 1.4 安装与首个流水线

目标:在一台 Ubuntu 24.04 服务器上完成 Laminar 1.4 的两种真实安装路径(官方发行包、源码编译),弄清安装后各目录的职责,并跑通一条包含准备、构建、校验、归档四个环节的最小流水线。文中所有命令与回显均来自 2026-10-27 的实机执行,未做修饰。

一、环境信息

本文的安装与运行实验在一台 Ubuntu 24.04.5 服务器上完成,主机名 ci-b1.example.com。核实命令与回显如下:

$ cat /etc/os-release | head -4
PRETTY_NAME="Ubuntu 24.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.5 LTS (Noble Numbat)"

$ uname -r
7.0.0-38-generic

$ systemctl --version | head -1
systemd 255 (255.4-1ubuntu8.17)

安装后的关键事实:

项目实测值
Laminar 版本1.4-1~upstream-ubuntu2404
运行账户uid=995(laminar) gid=994(laminar)
守护进程/usr/sbin/laminard(686328 字节)
命令行客户端/usr/bin/laminarc(108848 字节)
服务单元/usr/lib/systemd/system/laminar.service
服务环境文件/etc/laminar.conf
数据目录/var/lib/laminar
Web 监听*:8080
RPC 监听unix-abstract:laminar

二、前置条件

在动手装之前,先确认这五项,可以避开后面绝大多数「装完了但不工作」的问题:

  1. 必须是 systemd 作为 init 的系统。发行包只带 laminar.service,没有 SysV init 脚本;systemctl 不可用的环境只能源码运行。
  2. root 或 sudo 权限。安装包要创建 laminar 系统用户、写 /var/lib/laminar、注册 systemd 单元。
  3. 8080 端口空闲。被占用时守护进程启动即失败,可在 /etc/laminar.conf 里改 LAMINAR_BIND_HTTP(改法见《Laminar 1.4 配置参数详解》)。
  4. 磁盘空间。守护进程本身不到 1 MB,但 archive/ 与 workspace/ 会随构建产物增长,建议 CI 机器单独规划数据盘。
  5. 时间同步。构建编号、开始/结束时间都写入 SQLite 并在 Web 展示,机器时间漂移会让看板上的时间线错乱。

不需要预装编译器或前端工具链——发行包里 laminard 是静态打包了前端资源的完整二进制。

三、版本选型

写作时点 GitHub 官方仓库最新正式版为 1.4(发布 2026-08-16),预编译包按发行版分为多套,选包只需对齐「发行版 + 版本号」:

发行版包名后缀架构
Ubuntu 22.04ubuntu2204amd64
Ubuntu 24.04ubuntu2404amd64
Debian 13 / 14debian13 / debian14amd64、armhf
Rocky Linux 9 / 10rocky9 / rocky10x86_64
openSUSE 16opensuse16x86_64

完整文件名形如 laminar_1.4-1.upstream-ubuntu2404_amd64.deb。选包原则:

  • 操作系统与包后缀必须严格对应,跨版本硬装会因 libcapnp 版本差异导致运行期异常;
  • 同一台机器只保留一个 Laminar 版本,不要混装源码编译产物与发行包;
  • 没有对应发行包的系统(如 Alpine、Arch)走第五节源码编译。

四、方案一:官方发行包安装(推荐)

4.1 下载发行包

$ curl -Lo /root/laminar.deb https://github.com/ohwgiles/laminar/releases/download/1.4/laminar_1.4-1.upstream-ubuntu2404_amd64.deb
$ ls -l /root/laminar.deb
-rw-r--r-- 1 root root 320108 Oct 27 14:37 /root/laminar.deb

包体只有 320 KB,内网环境可以先在有网的机器下载后拷入。

4.2 安装

$ sudo apt-get install -y /root/laminar.deb

实测输出(节选,含依赖自动解析):

Reading package lists...
Building dependency tree...
Reading state information...
The following additional packages will be installed:
  libcapnp-1.0.1
The following NEW packages will be installed:
  laminar libcapnp-1.0.1
0 upgraded, 2 newly installed, 0 to remove and 2 not upgraded.
Need to get 1400 kB/1720 kB of archives.
After this operation, 5025 kB of additional disk space will be used.
Get:1 /root/laminar.deb laminar amd64 1.4-1~upstream-ubuntu2404 [320 kB]
Get:2 http://archive.ubuntu.com/ubuntu noble/universe amd64 libcapnp-1.0.1 amd64 1.0.1-4 [1400 kB]
Selecting previously unselected package libcapnp-1.0.1:amd64.
Selecting previously unselected package laminar.
Preparing to unpack /root/laminar.deb ...
Unpacking laminar (1.4-1~upstream-ubuntu2404) ...
Setting up libcapnp-1.0.1:amd64 (1.0.1-4) ...
Setting up laminar (1.4-1~upstream-ubuntu2404) ...
Creating laminar user with home in /var/lib/laminar
Processing triggers for libc-bin (2.39-0ubuntu8.9) ...

这里有两个值得注意的点:

  • libcapnp-1.0.1 是自动装上的,不需要手工处理依赖;Ubuntu 24.04 的 universe 源里就有。
  • 安装包只创建用户和配置目录,不会启动服务。这是有意为之,避免装完立刻占端口。

4.3 启用并启动服务

安装完成后先看状态,实测是「未启用、未运行」:

$ systemctl is-enabled laminar
disabled
$ systemctl is-active laminar
inactive

用一条命令完成「开机自启 + 立即启动」:

$ sudo systemctl enable --now laminar
Created symlink /etc/systemd/system/multi-user.target.wants/laminar.service → /usr/lib/systemd/system/laminar.service.

确认状态:

$ systemctl status laminar --no-pager
● laminar.service - Laminar continuous integration service
     Loaded: loaded (/usr/lib/systemd/system/laminar.service; enabled; preset: enabled)
     Active: active (running) since Tue 2026-10-27 15:03:27 UTC; 12ms ago
       Docs: man:laminard(8)
             https://laminar.ohwg.net/docs.html
   Main PID: 414 (laminard)
      Tasks: 1 (limit: 2932)
     Memory: 1.2M (peak: 1.2M)
        CPU: 4ms

Oct 27 15:03:27 ci-b1.example.com systemd[1]: Started laminar.service - Laminar continuous integration service.
Oct 27 15:03:27 ci-b1.example.com laminard[414]: laminar.cpp:560: info: Creating a default context with 6 executors

最后一行日志很重要:初次启动时 Laminar 会自动创建一个 6 执行槽的 default 上下文,此时 cfg/contexts 还是空目录。这个隐式上下文一旦被新增的 context 文件影响就会消失,是后续最容易踩的坑(见配置篇)。

4.4 验证 Web 与 RPC 两个端点

Laminar 同时提供两个入口,装完都要验:

$ curl -s -o /dev/null -w "%{http_code} %{size_download}\n" http://127.0.0.1:8080/
200 3112
$ curl -s -o /dev/null -w "%{http_code} %{size_download}\n" http://127.0.0.1:8080/jobs
200 3112
$ curl -s -o /dev/null -w "%{http_code} %{size_download}\n" http://127.0.0.1:8080/wallboard
200 3112

三个页面返回的都是同一个 3112 字节的单页应用骨架(/、/jobs、/wallboard 由前端路由区分),HTTP 200 即说明 laminard 的 HTTP 服务正常。

RPC 侧用客户端验证:

$ laminarc show-jobs

此时没有任何 job,命令输出为空。「空输出」是正常状态,不是故障。

五、方案二:源码编译

当目标系统没有对应发行包,或需要打补丁时走这条路径。官方 README 给出了 Debian/Ubuntu 的依赖清单。

5.1 安装编译依赖

$ sudo apt-get install -y capnproto cmake g++ libboost-dev libcapnp-dev \
    libsqlite3-dev make rapidjson-dev zlib1g-dev pkg-config

实测装好的版本(dpkg -l 摘录):

capnproto 1.0.1-4
cmake 3.28.3-1build7
g++ 4:13.2.0-7ubuntu1
libboost-dev:amd64 1.83.0.1ubuntu2
libcapnp-dev:amd64 1.0.1-4
libsqlite3-dev:amd64 3.45.1-1ubuntu2.8
pkg-config:amd64 1.8.1-2build1
rapidjson-dev 1.1.0+dfsg2-7.2
zlib1g-dev:amd64 1:1.3.dfsg-3.1ubuntu2.3

⚠️ 注意:libcapnp-dev 只提供头文件和库,不提供 capnp 编译器。只装 libcapnp-dev 时 make 会以 capnp: No such file or directory(Error 127)失败,必须同时装 capnproto。

5.2 配置与编译

$ tar xzf laminar-1.4.tar.gz
$ cd laminar-1.4
$ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DLAMINAR_VERSION=1.4 .
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /root/laminar-1.4

$ make -j4
[  4%] Built target laminar-manpages
[ 16%] Built target laminarc
[100%] Built target laminard

cmake 阶段有两件事必须知道:

  1. 首次配置会联网下载三个前端库(Vue、ansi_up、Chart.js),并校验 MD5。网络受限时直接失败,报错原文:
    file DOWNLOAD cannot compute hash on failed download status: [28;"Timeout was reached"]
    
    处理方式是给 cmake 带上代理环境变量后重跑,或预先在可达网络下完成一次配置。
  2. -DLAMINAR_VERSION 决定二进制里嵌入的版本号。不传该参数、源码目录也没有 git 元数据时,版本字符串会退化为 xx-unversioned。

5.3 产物与版本号验证

$ ls -l laminard laminarc
-rwxr-xr-x 1 root root 142920 Oct 27 14:55 laminarc
-rwxr-xr-x 1 root root 843232 Oct 27 14:55 laminard

$ ./laminard --version
Unknown argument --version
laminard version 1.4

laminard 没有真正的 --version 选项,会把参数当作非法参数提示一句 Unknown argument --version,但仍打印版本与用法——这是正常行为,不是编译出错。laminarc --version 同理,只认子命令。

5.4 安装到系统目录

编译完成后可用 make install 把产物安装到 CMake 里指定的前缀:laminard → /usr/sbin、laminarc → /usr/bin、laminar.conf → /etc、laminar.service → /lib/systemd/system,并附带 bash/zsh 补全与 man 手册。

💡 提示:在 deb 系发行版上,优先使用官方发行包。源码 make install 会绕过包管理器,之后 apt 升级会和手工安装的文件互相覆盖,卸载也不彻底。源码编译更适合无发行包的系统或需要自定义补丁的场景。

六、安装后的目录与文件语义

6.1 初始目录骨架

安装包刚装完、服务还没跑过一次时,数据目录非常干净:

$ find /var/lib/laminar | sort
/var/lib/laminar
/var/lib/laminar/cfg
/var/lib/laminar/cfg/contexts
/var/lib/laminar/cfg/jobs
/var/lib/laminar/cfg/scripts

只有 cfg 下的三个空目录。run/、archive/、laminar.sqlite 都是首次运行 job 时按需创建的。这点对备份策略有直接影响:只备份 cfg 不足以恢复历史,必须同时包含 laminar.sqlite 与 archive。

6.2 服务单元与配置文件

单元文件全文(实测):

[Unit]
Description=Laminar continuous integration service
After=network.target
Documentation=man:laminard(8)
Documentation=https://laminar.ohwg.net/docs.html

[Service]
User=laminar
EnvironmentFile=-/etc/laminar.conf
ExecStart=/usr/sbin/laminard -v

[Install]
WantedBy=multi-user.target

三个要点:

  • User=laminar:所有 job 脚本都以 laminar 用户身份执行,脚本里需要 root 权限的动作必须走 sudo 规则或外部提权;
  • EnvironmentFile=-/etc/laminar.conf:前面的 - 表示文件缺失也不报错,服务端配置全部来自这个文件;
  • -v:详细日志级别,日志进 journald。

/etc/laminar.conf 是一份全注释的配置模板,例如:

###
### LAMINAR_HOME
###
### Root location containing laminar configuration, database,
### build workspaces and archive.
###
### Default: /var/lib/laminar
###
#LAMINAR_HOME=/var/lib/laminar

### LAMINAR_BIND_HTTP
...
### Default: *:8080
###
#LAMINAR_BIND_HTTP=*:8080

默认值全部注释着,改配置只需去掉注释并改值,然后 systemctl restart laminar。各变量的完整清单与实测见《Laminar 1.4 配置参数详解》。

七、跑通首个流水线

7.1 写一个带阶段标记的 job

在 cfg/jobs/ 下新建 first-job.run。内容刻意做成「准备 → 构建 → 校验」三段,并在归档目录留下可验证产物:

sudo tee /var/lib/laminar/cfg/jobs/first-job.run >/dev/null <<'EOF'
#!/bin/bash -ex
echo "[1/3] prepare"
mkdir -p "$WORKSPACE/src"
echo "sources for $JOB #$RUN" > "$WORKSPACE/src/input.txt"
date -u +%FT%TZ > "$ARCHIVE/build-time.txt"

echo "[2/3] build"
tr "a-z" "A-Z" < "$WORKSPACE/src/input.txt" > "$ARCHIVE/manifest.txt"
cat "$ARCHIVE/manifest.txt"

echo "[3/3] verify"
test -s "$ARCHIVE/manifest.txt"
sha256sum "$ARCHIVE/manifest.txt"
EOF
sudo chown laminar:laminar /var/lib/laminar/cfg/jobs/first-job.run
sudo chmod +x /var/lib/laminar/cfg/jobs/first-job.run

三个约定要记住:

  • #!/bin/bash -ex:-e 让任意命令失败即中止并置 failed,-x 把每条命令打进日志,是 CI 脚本的基本素养;
  • $WORKSPACE 跨 run 持久,适合放源码缓存;$ARCHIVE 是本次 run 的永久归档目录,产物写这里才不会丢;
  • 文件必须有可执行位,否则 laminard 无法 exec 它。

再补一个必然失败的 job,用来验证失败判定:

sudo tee /var/lib/laminar/cfg/jobs/broken.run >/dev/null <<'EOF'
#!/bin/bash -ex
echo "this job always fails"
exit 7
EOF
sudo chown laminar:laminar /var/lib/laminar/cfg/jobs/broken.run
sudo chmod +x /var/lib/laminar/cfg/jobs/broken.run

确认 Laminar 已识别:

$ ls -l /var/lib/laminar/cfg/jobs/
total 8
-rwxr-xr-x 1 laminar laminar  52 Oct 27 15:03 broken.run
-rwxr-xr-x 1 laminar laminar 371 Oct 27 15:03 first-job.run

$ laminarc show-jobs
broken
first-job

job 定义是热加载的,新增文件不需要重启服务。

7.2 触发构建

$ laminarc run first-job
first-job:1

输出 first-job:1 即「job 名:构建号」,退出码 0 表示构建成功。run 是阻塞式的:客户端会等到该次构建结束才返回,适合在脚本里串联。

再跑失败用例:

$ laminarc run broken
broken:1

构建号同样是 1(每个 job 独立计数),但客户端退出码为 3,表示本次构建 failed。

7.3 看日志

构建日志有两处可查。HTTP 端点最直接:

$ curl -s http://127.0.0.1:8080/log/first-job/1
[laminar] Executing cfg/jobs/first-job.run
+ echo '[1/3] prepare'
[1/3] prepare
+ mkdir -p /var/lib/laminar/run/first-job/workspace/src
+ echo 'sources for first-job #1'
+ date -u +%FT%TZ
+ echo '[2/3] build'
[2/3] build
+ tr a-z A-Z
+ cat /var/lib/laminar/archive/first-job/1/manifest.txt
SOURCES FOR FIRST-JOB #1
+ echo '[3/3] verify'
[3/3] verify
+ test -s /var/lib/laminar/archive/first-job/1/manifest.txt
+ sha256sum /var/lib/laminar/archive/first-job/1/manifest.txt
d6588575f73481061daf2792b96659ab3e02d55908dfdbba1b6647a08201ddec  /var/lib/laminar/archive/first-job/1/manifest.txt

失败 job 的日志同样可查,末尾直接显示非零退出:

$ curl -s http://127.0.0.1:8080/log/broken/1
[laminar] Executing cfg/jobs/broken.run
+ echo 'this job always fails'
this job always fails
+ exit 7

另一处是 journald,能同时看到调度器视角的记录:

$ journalctl -u laminar --no-pager | tail -12
Oct 27 15:03:50 ci-b1.example.com laminard[414]: rpc.cpp:85: info: RPC run; jobName = first-job
Oct 27 15:03:50 ci-b1.example.com laminard[414]: laminar.cpp:714: info: Started job; run->name = first-job; run->build = 1; ctx->name = default
Oct 27 15:03:50 ci-b1.example.com laminard[414]: laminar.cpp:755: info: Run completed; r->name = first-job; to_string(r->result) = success
Oct 27 15:03:50 ci-b1.example.com laminard[414]: run.cpp:83: info: Run destroyed
Oct 27 15:03:50 ci-b1.example.com laminard[414]: rpc.cpp:85: info: RPC run; jobName = broken
Oct 27 15:03:50 ci-b1.example.com laminard[414]: laminar.cpp:714: info: Started job; run->name = broken; run->build = 1; ctx->name = default
Oct 27 15:03:50 ci-b1.example.com laminard[414]: laminar.cpp:755: info: Run completed; r->name = broken; to_string(r->result) = failed

日志里 ctx->name = default 印证了初次启动时自动创建的上下文;to_string(r->result) 是排障时最有价值的一个字段。

7.4 验证产物归档

$ cat /var/lib/laminar/archive/first-job/1/manifest.txt
SOURCES FOR FIRST-JOB #1

$ ls -l /var/lib/laminar/archive/first-job
total 4
drwxr-xr-x 2 laminar laminar 4096 Oct 27 15:03 1
lrwxrwxrwx 1 laminar laminar    1 Oct 27 15:03 latest -> 1

latest 是始终指向最近一次构建的软链接,Web 端的「最新产物」入口就靠它。

产物也能通过 HTTP 取回(默认由 laminard 自己提供,配置了 LAMINAR_ARCHIVE_URL 后可交给真正的 Web 服务器):

$ curl -s http://127.0.0.1:8080/archive/first-job/1/build-time.txt
2026-10-27T15:03:50Z

7.5 状态徽章

$ curl -s http://127.0.0.1:8080/badge/first-job.svg | grep -o 'success\|failed'
success
$ curl -s http://127.0.0.1:8080/badge/broken.svg | grep -o 'success\|failed'
failed

徽章是 SVG,可直接嵌入 README。注意它反映的是最近一次构建结果,不是历史汇总。

7.6 run 目录与 workspace 的区别

$ ls -l /var/lib/laminar/run/first-job
total 4
drwxr-xr-x 3 laminar laminar 4096 Oct 27 15:03 workspace

run/first-job/ 下只剩 workspace,本次构建号目录 1 已经不存在了——因为默认 LAMINAR_KEEP_RUNDIRS=0,run 目录在构建结束后立即删除。要保留失败现场,需把这个值改成 N(见配置篇)。

而 workspace 本身仍在,脚本里写入的 sources for first-job #1 会保留到下一次构建:

$ cat /var/lib/laminar/run/first-job/workspace/src/input.txt
sources for first-job #1

这就是「临时目录」与「持久工作区」的本质区别:中间产物放 run 目录,被复用或被下一次构建读取的内容放 workspace,需要长期留存的放 archive。

八、两种安装方案对比

维度官方发行包源码编译
适用场景发行版在支持列表内、追求稳定无对应发行包、需要打补丁或定制
前置依赖仅 libcapnp-1.0.1(自动解析)Cap'n Proto、SQLite、zlib、Boost、rapidjson、编译器等一整套
安装耗时十几秒依赖安装 + 编译,分钟级
版本字符串1.4-1~upstream-ubuntu2404由 -DLAMINAR_VERSION 决定,缺省 xx-unversioned
前端资源已编译进二进制首次配置需联网下载 Vue / ansi_up / Chart.js
升级方式apt 换新包重新编译并覆盖安装
风险点包与系统版本需严格对应容易与包管理器文件冲突,需自行维护

选择建议:能用发行包就用发行包;只有在发行包缺失、或必须修改源码时,才走编译路径,并记录好编译参数以便复现。

九、常见问题(实测)

9.1 装完 systemctl status 显示 inactive

不是安装失败。发行包有意不自动启服务,执行 systemctl enable --now laminar 即可;实测 is-enabled 初始为 disabled、is-active 为 inactive。

9.2 cmake 报 file DOWNLOAD cannot compute hash

完整报错:

file DOWNLOAD cannot compute hash on failed download status: [28;"Timeout was reached"]

原因是配置阶段要从 CDN 拉取三个前端 JS 库。让 cmake 走代理后重跑即可;内网机器也可以先在能上网的机器完成一次配置,再把源码树带进来。

9.3 make 报 capnp: No such file or directory

make[2]: capnp: No such file or directory
make[2]: *** [CMakeFiles/laminard.dir/build.make:...] Error 127

根因是只装了 libcapnp-dev 而没装 capnproto(前者没有 capnp 可执行文件)。补装 capnproto 后重新 make。

9.4 编译出来的版本显示 xx-unversioned

源码目录没有 git tag 信息时,CMake 会回退到这个占位版本。配置时显式传入 -DLAMINAR_VERSION=1.4,重编译后 ./laminard --version 即显示 laminard version 1.4。

9.5 Ubuntu 22.04 上 Web 页面全部无响应

实测现象:服务 active (running),laminarc show-jobs 等 RPC 命令正常,但 GET /、/jobs、/style.css、/badge/*、/log/* 全部挂起超时,laminard 进程 CPU 占用飙升到 100%,strace 看不到系统调用(纯用户态自旋)。

排查步骤与结论:

  1. 用 curl -m 5 逐个打 HTTP 端点,确认是 HTTP 全挂、RPC 正常;
  2. journalctl 无异常,说明不是脚本或配置问题;
  3. 换用 Ubuntu 24.04 的官方包(依赖 libcapnp-1.0.1)后同一套配置完全正常,/ 返回 200、CPU 归零。

因此在这台 22.04 机器上,问题定位到该发行版 libcapnp 0.8.0 与 1.4 二进制组合的兼容性,而非 Laminar 配置。结论:优先在与包后缀严格匹配的系统上安装,22.04 环境可考虑升级系统或改源码编译并链接较新的 Cap'n Proto。

9.6 徽章或产物 404

两个高频原因:

  • 产物必须写到 $ARCHIVE(archive/<job>/<run>/),写到 run 目录后会被默认清理,自然 404;
  • 徽章只对存在的 job 生效,/badge/<job>.svg 中的 job 名必须与 cfg/jobs/<job>.run 完全一致。

9.7 端口冲突

8080 被占用时服务起不来。改 /etc/laminar.conf 中的 LAMINAR_BIND_HTTP 后 systemctl restart laminar;客户端和浏览器要同步换端口。完整取值规则见《Laminar 1.4 配置参数详解》。

十、卸载与回滚

10.1 卸载

$ sudo systemctl disable --now laminar
$ sudo apt-get remove --purge laminar

卸载只处理包管理范围内的文件(二进制、单元文件、配置模板)。构建历史与归档不会自动消失:/var/lib/laminar 下的 cfg/、archive/、laminar.sqlite 需要你确认后再手工清理。清理前至少把 cfg 和 archive 备份走——它们就是整套 CI 配置与产物。

10.2 回滚

保留上一版发行包,回滚时重新安装旧包并重启服务即可。回滚前建议先停止服务、导出 laminar.sqlite,因为不同大版本之间数据库结构可能变化;回滚后再启动,确认 laminarc show-jobs 与 Web 页面均正常。

十一、参考链接

系列导航:

  • 《Laminar 1.4 完整部署与实战指南》 —— 定位、架构、能力边界与选型
  • 《Laminar 1.4 配置参数详解》 —— 服务端变量、contexts、全局钩子实测
  • 《Laminar 1.4 常用 CLI 命令与运维实践》 —— laminarc 全命令族与排障