6 Commits

Author SHA1 Message Date
21ffc1d017 v1.20.1
Some checks failed
Docker (tags) / release (push) Failing after 3s
2026-04-11 12:32:46 +00:00
2f16c5efae fix(docker): install required native build tools for Rust dependencies in the build image 2026-04-11 12:32:46 +00:00
254d7f3633 v1.20.0
Some checks failed
Docker (tags) / release (push) Failing after 3m53s
2026-04-11 12:01:54 +00:00
67537664df feat(docker): add multi-arch Docker build and tagged release pipeline 2026-04-11 12:01:54 +00:00
54129dcdae v1.19.2 2026-04-11 08:24:47 +00:00
8c6556dae3 fix(web-ui): normalize lucide icon names across SIP proxy views 2026-04-11 08:24:47 +00:00
16 changed files with 417 additions and 450 deletions

16
.dockerignore Normal file
View File

@@ -0,0 +1,16 @@
node_modules/
.nogit/
nogit/
.git/
.playwright-mcp/
.vscode/
test/
dist_rust/
dist_ts_web/
rust/target/
sip_trace.log
sip_trace_*.log
proxy.out
proxy_v2.out
*.pid
.server.pid

View File

@@ -0,0 +1,32 @@
name: Docker (tags)
on:
push:
tags:
- '*'
env:
IMAGE: code.foss.global/host.today/ht-docker-node:dbase_dind
NPMCI_LOGIN_DOCKER_GITEA: ${{ github.server_url }}|${{ gitea.repository_owner }}|${{ secrets.GITEA_TOKEN }}
NPMCI_LOGIN_DOCKER_DOCKERREGISTRY: ${{ secrets.NPMCI_LOGIN_DOCKER_DOCKERREGISTRY }}
jobs:
release:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @git.zone/tsdocker
- name: Release
run: |
tsdocker login
tsdocker build
tsdocker push

View File

@@ -8,5 +8,16 @@
"production": true
}
]
},
"@git.zone/tsrust": {
"targets": ["linux_amd64", "linux_arm64"]
},
"@git.zone/tsdocker": {
"registries": ["code.foss.global"],
"registryRepoMap": {
"code.foss.global": "serve.zone/siprouter",
"dockerregistry.lossless.digital": "serve.zone/siprouter"
},
"platforms": ["linux/amd64", "linux/arm64"]
}
}

74
Dockerfile Normal file
View File

@@ -0,0 +1,74 @@
# gitzone dockerfile_service
## STAGE 1 // BUILD
FROM code.foss.global/host.today/ht-docker-node:lts AS build
# System build tools that the Rust dep tree needs beyond the base image:
# - cmake : used by the `cmake` crate (transitive via ort_sys / a webrtc
# sub-crate) to build a C/C++ library from source when a
# prebuilt-binary download path doesn't apply.
# - pkg-config : used by audiopus_sys and other *-sys crates to locate libs
# on the native target (safe no-op if they vendor their own).
# These are normally pre-installed on dev machines but not in ht-docker-node:lts.
RUN apt-get update && apt-get install -y --no-install-recommends \
cmake \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# buildx sets TARGETARCH automatically for each platform it's building:
# linux/amd64 -> TARGETARCH=amd64
# linux/arm64 -> TARGETARCH=arm64
# We use it to tell tsrust to build ONLY the current container's arch. This
# overrides the `@git.zone/tsrust.targets` list in .smartconfig.json, which is
# right for local dev / CI (where you want both binaries) but wrong for per-
# platform Docker stages (each stage would then also try to cross-compile to
# the OTHER arch — which fails in the arm64 stage because no reverse cross-
# toolchain is installed).
#
# With --target set, tsrust builds a single target natively within whichever
# platform this stage is running under (native on amd64, QEMU-emulated on arm64).
ARG TARGETARCH
COPY ./ /app
WORKDIR /app
RUN pnpm config set store-dir .pnpm-store
RUN rm -rf node_modules && pnpm install
# tsrust --target takes precedence over .smartconfig.json's targets array.
# Writes dist_rust/proxy-engine_linux_amd64 or dist_rust/proxy-engine_linux_arm64.
# The TS layer (ts/proxybridge.ts buildLocalPaths) picks the right one at runtime
# via process.arch.
RUN pnpm exec tsrust --target linux_${TARGETARCH}
# Web bundle (esbuild — pure JS, uses the platform's native esbuild binary
# installed by pnpm above, so no cross-bundling concerns).
RUN pnpm run bundle
# Drop pnpm store to keep the image smaller. node_modules stays because the
# runtime entrypoint is tsx and siprouter has no separate dist_ts/ to run from.
RUN rm -rf .pnpm-store
## STAGE 2 // PRODUCTION
FROM code.foss.global/host.today/ht-docker-node:alpine-node AS production
# gcompat + libstdc++ let the glibc-linked proxy-engine binary run on Alpine.
RUN apk add --no-cache gcompat libstdc++
WORKDIR /app
COPY --from=build /app /app
ENV SIPROUTER_MODE=OCI_CONTAINER
ENV NODE_ENV=production
LABEL org.opencontainers.image.title="siprouter" \
org.opencontainers.image.description="SIP proxy with Rust data plane and WebRTC bridge" \
org.opencontainers.image.source="https://code.foss.global/serve.zone/siprouter"
# 5070 SIP signaling (UDP+TCP)
# 5061 SIP-TLS (optional, UDP+TCP)
# 3060 Web UI / WebSocket (HTTP or HTTPS, auto-detected from .nogit/cert.pem)
# 20000-20200/udp RTP media range (must match config.proxy.rtpPortRange)
EXPOSE 5070/udp 5070/tcp 5061/udp 5061/tcp 3060/tcp 20000-20200/udp
# exec replaces sh as PID 1 with tsx, so SIGINT/SIGTERM reach Node and
# ts/sipproxy.ts' shutdown handler (which calls shutdownProxyEngine) runs cleanly.
CMD ["sh", "-c", "exec ./node_modules/.bin/tsx ts/sipproxy.ts"]

