Require diagnostic provisioning in both installation paths

This commit is contained in:
2026-10-08 13:43:07 +08:00
parent 31466bd17b
commit 0f5efc6031
14 changed files with 180 additions and 15 deletions
+1 -1
View File
@@ -84,7 +84,7 @@
- 使用者批准独立的测试专用 `deploys/test/saas-mock/` 仅模拟缺失的 SaaS:从 0600 的静态快照提供五类正式 HTTP 配置,在专用 RabbitMQ vhost 中由模拟 SaaS 预建现行拓扑;不在 Dispatcher 内注入快照;默认不发布外呼消息,只有受限脚本已为同一 `event_id`/线路/原始号码启动抓包后,才可显式单次 MQ 投递;不替代 Agent/Asterisk/AI/OSS。测试用正式入口必须同时具备只读配置、专用 MQ、真实 Agent/Asterisk/AI/录音/OSS、逐通确认和拨号前活跃抓包证据,缺一项不得试拨。此模拟不构成真实 SaaS 签收。
- 开发按 TDD 分批,小步提交;不得覆盖使用者现存修改/未跟踪文件,不自动清理、迁移或覆盖任何现存 SQLite、spool、outbox 和 Agent 恢复文件。旧 `.executions` 及恢复根目录中旧 `.uploads`、`.upload-locks`、逐执行 `state.json` 的发现须只读失败关闭,现存未交付事实由使用者确认处置。真实云账号、EIP、线路、拨号、生产部署和共享数据操作分别需要明确授权。
- 2026-10-08 使用者批准独立测试工具 [`cmd/sip-call`](cmd/sip-call/README.md):单独编译,以 `sip-call call --sip <线路.env> <原始号码>` 自动加载固定 ENV、临时配置并核对原生 Asterisk,仅拨一通,不接 SaaS/MQ/AI/OSS,不重拨、不换线;业务 Agent/其他 ARI 应用须停止,活动通话或旧测试配置阻断执行。仅 `--debug/-D` 才使用 tcpdump/tshark 收集本通证据;不带参数只报告原生通道事实,不编造 SIP/媒体/抓包事实。`sip-go-agent agent` 同样增加 `--debug/-D`,默认不依赖外部 tcpdump/抓包凭证;调试复用现有抓证脚本并在拨号前严格核对活跃凭证,不能静默降级。HEP 真实 SIP 响应、业务授权、时段、额度及未知执行保留规则不变。开发核验已完成;2026-10-08 经使用者另行确认,独立工具及按当前表生成的三份 0600 ENV 已部署到登记测试机 `rogee` 用户的 `~/sip-call/`,未更新业务 Agent/Dispatcher、未加载或改动既有 Asterisk 线路、未拨号或调用 AI。默认模式不依赖抓包工具;主机未安装 tshark,`-D` 抓证尚未就绪,不能静默降级。部署来源、文件 hash 和只读主机事实见 [`docs/evidence/sip-call-test-deployment-20261008.md`](docs/evidence/sip-call-test-deployment-20261008.md)。独立工具不属于生产发布制品,也不授权真实试拨。
- 2026-10-08 使用者批准独立测试工具 [`cmd/sip-call`](cmd/sip-call/README.md):单独编译,以 `sip-call call --sip <线路.env> <原始号码>` 自动加载固定 ENV、临时配置并核对原生 Asterisk,仅拨一通,不接 SaaS/MQ/AI/OSS,不重拨、不换线;业务 Agent/其他 ARI 应用须停止,活动通话或旧测试配置阻断执行。仅 `--debug/-D` 才使用 tcpdump/tshark 收集本通证据;不带参数只报告原生通道事实,不编造 SIP/媒体/抓包事实。`sip-go-agent agent` 同样增加 `--debug/-D`,默认不依赖外部 tcpdump/抓包凭证;调试复用现有抓证脚本并在拨号前严格核对活跃凭证,不能静默降级。HEP 真实 SIP 响应、业务授权、时段、额度及未知执行保留规则不变。开发核验已完成;2026-10-08 经使用者另行确认,独立工具及按当前表生成的三份 0600 ENV 已部署到登记测试机 `rogee` 用户的 `~/sip-call/`,未更新业务 Agent/Dispatcher、未加载或改动既有 Asterisk 线路、未拨号或调用 AI。默认运行不调用抓包工具;使用者进一步要求生产和测试安装环境均统一预装 tcpdump/tshark,不按环境跳过,但抓包仍须显式 `--debug/-D`,不足时不能静默降级。登记测试机现已安装并核对 tcpdump 4.99.5、TShark 4.4.19 的 SIP/RTP 解析能力;未开启抓包或更改抓包权限,不能把工具安装当作逐通抓证、线路接通或生产签收。部署来源、文件 hash 和只读主机事实见 [`docs/evidence/sip-call-test-deployment-20261008.md`](docs/evidence/sip-call-test-deployment-20261008.md)。独立工具不属于生产发布制品,也不授权真实试拨。
## SaaS、Dispatcher 与 Agent 的现行边界
+2 -2
View File
@@ -14,9 +14,9 @@ chmod 600 sip-xx.env
./dist/sip-call call --sip sip-xx.env -D 18601010101
```
`--debug` 与 `-D` 等价。默认模式不调用或检查 tcpdump/tshark;调试模式要求两者可执行、指定接口可抓包且权限充分。调试抓包未就绪或在 Dial 前已退出就不拨号。参数后的号码是原始号码,仅添加该文件声明的前缀一次;不要自己先加线路前缀。
`--debug` 与 `-D` 等价。环境安装统一预装 tcpdump/tshark(生产与测试相同);预装不等于开启抓包。默认模式不调用或检查这些工具;调试模式要求两者可执行、指定接口可抓包且权限充分。调试抓包未就绪或在 Dial 前已退出就不拨号。参数后的号码是原始号码,仅添加该文件声明的前缀一次;不要自己先加线路前缀。
**命令会真实拨号。每次执行都须另获线路和号码的明确授权;本文示例不是授权。** 本次代码交付只做本地测试,未部署、未试拨。
**命令会真实拨号。每次执行都须另获线路和号码的明确授权;本文示例不是授权。** 独立工具已另经使用者批准部署到登记测试机;没有执行真实试拨。
## 运行条件
+46
View File
@@ -0,0 +1,46 @@
package contracts
import (
"bytes"
"os"
"os/exec"
"strings"
"testing"
)
func TestSharedContractIsOnlyRuntimeSource(t *testing.T) {
if _, err := os.Stat("local"); !os.IsNotExist(err) {
t.Fatal("contracts/local must not remain a parallel contract source")
}
out, err := exec.Command("git", "ls-files", "--stage", "--", "schema").CombinedOutput()
if err != nil || !strings.HasPrefix(string(out), "160000 ") {
t.Fatalf("contracts/schema must be a Git submodule: %s (%v)", out, err)
}
for _, name := range []string{"config-read.schema.json", "task-discovery.schema.json", "mq.schema.json", "mq.topology.json", "manifest.json"} {
t.Run(name, func(t *testing.T) {
embedded, err := ReadCurrent(name)
if err != nil {
t.Fatal(err)
}
shared, err := os.ReadFile("schema/" + name)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(embedded, shared) {
t.Fatal("runtime contract differs from shared repository")
}
})
}
}
func TestSharedEntrypointsReferenceCanonicalDefinitions(t *testing.T) {
for _, name := range []string{"config-read.schema.json", "task-discovery.schema.json", "mq.schema.json"} {
data, err := os.ReadFile("schema/" + name)
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(data, []byte(".schema.json#")) {
t.Fatalf("%s must reference canonical split schemas, not copy their definitions", name)
}
}
}
+7 -3
View File
@@ -34,9 +34,13 @@ tar -xzf sip-go-agent-<version>-linux-amd64.tar.gz
./install.sh
```
The package contains only the two Go services, their systemd units, production
environment templates, the endpoint inventory, the version lock and the
installer. Credentials, certificates, broker URLs and the approved static Cell
The package contains the two Go services, their systemd units, production
environment templates, the endpoint inventory, the version lock, the installer,
and `cell/install-diagnostics.sh`. Both production and test host installation
always provision `tcpdump` and `tshark` through system packages, verify command
versions and SIP/RTP decoders, and fail on errors. Tools being installed does
not enable capture: `--debug/-D` remains explicit. No capture capabilities,
user-group membership or sudo rules are added by this helper. Credentials, certificates, broker URLs and the approved static Cell
artifact are injected separately. Asterisk is built or installed with the
scripts under [`cell/`](cell/), and its management-owned configuration is never
overwritten.
+3 -2
View File
@@ -19,13 +19,14 @@ RELEASE_VERSION="$VERSION" "$ROOT/scripts/build-release.sh" "$RELEASE_DIR"
mkdir -p -- "$(dirname -- "$ARCHIVE")"
mkdir -- "$STAGE"
cp -a "$RELEASE_DIR/." "$STAGE/"
mkdir -p "$STAGE/systemd" "$STAGE/env" "$STAGE/config"
mkdir -p "$STAGE/systemd" "$STAGE/env" "$STAGE/config" "$STAGE/cell"
cp "$ROOT/deploys/install.sh" "$STAGE/install.sh"
cp "$ROOT/deploys/cell/install-diagnostics.sh" "$STAGE/cell/install-diagnostics.sh"
cp "$ROOT/deploys/systemd/"*.service "$STAGE/systemd/"
cp "$ROOT/deploys/env/agent.env.example" "$ROOT/deploys/env/dispatcher.env.example" "$STAGE/env/"
cp "$ROOT/deploys/config/agent-endpoints.example.json" "$STAGE/config/"
cp "$LOCK" "$STAGE/versions.lock.json"
chmod 0755 "$STAGE/install.sh"
chmod 0755 "$STAGE/install.sh" "$STAGE/cell/install-diagnostics.sh"
chmod 0644 "$STAGE/systemd/"*.service "$STAGE/env/"*.env.example "$STAGE/config/agent-endpoints.example.json" "$STAGE/versions.lock.json"
(
cd "$STAGE"
+9 -2
View File
@@ -14,8 +14,15 @@ The pinned source input is:
`build-asterisk-native.sh` reproduces the stage from the pinned source and
local dependency cache; the build selects no downloaded core sounds/MOH.
The native package includes its `build-platform` marker and
`install-asterisk-user.sh`. Run that installer as `rogee`, never as root;
The native package includes its `build-platform` marker,
`install-asterisk-user.sh`, and checksum-verified `install-diagnostics.sh`.
Every production/test installation provisions `tcpdump` and `tshark` using the
same root package helper; there is no environment switch or skip option.
The native user installer invokes this helper through existing `sudo -n`
administrative access after preflight validation and before changing Asterisk.
The helper checks versions and SIP/RTP decoders, does not activate capture,
and does not change capture permissions or user-group membership.
Run the native installer as `rogee`, never as root;
it verifies the stage hash and required libraries, installs into `~/.local/opt/`,
sets up `~/.config/go-sip-asterisk/` without overwriting existing files, and
installs `go-sip-asterisk.service` under `systemd --user`. Production requires
+3 -1
View File
@@ -42,10 +42,12 @@ rm -rf -- "$STAGE/etc/asterisk"
tar -C "$STAGE" -cpf "$OUT/asterisk-$VERSION-native-stage.tar" .
cp "$ROOT/deploys/cell/install-asterisk-user.sh" "$OUT/install-asterisk-user.sh"
cp "$ROOT/deploys/cell/native-platform.sh" "$OUT/native-platform.sh"
cp "$ROOT/deploys/cell/install-diagnostics.sh" "$OUT/install-diagnostics.sh"
printf 'debian:%s:x86_64\n' "$OS_VERSION" > "$OUT/build-platform"
chmod 0755 "$OUT/install-asterisk-user.sh"
chmod 0755 "$OUT/install-asterisk-user.sh" "$OUT/install-diagnostics.sh"
(
cd "$OUT"
sha256sum "asterisk-$VERSION-native-stage.tar" > "asterisk-$VERSION-native-stage.tar.sha256"
sha256sum install-diagnostics.sh > install-diagnostics.sh.sha256
)
printf 'native_stage=%s\nservice=%s\n' "$OUT/asterisk-$VERSION-native-stage.tar" "$OUT/asterisk.service"
+4
View File
@@ -27,6 +27,10 @@ runtime="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/go-sip-asterisk"
[[ ! -e $prefix && ! -e $unit && ! -e $config/asterisk.conf && ! -e $config/modules.conf ]] || { echo 'existing Asterisk installation or user configuration; refuse to overwrite' >&2; exit 1; }
[[ $HOME != *[[:space:]]* ]] || { echo 'home path with whitespace is unsupported by the unit' >&2; exit 1; }
# System tools are required for every environment, independently of --nonprod.
(cd "$package" && sha256sum -c install-diagnostics.sh.sha256)
sudo -n -- bash "$package/install-diagnostics.sh"
mkdir -p "$(dirname "$prefix")"
staged=$(mktemp -d "$(dirname "$prefix")/.staged.XXXXXXXX")
trap 'rm -rf "$staged"' EXIT
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Required on every host: tool availability does not enable packet capture.
set -euo pipefail
[[ $# == 0 ]] || { echo 'usage: install-diagnostics.sh (no environment switches)' >&2; exit 2; }
[[ $EUID == 0 ]] || { echo 'diagnostic package installation requires root' >&2; exit 1; }
for command in apt-get awk; do
command -v "$command" >/dev/null || { echo "required installer command missing: $command" >&2; exit 1; }
done
# Noninteractive package defaults do not add users to the wireshark group or
# grant capture capabilities. Existing operator capture permissions are retained.
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends tcpdump tshark
for command in tcpdump tshark; do
command -v "$command" >/dev/null || { echo "installed diagnostic command missing: $command" >&2; exit 1; }
done
tcpdump_version=$(tcpdump --version)
tshark_version=$(tshark --version)
printf '%s\n%s\n' "${tcpdump_version%%$'\n'*}" "${tshark_version%%$'\n'*}"
protocols=$(tshark -G protocols)
if ! awk -F '\t' '$3 == "sip" {sip=1} $3 == "rtp" {rtp=1} END {exit !(sip && rtp)}' <<< "$protocols"; then
echo 'installed tshark lacks SIP/RTP dissectors' >&2
exit 1
fi
printf 'tcpdump and tshark installed; SIP/RTP decoders verified; capture remains opt-in\n'
+2
View File
@@ -37,6 +37,8 @@ LOCK_VERSION=$(awk -F'"' '/"version"[[:space:]]*:/ {print $4; exit}' versions.lo
[[ "$VERSION" =~ ^[0-9A-Za-z][0-9A-Za-z.+_-]*$ ]] || { echo 'invalid release version' >&2; exit 1; }
[[ "$VERSION" == "$LOCK_VERSION" ]] || { echo 'release/version lock mismatch' >&2; exit 1; }
bash "$PACKAGE_DIR/cell/install-diagnostics.sh"
id -u rogee >/dev/null 2>&1 || useradd --create-home --shell /bin/bash rogee
install -d -m 0755 /opt/sip-go-agent/releases \
/etc/sip-go-agent/pki /etc/sip-go-agent/artifacts
+9 -3
View File
@@ -31,9 +31,15 @@ before installation. Without lingering, non-production start is session-scoped
and reboot persistence must be reported as unverified.
The Go installer creates `/opt/sip-go-agent`, `/etc/sip-go-agent` and
`/var/lib/sip-go-agent`, installs the two Go units, and enables them. It does
not install test tooling, Docker, a broker, provider SDK credentials or AI
snapshots. Start services only after the injected environment, mTLS identity,
`/var/lib/sip-go-agent`, installs the two Go units, and enables them. It
always installs `tcpdump` and `tshark` through the shared
[`cell/install-diagnostics.sh`](cell/install-diagnostics.sh), for production and
test hosts alike. The native Asterisk installer invokes the same helper with
existing `sudo -n` administrative access before changing the installation.
Versions and SIP/RTP dissectors are checked; package or validation failures
abort installation. Capture still requires explicit `--debug/-D` and existing
operator privileges. These tools are not business services. The installer does
not install Docker, a broker, provider SDK credentials or AI snapshots. Start services only after the injected environment, mTLS identity,
broker ACL and static Cell artifact have been reviewed.
For local or isolated testing, use the Docker-backed RabbitMQ target
@@ -45,5 +45,5 @@
- 远端程序 `call --help` 运行通过,支持 `--sip`、`--debug/-D`。
- 文件 hash、权限及目标目录验证通过;默认模式不依赖外部抓包工具。
- 测试机已有 tcpdump,**没有 tshark**。本次未安装额外软件或改动抓包权限;`-D` 抓证尚未就绪,缺少工具时程序明确拒绝拨号,不静默降级。
- 此次初始部署时测试机已有 tcpdump、尚无 tshark,未安装额外软件或改动抓包权限。随后使用者明确要求统一安装生产/测试诊断工具,tshark 已安装,详见 [`diagnostics-tools-install-20261008.md`](diagnostics-tools-install-20261008.md)。原始 `verification.json` 保留初始时间点事实,新的诊断工具核验另存,不覆盖历史。
- 未执行任何 `call --sip ... <号码>`,没有本次工具发起的外呼,也没有本次采集的通话结果或抓包证据;不能据此次安装声称任一线路已正常接通。
+1
View File
@@ -20,6 +20,7 @@ if [[ -e scripts/mq-only-acceptance-local.sh ]]; then
fi
./scripts/check-contracts.sh
bash ./scripts/check-install-diagnostics.sh
go mod verify
go test -race ./... -count=1
go vet ./...
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Root installer tests use fake package commands in a network-disabled container.
set -euo pipefail
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
helper="$ROOT/deploys/cell/install-diagnostics.sh"
[[ -f "$helper" ]] || { echo 'diagnostic installer missing' >&2; exit 1; }
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
mkdir "$tmp/bin"
cat > "$tmp/bin/apt-get" <<'SH'
#!/bin/sh
printf '%s frontend=%s\n' "$*" "${DEBIAN_FRONTEND:-}" >> /fixture/apt.log
if [ "$*" = update ] && [ "${FAIL_UPDATE:-0}" = 1 ]; then exit 41; fi
if [ "${1:-}" = install ] && [ "${FAIL_INSTALL:-0}" = 1 ]; then exit 42; fi
SH
cat > "$tmp/bin/tcpdump" <<'SH'
#!/bin/sh
[ "${FAIL_TCPDUMP:-0}" = 0 ] || exit 43
printf 'tcpdump version fixture\n'
SH
cat > "$tmp/bin/tshark" <<'SH'
#!/bin/sh
[ "${FAIL_TSHARK:-0}" = 0 ] || exit 44
case "$*" in
--version) printf 'TShark fixture\n' ;;
'-G protocols')
printf 'Session Initiation Protocol\tSIP\tsip\n'
[ "${MISSING_RTP:-0}" = 0 ] && printf 'Real-Time Transport Protocol\tRTP\trtp\n'
exit 0 ;;
*) exit 45 ;;
esac
SH
chmod 755 "$tmp/bin/"*
cat > "$tmp/check.sh" <<'SH'
#!/bin/bash
set -euo pipefail
export PATH=/fixture/bin:/usr/bin:/bin
for environment in production development test; do
rm -f /fixture/apt.log
AGENT_ENVIRONMENT="$environment" /bin/bash /install-diagnostics.sh
grep -Fx 'update frontend=noninteractive' /fixture/apt.log
grep -Fx 'install -y --no-install-recommends tcpdump tshark frontend=noninteractive' /fixture/apt.log
done
for failure in FAIL_UPDATE FAIL_INSTALL FAIL_TCPDUMP FAIL_TSHARK MISSING_RTP; do
if env "$failure=1" /bin/bash /install-diagnostics.sh; then
echo "installer swallowed failure: $failure" >&2; exit 1
fi
done
if /bin/bash /install-diagnostics.sh --nonprod; then echo 'installer accepted environment switch' >&2; exit 1; fi
printf 'diagnostic installer root checks passed\n'
SH
chmod 755 "$tmp/check.sh"
docker run --rm --network none -v "$tmp:/fixture" -v "$helper:/install-diagnostics.sh:ro" debian:13-slim /bin/bash /fixture/check.sh
if [[ $EUID != 0 ]] && bash "$helper"; then echo 'installer accepted unprivileged execution' >&2; exit 1; fi
python3 - "$ROOT" <<'PY'
import pathlib, sys
root=pathlib.Path(sys.argv[1])
production=(root/'deploys/install.sh').read_text()
native=(root/'deploys/cell/install-asterisk-user.sh').read_text()
package=(root/'deploys/build-package.sh').read_text()
build=(root/'deploys/cell/build-asterisk-native.sh').read_text()
assert 'cell/install-diagnostics.sh' in production, 'production installer does not provision diagnostics'
assert 'install-diagnostics.sh' in native and native.index('install-diagnostics.sh') < native.index('mkdir -p "$(dirname "$prefix")"'), 'native installer must provision diagnostics before changing Asterisk'
assert 'cell/install-diagnostics.sh' in package, 'production package omits helper'
assert 'install-diagnostics.sh' in build, 'native package omits helper'
print('both installation paths include unconditional diagnostic provisioning')
PY