merged from gramsrv upstream

This commit is contained in:
onysd 2026-09-01 12:06:31 +03:00
parent 79c64ee916
commit 21a0856587
651 changed files with 54774 additions and 4590 deletions

130
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,130 @@
name: Build and Release
on:
workflow_dispatch:
push:
tags:
- 'v*'
permissions:
contents: read
concurrency:
group: build-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
ci:
name: CI
uses: ./.github/workflows/ci.yml
build:
name: Build ${{ matrix.goos }}/${{ matrix.goarch }}
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- goos: linux
goarch: amd64
- goos: linux
goarch: arm64
- goos: windows
goarch: amd64
- goos: windows
goarch: arm64
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: '22'
cache: npm
cache-dependency-path: cmd/telesrv-admin/web/package-lock.json
- name: Build admin web assets
working-directory: cmd/telesrv-admin/web
run: |
npm ci
npm run build
- name: Download Go modules
run: go mod download
- name: Build binaries
env:
CGO_ENABLED: '0'
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: |
mkdir -p dist
suffix=""
if [ "${GOOS}" = "windows" ]; then
suffix=".exe"
fi
go build \
-trimpath \
-ldflags="-s -w" \
-o "dist/gramsrv-${GOOS}-${GOARCH}${suffix}" \
./cmd/telesrv
go build \
-trimpath \
-ldflags="-s -w" \
-o "dist/gramsrv-admin-${GOOS}-${GOARCH}${suffix}" \
./cmd/telesrv-admin
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: gramsrv-${{ matrix.goos }}-${{ matrix.goarch }}
path: dist/*
if-no-files-found: error
retention-days: 7
release:
name: Publish GitHub Release
needs:
- ci
- build
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: write
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
pattern: gramsrv-*
path: dist
merge-multiple: true
- name: Generate combined checksums
working-directory: dist
run: |
sha256sum gramsrv-* > SHA256SUMS
- name: Publish GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${{ github.ref_name }}" \
dist/* \
--title "${{ github.ref_name }}" \
--generate-notes

180
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,180 @@
name: CI
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
go-test:
name: Go tests
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: Download Go modules
run: go mod download
- name: Test
run: go test ./... -count=1
admin-web:
name: Admin web build
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: '22'
cache: npm
cache-dependency-path: cmd/telesrv-admin/web/package-lock.json
- name: Install dependencies
working-directory: cmd/telesrv-admin/web
run: npm ci
- name: Build
working-directory: cmd/telesrv-admin/web
run: npm run build
grammystore:
name: Grammy store bot
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: '22'
cache: npm
cache-dependency-path: cmd/bots/grammystore/package-lock.json
- name: Install dependencies
working-directory: cmd/bots/grammystore
run: npm ci
- name: Check syntax
working-directory: cmd/bots/grammystore
run: npm run check
- name: Test
working-directory: cmd/bots/grammystore
run: npm test
docker-smoke:
name: Docker main topology smoke
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Generate isolated environment
run: ./scripts/new-docker-env.sh --advertise-ip 127.0.0.1
- name: Validate deployment inputs
run: |
docker compose version
sh -n scripts/new-docker-env.sh
bash -n scripts/start-docker.sh
sh -n deploy/docker/docker-entrypoint.sh
compose=(docker compose -p telesrv-main-ci --project-directory deploy/docker --env-file deploy/docker/.env -f deploy/docker/compose.yaml)
bridge=("${compose[@]}" -f deploy/docker/compose.bridge-network.yaml)
"${compose[@]}" config --quiet
"${compose[@]}" config --format json | python3 -c 'import json,sys; s=json.load(sys.stdin)["services"]; assert s["server"].get("network_mode") == "host"; assert s["admin"].get("network_mode") == "host"; assert len(s["server"].get("ports", [])) == 0; assert len(s["admin"].get("ports", [])) == 0; assert str(s["server"]["environment"]["TELESRV_TURN_RELAY_MAX_PORT"]) == "12999"'
"${bridge[@]}" config --quiet
"${bridge[@]}" config --format json | python3 -c 'import json,sys; c=json.load(sys.stdin); s=c["services"]; assert s["server"].get("network_mode") != "host"; assert s["admin"].get("network_mode") != "host"; assert len(s["server"].get("ports", [])) == 69; assert len(s["admin"].get("ports", [])) == 1; assert "admin_host_access" in s["admin"]["networks"]; assert not c["networks"]["admin_host_access"].get("internal", False); assert str(s["server"]["environment"]["TELESRV_TURN_RELAY_MAX_PORT"]) == "12563"'
- name: Validate PowerShell launchers
shell: pwsh
run: |
$tokens = $null
$errors = $null
[void][System.Management.Automation.Language.Parser]::ParseFile("scripts/new-docker-env.ps1", [ref]$tokens, [ref]$errors)
if ($errors.Count -gt 0) { $errors | ForEach-Object { Write-Error $_ }; exit 1 }
$tokens = $null
$errors = $null
[void][System.Management.Automation.Language.Parser]::ParseFile("scripts/start-docker.ps1", [ref]$tokens, [ref]$errors)
if ($errors.Count -gt 0) { $errors | ForEach-Object { Write-Error $_ }; exit 1 }
- name: Build application images
run: |
compose=(docker compose -p telesrv-main-ci --project-directory deploy/docker --env-file deploy/docker/.env -f deploy/docker/compose.yaml)
"${compose[@]}" build --pull server admin
- name: Start and wait for readiness
run: docker compose -p telesrv-main-ci --project-directory deploy/docker --env-file deploy/docker/.env -f deploy/docker/compose.yaml up -d --no-build --wait --wait-timeout 600
- name: Verify runtime and media listeners
run: |
compose=(docker compose -p telesrv-main-ci --project-directory deploy/docker --env-file deploy/docker/.env -f deploy/docker/compose.yaml)
for service in server admin; do
container_id="$("${compose[@]}" ps --quiet "$service")"
test -n "$container_id"
test "$(docker inspect --format '{{.Config.User}}' "$container_id")" = "10001:10001"
test "$(docker inspect --format '{{.HostConfig.ReadonlyRootfs}}' "$container_id")" = "true"
test "$(docker inspect --format '{{json .HostConfig.CapDrop}}' "$container_id")" = '["ALL"]'
test "$(docker inspect --format '{{.HostConfig.PidsLimit}}' "$container_id")" = "1024"
test "$(docker inspect --format '{{json .HostConfig.SecurityOpt}}' "$container_id")" = '["no-new-privileges:true"]'
test "$(docker inspect --format '{{.HostConfig.NetworkMode}}' "$container_id")" = "host"
done
curl --fail --silent --show-error http://127.0.0.1:2401/healthz | grep -qx ok
curl --fail --silent --show-error http://127.0.0.1:2600/ >/dev/null
timeout 5 bash -c 'exec 3<>/dev/tcp/127.0.0.1/2400'
python3 - <<'PY'
import os
import socket
import struct
transaction_id = os.urandom(12)
request = struct.pack("!HHI12s", 0x0001, 0, 0x2112A442, transaction_id)
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client:
client.settimeout(5)
client.sendto(request, ("127.0.0.1", 12400))
response, _ = client.recvfrom(2048)
message_type, _, cookie = struct.unpack("!HHI", response[:8])
assert message_type == 0x0101, hex(message_type)
assert cookie == 0x2112A442, hex(cookie)
assert response[8:20] == transaction_id
PY
server_logs="$("${compose[@]}" logs --no-color server)"
case "$server_logs" in *"sfu listening"*) ;; *) echo "Embedded SFU did not become ready" >&2; exit 1 ;; esac
case "$server_logs" in *"turn listening"*) ;; *) echo "Embedded TURN did not become ready" >&2; exit 1 ;; esac
case "$server_logs" in *"live stream rtmp ingest listening"*) ;; *) echo "RTMP listener did not become ready" >&2; exit 1 ;; esac
- name: Show logs on failure
if: failure()
run: docker compose -p telesrv-main-ci --project-directory deploy/docker --env-file deploy/docker/.env -f deploy/docker/compose.yaml logs --no-color --tail 200
- name: Remove isolated stack
if: always()
run: docker compose -p telesrv-main-ci --project-directory deploy/docker --env-file deploy/docker/.env -f deploy/docker/compose.yaml down --volumes --remove-orphans

76
.github/workflows/container-images.yml vendored Normal file
View file

@ -0,0 +1,76 @@
name: Publish main container images (manual)
on:
workflow_dispatch:
permissions:
contents: read
packages: write
concurrency:
group: containers-main-${{ github.ref }}
cancel-in-progress: true
jobs:
publish:
name: Publish ${{ matrix.role }}
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- role: server
target: server-test
- role: admin
target: admin
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Log in to GHCR
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate image metadata
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ghcr.io/${{ github.repository }}/${{ matrix.role }}
tags: |
type=raw,value=main
type=sha,prefix=sha-
- name: Set build date
id: build
shell: bash
run: echo "date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT"
- name: Build and publish
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
file: Dockerfile
target: ${{ matrix.target }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VCS_REF=${{ github.sha }}
VCS_BRANCH=${{ github.ref_name }}
VCS_TREE_STATE=clean
BUILD_DATE=${{ steps.build.outputs.date }}
cache-from: type=gha,scope=main-${{ matrix.role }}
cache-to: type=gha,mode=max,scope=main-${{ matrix.role }}