View File

@@ -1,5 +1,26 @@
# Changelog
## 2026-04-11 - 1.20.1 - fix(docker)
install required native build tools for Rust dependencies in the build image
- Add cmake and pkg-config to the Docker build stage so Rust native dependencies can compile successfully in the container
- Document why these tools are needed for transitive Rust crates that build or detect native libraries
## 2026-04-11 - 1.20.0 - feat(docker)
add multi-arch Docker build and tagged release pipeline
- Add a production Dockerfile for building and running the SIP router with the Rust proxy engine and web bundle
- Configure tsdocker and tsrust for linux/amd64 and linux/arm64 image builds and registry mapping
- Add a tag-triggered Gitea workflow to build and push Docker images
- Update runtime binary resolution to load architecture-specific Rust artifacts in Docker and CI environments
- Add Docker-related package scripts, dependency updates, and ignore rules for container builds
## 2026-04-11 - 1.19.2 - fix(web-ui)
normalize lucide icon names across SIP proxy views
- Updates icon identifiers to the expected PascalCase lucide format in app navigation, calls, IVR, overview, providers, and voicemail views.
- Fixes UI icon rendering for stats cards and action menus such as transfer, delete, status, and call direction indicators.
## 2026-04-10 - 1.19.1 - fix(readme)
refresh documentation for jitter buffering, voicemail, and WebSocket signaling details

View File

@@ -1,11 +1,14 @@
{
"name": "siprouter",
"version": "1.19.1",
"version": "1.20.1",
"private": true,
"type": "module",
"scripts": {
"bundle": "node node_modules/.pnpm/esbuild@0.27.7/node_modules/esbuild/bin/esbuild ts_web/index.ts --bundle --format=esm --outfile=dist_ts_web/bundle.js --platform=browser --target=es2022 --minify",
"buildRust": "tsrust",
"build": "pnpm run buildRust && pnpm run bundle",
"build:docker": "tsdocker build --verbose",
"release:docker": "tsdocker push --verbose",
"start": "tsx ts/sipproxy.ts",
"restartBackground": "pnpm run buildRust && pnpm run bundle; test -f .server.pid && kill $(cat .server.pid) 2>/dev/null; sleep 1; rm -f sip_trace.log proxy.out && nohup tsx ts/sipproxy.ts > proxy.out 2>&1 & echo $! > .server.pid; sleep 2; cat proxy.out"
},
@@ -14,10 +17,12 @@
"@design.estate/dees-element": "^2.2.4",
"@push.rocks/smartrust": "^1.3.2",
"@push.rocks/smartstate": "^2.3.0",
"tsx": "^4.21.0",
"ws": "^8.20.0"
},
"devDependencies": {
"@git.zone/tsbundle": "^2.10.0",
"@git.zone/tsdocker": "^2.2.4",
"@git.zone/tsrust": "^1.3.2",
"@git.zone/tswatch": "^3.3.2",
"@types/ws": "^8.18.1"

650
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: 'siprouter',
version: '1.19.1',
version: '1.20.1',
description: 'undefined'
}

