Install native Asterisk as a verified user systemd service

This commit is contained in:
2026-10-03 05:30:56 +08:00
parent b91016511c
commit 57960c9fca
11 changed files with 142 additions and 84 deletions
+1 -1
View File
@@ -108,7 +108,7 @@
## 运行环境、诊断与开发门禁
- 项目是独立 Go module,工具链 Go **1.27.1**;普通构建、测试、运行不读取父项目业务模块、数据库、env 或夹具。标准库和成熟官方 SDK 优先,Cobra 显式 `agent`/`dispatcher`,单制品分角色/权限/目录。禁止自行重写 SIP/ARI、RTP/RTCP/G.711、WebSocket、AMQP、SQLite 驱动、OSS 签名及 SDK 已覆盖的 AI 协议;核验现有依赖能力后再新增库。生产原生 Asterisk 仍由独立 Cell 的 systemd 统一管理,不声称当前 Mock 已完成真实媒体或 1000 路容量验收。
- 生产 ECS 优先 Debian 13(Trixie)minimal;只有阿里云北京无可用镜像时允许 Ubuntu 24.04 LTS。Debian 12 仅供明确标记的非生产测试:必须在 Debian 12 原生构建 Asterisk,不得部署 Debian 13 编译的制品,也不得据此批准生产发布。Asterisk 直接安装在承载 ECS 主机,由 systemd 启用自启动并核对 `enabled+active`;不得以前台进程或容器入口代替。
- 生产 ECS 优先 Debian 13(Trixie)minimal;只有阿里云北京无可用镜像时允许 Ubuntu 24.04 LTS。Debian 12 仅供明确标记的非生产测试:必须在 Debian 12 原生构建 Asterisk,不得部署 Debian 13 编译的制品,也不得据此批准生产发布。Asterisk 直接安装在承载 ECS 主机,以 `rogee` 的 `systemd --user` 服务管理,核对 `enabled+active`;生产环境还须启用 `loginctl enable-linger rogee` 并验证重启后持续运行。非生产若未启用 lingering,必须标记重启自启动未验收。不得以前台进程或容器入口代替。
- 开发、Mock、mixed、real 的**非生产主机**部署与诊断步骤默认强制,不因时间/旧环境/调用方参数跳过或静默降级;显式关闭即失败。每次新主机/版本/Cell 至少留存脱敏 ECS/EIP/网络只读核验、Debian/架构/磁盘/权限、`rogee` SSH 与加固、发布包/依赖 SHA-256、Asterisk/systemd `enabled+active`、ARI/PJSIP endpoint/contact、媒体 profile/端口及运行版本。
- 非生产 mixed/real 呼叫必须先通过上述真实时间门禁;**拨号前**启动受限 SIP/RTP 抓包和 Asterisk PJSIP logger,结束后采集 SIP 响应/INVITE–BYE 时间线、SDP codec/媒体地址端口、RTP 包/字节、录音与 ASR/LLM/TTS 事实及 SHA-256。失败通话也保存状态和抓包;tcpdump/CAP_NET_RAW、PJSIP logger 或 ARI/PJSIP 状态任一不可用须失败关闭。原始抓包/日志/录音只写受限证据目录,聊天、提交与长期证据只存脱敏摘要/计数/状态码/hash,不含完整用户音频/对话或凭据。统一入口见 [`deploys/test/nonprod-call-evidence.sh`](deploys/test/nonprod-call-evidence.sh);本地 `make check` 与 `make release-check-local` **不能代签主机诊断或生产门禁**。
- 当前完成前至少检查格式、当前合同和 Proto 来源/hash、`go vet ./...`、`go test -race ./...`、构建、确实运行的隔离 RabbitMQ/HTTPS/双向 TLS 端到端测试、业务单元覆盖率 ≥65% 及 A01–A12/K01–K16 对照。真实 SaaS/management/OSS/AI/Asterisk/ECS、第二节点/Cell/租户、多 D 额度、容量/N+1及生产切换必须另有事实与授权,任何本机 Mock 通过不得写成其签收。
+1 -1
View File
@@ -7,7 +7,7 @@ by this project are:
- `sip-go-agent-dispatcher.service`
- `sip-go-agent-agent.service`
- the separately managed native `asterisk.service`
- the separately managed native `go-sip-asterisk.service` under `rogee`'s `systemd --user` (production requires lingering)
RabbitMQ, OSS, AI providers and SaaS are external endpoints. They are not
installed by the production package and are not started by systemd or Docker.
+15 -10
View File
@@ -12,16 +12,21 @@ The pinned source input is:
- Archive `../packages/asterisk-22.10.1-source.tar.gz`
- SHA-256 `373c98f4d4a1b923b42def0aee03f4e36aca9d1c244a8eeda646da8a97f89663`
`build-asterisk-native.sh` can reproduce a native stage from the local pinned
source/dependency archives using the Debian package list in
`debian-build-packages.lock`; `install-asterisk-native.sh` installs that stage
and the systemd unit without overwriting `/etc/asterisk`. Before production use,
the Cell owner must verify its dependencies/licence/security review, install the
management-approved static `pjsip.conf`/ARI/RTP configuration, and review/start
the systemd unit explicitly. Do not silently substitute another Asterisk version or a
container image. The Go Agent package only consumes the resulting approved static Cell artifact
and reports its applied revision. Local validation may use isolated MQ/OSS/AI
fixtures, but those are not production deployments.
`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;
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
`loginctl enable-linger rogee` and a verified reboot-persistent `enabled+active`
service; `--nonprod` allows a session-scoped Debian 12 native build but does
not certify reboot persistence. The management-approved static `pjsip.conf`,
ARI and RTP configuration must be supplied separately. The bundled stage has
no live SIP trunk or dialing authorization; verify loaded endpoints and contacts
before any call. Do not substitute another Asterisk version or a container image.
The Go Agent package only consumes the resulting approved Cell artifact and
reports its applied revision. Local Mock fixtures do not prove real services.
Before every real outbound attempt, the operator must obtain a fresh user
confirmation in the current conversation that names the SIP channel, raw target
-22
View File
@@ -1,22 +0,0 @@
[Unit]
Description=Asterisk SIP Cell 22.10.1 (physical host)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=asterisk
Group=asterisk
WorkingDirectory=/var/lib/asterisk
ExecStart=/usr/sbin/asterisk -f -U asterisk -G asterisk -vvvg
ExecStop=/usr/sbin/asterisk -rx "core stop now"
Restart=on-failure
RestartSec=5s
UMask=0077
LimitNOFILE=65536
PrivateTmp=yes
ProtectHome=yes
ReadWritePaths=/etc/asterisk /var/lib/asterisk /var/log/asterisk /var/spool/asterisk /var/run/asterisk
[Install]
WantedBy=multi-user.target
+10 -6
View File
@@ -11,9 +11,10 @@ OUT=${OUT_DIR:-"$PKG/asterisk-$VERSION-native"}
JOBS=${JOBS:-1}
WORK=${WORK_DIR:-"$ROOT/.local/asterisk-build-$VERSION"}
NONPROD=${NONPROD:-0}
. /etc/os-release
OS_ID=$(. /etc/os-release; printf '%s' "$ID")
OS_VERSION=$(. /etc/os-release; printf '%s' "$VERSION_ID")
source "$ROOT/deploys/cell/native-platform.sh"
require_native_platform "$ID" "$VERSION_ID" "$(uname -m)" "debian:$VERSION_ID:x86_64" "$NONPROD"
require_native_platform "$OS_ID" "$OS_VERSION" "$(uname -m)" "debian:$OS_VERSION:x86_64" "$NONPROD"
[[ -f "$SRC_ARCHIVE" && -f "$SRC_SHA" ]] || { echo 'Asterisk source archive/checksum missing' >&2; exit 1; }
[[ -d "$DEPS" ]] || { echo 'Asterisk dependency cache missing' >&2; exit 1; }
@@ -26,6 +27,10 @@ tar -xzf "$SRC_ARCHIVE" -C "$WORK"
SRC="$WORK/asterisk-$VERSION"
cd "$SRC"
EXTERNALS_CACHE_DIR="$OUT/cache" ./configure --with-pjproject-bundled --with-jansson-bundled
# Sound archives are not part of the pinned offline inputs; never fetch
# unverified downloads during an otherwise reproducible native build.
EXTERNALS_CACHE_DIR="$OUT/cache" make menuselect.makeopts
./menuselect/menuselect --disable CORE-SOUNDS-EN-GSM --disable MOH-OPSOUND-WAV menuselect.makeopts
EXTERNALS_CACHE_DIR="$OUT/cache" make -j"$JOBS"
STAGE="$WORK/stage"
rm -rf -- "$STAGE"
@@ -35,11 +40,10 @@ EXTERNALS_CACHE_DIR="$OUT/cache" make install DESTDIR="$STAGE"
# binary package.
rm -rf -- "$STAGE/etc/asterisk"
tar -C "$STAGE" -cpf "$OUT/asterisk-$VERSION-native-stage.tar" .
cp "$ROOT/deploys/cell/asterisk.service" "$OUT/asterisk.service"
cp "$ROOT/deploys/cell/install-asterisk-native.sh" "$OUT/install-asterisk-native.sh"
cp "$ROOT/deploys/cell/install-asterisk-user.sh" "$OUT/install-asterisk-user.sh"
cp "$ROOT/deploys/cell/native-platform.sh" "$OUT/native-platform.sh"
printf 'debian:%s:x86_64\n' "$VERSION_ID" > "$OUT/build-platform"
chmod 0755 "$OUT/install-asterisk-native.sh"
printf 'debian:%s:x86_64\n' "$OS_VERSION" > "$OUT/build-platform"
chmod 0755 "$OUT/install-asterisk-user.sh"
(
cd "$OUT"
sha256sum "asterisk-$VERSION-native-stage.tar" > "asterisk-$VERSION-native-stage.tar.sha256"
-37
View File
@@ -1,37 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
[[ ${EUID} -eq 0 ]] || { echo 'install-asterisk-native.sh must run as root' >&2; exit 1; }
START=false
NONPROD=0
for arg in "$@"; do
case "$arg" in
--start) START=true ;;
--nonprod) NONPROD=1 ;;
*) echo "unknown option: $arg" >&2; exit 2 ;;
esac
done
PACKAGE_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
cd -- "$PACKAGE_DIR"
. /etc/os-release
[[ -f native-platform.sh && -f build-platform ]] || { echo 'native build platform metadata is missing' >&2; exit 1; }
source ./native-platform.sh
require_native_platform "$ID" "$VERSION_ID" "$(uname -m)" "$(<build-platform)" "$NONPROD"
STAGE=asterisk-22.10.1-native-stage.tar
sha256sum -c "$STAGE.sha256"
[[ -f asterisk.service ]] || { echo 'asterisk.service is missing' >&2; exit 1; }
getent group asterisk >/dev/null || groupadd --system asterisk
id -u asterisk >/dev/null 2>&1 || useradd --system --home-dir /var/lib/asterisk --shell /usr/sbin/nologin --gid asterisk asterisk
# Keep management-owned /etc/asterisk configuration intact.
tar --exclude='etc/asterisk/*' -xpf "$STAGE" -C /
ldconfig
install -d -o asterisk -g asterisk -m 0750 /var/lib/asterisk /var/log/asterisk /var/spool/asterisk /var/run/asterisk
install -o root -g root -m 0644 asterisk.service /etc/systemd/system/asterisk.service
systemctl daemon-reload
systemctl enable asterisk.service
if [[ "$START" == true ]]; then
systemctl restart asterisk.service
fi
/usr/sbin/asterisk -V
printf 'installed asterisk=22.10.1 start=%s config_preserved=true\n' "$START"
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# Native, unprivileged Asterisk installation for the approved rogee user.
set -euo pipefail
nonprod=0
if [[ ${1:-} == --nonprod ]]; then nonprod=1; shift; fi
[[ $# == 1 ]] || { echo 'usage: install-asterisk-user.sh [--nonprod] <verified-native-package-dir>' >&2; exit 2; }
[[ $(id -u) != 0 ]] || { echo 'do not run the user service installer as root' >&2; exit 1; }
package=$(cd "$1" && pwd)
. /etc/os-release
source "$(dirname "$0")/native-platform.sh"
[[ -f $package/build-platform ]] || { echo 'native build platform metadata is missing' >&2; exit 1; }
require_native_platform "$ID" "$VERSION_ID" "$(uname -m)" "$(<"$package/build-platform")" "$nonprod"
archive=asterisk-22.10.1-native-stage.tar
(cd "$package" && sha256sum -c "$archive.sha256")
if [[ $nonprod == 0 && $(loginctl show-user "$(id -un)" -p Linger --value) != yes ]]; then
echo 'enable user lingering before a production user-service installation' >&2
exit 1
fi
prefix="$HOME/.local/opt/go-sip-asterisk/22.10.1"
config="$HOME/.config/go-sip-asterisk"
state="$HOME/.local/state/go-sip-asterisk"
data="$HOME/.local/share/go-sip-asterisk"
cache="$HOME/.cache/go-sip-asterisk"
unit="$HOME/.config/systemd/user/go-sip-asterisk.service"
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; }
mkdir -p "$(dirname "$prefix")"
staged=$(mktemp -d "$(dirname "$prefix")/.staged.XXXXXXXX")
trap 'rm -rf "$staged"' EXIT
tar -xpf "$package/$archive" -C "$staged"
[[ -x $staged/usr/sbin/asterisk ]] || { echo 'native package has no Asterisk executable' >&2; exit 1; }
if LD_LIBRARY_PATH="$staged/usr/lib" ldd "$staged/usr/sbin/asterisk" | grep -q 'not found'; then
echo 'native Asterisk runtime libraries are missing' >&2; exit 1
fi
LD_LIBRARY_PATH="$staged/usr/lib" "$staged/usr/sbin/asterisk" -V
mv "$staged" "$prefix"
trap - EXIT
mkdir -p "$config" "$state/log" "$state/spool" "$data/db" "$data/keys" "$data/agi-bin" "$cache" "$(dirname "$unit")"
cat > "$config/asterisk.conf" <<EOF
[directories]
astcachedir => $cache
astetcdir => $config
astmoddir => $prefix/usr/lib/asterisk/modules
astvarlibdir => $data
astdbdir => $data/db
astkeydir => $data/keys
astdatadir => $prefix/var/lib/asterisk
astagidir => $data/agi-bin
astspooldir => $state/spool
astrundir => $runtime
astlogdir => $state/log
EOF
printf '[modules]\nautoload=yes\n' > "$config/modules.conf"
cat > "$unit" <<EOF
[Unit]
Description=Go SIP Cell native Asterisk (user service)
After=network-online.target
Wants=network-online.target
[Service]
Type=exec
Environment=LD_LIBRARY_PATH=$prefix/usr/lib
RuntimeDirectory=go-sip-asterisk
WorkingDirectory=$data
ExecStart=$prefix/usr/sbin/asterisk -f -C $config/asterisk.conf
ExecReload=$prefix/usr/sbin/asterisk -C $config/asterisk.conf -rx "module reload res_pjsip.so"
Restart=on-failure
RestartSec=3
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now go-sip-asterisk.service
systemctl --user is-enabled go-sip-asterisk.service
sleep 2
systemctl --user is-active --quiet go-sip-asterisk.service || { echo 'Asterisk exited during startup' >&2; exit 1; }
LD_LIBRARY_PATH="$prefix/usr/lib" "$prefix/usr/sbin/asterisk" -C "$config/asterisk.conf" -rx 'core show version' | grep -q 'Asterisk 22.10.1' || { echo 'Asterisk CLI did not report the expected live version' >&2; exit 1; }
if [[ $(loginctl show-user "$(id -un)" -p Linger --value) != yes ]]; then
echo 'nonproduction only: user lingering disabled; reboot persistence NOT verified' >&2
fi
@@ -0,0 +1 @@
debian:13:x86_64
+8 -5
View File
@@ -10,8 +10,8 @@ to Debian 12. This exception does not approve production deployment or real call
Production remains a small systemd installation on Debian 13 amd64:
1. native Asterisk Cell (`asterisk.service`), owned by the approved SIP
management release;
1. native Asterisk Cell (`go-sip-asterisk.service`) under `rogee`'s `systemd --user`,
with lingering enabled and reboot-persistent `enabled+active` verified;
2. `sip-go-agent-agent.service`;
3. `sip-go-agent-dispatcher.service`.
@@ -23,9 +23,12 @@ The pinned versions are in [`versions.lock.json`](versions.lock.json). Build
a release candidate with `build-package.sh`; it writes the archive to
`dist/packages/` and is intentionally not production-approved. Production
installation requires a clean, externally approved manifest. Build/install
Asterisk separately with the scripts in [`cell/`](cell/). The Asterisk
installer preserves `/etc/asterisk`; the management-approved static Cell
configuration is installed separately.
Asterisk separately with the scripts in [`cell/`](cell/). The Asterisk user installer preserves existing files and places the Cell
configuration under `~rogee/.config/go-sip-asterisk/`; management approves and
updates `pjsip.conf` separately. Run `install-asterisk-user.sh --nonprod <native-package-dir>`
for a Debian 12 test build; without `--nonprod`, user lingering is required
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
+19
View File
@@ -0,0 +1,19 @@
# Nonproduction native Asterisk user service — 2026-10-03
This is **host-only evidence**, not SIP trunk, outbound-call, SaaS, or production acceptance.
The test host's address and raw logs are omitted from committed evidence.
- Fresh Debian 13 amd64 host, root directory mode `0755 root:root` (read-only inspection).
- Native Asterisk 22.10.1 stage SHA-256: `68006a1a8efed288be4ca4a2ae3cb9554a31d733eac08eaacf4c646c95faf74d`.
- Installed under `rogee`'s home without sudo; `systemctl --user` reported `go-sip-asterisk.service` **enabled and active**, and the live CLI returned Asterisk 22.10.1.
- `systemctl --user reload` succeeded and `res_pjsip.so` reported running. **No PJSIP endpoints were configured or loaded**; this does not verify adding/changing a real trunk or reloading it during an active call.
- User lingering was **disabled** by user choice: reboot persistence is **not verified**. The production installer now refuses installation without lingering; `--nonprod` is explicitly session-scoped.
- An earlier user-install draft generated a template-only `[directories](!)` stanza and missed a startup crash. Corrected to `[directories]` and a live CLI readiness check; the corrected installer was checked for platform/lingering and no-overwrite behavior, but a fresh-install run of that final revision remains **unverified**.
- Prior Debian 12 nonproduction host: a native, same-OS Asterisk 22.10.1 stage was built and its checksum checked. The default installer rejected Debian 12; `--nonprod` installed the stage. Its system service could not be accepted and the host was subsequently reinstalled; do not count that as a successful Cell deployment.
## Still required before a real call or production acceptance
1. Implement and prove real Dispatcher→Agent SIP apply/reload and Agent-observed loaded state (current Go call runtime is still Mock-only). Validate endpoint add/edit against Asterisk while a call is active; explicitly reject unsupported authentication/REGISTER and transport changes.
2. Provide approved real line configuration and arrange each whitelist trial (trunk, original number, time, attempt count). The fixed Asia/Shanghai 09:00–20:00 gate and per-number daily cap remain mandatory.
3. Establish RabbitMQ/OSS/AI real integrations and nonproduction call-evidence capture. `deploys/test/nonprod-call-evidence.sh` requires root or the required capture capabilities; this host's `rogee` currently has no sudo. If tcpdump, logger, or ARI/PJSIP state is unavailable, do not dial.
4. Enable `rogee` user lingering and verify reboot-persistent `enabled+active` before claiming production readiness. Complete the host/network/dependency diagnostics and external signoffs separately; local `make check` cannot replace them.
+2 -2
View File
@@ -12,7 +12,7 @@ func TestDeploymentHasOneCanonicalDirectory(t *testing.T) {
if _, err := os.Stat(filepath.Join(root, "deploy")); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("obsolete deploy directory must not remain: %v", err)
}
for _, path := range []string{"README.md", "build-package.sh", "install.sh", "versions.lock.json", "cell/install-asterisk-native.sh", "test/nonprod-call-evidence.sh", "test/README.md", "systemd/sip-go-agent-agent.service", "systemd/sip-go-agent-dispatcher.service", "config/agent-endpoints.example.json"} {
for _, path := range []string{"README.md", "build-package.sh", "install.sh", "versions.lock.json", "cell/install-asterisk-user.sh", "test/nonprod-call-evidence.sh", "test/README.md", "systemd/sip-go-agent-agent.service", "systemd/sip-go-agent-dispatcher.service", "config/agent-endpoints.example.json"} {
info, err := os.Stat(filepath.Join(root, "deploys", path))
if err != nil {
t.Errorf("canonical deployment entry %s: %v", path, err)
@@ -22,7 +22,7 @@ func TestDeploymentHasOneCanonicalDirectory(t *testing.T) {
t.Errorf("deployment entry %s is not a regular file", path)
}
}
for _, path := range []string{"config/dispatcher.json.example", "config/static-cell-artifact-v2.json", "config/static-cell-runtime-v1.json", "test/ai-dental-meiba-v1.json"} {
for _, path := range []string{"config/dispatcher.json.example", "config/static-cell-artifact-v2.json", "config/static-cell-runtime-v1.json", "test/ai-dental-meiba-v1.json", "cell/install-asterisk-native.sh", "cell/asterisk.service"} {
if _, err := os.Lstat(filepath.Join(root, "deploys", path)); !errors.Is(err, os.ErrNotExist) {
t.Errorf("retired deployment example %s still active: %v", path, err)
}