feat(deploy): ship Server containers and repair NAT-PMP mappings (#400)

## Problem and outcome / 问题与结果

Self-hosting currently requires assembling a Server installation, while App port mappings can use the wrong external port or remain active after cancellation. This phase adds a verified Linux x64 Server image and Compose recipe, and repairs the existing NAT-PMP dependencies so mapping results, cancellation and cleanup follow the gateway response and request lifetime.

The image wraps the exact verified Server archive and is tested before publication. Version tags retain their original image; `latest` follows the latest published GitHub release. The two scoped dependency replacements retain upstream source, tests and licenses, with their removal criteria documented in `internal/thirdparty/README.md`.

Planned release: **v1.3.0** (compatible Docker distribution feature plus NAT fixes).

## Release notes

### 中文

- 新增 Linux x64 Docker 镜像与 Compose 部署方式,内置网页、信令和 STUN,可按需开启 SFU 兜底。提供部署、更新与备份教程。
- 修复 App 的 NAT-PMP 端口映射:正确使用路由器分配的外部端口,并在关闭时尝试删除映射。
- NAT-PMP 请求会随取消或超时结束,避免连接尝试结束后继续在后台重试。

已有配置和房间数据格式保持兼容,更新无需迁移。

### English

- Added a Linux x64 Docker image and Compose setup with the Web UI, signaling, STUN and optional SFU fallback, plus deployment, update and backup guides.
- Fixed App NAT-PMP mappings to use the external port assigned by the router and attempt mapping removal on shutdown.
- NAT-PMP requests now stop on cancellation or timeout, preventing retries from continuing after a connection attempt has ended.

Existing configuration and room data formats remain compatible; no migration is required.

## Verification / 验证

[Candidate CI](https://github.com/TNTcraftHIM/Piik/actions/runs/34761969362) passed for `1d8de6c4690434cd2cf44fbdee4f45b1776432fa`. The final two files changed after that run are documentation only (`docs/todo.md` and `docs/verification-status.md`), with repository hygiene checked separately.

- Web type checks, unit tests and production build; Go core tests, vet and target builds; NAT and media-edge race detection.
- Actual packaged-image checks with Docker Compose: production startup, embedded Web, UDP STUN, optional SFU startup, retained room authority across recreation, diagnostic ZIP export, non-root/read-only execution and clean shutdown.
- Windows x64, Linux x64 and macOS arm64 App packaging.
- Scoped NAT dependency regressions cover assigned ports, renewal, deletion, cancellation, lost replies and PCPv6 composition using real local UDP endpoints.

The publishing pipeline reruns the image checks for the merged release revision. First publication also requires making the GHCR package public and checking anonymous pulls of its version and `latest` tags. Representative physical routers, public-media networks and operator-specific HTTPS/firewall settings remain outside the container smoke test.

## Impact / 影响

No signaling, native-control or storage-format change. Existing App/Server installations remain compatible. Docker supports `linux/amd64` in this phase; its default is P2P, and SFU remains an explicit deployment option. Existing deployments keep their configuration.

README, bilingual self-hosting guides, container maintenance, versioning, dependency provenance and NAT evidence are updated. Completed feedback is removed from TODO; ongoing network evidence remains tracked there.
This commit is contained in:
AAA authored and GitHub committed 2026-09-13 23:09:14 +08:00
1 parent a5eaf3a54b
commit 02e71621f2
57 files changed
+5885 -127

No files matched your search

+39 -1
View File
@@ -73,6 +73,8 @@ jobs:
cache-dependency-path: go.sum
- name: Check Go core
run: npm run check:client-core
- name: Check NAT and media ownership with the race detector
run: npm run check:client-race
- name: Type-check and test
run: npm run typecheck && npm test
env:
@@ -105,7 +107,20 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Package application release
run: node scripts/package-app-release.mjs "$RUNNER_TEMP/app-release"
run: >-
node scripts/package-app-release.mjs "$RUNNER_TEMP/app-release"
--container-image "piik-candidate:$GITHUB_SHA"
- name: Check the container and deployment recipe
run: npm run check:container -- "piik-candidate:$GITHUB_SHA" "$GITHUB_SHA"
- name: Save the verified container
run: docker save "piik-candidate:$GITHUB_SHA" --output "$RUNNER_TEMP/piik-container.tar"
- name: Upload the verified container
uses: actions/upload-artifact@v7
with:
name: piik-container-${{ github.sha }}
path: ${{ runner.temp }}/piik-container.tar
if-no-files-found: error
retention-days: 14
- name: Upload application release
uses: actions/upload-artifact@v7
with:
@@ -195,6 +210,7 @@ jobs:
timeout-minutes: 30
permissions:
contents: write
packages: write
steps:
- name: Check out repository
uses: actions/checkout@v6
@@ -218,16 +234,38 @@ jobs:
pattern: piik-app-*-${{ github.sha }}
path: ${{ runner.temp }}/publish-release
merge-multiple: true
- name: Download the verified container
uses: actions/download-artifact@v8
with:
name: piik-container-${{ github.sha }}
path: ${{ runner.temp }}/container
- name: Load the verified container
run: docker load --input "$RUNNER_TEMP/container/piik-container.tar"
- name: Log in to GitHub Container Registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
# Local descriptors/manifests/checksums are validated before publishing.
# Release assets contain packages only; checksums appear in the release body.
- name: Publish packages with release notes and checksums
id: github_release
run: >-
node scripts/publish-release.mjs
"$RUNNER_TEMP/publish-release" "$PIIK_RELEASE_VERSION" "$GITHUB_SHA"
env:
GH_TOKEN: ${{ github.token }}
PIIK_RELEASE_VERSION: ${{ needs.validate.outputs.version }}
- name: Publish the matching Server container
run: >-
node scripts/publish-container.mjs "piik-candidate:$GITHUB_SHA"
"$RUNNER_TEMP/publish-release" "$PIIK_RELEASE_VERSION" "$GITHUB_SHA"
env:
GH_TOKEN: ${{ github.token }}
PIIK_RELEASE_VERSION: ${{ needs.validate.outputs.version }}
- name: Mirror verified release to Gitee
if: ${{ !cancelled() && steps.github_release.outcome == 'success' }}
run: >-
node scripts/mirror-release.mjs
"$RUNNER_TEMP/publish-release" "$PIIK_RELEASE_VERSION" "$GITHUB_SHA"