View File

@@ -134,8 +134,22 @@ let logFn: ((msg: string) => void) | undefined;
function buildLocalPaths(): string[] {
const root = process.cwd();
// Map Node's process.arch to tsrust's friendly target name.
// tsrust writes multi-target binaries as <bin>_<os>_<arch>,
// e.g. proxy-engine_linux_amd64 / proxy-engine_linux_arm64.
const archSuffix =
process.arch === 'arm64' ? 'linux_arm64' :
process.arch === 'x64' ? 'linux_amd64' :
null;
const multiTarget = archSuffix
? [path.join(root, 'dist_rust', `proxy-engine_${archSuffix}`)]
: [];
return [
// 1. Multi-target output matching the running host arch (Docker image, CI, multi-target dev).
...multiTarget,
// 2. Single-target (unsuffixed) output — legacy/fallback when tsrust runs without targets.
path.join(root, 'dist_rust', 'proxy-engine'),
// 3. Direct cargo builds for dev iteration.
path.join(root, 'rust', 'target', 'release', 'proxy-engine'),
path.join(root, 'rust', 'target', 'debug', 'proxy-engine'),
];

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: 'siprouter',
version: '1.19.1',
version: '1.20.1',
description: 'undefined'
}

View File

@@ -18,7 +18,7 @@ const VIEW_TABS = [
{ name: 'Phone', iconName: 'lucide:headset', element: SipproxyViewPhone },
{ name: 'Routes', iconName: 'lucide:route', element: SipproxyViewRoutes },
{ name: 'Voicemail', iconName: 'lucide:voicemail', element: SipproxyViewVoicemail },
{ name: 'IVR', iconName: 'lucide:list-tree', element: SipproxyViewIvr },
{ name: 'IVR', iconName: 'lucide:ListTree', element: SipproxyViewIvr },
{ name: 'Contacts', iconName: 'lucide:contactRound', element: SipproxyViewContacts },
{ name: 'Providers', iconName: 'lucide:server', element: SipproxyViewProviders },
{ name: 'Log', iconName: 'lucide:scrollText', element: SipproxyViewLog },

View File

@@ -422,7 +422,7 @@ export class SipproxyViewCalls extends DeesElement {
menuOptions: [
{
name: 'Transfer',
iconName: 'lucide:arrow-right-left',
iconName: 'lucide:ArrowRightLeft',
action: async (modalRef: any) => {
if (!targetCallId || !targetLegId) {
deesCatalog.DeesToast.error('Please select both a target call and a leg');
@@ -620,7 +620,7 @@ export class SipproxyViewCalls extends DeesElement {
title: 'Inbound',
value: inboundCount,
type: 'number',
icon: 'lucide:phone-incoming',
icon: 'lucide:PhoneIncoming',
description: 'Incoming calls',
},
{
@@ -628,7 +628,7 @@ export class SipproxyViewCalls extends DeesElement {
title: 'Outbound',
value: outboundCount,
type: 'number',
icon: 'lucide:phone-outgoing',
icon: 'lucide:PhoneOutgoing',
description: 'Outgoing calls',
},
];

View File

@@ -140,7 +140,7 @@ export class SipproxyViewIvr extends DeesElement {
title: 'Total Menus',
value: ivr.menus.length,
type: 'number',
icon: 'lucide:list-tree',
icon: 'lucide:ListTree',
description: 'IVR menu definitions',
},
{
@@ -148,7 +148,7 @@ export class SipproxyViewIvr extends DeesElement {
title: 'Entry Menu',
value: entryMenu?.name || '(none)',
type: 'text' as any,
icon: 'lucide:door-open',
icon: 'lucide:DoorOpen',
description: entryMenu ? `ID: ${entryMenu.id}` : 'No entry menu set',
},
{
@@ -156,7 +156,7 @@ export class SipproxyViewIvr extends DeesElement {
title: 'Status',
value: ivr.enabled ? 'Enabled' : 'Disabled',
type: 'text' as any,
icon: ivr.enabled ? 'lucide:check-circle' : 'lucide:x-circle',
icon: ivr.enabled ? 'lucide:CheckCircle' : 'lucide:XCircle',
color: ivr.enabled ? 'hsl(142.1 76.2% 36.3%)' : 'hsl(0 84.2% 60.2%)',
description: ivr.enabled ? 'IVR is active' : 'IVR is inactive',
},
@@ -228,7 +228,7 @@ export class SipproxyViewIvr extends DeesElement {
},
{
name: 'Set as Entry',
iconName: 'lucide:door-open' as any,
iconName: 'lucide:DoorOpen' as any,
type: ['inRow'] as any,
actionFunc: async ({ item }: { item: IIvrMenu }) => {
await this.setEntryMenu(item.id);
@@ -236,7 +236,7 @@ export class SipproxyViewIvr extends DeesElement {
},
{
name: 'Delete',
iconName: 'lucide:trash-2' as any,
iconName: 'lucide:Trash2' as any,
type: ['inRow'] as any,
actionFunc: async ({ item }: { item: IIvrMenu }) => {
await this.confirmDeleteMenu(item);
@@ -295,7 +295,7 @@ export class SipproxyViewIvr extends DeesElement {
},
{
name: 'Delete',
iconName: 'lucide:trash-2',
iconName: 'lucide:Trash2',
action: async (modalRef: any) => {
const ivr = this.getIvrConfig();
const menus = ivr.menus.filter((m) => m.id !== menu.id);

View File

@@ -107,7 +107,7 @@ export class SipproxyViewOverview extends DeesElement {
title: 'Inbound Calls',
value: inboundCalls,
type: 'number',
icon: 'lucide:phone-incoming',
icon: 'lucide:PhoneIncoming',
description: 'Currently active',
},
{
@@ -115,7 +115,7 @@ export class SipproxyViewOverview extends DeesElement {
title: 'Outbound Calls',
value: outboundCalls,
type: 'number',
icon: 'lucide:phone-outgoing',
icon: 'lucide:PhoneOutgoing',
description: 'Currently active',
},
{

View File

@@ -86,7 +86,7 @@ export class SipproxyViewProviders extends DeesElement {
title: 'Registered',
value: registered,
type: 'number',
icon: 'lucide:check-circle',
icon: 'lucide:CheckCircle',
color: 'hsl(142.1 76.2% 36.3%)',
description: 'Active registrations',
},
@@ -95,7 +95,7 @@ export class SipproxyViewProviders extends DeesElement {
title: 'Unregistered',
value: unregistered,
type: 'number',
icon: 'lucide:alert-circle',
icon: 'lucide:AlertCircle',
color: unregistered > 0 ? 'hsl(0 84.2% 60.2%)' : undefined,
description: unregistered > 0 ? 'Needs attention' : 'All healthy',
},
@@ -153,7 +153,7 @@ export class SipproxyViewProviders extends DeesElement {
},
{
name: 'Delete',
iconName: 'lucide:trash-2',
iconName: 'lucide:Trash2',
type: ['inRow'] as any,
actionFunc: async (actionData: any) => {
await this.confirmDelete(actionData.item);
@@ -579,7 +579,7 @@ export class SipproxyViewProviders extends DeesElement {
},
{
name: 'Delete',
iconName: 'lucide:trash-2',
iconName: 'lucide:Trash2',
action: async (modalRef: any) => {
try {
const result = await appState.apiSaveConfig({

View File

@@ -239,7 +239,7 @@ export class SipproxyViewVoicemail extends DeesElement {
},
{
name: 'Delete',
iconName: 'lucide:trash-2',
iconName: 'lucide:Trash2',
action: async (modalRef: any) => {
try {
await fetch(
@@ -281,7 +281,7 @@ export class SipproxyViewVoicemail extends DeesElement {
title: 'Unheard Messages',
value: unheard,
type: 'number',
icon: 'lucide:bell-ring',
icon: 'lucide:BellRing',
color: unheard > 0 ? 'hsl(0 84.2% 60.2%)' : 'hsl(142.1 76.2% 36.3%)',
description: unheard > 0 ? 'Needs attention' : 'All caught up',
},
@@ -372,7 +372,7 @@ export class SipproxyViewVoicemail extends DeesElement {
},
{
name: 'Delete',
iconName: 'lucide:trash-2',
iconName: 'lucide:Trash2',
type: ['inRow'] as any,
actionFunc: async (actionData: any) => {
await this.deleteMessage(actionData.item as IVoicemailMessage);