feat: rename product and runtime to Piik

Rename the active product, Go module, binaries, configuration, protocols and documentation to Piik while retaining the TV mascot and existing media/room behavior.

Use one current private contract without old-name aliases. Preserve SQLite authority and copy compatible local settings once; other settings start fresh. Git-message cleanup and the infrastructure name cutover are coordinated with this phase, with original history and deployment data archived externally.

Validated locally with Web and Go checks, Windows capture compilation, matched Client/Server builds, packaged Client control handshake and history-rewrite integrity checks. Skip duplicate hosted CI for this owner-authorized local release.

[skip ci]
This commit is contained in:
AAA authored and GitHub committed 2026-09-10 11:10:12 +08:00
1 parent 35f9251088
commit 1332473513
254 files changed
+1443 -1321

No files matched your search

+3 -3
View File
@@ -20,7 +20,7 @@ MAX_VIEWERS_PER_ROOM=8
# A peer child or Host SFU publication consumes one copy. Valid values: 1, 2, 3.
ENDPOINT_MEDIA_COPY_CAPACITY=2
# During pre-release route canaries, this enables sanitized room/ordinal events.
# SCREENER_DEBUG=route
# PIIK_DEBUG=route
# Optional in-process automatic SFU fallback. Set SFU_UDP_PORT=7882 to enable;
# unset or empty keeps it disabled. Local Client modes never create this listener.
@@ -50,11 +50,11 @@ NAT_PREDICTION_ENABLED=false
# Development example (two processes): Vite serves the Browser on 8787 and
# proxies /api, /healthz and /signal to the Go server on 8788. Keep
# PUBLIC_BASE_URL and ALLOWED_ORIGINS on the Browser origin above.
# SCREENER_ENV=development
# PIIK_ENV=development
# PORT=8788
# Minimal production example (use independent untracked secrets):
# SCREENER_ENV=production
# PIIK_ENV=production
# SITE_ACCESS_PASSWORD=replace-with-an-independent-access-key
# STUN_URLS=stun:stun.example.com:3478
# NAT_PREDICTION_ENABLED=true
+5 -5
View File
@@ -7,7 +7,7 @@ on:
workflow_dispatch:
inputs:
client_checks:
description: Run the optional Screener Client checks
description: Run the optional Piik Client checks
required: false
default: false
type: boolean
@@ -31,7 +31,7 @@ jobs:
- name: Validate tracked project state
run: sh scripts/check-project-state.sh
env:
SCREENER_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
PIIK_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
- name: Install dependencies
run: npm ci
# The Go build embeds internal/server/webassets/dist, so Vite runs first.
@@ -83,7 +83,7 @@ jobs:
- name: Upload application release
uses: actions/upload-artifact@v7
with:
name: screener-app-${{ github.sha }}
name: piik-app-${{ github.sha }}
path: ${{ runner.temp }}/app-release/*
if-no-files-found: error
retention-days: 14
@@ -142,7 +142,7 @@ jobs:
- name: Download application release
uses: actions/download-artifact@v8
with:
name: screener-app-${{ github.sha }}
name: piik-app-${{ github.sha }}
path: ${{ runner.temp }}/app-release
- name: Build and verify Client candidate
run: >-
@@ -153,7 +153,7 @@ jobs:
- name: Upload Client release candidate
uses: actions/upload-artifact@v7
with:
name: screener-client-${{ matrix.target }}-${{ github.sha }}
name: piik-client-${{ matrix.target }}-${{ github.sha }}
path: ${{ runner.temp }}/client-release/*
if-no-files-found: error
compression-level: 0
+1 -1
View File
@@ -23,7 +23,7 @@
## Product Contract
- Screener is private game screen sharing for one Host and up to 20 authenticated
- Piik is private game screen sharing for one Host and up to 20 authenticated
Viewers, with Web Host/Viewer/relay as the current product surface.
- Current product truth is split by owner:
[rooms/access](./docs/product/rooms-access.md),
+1 -1
View File
@@ -55,7 +55,7 @@ Clean up worktrees and branches only after semantic review and integration are c
- Use current community issues and operational reports to identify real compatibility failures, but label anecdotal evidence as such.
- Include a date and direct URLs. Separate verified facts, measurements, assumptions, and recommendations.
- Review licenses before copying implementation code. A useful reference is not automatically a compatible dependency.
- Screener-owned code is [MIT-licensed](./LICENSE); third-party components retain
- Piik-owned code is [MIT-licensed](./LICENSE); third-party components retain
their own licenses and notices. GPL/AGPL implementation sources remain
study-only unless a separate distribution decision accepts their obligations.
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2026 TNTcraft and Screener contributors
Copyright (c) 2026 TNTcraft and Piik contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+2 -2
View File
@@ -4,10 +4,10 @@ The authoritative license is the English [LICENSE](./LICENSE).
This translation is for convenience and adds no terms or restrictions.
本文件为方便阅读提供中文参考译文,不替代英文 [LICENSE](./LICENSE),
也不增加条件或限制。Screener 自有代码的 MIT 许可不替代
也不增加条件或限制。Piik 自有代码的 MIT 许可不替代
[第三方组件的许可与声明](./licenses/README.md)。
版权所有 (c) 2026 TNTcraft 和 Screener 贡献者
版权所有 (c) 2026 TNTcraft 和 Piik 贡献者
特此免费授予任何获得本软件及相关文档文件(以下简称“软件”)副本的人,
不受限制地处置本软件的权利,包括但不限于使用、复制、修改、合并、出版、
+12 -12
View File
@@ -1,18 +1,18 @@
<img src="./public/favicon.svg" width="64" height="64" alt="Screener TV logo">
<img src="./public/favicon.svg" width="64" height="64" alt="Piik TV logo">
# Screener
# Piik
English | [简体中文](./README.zh-CN.md)
Share your screen. Bring your friends.
Screener is private screen sharing for one Host and up to 20 invited friends.
Piik is private screen sharing for one Host and up to 20 invited friends.
Friends watch in their browsers, with no installation.
```mermaid
flowchart LR
Browser[Browser] --> Host[Host a room]
Client[Screener Client] --> Host
Client[Piik Client] --> Host
Host -->|Invite link| Friends[Friends watch in browsers]
```
@@ -21,12 +21,12 @@ flowchart LR
1. Open the invitation your friend sent.
2. Press Play if your browser asks. Use the video's sound and fullscreen controls.
Got a room code instead? Open the same Screener site and choose **Join a room**.
Got a room code instead? Open the same Piik site and choose **Join a room**.
Site access and the room's password settings still apply.
## Share from your browser
1. Open the Screener site your group uses. Enter its site password if asked.
1. Open the Piik site your group uses. Enter its site password if asked.
2. Choose **Start sharing**, pick a screen, window or tab, and check the sound switch.
3. Send the room's invitation link to your friends. Keep the sharing tab open.
@@ -39,20 +39,20 @@ same controls, with light and dark themes.
With a Client package for your platform:
1. Extract the whole package, keeping `runtime` beside the executable.
2. Open `screener-client.exe` on Windows, `Screener Client.app` on macOS, or
`./screener-client` on Linux.
2. Open `piik-client.exe` on Windows, `Piik Client.app` on macOS, or
`./piik-client` on Linux.
3. Choose a mode, pick what to share, and send the invitation. Keep Client running.
| Mode | Use it for |
| --- | --- |
| **Local room** | Friends on the same local network. An optional site password controls access. |
| **Public invite** | A temporary Internet invitation. The link lasts for this Client run; media remains P2P. |
| **Connect to Site** | An existing Screener site, with Client capture and media capabilities available in the same browser UI. |
| **Connect to Site** | An existing Piik site, with Client capture and media capabilities available in the same browser UI. |
Client opens your system browser. Its package includes the Go application and
capture/link helpers; running it needs no Node.js, npm or Go installation.
Linux native capture uses the system Portal, PipeWire and GStreamer stack.
See the [Client guide](./cmd/screener-client/README.md) for platform details.
See the [Client guide](./cmd/piik-client/README.md) for platform details.
Temporary public links use Cloudflare Quick Tunnel, which has no uptime guarantee.
Media needs a working UDP path; restrictive networks or browser/OS suspension
@@ -75,9 +75,9 @@ Include your version, OS/browser, expected result and steps to reproduce.
For a local report, start Client with `--debug` and press `D`, or add `?debug=1`
before any `#` in the Browser page URL and use its header download button. Review reports before
sharing; see [diagnostics and export](./docs/reference/configuration.md#diagnostics).
Connection trouble? Try the [Chromium WebRTC FAQ](./cmd/screener-client/README.md#chromium-webrtc-connections).
Connection trouble? Try the [Chromium WebRTC FAQ](./cmd/piik-client/README.md#chromium-webrtc-connections).
## License
Screener-owned code is [MIT licensed](./LICENSE). Third-party components retain
Piik-owned code is [MIT licensed](./LICENSE). Third-party components retain
their own licenses and notices: [licensing guide](./licenses/README.md).
+12 -12
View File
@@ -1,18 +1,18 @@
<img src="./public/favicon.svg" width="64" height="64" alt="Screener 小电视标志">
<img src="./public/favicon.svg" width="64" height="64" alt="Piik 小电视标志">
# Screener
# Piik
[English](./README.md) | 简体中文
分享你的屏幕,叫上朋友一起看。
Screener 是一个私密屏幕分享工具:一位房主,最多 20 位受邀好友。
Piik 是一个私密屏幕分享工具:一位房主,最多 20 位受邀好友。
朋友用浏览器就能观看,无须安装。
```mermaid
flowchart LR
Browser[浏览器] --> Host[开房分享]
Client[Screener Client] --> Host
Client[Piik Client] --> Host
Host -->|邀请链接| Friends[好友用浏览器观看]
```
@@ -21,12 +21,12 @@ flowchart LR
1. 打开朋友发来的邀请链接。
2. 如果浏览器提示,点击播放。声音和全屏由视频控件控制。
只有房间号?打开同一个 Screener 站点,选择 **加入房间**。
只有房间号?打开同一个 Piik 站点,选择 **加入房间**。
站点访问权限和房间密码设置仍然适用。
## 用浏览器分享
1. 打开你们使用的 Screener 站点,按提示输入站点密码。
1. 打开你们使用的 Piik 站点,按提示输入站点密码。
2. 点击 **开始分享**,选择屏幕、窗口或标签页,并检查声音开关。
3. 把房间邀请链接发给朋友,保持分享标签页打开。
@@ -38,19 +38,19 @@ flowchart LR
拿到对应平台的 Client 程序包后:
1. 完整解压,保留可执行文件旁的 `runtime` 目录。
2. Windows 打开 `screener-client.exe`,macOS 打开 `Screener Client.app`,
Linux 运行 `./screener-client`。
2. Windows 打开 `piik-client.exe`,macOS 打开 `Piik Client.app`,
Linux 运行 `./piik-client`。
3. 选择模式和分享来源,发出邀请。分享期间保持 Client 运行。
| 模式 | 适合什么情况 |
| --- | --- |
| **本地房间** | 与同一局域网内的好友分享,可设置站点访问密码。 |
| **公网邀请** | 生成本次 Client 运行期间有效的临时邀请链接;媒体仍走 P2P。 |
| **连接站点** | 使用已有 Screener 站点,并在同一浏览器界面使用 Client 的采集与媒体能力。 |
| **连接站点** | 使用已有 Piik 站点,并在同一浏览器界面使用 Client 的采集与媒体能力。 |
Client 会打开系统浏览器。程序包包含 Go 应用和采集、临时链接辅助程序,
运行时无须安装 Node.js、npm 或 Go。Linux 原生采集使用系统的 Portal、PipeWire
和 GStreamer,平台细节见 [Client 指南](./cmd/screener-client/README.md)。
和 GStreamer,平台细节见 [Client 指南](./cmd/piik-client/README.md)。
临时公网链接使用 Cloudflare Quick Tunnel,不保证持续可用。
媒体需要可用的 UDP 路径;网络限制、浏览器或系统挂起都可能中断分享。
@@ -71,9 +71,9 @@ SFU 后备路径。搭建自己的站点,请看 [自托管指南](./docs/opera
Client 可用 `--debug` 启动后按 `D` 导出本地报告;浏览器网址加上 `?debug=1`
后(放在 `#` 之前),可点击顶部下载按钮。分享前请检查报告内容,详见
[诊断与导出](./docs/reference/configuration.md#diagnostics)。
连接不上时,也可以查看 [Chromium WebRTC 常见问题](./cmd/screener-client/README.md#chromium-webrtc-connections)。
连接不上时,也可以查看 [Chromium WebRTC 常见问题](./cmd/piik-client/README.md#chromium-webrtc-connections)。
## 许可
Screener 自有代码采用 [MIT 许可](./LICENSE),提供 [中文参考译文](./LICENSE.zh-CN.md)。
Piik 自有代码采用 [MIT 许可](./LICENSE),提供 [中文参考译文](./LICENSE.zh-CN.md)。
第三方组件保留各自的许可与声明,详见 [许可说明](./licenses/README.md)。
@@ -1,14 +1,14 @@
# Screener Client
# Piik Client
Screener Client is the self-contained and native-capability runtime for the same
Browser application used by Hosted Screener. It does not implement another UI,
Piik Client is the self-contained and native-capability runtime for the same
Browser application used by Hosted Piik. It does not implement another UI,
room store, signaling protocol, or route controller.
## Run A Package
Extract the matching platform bundle in full and keep `runtime` beside the
Client executable. Run `screener-client.exe` on Windows, `./screener-client` on
Linux, or `Screener Client.app` on macOS. The launcher opens in the system Browser;
Client executable. Run `piik-client.exe` on Windows, `./piik-client` on
Linux, or `Piik Client.app` on macOS. The launcher opens in the system Browser;
the Client does not embed a browser UI. Packaged execution needs no Node.js, npm,
or Go installation. Linux native capture uses the system dependencies described
in the [Linux capture guide](../../native/capture/linux/README.md).
@@ -111,7 +111,7 @@ to one Browser brand or to VPN use.
Check the Browser's WebRTC/IP-handling policy and any extension's WebRTC or
IP-leak protection setting. Restore a policy that permits WebRTC UDP, reload
Screener, and verify that another extension or managed policy has not overridden
Piik, and verify that another extension or managed policy has not overridden
the choice. Setting names and availability differ between Browsers. For example,
Vivaldi exposes **Settings > Privacy and Security > WebRTC IP Handling >
Broadcast IP for Best WebRTC Performance**. Changing this policy can expose
@@ -139,7 +139,7 @@ The binary embeds the Browser assets, so build them before running it:
npm run build:client
go test ./...
go vet ./...
go run ./cmd/screener-client
go run ./cmd/piik-client
```
The repository-level entry used locally and by CI is:
@@ -200,11 +200,11 @@ explicit CI packaging, Release publication and updates are documented in
The extracted bundle contains:
```text
screener-client[.exe]
piik-client[.exe]
REVISION
LICENSE
THIRD-PARTY-NOTICES.txt
runtime/native/screener-client-capture[.exe] # supported native-media packages
runtime/native/piik-client-capture[.exe] # supported native-media packages
runtime/tunnel/cloudflared[.exe] # packages that support --link
```
@@ -212,11 +212,11 @@ The executable embeds the Browser assets of the consumed application release and
carries that same full Git revision as the package `REVISION`. No compatibility
reader accepts a mismatched private build.
The Windows Client embeds the shared Screener mark through the
`cmd/screener-client/screener_windows_amd64.syso` resource; the platform
The Windows Client embeds the shared Piik mark through the
`cmd/piik-client/piik_windows_amd64.syso` resource; the platform
suffix keeps that Windows resource out of Linux and macOS builds.
Linux packages include the standard `share/applications` desktop entry and
hicolor icon. macOS packages include a thin `Screener Client.app` launcher
hicolor icon. macOS packages include a thin `Piik Client.app` launcher
with an ICNS resource; the raw Go executable remains available beside it.
For a native Host smoke run, launch the Client and select a window in the Host
@@ -235,53 +235,53 @@ and without Chromium Local Network Access permission. Environment syntax below
is POSIX; use equivalent variables on Windows.
```sh
SCREENER_CLIENT_LOCAL_GATE=true \
PIIK_CLIENT_LOCAL_GATE=true \
CHROME_PATH=/path/to/chrome \
SCREENER_CLIENT_EXE=/path/to/screener-client \
SCREENER_CLIENT_GATE_LAN_ADDRESS=192.168.1.10 \
PIIK_CLIENT_EXE=/path/to/piik-client \
PIIK_CLIENT_GATE_LAN_ADDRESS=192.168.1.10 \
npm run gate:client-local
SCREENER_CLIENT_LOOPBACK_GATE=true \
PIIK_CLIENT_LOOPBACK_GATE=true \
CHROME_PATH=/path/to/chrome \
SCREENER_CLIENT_EXE=/path/to/screener-client \
PIIK_CLIENT_EXE=/path/to/piik-client \
npm run probe:client-loopback
SCREENER_CLIENT_MEDIA_GATE=true \
PIIK_CLIENT_MEDIA_GATE=true \
CHROME_PATH=/path/to/chrome \
SCREENER_CLIENT_GATE_STUN_URLS=stun:<stun-host>:3478 \
PIIK_CLIENT_GATE_STUN_URLS=stun:<stun-host>:3478 \
npm run gate:client-media
SCREENER_CLIENT_NATIVE_HOST_GATE=true \
PIIK_CLIENT_NATIVE_HOST_GATE=true \
CHROME_PATH=/path/to/chrome \
SCREENER_GO=/path/to/go \
PIIK_GO=/path/to/go \
npm run gate:client-native-host
SCREENER_CLIENT_NATIVE_HOST_GATE=true \
SCREENER_CLIENT_CROSS_NAT_GATE=true \
PIIK_CLIENT_NATIVE_HOST_GATE=true \
PIIK_CLIENT_CROSS_NAT_GATE=true \
CHROME_PATH=/path/to/chrome \
SCREENER_GO=/path/to/go \
SCREENER_REMOTE_HOST=<public-test-host> \
SCREENER_REMOTE_USER=<ssh-user> \
SCREENER_REMOTE_SSH_KEY=/path/to/key \
SCREENER_CLIENT_GATE_STUN_URLS=stun:<stun-host>:3478 \
PIIK_GO=/path/to/go \
PIIK_REMOTE_HOST=<public-test-host> \
PIIK_REMOTE_USER=<ssh-user> \
PIIK_REMOTE_SSH_KEY=/path/to/key \
PIIK_CLIENT_GATE_STUN_URLS=stun:<stun-host>:3478 \
npm run gate:client-native-host
SCREENER_CLIENT_NATIVE_HOST_GATE=true \
SCREENER_CLIENT_LINK_MEDIA_GATE=true \
PIIK_CLIENT_NATIVE_HOST_GATE=true \
PIIK_CLIENT_LINK_MEDIA_GATE=true \
CHROME_PATH=/path/to/chrome \
SCREENER_GO=/path/to/go \
SCREENER_CLOUDFLARED=/path/to/cloudflared \
SCREENER_REMOTE_HOST=<public-test-host> \
SCREENER_REMOTE_USER=<ssh-user> \
SCREENER_REMOTE_SSH_KEY=/path/to/key \
PIIK_GO=/path/to/go \
PIIK_CLOUDFLARED=/path/to/cloudflared \
PIIK_REMOTE_HOST=<public-test-host> \
PIIK_REMOTE_USER=<ssh-user> \
PIIK_REMOTE_SSH_KEY=/path/to/key \
npm run gate:client-native-host
SCREENER_CLIENT_LINK_GATE=true \
SCREENER_GO=/path/to/go \
SCREENER_CLOUDFLARED=/path/to/cloudflared \
SCREENER_REMOTE_HOST=<public-test-host> \
SCREENER_REMOTE_USER=<ssh-user> \
SCREENER_REMOTE_SSH_KEY=/path/to/key \
PIIK_CLIENT_LINK_GATE=true \
PIIK_GO=/path/to/go \
PIIK_CLOUDFLARED=/path/to/cloudflared \
PIIK_REMOTE_HOST=<public-test-host> \
PIIK_REMOTE_USER=<ssh-user> \
PIIK_REMOTE_SSH_KEY=/path/to/key \
npm run gate:client-link
```
@@ -1,5 +1,5 @@
// Command screener-client is the self-contained Screener Client: it opens a
// configured Screener Site, or runs the Local room authority in-process
// Command piik-client is the self-contained Piik Client: it opens a
// configured Piik Site, or runs the Local room authority in-process
// (--local / --link) together with the loopback capability service and the
// native media edge. Flags select the mode; clientapp owns every mode's
// lifecycle.
@@ -13,12 +13,12 @@ import (
"os/signal"
"syscall"
"github.com/TNTcraftHIM/Screener/internal/client/clientapp"
"github.com/TNTcraftHIM/Piik/internal/client/clientapp"
)
func main() {
var options clientapp.Options
flag.Func("site", "save and open a Screener Site origin", func(value string) error {
flag.Func("site", "save and open a Piik Site origin", func(value string) error {
options.Site = value
options.SiteSet = true
return nil
@@ -26,14 +26,14 @@ func main() {
flag.BoolVar(&options.Local, "local", false, "use the self-contained Local room authority")
flag.BoolVar(&options.Link, "link", false, "create one public Viewer invitation link")
flag.BoolVar(&options.Debug, "debug", false, "save opt-in Client diagnostics to rotated files")
flag.StringVar(&options.LogDir, "log-dir", "", "diagnostic directory (overrides SCREENER_LOG_DIR)")
flag.StringVar(&options.LogDir, "log-dir", "", "diagnostic directory (overrides PIIK_LOG_DIR)")
flag.StringVar(&options.CaptureProcess, "capture-process", "", "path to the platform native capture process")
flag.StringVar(&options.TunnelProcess, "tunnel-process", "", "path to the packaged public tunnel process")
flag.StringVar(&options.ConfigPath, "config", "", "path to the Client configuration file")
flag.StringVar(&options.LANAddress, "lan-address", "", "LAN IPv4 address used in Local invitations")
flag.IntVar(&options.Port, "port", clientapp.DefaultLocalPort, "Local Screener server port")
flag.IntVar(&options.Port, "port", clientapp.DefaultLocalPort, "Local Piik server port")
flag.Parse()
options.DisableBrowser = os.Getenv("SCREENER_CLIENT_GATE_NO_BROWSER") == "true"
options.DisableBrowser = os.Getenv("PIIK_CLIENT_GATE_NO_BROWSER") == "true"
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
File renamed without changes.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

@@ -13,7 +13,7 @@ import (
"sync"
"time"
"github.com/TNTcraftHIM/Screener/internal/server/protocol"
"github.com/TNTcraftHIM/Piik/internal/server/protocol"
"github.com/coder/websocket"
"github.com/coder/websocket/wsjson"
"github.com/pion/interceptor"
File renamed without changes.
@@ -7,7 +7,7 @@ import (
"os/signal"
"syscall"
"github.com/TNTcraftHIM/Screener/internal/diagnostics"
"github.com/TNTcraftHIM/Piik/internal/diagnostics"
)
func watchDiagnosticExport(recorder *diagnostics.Recorder) func() {
@@ -1,6 +1,6 @@
package main
import "github.com/TNTcraftHIM/Screener/internal/diagnostics"
import "github.com/TNTcraftHIM/Piik/internal/diagnostics"
// Windows exports at orderly shutdown; it has no SIGUSR1 equivalent.
func watchDiagnosticExport(*diagnostics.Recorder) func() { return func() {} }
@@ -1,4 +1,4 @@
// Command screener-server is the Hosted Screener entry point: it loads the
// Command piik-server is the Hosted Piik entry point: it loads the
// environment, serves the embedded Browser UI together with the signaling and
// room API, and stops on SIGINT or SIGTERM. It replaces src/server/index.ts.
//
@@ -23,10 +23,10 @@ import (
"syscall"
"time"
"github.com/TNTcraftHIM/Screener/internal/diagnostics"
"github.com/TNTcraftHIM/Screener/internal/server/app"
"github.com/TNTcraftHIM/Screener/internal/server/config"
"github.com/TNTcraftHIM/Screener/internal/server/webassets"
"github.com/TNTcraftHIM/Piik/internal/diagnostics"
"github.com/TNTcraftHIM/Piik/internal/server/app"
"github.com/TNTcraftHIM/Piik/internal/server/config"
"github.com/TNTcraftHIM/Piik/internal/server/webassets"
)
// shutdownTimeout bounds the ordered stop (signaling, listener, room store).
@@ -81,11 +81,11 @@ func fail(err error) {
func serve(debug bool) (returnedErr error) {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ReplaceAttr: diagnostics.ReplaceAttr}))
var recorder *diagnostics.Recorder
if debug || serverDebugEnabled(os.Getenv("SCREENER_DEBUG")) {
if debug || serverDebugEnabled(os.Getenv("PIIK_DEBUG")) {
var err error
recorder, err = diagnostics.Open(serverLogDirectory(), "server", BuildRevision)
if err != nil {
return fmt.Errorf("Screener server diagnostics are unavailable: %w", err)
return fmt.Errorf("Piik server diagnostics are unavailable: %w", err)
}
logger = slog.New(slog.NewMultiHandler(logger.Handler(), recorder.Logger().Handler()))
previous, previousWriter, previousFlags := slog.Default(), log.Writer(), log.Flags()
@@ -93,18 +93,18 @@ func serve(debug bool) (returnedErr error) {
dependencyLog := diagnostics.Writer("stdlib")
log.SetOutput(dependencyLog)
log.SetFlags(previousFlags)
fmt.Fprintln(os.Stderr, "Screener diagnostic log:", recorder.LogPath())
fmt.Fprintln(os.Stderr, "Piik diagnostic log:", recorder.LogPath())
stopExport := watchDiagnosticExport(recorder)
defer func() {
stopExport()
_ = dependencyLog.Close()
logger.Info("screener-server", "event", "stopped", "failed", returnedErr != nil, diagnostics.Error(returnedErr))
logger.Info("piik-server", "event", "stopped", "failed", returnedErr != nil, diagnostics.Error(returnedErr))
returnedErr = errors.Join(returnedErr, exportServerDiagnostics(recorder), recorder.Close())
slog.SetDefault(previous)
log.SetOutput(previousWriter)
log.SetFlags(previousFlags)
}()
logger.Info("screener-server", "event", "start", "revision", BuildRevision)
logger.Info("piik-server", "event", "start", "revision", BuildRevision)
}
configuration, err := config.Load(environment())
if err != nil {
@@ -133,7 +133,7 @@ func serve(debug bool) (returnedErr error) {
if err != nil {
return err
}
fmt.Printf("Screener %s is listening on %s:%d; public URL %s\n",
fmt.Printf("Piik %s is listening on %s:%d; public URL %s\n",
BuildRevision, configuration.ListenHost, port, config.Origin(configuration.PublicBaseURL))
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
@@ -155,7 +155,7 @@ func serverDebugEnabled(value string) bool {
}
func serverLogDirectory() string {
if directory := strings.TrimSpace(os.Getenv("SCREENER_LOG_DIR")); directory != "" {
if directory := strings.TrimSpace(os.Getenv("PIIK_LOG_DIR")); directory != "" {
return directory
}
if directories := filepath.SplitList(os.Getenv("LOGS_DIRECTORY")); len(directories) > 0 && directories[0] != "" {
@@ -167,10 +167,10 @@ func serverLogDirectory() string {
func exportServerDiagnostics(recorder *diagnostics.Recorder) error {
path, err := recorder.Export()
if err != nil {
fmt.Fprintln(os.Stderr, "Screener diagnostic export failed:", err)
fmt.Fprintln(os.Stderr, "Piik diagnostic export failed:", err)
return err
}
fmt.Fprintln(os.Stderr, "Screener diagnostic bundle:", path)
fmt.Fprintln(os.Stderr, "Piik diagnostic bundle:", path)
return nil
}
@@ -7,27 +7,27 @@ import (
)
func TestLoadEnvironmentFileFillsOnlyAbsentVariables(t *testing.T) {
t.Setenv("SCREENER_TEST_PRESET", "from-the-service")
t.Setenv("PIIK_TEST_PRESET", "from-the-service")
path := filepath.Join(t.TempDir(), ".env")
content := "# Application\n" +
"SCREENER_TEST_PRESET=from-the-file\n" +
"export SCREENER_TEST_EXPORTED=exported\n" +
" SCREENER_TEST_QUOTED = \"a b#c\" \n" +
"SCREENER_TEST_SINGLE=' padded '\n" +
"SCREENER_TEST_EMPTY=\n" +
"SCREENER_TEST_MARK=has#hash\n" +
"PIIK_TEST_PRESET=from-the-file\n" +
"export PIIK_TEST_EXPORTED=exported\n" +
" PIIK_TEST_QUOTED = \"a b#c\" \n" +
"PIIK_TEST_SINGLE=' padded '\n" +
"PIIK_TEST_EMPTY=\n" +
"PIIK_TEST_MARK=has#hash\n" +
"\n" +
"not an assignment\n" +
"#SCREENER_TEST_COMMENTED=never\r\n" +
"SCREENER_TEST_LAST=tail"
"#PIIK_TEST_COMMENTED=never\r\n" +
"PIIK_TEST_LAST=tail"
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatal(err)
}
// Setenv registers the restore, Unsetenv makes the name absent for the run.
for _, name := range []string{
"SCREENER_TEST_EXPORTED", "SCREENER_TEST_QUOTED", "SCREENER_TEST_SINGLE",
"SCREENER_TEST_EMPTY", "SCREENER_TEST_MARK", "SCREENER_TEST_COMMENTED",
"SCREENER_TEST_LAST",
"PIIK_TEST_EXPORTED", "PIIK_TEST_QUOTED", "PIIK_TEST_SINGLE",
"PIIK_TEST_EMPTY", "PIIK_TEST_MARK", "PIIK_TEST_COMMENTED",
"PIIK_TEST_LAST",
} {
t.Setenv(name, "absent")
os.Unsetenv(name)
@@ -38,19 +38,19 @@ func TestLoadEnvironmentFileFillsOnlyAbsentVariables(t *testing.T) {
}
for name, want := range map[string]string{
// A variable the service already supplies is never replaced.
"SCREENER_TEST_PRESET": "from-the-service",
"SCREENER_TEST_EXPORTED": "exported",
"SCREENER_TEST_QUOTED": "a b#c",
"SCREENER_TEST_SINGLE": " padded ",
"SCREENER_TEST_EMPTY": "",
"SCREENER_TEST_MARK": "has#hash",
"SCREENER_TEST_LAST": "tail",
"PIIK_TEST_PRESET": "from-the-service",
"PIIK_TEST_EXPORTED": "exported",
"PIIK_TEST_QUOTED": "a b#c",
"PIIK_TEST_SINGLE": " padded ",
"PIIK_TEST_EMPTY": "",
"PIIK_TEST_MARK": "has#hash",
"PIIK_TEST_LAST": "tail",
} {
if got, present := os.LookupEnv(name); !present || got != want {
t.Errorf("%s = %q, present %t; want %q", name, got, present, want)
}
}
if value, present := os.LookupEnv("SCREENER_TEST_COMMENTED"); present {
if value, present := os.LookupEnv("PIIK_TEST_COMMENTED"); present {
t.Errorf("commented assignment was applied as %q", value)
}
}
@@ -75,7 +75,7 @@ func TestServerDiagnosticSelection(t *testing.T) {
t.Fatalf("serverDebugEnabled(%q) = %v, want %v", value, got, want)
}
}
t.Setenv("SCREENER_LOG_DIR", "")
t.Setenv("PIIK_LOG_DIR", "")
t.Setenv("LOGS_DIRECTORY", "")
if got := serverLogDirectory(); got != "logs" {
t.Fatalf("default directory = %q", got)
@@ -84,7 +84,7 @@ func TestServerDiagnosticSelection(t *testing.T) {
if got := serverLogDirectory(); got != "service-logs" {
t.Fatalf("systemd directory = %q", got)
}
t.Setenv("SCREENER_LOG_DIR", "explicit-logs")
t.Setenv("PIIK_LOG_DIR", "explicit-logs")
if got := serverLogDirectory(); got != "explicit-logs" {
t.Fatalf("explicit directory = %q", got)
}
@@ -16,7 +16,7 @@ import (
// defaultReleaseAPIURL is DEFAULT_RELEASE_API_URL: the same published release
// contract the Browser update notice uses (src/client/lib/release-update.ts).
const defaultReleaseAPIURL = "https://api.github.com/repos/TNTcraftHIM/Screener/releases/latest"
const defaultReleaseAPIURL = "https://api.github.com/repos/TNTcraftHIM/Piik/releases/latest"
// Exit codes are UPDATE_EXIT_CODES. deploy/check-release.sh forwards them, so a
// checker failure stays distinguishable from "an update exists".
@@ -31,13 +31,13 @@ const (
// releaseUsage is the usage string parseArguments threw, with this binary's
// name in place of the script's.
const releaseUsage = "Usage: screener-server --check-release --current-file <REVISION> [--api-url <URL>]"
const releaseUsage = "Usage: piik-server --check-release --current-file <REVISION> [--api-url <URL>]"
const (
// releaseTimeout bounds the whole check; an operator runs it interactively.
releaseTimeout = 5 * time.Second
// releasePathPrefix is RELEASE_PATH_PREFIX: the only accepted release page.
releasePathPrefix = "/TNTcraftHIM/Screener/releases/tag/"
releasePathPrefix = "/TNTcraftHIM/Piik/releases/tag/"
releaseHost = "github.com"
releaseAPIHost = "api.github.com"
)
@@ -116,7 +116,7 @@ func checkRelease(ctx context.Context, currentRevision, apiURL, token string) re
}
request.Header.Set("Accept", "application/vnd.github+json")
request.Header.Set("X-GitHub-Api-Version", "2022-11-28")
request.Header.Set("User-Agent", "Screener-release-check")
request.Header.Set("User-Agent", "Piik-release-check")
if authorizesGitHub(endpoint, token) {
request.Header.Set("Authorization", "Bearer "+strings.TrimSpace(token))
}
@@ -203,7 +203,7 @@ type releaseMetadata struct {
// parseReleaseMetadata ports parseReleaseMetadata. It accepts one shape only: a
// published, non-draft release whose tag is a full commit revision and whose
// page is exactly https://github.com/TNTcraftHIM/Screener/releases/tag/<that
// page is exactly https://github.com/TNTcraftHIM/Piik/releases/tag/<that
// revision> with no credentials, port, query or fragment. The returned URL is
// rebuilt from the validated parts so nothing unverified reaches the operator.
func parseReleaseMetadata(payload []byte) (revision string, releaseURL string, ok bool) {
@@ -16,7 +16,7 @@ import (
const (
deployedRevision = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
latestRevision = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
latestReleaseURL = "https://github.com/TNTcraftHIM/Screener/releases/tag/" + latestRevision
latestReleaseURL = "https://github.com/TNTcraftHIM/Piik/releases/tag/" + latestRevision
latestRelease = `{"tag_name":"` + latestRevision + `","html_url":"` + latestReleaseURL + `"}`
)
@@ -40,29 +40,29 @@ func TestParseReleaseMetadataAcceptsOnlyTheStrictReleaseIdentity(t *testing.T) {
{"other host", tagged("https://evil.example"), "", ""},
{
"userinfo",
tagged("https://user@github.com/TNTcraftHIM/Screener/releases/tag/" + latestRevision),
tagged("https://user@github.com/TNTcraftHIM/Piik/releases/tag/" + latestRevision),
"", "",
},
{
"port",
tagged("https://github.com:8443/TNTcraftHIM/Screener/releases/tag/" + latestRevision),
tagged("https://github.com:8443/TNTcraftHIM/Piik/releases/tag/" + latestRevision),
"", "",
},
{
"plaintext page",
tagged("http://github.com/TNTcraftHIM/Screener/releases/tag/" + latestRevision),
tagged("http://github.com/TNTcraftHIM/Piik/releases/tag/" + latestRevision),
"", "",
},
{"query", tagged(latestReleaseURL + "?asset=1"), "", ""},
{"fragment", tagged(latestReleaseURL + "#assets"), "", ""},
{
"encoded path separator",
tagged("https://github.com/TNTcraftHIM%2FScreener/releases/tag/" + latestRevision),
tagged("https://github.com/TNTcraftHIM%2FPiik/releases/tag/" + latestRevision),
"", "",
},
{
"page tag disagrees with the release tag",
tagged("https://github.com/TNTcraftHIM/Screener/releases/tag/" + deployedRevision),
tagged("https://github.com/TNTcraftHIM/Piik/releases/tag/" + deployedRevision),
"", "",
},
{
@@ -144,7 +144,7 @@ func TestCheckReleaseReportsTheDeployedComparison(t *testing.T) {
if got := request.Header.Get("X-GitHub-Api-Version"); got != "2022-11-28" {
t.Errorf("X-GitHub-Api-Version = %q", got)
}
if got := request.Header.Get("User-Agent"); got != "Screener-release-check" {
if got := request.Header.Get("User-Agent"); got != "Piik-release-check" {
t.Errorf("User-Agent = %q", got)
}
writer.WriteHeader(testCase.status)
+2 -2
View File
@@ -6,6 +6,6 @@ if [ "$#" -gt 1 ]; then
exit 2
fi
current_file="${1:-/opt/screener/current/REVISION}"
current_file="${1:-/opt/piik/current/REVISION}"
exec /opt/screener/current/screener-server --check-release --current-file "$current_file"
exec /opt/piik/current/piik-server --check-release --current-file "$current_file"
+3 -3
View File
@@ -1,8 +1,8 @@
FROM gcr.io/distroless/static-debian13:nonroot-amd64@sha256:e754765ad9e167b0677b41c617fd44afb7b9818a477f48f17bda08e12cfb98cb
WORKDIR /app
COPY --chmod=0555 screener-server /app/
COPY --chmod=0555 piik-server /app/
COPY --chmod=0444 LICENSE THIRD-PARTY-NOTICES.txt REVISION /app/
ENV LISTEN_HOST=0.0.0.0 SCREENER_LOG_DIR=/home/nonroot/logs ROOM_DATABASE_PATH=/home/nonroot/rooms.sqlite
ENV LISTEN_HOST=0.0.0.0 PIIK_LOG_DIR=/home/nonroot/logs ROOM_DATABASE_PATH=/home/nonroot/rooms.sqlite
USER 65532:65532
EXPOSE 8787/tcp 3478/udp 3479/udp 3480/udp 7882/udp
ENTRYPOINT ["/app/screener-server"]
ENTRYPOINT ["/app/piik-server"]
+6 -6
View File
@@ -1,9 +1,9 @@
map $http_upgrade $screener_connection_upgrade {
map $http_upgrade $piik_connection_upgrade {
default upgrade;
'' close;
}
limit_req_zone $binary_remote_addr zone=screener_site_access:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=piik_site_access:10m rate=5r/m;
server {
listen 80;
@@ -30,8 +30,8 @@ server {
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
access_log /var/log/nginx/screener.access.log;
error_log /var/log/nginx/screener.error.log warn;
access_log /var/log/nginx/piik.access.log;
error_log /var/log/nginx/piik.error.log warn;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
@@ -48,7 +48,7 @@ server {
}
location = /api/site-access {
limit_req zone=screener_site_access burst=5 nodelay;
limit_req zone=piik_site_access burst=5 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:8787;
proxy_http_version 1.1;
@@ -65,7 +65,7 @@ server {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $screener_connection_upgrade;
proxy_set_header Connection $piik_connection_upgrade;
proxy_read_timeout 5h;
proxy_send_timeout 5h;
proxy_buffering off;
+31 -31
View File
@@ -3,15 +3,15 @@ set -Eeuo pipefail
umask 022
if [ "$#" -ne 1 ]; then
printf 'usage: SCREENER_PUBLIC_ORIGIN=https://share.example.com %s /opt/screener/uploads/<release>.release.json\n' "$0" >&2
printf 'usage: PIIK_PUBLIC_ORIGIN=https://share.example.com %s /opt/piik/uploads/<release>.release.json\n' "$0" >&2
exit 2
fi
upload_root='/opt/screener/uploads'
release_root='/opt/screener/releases'
current='/opt/screener/current'
lock='/opt/screener/deploy.lock'
public_origin="${SCREENER_PUBLIC_ORIGIN:-}"
upload_root='/opt/piik/uploads'
release_root='/opt/piik/releases'
current='/opt/piik/current'
lock='/opt/piik/deploy.lock'
public_origin="${PIIK_PUBLIC_ORIGIN:-}"
descriptor="$(realpath -e -- "$1")"
stage=''
release_owned=0
@@ -39,8 +39,8 @@ main_asset="$(descriptor_text mainAsset)"
[[ "$revision" =~ ^[0-9a-f]{40}$ ]]
[[ "$release_id" =~ ^[0-9a-f]{7}$ ]]
test "$release_id" = "${revision:0:7}"
test "$artifact_name" = "screener-${release_id}-runtime.tar.gz"
test "$manifest_name" = "screener-${release_id}.manifest.tsv"
test "$artifact_name" = "piik-${release_id}-runtime.tar.gz"
test "$manifest_name" = "piik-${release_id}.manifest.tsv"
[[ "$artifact_sha" =~ ^[0-9a-f]{64}$ ]]
[[ "$manifest_sha" =~ ^[0-9a-f]{64}$ ]]
[[ "$file_count" =~ ^[1-9][0-9]*$ ]]
@@ -105,7 +105,7 @@ recover() {
trap - ERR HUP INT TERM EXIT
set +e
if [ "$cutover_started" -eq 1 ]; then
systemctl stop screener.service || ok=0
systemctl stop piik.service || ok=0
if [ "$(readlink -f -- "$current" 2>/dev/null)" != "$old_release" ]; then
link="${current}.recover-${release_id}-$$"
if [ -e "$link" ] || [ -L "$link" ]; then
@@ -115,7 +115,7 @@ recover() {
fi
fi
test "$(readlink -f -- "$current" 2>/dev/null)" = "$old_release" || ok=0
systemctl start screener.service || ok=0
systemctl start piik.service || ok=0
wait_for_health || ok=0
fi
rm -f -- "${current}.${release_id}-$$" "${current}.recover-${release_id}-$$" || ok=0
@@ -126,7 +126,7 @@ recover() {
printf 'deployment_failed=%s recovery_ok=%s current=%s active=%s\n' \
"$code" "$ok" \
"$(readlink -f -- "$current" 2>/dev/null || true)" \
"$(systemctl show screener.service -p ActiveState --value 2>/dev/null || true)" >&2
"$(systemctl show piik.service -p ActiveState --value 2>/dev/null || true)" >&2
if [ "$ok" -ne 1 ]; then
exit 90
fi
@@ -150,15 +150,15 @@ test -f "$artifact"
test -f "$manifest"
test "$(sha256sum "$artifact" | awk '{print $1}')" = "$artifact_sha"
test "$(sha256sum "$manifest" | awk '{print $1}')" = "$manifest_sha"
for service in screener nginx; do
for service in piik nginx; do
test "$(systemctl show "${service}.service" -p ActiveState --value)" = 'active'
test "$(systemctl show "${service}.service" -p NRestarts --value)" = '0'
done
# The first Go/embedded-media cutover owns its infrastructure recovery separately.
test -x "$old_release/screener-server"
old_pid="$(systemctl show screener.service -p MainPID --value)"
test -x "$old_release/piik-server"
old_pid="$(systemctl show piik.service -p MainPID --value)"
test "$old_pid" -gt 1
test "$(readlink -f "/proc/${old_pid}/exe")" = "$old_release/screener-server"
test "$(readlink -f "/proc/${old_pid}/exe")" = "$old_release/piik-server"
while IFS= read -r raw_entry; do
entry="$raw_entry"
@@ -170,7 +170,7 @@ while IFS= read -r raw_entry; do
esac
case "$entry" in
/*|*\\*) printf 'invalid archive entry: %s\n' "$raw_entry" >&2; exit 41 ;;
LICENSE|REVISION|THIRD-PARTY-NOTICES.txt|screener-server) ;;
LICENSE|REVISION|THIRD-PARTY-NOTICES.txt|piik-server) ;;
*) printf 'unexpected archive entry: %s\n' "$raw_entry" >&2; exit 42 ;;
esac
done < <(tar -tzf "$artifact")
@@ -183,7 +183,7 @@ tar --no-same-owner --no-same-permissions -xzf "$artifact" -C "$stage"
tab=$'\t'
if [ -n "$(tail -c 1 "$manifest")" ]; then exit 61; fi
if grep -qEv "^[0-9a-f]{64}${tab}[0-9]+${tab}[^${tab}]*$" "$manifest"; then exit 62; fi
if grep -qEv "^[0-9a-f]{64}${tab}[0-9]+${tab}(LICENSE|REVISION|THIRD-PARTY-NOTICES\.txt|screener-server)$" "$manifest"; then exit 63; fi
if grep -qEv "^[0-9a-f]{64}${tab}[0-9]+${tab}(LICENSE|REVISION|THIRD-PARTY-NOTICES\.txt|piik-server)$" "$manifest"; then exit 63; fi
if [ -n "$(cut -f3 "$manifest" | sort | uniq -d)" ]; then exit 64; fi
if [ "$(wc -l < "$manifest")" -ne "$file_count" ]; then exit 65; fi
if [ -n "$(find "$stage" -type l)" ]; then exit 66; fi
@@ -199,23 +199,23 @@ printf 'artifact_manifest=ok files=%s revision=%s\n' "$file_count" "$revision"
# Set the mode before testing it: an archive packaged on a host without an
# executable bit (Windows) records 0644, and the release owns the bit here.
chmod 0755 "$stage/screener-server"
test -x "$stage/screener-server"
chmod 0755 "$stage/piik-server"
test -x "$stage/piik-server"
chmod 0644 "$stage/LICENSE" "$stage/REVISION" "$stage/THIRD-PARTY-NOTICES.txt"
# Runs as the service user under the manager (no sudo/polkit round trip) so a
# binary or environment file the service cannot use fails here, before cutover.
systemd-run \
--uid=screener \
--gid=screener \
--uid=piik \
--gid=piik \
--wait \
--collect \
--quiet \
--service-type=exec \
--property='EnvironmentFile=/etc/screener/screener.env' \
--property='Environment=SCREENER_ENV=production' \
--property='EnvironmentFile=/etc/piik/piik.env' \
--property='Environment=PIIK_ENV=production' \
--property='RuntimeMaxSec=20s' \
"$stage/screener-server" --check-config
"$stage/piik-server" --check-config
if [ -n "$(comm -12 <(find "$old_release" -type f -printf '%D:%i\n' | sort -u) <(find "$stage" -type f -printf '%D:%i\n' | sort -u))" ]; then
exit 80
@@ -237,21 +237,21 @@ nginx_restarts="$(systemctl show nginx.service -p NRestarts --value)"
cutover_since="$(date '+%Y-%m-%d %H:%M:%S')"
cutover_start="$(date +%s%3N)"
cutover_started=1
systemctl stop screener.service
test "$(systemctl show screener.service -p ActiveState --value)" = 'inactive'
systemctl stop piik.service
test "$(systemctl show piik.service -p ActiveState --value)" = 'inactive'
ln -s "$release" "${current}.${release_id}-$$"
mv -Tf "${current}.${release_id}-$$" "$current"
test "$(readlink -f -- "$current")" = "$release"
systemctl start screener.service
systemctl start piik.service
wait_for_health
health_ready="$(date +%s%3N)"
test "$(systemctl show screener.service -p ActiveState --value)" = 'active'
test "$(systemctl show screener.service -p NRestarts --value)" = '0'
test "$(systemctl show piik.service -p ActiveState --value)" = 'active'
test "$(systemctl show piik.service -p NRestarts --value)" = '0'
test "$(systemctl show nginx.service -p NRestarts --value)" = "$nginx_restarts"
test "$(nft list table inet bonfire_filter | sha256sum | awk '{print $1}')" = "$firewall_before"
test "$(readlink -f -- "$current")" = "$release"
pid="$(systemctl show screener.service -p MainPID --value)"
pid="$(systemctl show piik.service -p MainPID --value)"
test "$pid" -gt 1
test "$(readlink -f "/proc/${pid}/cwd")" = "$release"
test "$(curl -fsS --max-time 5 http://127.0.0.1:8787/healthz)" = '{"status":"ok"}'
@@ -262,7 +262,7 @@ test "$(curl -sS -o /dev/null -w '%{http_code}' --max-time 8 "$public_origin/$ma
if [ "$old_asset" != "$main_asset" ]; then
test "$(curl -sS -o /dev/null -w '%{http_code}' --max-time 8 "$public_origin/$old_asset")" = '404'
fi
test -z "$(journalctl -u screener.service --since "$cutover_since" -p warning --no-pager --output=cat)"
test -z "$(journalctl -u piik.service --since "$cutover_since" -p warning --no-pager --output=cat)"
asset_sha="$(curl -fsS --max-time 8 "$public_origin/$main_asset" | sha256sum | awk '{print $1}')"
release_owned=0
@@ -1,24 +1,24 @@
[Unit]
Description=Screener web, signaling, STUN and SFU application
Description=Piik web, signaling, STUN and SFU application
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=screener
Group=screener
WorkingDirectory=/opt/screener/current
Environment=SCREENER_ENV=production
Environment=ROOM_DATABASE_PATH=/var/lib/screener/rooms.sqlite
EnvironmentFile=/etc/screener/screener.env
ExecStart=/opt/screener/current/screener-server
User=piik
Group=piik
WorkingDirectory=/opt/piik/current
Environment=PIIK_ENV=production
Environment=ROOM_DATABASE_PATH=/var/lib/piik/rooms.sqlite
EnvironmentFile=/etc/piik/piik.env
ExecStart=/opt/piik/current/piik-server
Restart=on-failure
RestartSec=3s
TimeoutStopSec=20s
UMask=0077
StateDirectory=screener
StateDirectory=piik
StateDirectoryMode=0700
LogsDirectory=screener
LogsDirectory=piik
LogsDirectoryMode=0700
NoNewPrivileges=true
+6 -6
View File
@@ -2,12 +2,12 @@
[English Quick Start](../README.md) · [中文快速开始](../README.zh-CN.md)
## Use Screener
## Use Piik
| What you need | Start here |
| --- | --- |
| Join a friend or share a screen | [Quick Start](../README.md) |
| Run Client, choose a mode, or fix capture trouble | [Client guide](../cmd/screener-client/README.md) |
| Run Client, choose a mode, or fix capture trouble | [Client guide](../cmd/piik-client/README.md) |
| Collect a useful bug report | [Diagnostics and export](./reference/configuration.md#diagnostics) |
| Host your own site | [Self-hosting](./operations/self-hosting.md) |
| Configure ports, access or persistence | [Configuration reference](./reference/configuration.md) |
@@ -33,7 +33,7 @@ npm run dev
In a second terminal, start the server. For a POSIX shell:
```sh
PORT=8788 PUBLIC_BASE_URL=http://localhost:8787 go run ./cmd/screener-server
PORT=8788 PUBLIC_BASE_URL=http://localhost:8787 go run ./cmd/piik-server
```
Or in PowerShell:
@@ -41,8 +41,8 @@ Or in PowerShell:
```powershell
$env:PORT = '8788'
$env:PUBLIC_BASE_URL = 'http://localhost:8787'
go build -o build/dev/screener-server.exe ./cmd/screener-server
./build/dev/screener-server.exe
go build -o build/dev/piik-server.exe ./cmd/piik-server
./build/dev/piik-server.exe
```
The fixed Windows executable path avoids repeated firewall prompts from `go run`.
@@ -87,7 +87,7 @@ Browse [all ADRs](./adr/) for their context and consequences.
results, failed controls and limits; neither promises universal performance.
- [Research directory](./research/): transport, capture, platform and backend evidence.
- [Maintenance](./maintenance.md): document ownership and repository lifecycle.
- [Licensing](../licenses/README.md): Screener's MIT scope and third-party notices.
- [Licensing](../licenses/README.md): Piik's MIT scope and third-party notices.
Research distinguishes observations from assumptions. Product modules own
accepted behavior, source and tests own implementation detail, and Git/PRs own
+1 -1
View File
@@ -5,7 +5,7 @@
## Durable Rationale
Screener serves one broadcaster and a small group of trusted friends. Low
Piik serves one broadcaster and a small group of trusted friends. Low
latency, private access, browser viewing, and low server media cost matter more
than public-broadcast scale. Direct WebRTC therefore remains the preferred
media path, while centralized media is fallback infrastructure rather than the
@@ -5,7 +5,7 @@
## Context
Screener serves one Host and at most 20 trusted Viewers. The application server
Piik serves one Host and at most 20 trusted Viewers. The application server
owns a small room and authorization aggregate while WebRTC and LiveKit own live
media sessions. Frequent application releases should not force the Host to
create another room or redistribute invitations, but a database must not become
@@ -12,7 +12,7 @@ describe the earlier implementation and its production history.
## Context
Direct Browser WebRTC gives Screener its desired latency and distributed cost,
Direct Browser WebRTC gives Piik its desired latency and distributed cost,
but one Host cannot fan out to every Viewer and some endpoint pairs cannot
establish usable direct media. The product needs automatic peer distribution and
bounded server fallback without exposing topology choices to users or turning
@@ -214,7 +214,7 @@ tokens. Replacement, abort, timeout, confirmed media-participant loss, share
rollover, stop, and room deletion transfer the resources they own into the same typed drain path.
Only successful room deletion plus an absence readback releases that generation,
so a stale token cannot recreate off-ledger media. The deployment must dedicate
the LiveKit namespace to Screener and complete startup ownership/cleanup before
the LiveKit namespace to Piik and complete startup ownership/cleanup before
accepting traffic; [self-hosting operations](../operations/self-hosting.md) owns
that procedure.
@@ -236,7 +236,7 @@ capacity poll or second resource-specific controller.
Ordinary Peers use deployment STUN only. Browser SFU PeerConnections use an
empty external ICE-server list while retaining LiveKit-signaled UDP candidates.
Screener configures no TURN, ICE/TCP, media TCP, or TLS-relayed media. A future
Piik configures no TURN, ICE/TCP, media TCP, or TLS-relayed media. A future
strict-firewall transport must be accepted as a LiveKit-internal capability, not
as another application route candidate.
@@ -270,7 +270,7 @@ they mean only one complete same-identity `qualityLimitationReason` delta in
experiment; it does not locate the physical bottleneck or prove end-to-end
quality. `other`, missing, reset, hidden, or stale evidence is unknown. SFU
publication and subscription state remains owned by LiveKit plus the exact
Viewer's continuing decoded progress. Screener does not combine loss, RTT,
Viewer's continuing decoded progress. Piik does not combine loss, RTT,
jitter, bitrate, FPS, resolution, or freezes into a weighted route score.
Routing degradation requires three consecutive complete degraded deltas for the
@@ -12,12 +12,12 @@ implementation; [media quality](../product/media-quality.md) owns current behavi
## Context
Screener sends one realtime game-screen source through independent P2P
Piik sends one realtime game-screen source through independent P2P
connections and, when needed, one shared LiveKit publication. WebRTC and
LiveKit already own congestion control, encoder adaptation, retransmission,
simulcast construction, subscriber bandwidth estimation, and layer selection.
Earlier experiments added a Screener-defined lower SFU encoding and treated
Earlier experiments added a Piik-defined lower SFU encoding and treated
quality preference as an application policy. Production evidence showed that
an always-active lower encoding can compete with the highest encoding on a
constrained Host-to-SFU path. It did not justify replacing LiveKit's native
@@ -123,7 +123,7 @@ than claiming complete source isolation.
## Consequences
- Screener owns fewer media mechanisms and follows the pinned frameworks'
- Piik owns fewer media mechanisms and follows the pinned frameworks'
supported control surfaces.
- A capable Browser Host or relay can use H.264 hardware encoding while another
sender remains on VP8 without a room-protocol branch; the Host can explicitly
@@ -136,7 +136,7 @@ than claiming complete source isolation.
outbound video sender but no additional capture or encoder beyond the sender
that already exists.
- A Host may need to lower its explicit share profile when encoder or uplink
capacity is insufficient; Screener does not silently remove constrained
capacity is insufficient; Piik does not silently remove constrained
Viewer support.
- ADR-0005's native-edge local convergence is deployed and performs bounded
real-candidate comparisons without owning media adaptation. Weighted or global
@@ -144,7 +144,7 @@ than claiming complete source isolation.
## Stop Lines
- No Screener resolution/FPS/bitrate ladder or representation formula.
- No Piik resolution/FPS/bitrate ladder or representation formula.
- No codec wire/state, GPU/MFT allowlist, persistent codec cache, parallel
VP8/H.264 route, active-edge codec churn, or LiveKit backup publication. The
local pre-share Host selector is the only manual codec surface.
@@ -15,12 +15,12 @@
## Context
The Browser is the current Screener interaction surface, but it cannot own native
The Browser is the current Piik interaction surface, but it cannot own native
capture, hardware encode, or reusable native sockets. A self-contained package
must add those capabilities without creating another room model, signaling
protocol, route controller, or UI.
Existing Screener room, admission, signaling, and routing behavior already has
Existing Piik room, admission, signaling, and routing behavior already has
one TypeScript implementation. Rewriting that behavior in the Helper would make
Hosted and Local deployments diverge without improving the media path.
@@ -37,7 +37,7 @@ topologies and makes a saved Site unavailable while another room source runs.
core and the same built Browser assets with local configuration; no
deployment gets a second product core. Its implementation is owned by
[ADR-0012](./0012-shared-go-backend-core.md).
2. The packaged product is Screener Client. Its Go process is the cross-platform
2. The packaged product is Piik Client. Its Go process is the cross-platform
entry and native capability provider. It starts `/health` and one `/control`
endpoint on IPv4 loopback within ports `39721` through `39730` before room-
source selection and retains them until the process exits.
@@ -61,7 +61,7 @@ topologies and makes a saved Site unavailable while another room source runs.
with the page; a picker, share, or room source does not own the socket. The
Browser forwards current signaling and remains the participant.
5. A platform package contains one Go executable carrying the same core and
embedded Browser assets used by Hosted Screener. It may also carry one
embedded Browser assets used by Hosted Piik. It may also carry one
process-isolated capture binary and the pinned `cloudflared` sidecar. Those
sidecars are the only supervised children and keep their bounded lifetime.
Platform packaging is metadata around that entry, not another long-running
@@ -74,7 +74,7 @@ topologies and makes a saved Site unavailable while another room source runs.
network permission. Clearing Site data simply requires opening it from the
Client again.
7. A self-contained Local deployment serves reachable LAN peers without a
central Screener service. Its explicit `--link` mode starts one accountless
central Piik service. Its explicit `--link` mode starts one accountless
Cloudflare Quick Tunnel for the same HTTP/WebSocket surface. The Client
reserves the exact IPv4 listener before creating that tunnel, then applies
its HTTPS origin before the server starts serving that listener. It retains the same
@@ -162,7 +162,7 @@ topologies and makes a saved Site unavailable while another room source runs.
target and must start the assembled Local authority, pass `/healthz`, and
stop it cleanly before the candidate is accepted.
14. The root package manifest is the single dependency contract for the Browser
bundle and repository tooling. No host installs packages to run Screener, so
bundle and repository tooling. No host installs packages to run Piik, so
the manifest has no runtime half, and no second Client dependency list or
post-install package surgery is used.
+2 -2
View File
@@ -26,7 +26,7 @@ the same Browser build.
## Decision
1. One Go module at the repository root owns the product core.
`cmd/screener-server` (Hosted) and `cmd/screener-client` (Client) are entry
`cmd/piik-server` (Hosted) and `cmd/piik-client` (Client) are entry
points over the same `internal/server` packages and own only their defaults,
reachability, and lifecycle policy. Neither reimplements rooms, admission,
signaling, or routing. The TypeScript server, its bundled runtime, the process
@@ -65,7 +65,7 @@ the same Browser build.
[ADR-0002](./0002-memory-resident-protected-rooms.md). Both storage modes,
their validation, and their failure policy are unchanged, and no build
requires a C toolchain.
8. The port changes no contract. Signaling stays `screener-v21`, the Client
8. The port changed no contract. Signaling stayed on its then-current v21 wire; the Client
loopback stays v8, capture framing stays v4, and the HTTP API, cookies, error
codes, and close codes keep their exact shapes. No compatibility alias, dual
reader, or migration is added for a change of implementation language.
+2 -2
View File
@@ -5,7 +5,7 @@
## Context
The owner accepts one self-contained Screener process for the current STUN and
The owner accepts one self-contained Piik process for the current STUN and
SFU services, and one shared encoded-media model for capable Native P2P nodes
and the SFU. Bundling or supervising coturn/LiveKit executables does not meet
this requirement. The existing product uses coturn only as STUN, not TURN.
@@ -28,7 +28,7 @@ acceptance. Mature simulcast implementations provide the starting model.
produce the highest required output and its lower fallbacks, and stop upper
outputs no child needs. This is not a promise of a single Host encode under
all conditions. Representation settings come from the chosen media component
within Host ceilings, not a new Screener quality score or arbitrary ladder.
within Host ceilings, not a new Piik quality score or arbitrary ladder.
4. Each child receives only its selected output. Multiple active encodings do
not imply sending every output to every child. Preserve the original input,
source clock and unaffected siblings. Playback quality, cadence and recovery
@@ -29,7 +29,7 @@ in [ADR-0011](./0011-browser-assisted-native-fanout.md) remains independently ow
upstream subscription, room-wide minimum or topology operation is introduced.
2. Each group uses stock WebRTC encoding and adaptation through an independent
local producer. Each outgoing connection retains stock ICE, DTLS-SRTP,
bandwidth estimation, RTP packetization, pacing and loss recovery. Screener
bandwidth estimation, RTP packetization, pacing and loss recovery. Piik
connects existing demand observations to group budgets and membership; it
adds no congestion estimator, quality score or arbitrary resolution ladder.
3. Reuse `HostPeer` and its existing signaling, sender-mutation and retirement
+20 -18
View File
@@ -15,7 +15,7 @@ bootstrap/updater; do not call this wrapper generic.
## Release Boundary
A routine application release changes only the built Screener server binary and
A routine application release changes only the built Piik server binary and
the Browser assets it embeds. It does not change infrastructure,
service units, proxy/firewall rules, secrets, media listener configuration, or
persistent room state.
@@ -42,16 +42,16 @@ The packager refuses a dirty tree, builds the Browser assets and the
linux/amd64 server binary from that exact revision without cgo, records the
full revision, emits a runtime archive plus path/size/SHA-256 manifest and release descriptor, and
extracts its own artifact to verify it. The archive contains exactly
`screener-server`, `LICENSE`, `THIRD-PARTY-NOTICES.txt`, and `REVISION`. Upload
the archive, manifest, and descriptor together to `/opt/screener/uploads`.
`piik-server`, `LICENSE`, `THIRD-PARTY-NOTICES.txt`, and `REVISION`. Upload
the archive, manifest, and descriptor together to `/opt/piik/uploads`.
The same application descriptor is also the Client assembly input. On each
target platform, provide that platform's Go toolchain:
```sh
SCREENER_GO=/path/to/go node scripts/assemble-client.mjs \
/outside/repository/app-release/screener-<revision>.release.json \
/outside/repository/Screener-Client \
PIIK_GO=/path/to/go node scripts/assemble-client.mjs \
/outside/repository/app-release/piik-<revision>.release.json \
/outside/repository/Piik-Client \
--target windows-amd64 \
--capture /path/to/platform-capture \
--tunnel /path/to/cloudflared
@@ -114,7 +114,7 @@ current CI workflow produces short-lived candidates but does not publish a
GitHub Release; publication remains an explicit distribution decision.
The default Client launcher starts immediately, then performs one background
request to the official Screener GitHub Releases API. It shows a link only when
request to the official Piik GitHub Releases API. It shows a link only when
the latest release has a valid full revision different from the packaged one.
The request sends no current revision, credentials, room data, or media data;
network errors, private-repository responses, and missing releases are treated
@@ -127,7 +127,7 @@ bash deploy/check-release.sh
```
The script runs the deployed binary's release check, which reads
`/opt/screener/current/REVISION` and prints one JSON result.
`/opt/piik/current/REVISION` and prints one JSON result.
Exit status `0` means the deployed revision is current, `10` means a newer
release is available, and `20` means the check could not establish a valid
release identity. For a private repository, inject a short-lived `GITHUB_TOKEN`
@@ -137,9 +137,11 @@ state. A different current-revision file may be supplied as its only argument.
## Permanent-Room Schema Cutover
Schema 2 / signaling `screener-v23` requires matching Web/Client/Server builds
and an accepted active-session interruption. Native control stays v9; Browser
credential keys stay unchanged. This is not an app-only release.
The schema 2 / signaling v23 cutover required matching Web/Client/Server builds
and an accepted active-session interruption. Native control stayed v9; that
schema change preserved Browser credential keys. It was not an app-only release.
The separate [brand cutover](./research/piik-rename-plan.md) must preserve this
database rather than repeat the schema change.
1. Verify the candidate; record the current release, environment, absolute DB
path and ownership. Stop ingress and the old application; take and retain a
@@ -184,21 +186,21 @@ restore the previous infrastructure.
Run the tracked server entry with the uploaded descriptor:
```sh
sudo env SCREENER_PUBLIC_ORIGIN=https://share.example.com \
sudo env PIIK_PUBLIC_ORIGIN=https://share.example.com \
bash /path/to/repository/deploy/release-app.sh \
/opt/screener/uploads/screener-<revision>.release.json
/opt/piik/uploads/piik-<revision>.release.json
```
The wrapper:
1. takes a deployment lock and validates the descriptor, revision, archive,
manifest, current Go process, Screener/nginx service state, and public origin;
manifest, current Go process, Piik/nginx service state, and public origin;
2. rejects unexpected archive paths, links, file types, or duplicate inodes;
3. extracts to a new release directory and verifies every file hash and size;
4. validates production configuration by running the packaged binary's
configuration check as the service user under a bounded runtime;
5. proves the old and new releases share no regular-file inode;
6. atomically switches `/opt/screener/current`, starts the service, and polls
6. atomically switches `/opt/piik/current`, starts the service, and polls
bounded local/public health; and
7. restores the exact prior symlink and service when cutover or health fails.
@@ -225,8 +227,8 @@ Verify only the surfaces relevant to the release:
- local and public `/healthz` return `{"status":"ok"}`;
- the public HTML references the new immutable main asset;
- Screener and nginx have expected active/restart state;
- Screener owns each configured STUN/SFU UDP listener and public reachability
- Piik and nginx have expected active/restart state;
- Piik owns each configured STUN/SFU UDP listener and public reachability
matches the configuration;
- the current symlink and served revision match the descriptor;
- room creation, Host authentication, one direct Viewer, and configured SFU
@@ -240,7 +242,7 @@ to [verification status](./verification-status.md), not routine postflight.
## Recovery
Before cutover, the wrapper keeps the exact prior release path. On failure after
the switch begins, it stops Screener, restores that path atomically, restarts the
the switch begins, it stops Piik, restores that path atomically, restarts the
service, and waits for health. A failed recovery exits distinctly and requires
operator intervention; it must not silently report the new release as active.
+39 -39
View File
@@ -6,7 +6,7 @@ This runbook owns initial single-host service setup. Application releases use
## Embedded Media Configuration
The current source embeds STUN and SFU in the Screener process under
The current source embeds STUN and SFU in the Piik process under
[ADR-0013](../adr/0013-embedded-node-local-media.md). Hosted `STUN_URLS`
advertises ordinary discovery and enables the UDP 3478 listener on
`STUN_LISTEN_HOST` (IPv4, default `0.0.0.0`). Enabling
@@ -16,7 +16,7 @@ public-link Client modes use discovery without creating media-service listeners.
Set `SFU_UDP_PORT=7882` to enable automatic embedded SFU fallback. Its IPv4 bind
address is `SFU_LISTEN_HOST`, default `0.0.0.0`; `SFU_PUBLIC_IP` optionally
overrides the advertised IPv4 address when the host is behind NAT. Unset or
blank `SFU_UDP_PORT` keeps SFU disabled. SFU control uses authenticated Screener
blank `SFU_UDP_PORT` keeps SFU disabled. SFU control uses authenticated Piik
signaling through the Web origin. No separate media-service executable, control
origin, or API credentials are needed by this source contract.
@@ -26,27 +26,27 @@ the active-session check, matching artifacts and public media verification below
## Single-Host Topology
```text
Browser -- HTTPS/WSS --> nginx :443 --> Screener :8787
Browser -- HTTPS/WSS --> nginx :443 --> Piik :8787
Browser <---------- DTLS-SRTP P2P ----------> Browser
Browser -------- ordinary STUN/UDP --------> Screener :3478
Browser -------- optional STUN/UDP --------> Screener :3479/3480
Browser <--------- DTLS-SRTP/UDP ----------> Screener :7882
Browser -------- ordinary STUN/UDP --------> Piik :3478
Browser -------- optional STUN/UDP --------> Piik :3479/3480
Browser <--------- DTLS-SRTP/UDP ----------> Piik :7882
```
The public baseline uses one Screener Go binary, nginx, a valid Web TLS
The public baseline uses one Piik Go binary, nginx, a valid Web TLS
certificate, and time synchronization. nginx terminates Web HTTPS/WSS and
proxies only to Screener's private application listener; media UDP reaches the
proxies only to Piik's private application listener; media UDP reaches the
Go process directly. There is no separate SFU token endpoint or TCP 7880 service.
Web and STUN may use different DNS names on the same public IP. Another proxy or
firewall owner needs its own matching updater and recovery checks.
Expose only the public listeners in
[configuration reference](../reference/configuration.md). Screener TCP 8787 stays
[configuration reference](../reference/configuration.md). Piik TCP 8787 stays
private. STUN answers Binding requests only; TURN allocations remain unavailable.
## Templates
- [Screener systemd unit](../../deploy/systemd/screener.service.example)
- [Piik systemd unit](../../deploy/systemd/piik.service.example)
- [nginx site](../../deploy/nginx/share.bonfire.icu.conf.example)
Copy templates outside the repository, inject independent secrets through an
@@ -56,16 +56,16 @@ domains, certificates, users, and resource limits required by the host.
## Initial Setup
1. Configure DNS and TLS for the Web origin and STUN name.
2. Install nginx. The Screener release is one static binary and needs no
2. Install nginx. The Piik release is one static binary and needs no
language runtime or separate media-service executable.
3. Create an unprivileged `screener` service account, `/opt/screener/releases`,
`/opt/screener/uploads`, and an access-restricted environment file.
3. Create an unprivileged `piik` service account, `/opt/piik/releases`,
`/opt/piik/uploads`, and an access-restricted environment file.
4. Provision one verified initial immutable release and atomically point
`/opt/screener/current` to it before enabling the unit. The tracked release
`/opt/piik/current` to it before enabling the unit. The tracked release
wrapper is upgrade-only and deliberately refuses a missing prior release;
this repository does not provide a generic first-install transaction.
5. SQLite is the Hosted default. The service template selects
`/var/lib/screener/rooms.sqlite` in its service-owned StateDirectory; retain
`/var/lib/piik/rooms.sqlite` in its service-owned StateDirectory; retain
that absolute path in the environment file and include it in the host's
backup/recovery policy. Use `ROOM_DATABASE_PATH=:memory:` only for intentional
process-only rooms. Do not leave an empty override in a service environment.
@@ -75,7 +75,7 @@ domains, certificates, users, and resource limits required by the host.
preserve the private application boundary.
7. Apply the public-port allowlist and confirm every retired TURN/TCP port
is closed.
8. Start Screener, then nginx. Screener binds its configured media listeners
8. Start Piik, then nginx. Piik binds its configured media listeners
before accepting signaling and closes them with the application; a listener
bind failure rolls back startup. Verify public UDP reachability separately.
@@ -93,15 +93,15 @@ proxy/firewall configuration; verify those on the target deployment.
Verify the release archive against its descriptor and manifest using the
[release procedure](../deployment.md), then extract its four files into a new
build-context directory: `screener-server`, `LICENSE`, `THIRD-PARTY-NOTICES.txt`
build-context directory: `piik-server`, `LICENSE`, `THIRD-PARTY-NOTICES.txt`
and `REVISION`. Use that directory, not the repository or a secrets directory:
```sh
docker build --platform linux/amd64 \
-f /path/to/Screener/deploy/container/Dockerfile \
-t screener:<full-revision> /path/to/extracted-runtime
docker run --rm --env-file /path/to/screener.env \
screener:<full-revision> --check-config
-f /path/to/Piik/deploy/container/Dockerfile \
-t piik:<full-revision> /path/to/extracted-runtime
docker run --rm --env-file /path/to/piik.env \
piik:<full-revision> --check-config
```
Use the existing [production configuration](../reference/configuration.md).
@@ -116,14 +116,14 @@ to a proxy running on the same host. Omit UDP publish options for disabled SFU
or auxiliary STUN listeners; use the matching port if configuration changes it.
```sh
docker run -d --name screener --restart unless-stopped --stop-timeout 20 \
docker run -d --name piik --restart unless-stopped --stop-timeout 20 \
--read-only --cap-drop ALL --security-opt no-new-privileges \
--env-file /path/to/screener.env \
--mount type=volume,source=screener-data,target=/home/nonroot \
--env-file /path/to/piik.env \
--mount type=volume,source=piik-data,target=/home/nonroot \
-e ROOM_DATABASE_PATH=/home/nonroot/rooms.sqlite \
-p 127.0.0.1:8787:8787/tcp \
-p 3478:3478/udp -p 3479:3479/udp -p 3480:3480/udp -p 7882:7882/udp \
screener:<full-revision>
piik:<full-revision>
```
The image's `/home/nonroot` is owned by UID/GID `65532:65532` with mode `0700`.
@@ -132,7 +132,7 @@ must already be writable by that identity. Keep this volume when replacing the
container. Set `ROOM_DATABASE_PATH=:memory:` for memory-only room authority. Diagnostics
remain opt-in and use `/home/nonroot/logs`; [export and retention](../reference/configuration.md#diagnostics)
remain the operator's responsibility. Do not mount application files writable.
With diagnostics enabled, `docker kill --signal=USR1 screener` requests a local
With diagnostics enabled, `docker kill --signal=USR1 piik` requests a local
export without stopping the container; the ZIP remains in the mounted log directory.
Check `/healthz` from the host and public proxy, then verify configured STUN/SFU
@@ -154,28 +154,28 @@ The routine application wrapper cannot perform or recover it.
Client artifacts. Check active sessions and obtain the owner's acceptance of
share interruption before cutover; old pages and Clients must reload or
update together.
2. Retain the exact prior release path, Screener unit and drop-ins, environment,
2. Retain the exact prior release path, Piik unit and drop-ins, environment,
nginx configuration, and external media-service units, configuration, secrets,
and enabled/active state for rollback. Keep this material access-restricted
outside the repository. Define recovery for any persistent-state or firewall
surface the transaction actually changes.
3. Stage the current unit with
`ExecStart=/opt/screener/current/screener-server` and
`SCREENER_ENV=production`. Remove retired `NODE_ENV`, `PEER_ASSISTED_MEDIA`
`ExecStart=/opt/piik/current/piik-server` and
`PIIK_ENV=production`. Remove retired `NODE_ENV`, `PEER_ASSISTED_MEDIA`
and external `LIVEKIT_*` configuration; use the current
[environment contract](../reference/configuration.md), including
`SFU_UDP_PORT=7882` when fallback is enabled. Replace route `NODE_DEBUG` with
`SCREENER_DEBUG=route` when needed. Validate the new binary's production
`PIIK_DEBUG=route` when needed. Validate the new binary's production
configuration as the service user before switching.
4. Stop Screener and the old coturn/LiveKit services. Disable those old media
services and remove the installed Screener LiveKit-readiness drop-in so they
4. Stop Piik and the old coturn/LiveKit services. Disable those old media
services and remove the installed Piik LiveKit-readiness drop-in so they
cannot reclaim ports or delay startup. Verify that UDP 3478/3479/3480/7882
are released before the new Go process binds them. Install the current unit,
environment and nginx template, remove the old RTC proxy locations, validate
nginx, and reload systemd. Preserve unrelated proxy headers, limits, TLS,
asset handling, and firewall rules.
5. Atomically point the current release at the verified candidate, start
Screener, and reload nginx. Verify the actual unit command and environment,
Piik, and reload nginx. Verify the actual unit command and environment,
local/public health, the matching Web asset, UDP listener ownership, public
STUN Binding and admitted SFU media, and the agreed active-session outcome.
6. On failure, stop the new Go process and confirm it released its UDP sockets.
@@ -196,11 +196,11 @@ Later application-only releases use the ordinary
Allow UDP 3479 and 3480 in both the cloud security group and the existing
nftables input rule, validating the complete ruleset with
`nft -c -f /etc/nftables.conf`. Set `NAT_PREDICTION_ENABLED=true` and restart
Screener within an accepted service window. Verify `ss -lunp` shows all three
Screener STUN listeners and that a public Binding request succeeds on each.
Piik within an accepted service window. Verify `ss -lunp` shows all three
Piik STUN listeners and that a public Binding request succeeds on each.
Do not add auxiliary URLs to `STUN_URLS`; enabled connections derive them from
the ordinary STUN authority. Keep both firewall rules while the capability is
enabled. Set `NAT_PREDICTION_ENABLED=false` and restart Screener before removing
enabled. Set `NAT_PREDICTION_ENABLED=false` and restart Piik before removing
the auxiliary firewall rules. Avoid verbose STUN/ICE logging because those
requests contain client IP addresses.
@@ -211,15 +211,15 @@ public IPv4.
## Operational Verification
- `/healthz` returns `{"status":"ok"}` locally and through the public origin.
- Screener and nginx are active with expected restart counts and bounded
logs/resources; Screener owns the configured STUN/SFU UDP listeners.
- Piik and nginx are active with expected restart counts and bounded
logs/resources; Piik owns the configured STUN/SFU UDP listeners.
- The official Trickle ICE sample obtains an ordinary UDP `srflx` candidate from
the configured STUN service and no relay candidate.
- A direct room presents media on two networks when Peer ICE succeeds.
- A constrained route creates one managed Host publication and only admitted
subscriptions over embedded SFU UDP; Peer descendants remain ordinary P2P.
- Blocking all UDP reaches a clear bounded failure.
- Restarting Screener in stable mode retains room authority while Browser
- Restarting Piik in stable mode retains room authority while Browser
sessions and media reconnect from fresh process state.
Representative network, mobile, audio, 20-Viewer, and long-running evidence is
+2 -2
View File
@@ -132,7 +132,7 @@ Each ordinary Browser PeerConnection owns stock WebRTC congestion control and
sender adaptation. Clones share one underlying media source and therefore do not provide
complete simultaneous isolation: framework source-wants aggregation may still
partially reduce frames available to sibling clones. Sibling outputs may differ;
Screener does not impose a room-wide minimum. The Host's one Browser SFU
Piik does not impose a room-wide minimum. The Host's one Browser SFU
publication uses the share-generation codec and selected ceilings. Its ordinary
simulcast outputs follow pinned LiveKit screen-share construction: original and
half size at the same frame rate, with a quarter-bitrate lower ceiling and its
@@ -200,7 +200,7 @@ native source. This gives one difficult path stock per-sender adaptation without
lowering the shared representation for healthy paths. It adds no quality score,
timer, or room-wide media setting.
Screener does not maintain an application bitrate/resolution ladder, scene
Piik does not maintain an application bitrate/resolution ladder, scene
detector, periodic quality controller, manual SFU layer selector, or
application-defined whole-room lowest-common-denominator target.
+1 -1
View File
@@ -17,7 +17,7 @@ room identity, current state, roster, and one manual media-reconnect action.
Controls and text must remain usable without overlap or horizontal overflow on
desktop and mobile viewports.
Screener Client opens this same application in the system Browser. Its small
Piik Client opens this same application in the system Browser. Its small
startup surface selects Local, temporary public invitation, or a configured
Site before entering the Host workspace. Viewer links opened at that activated
origin may use the same running Client without changing the Viewer UI. Starting a share offers the
+4 -4
View File
@@ -128,9 +128,9 @@ SFU offer/answer, ICE and output demand use the existing authenticated room
WebSocket. The server binds each physical connection to the current participant,
share and publication; it issues no separate media token or room-service URL.
Browser SFU PeerConnections use the server's UDP candidates and no external
ICE-server list. Ordinary peer connections use deployment STUN. Hosted Screener
ICE-server list. Ordinary peer connections use deployment STUN. Hosted Piik
owns its configured Binding-only STUN listeners and optional SFU UDP listener in
the same Go process. Screener
the same Go process. Piik
configures no TURN, ICE/TCP, media TCP, or TLS-relayed media path. HTTPS/WSS is a
separate control transport and remains TLS/TCP.
@@ -151,7 +151,7 @@ the same bounded route recovery. Source capture remains Host-owned throughout.
Browser WebRTC and the shared Pion/LiveKit media adapter own ICE, consent,
congestion control, bitrate, frame rate, resolution, retransmission, pacing and
layer selection. Direct-child media demand does not create a topology operation.
When the per-share convergence gate is enabled, Screener uses an exact persistent native
When the per-share convergence gate is enabled, Piik uses an exact persistent native
sender limitation only to trigger one measured experiment through the existing
serial operation. Three fresh complete limited deltas from one exact sender may
trigger that experiment on a newly committed availability or direct-convergence
@@ -181,7 +181,7 @@ frame rate, or bitrate across persistent complete windows and produces no
freeze or pause; otherwise the old Peer route stays. The Host publication
ingress and every Viewer subscription are separate quality paths. One Viewer's
SFU proof never authorizes another Viewer. Disabling the gate leaves quality
state diagnostic. Screener has no weighted route score, general parent-wide
state diagnostic. Piik has no weighted route score, general parent-wide
prediction, all-pairs probe, periodic rebalance, persistent parent blacklist,
NAT classification, or independent depth cap.
+4 -4
View File
@@ -2,7 +2,7 @@
Last updated: 2026-09-10
Screener is private, low-latency game screen sharing for one Host and up to 20
Piik is private, low-latency game screen sharing for one Host and up to 20
authenticated friends. The current product surface is Web Host, Web Viewer, and
Browser relay; desktop and mobile Browsers are Viewer targets. It is not a
public broadcast service. Hosted and Client run one shared Go core
@@ -68,7 +68,7 @@ implementation and routine UI detail.
available, and anonymous provenance distinguishes ordinary from predicted
selected paths. The configuration default remains stock ICE. The only
application fallback is one bounded embedded SFU/UDP
publication; Screener configures no TURN or TCP media route. A pre-share
publication; Piik configures no TURN or TCP media route. A pre-share
peer-only policy can exclude that SFU suffix for one share generation.
- Every endpoint shares one steady outbound-copy cap, default `2` and limited to
`1..3`. One room controller owns one committed graph, one reconcile loop, and
@@ -94,12 +94,12 @@ implementation and routine UI detail.
## Current Snapshot
The permanent-room candidate uses strict `screener-v23` Browser/server signaling,
The current contract uses strict `piik-v23` Browser/server signaling,
Native control v9 and capture v7 together. Embedded SFU SDP/ICE and demand travel over the
authenticated room WebSocket, without an external room service or media token.
Committed first-frame readiness, bounded candidate-relative progress, durable
room authority and the H.264/VP8 sender gate remain. [Status](./status.md)
separates this source from last known production; protocol rollback requires
indexes source and deployment evidence; protocol rollback requires
matching Browser and Client artifacts, not only an application symlink.
Current work is owned by the [TODO ledger](./todo.md). Open physical evidence is
+15 -15
View File
@@ -8,7 +8,7 @@ service secret store or an untracked access-restricted environment file.
| Variable | Contract |
| --- | --- |
| `SCREENER_ENV` | `development` or `production`, default `development`; required for every server deployment: `production` enables production-only validation and the `Secure` site-access cookie. |
| `PIIK_ENV` | `development` or `production`, default `development`; required for every server deployment: `production` enables production-only validation and the `Secure` site-access cookie. |
| `LISTEN_HOST` | Defaults to `0.0.0.0`; bare-metal production normally uses `127.0.0.1`. |
| `PORT` | Positive TCP port, default `8787`; the tracked release wrapper supports only that default. |
| `PUBLIC_BASE_URL` | Exact public HTTP(S) origin; production requires HTTPS. |
@@ -35,7 +35,7 @@ public-link Client construction create no SFU listener. The relay does not
provide application E2EE.
The SQLite parent directory must exist and be writable. The systemd template
sets `/var/lib/screener/rooms.sqlite` under its managed state directory; the
sets `/var/lib/piik/rooms.sqlite` under its managed state directory; the
container image sets `/home/nonroot/rooms.sqlite` under its writable data
directory. Keep the existing database path and data across application updates.
Room authority has no idle expiry; the separate site-access cookie keeps its
@@ -53,26 +53,26 @@ Diagnostics are local and opt-in. Enable them **before** reproducing the problem
| Surface | Enable | Export |
| --- | --- | --- |
| Client | Start with `--debug` or `SCREENER_DEBUG=client` | Press `D` in the terminal for a ZIP |
| Client | Start with `--debug` or `PIIK_DEBUG=client` | Press `D` in the terminal for a ZIP |
| Browser Host/Viewer | Add `?debug=1` to the page URL, before any invitation fragment | Use the download button beside language/theme controls |
| Hosted Server | Start with `--debug`, `SCREENER_DEBUG=server` or `SCREENER_DEBUG=route` | On Unix, `kill -USR1 <pid>`; also exported at orderly shutdown |
| Hosted Server | Start with `--debug`, `PIIK_DEBUG=server` or `PIIK_DEBUG=route` | On Unix, `kill -USR1 <pid>`; also exported at orderly shutdown |
Client/Server ZIP and Browser JSON reports are separate: when investigating
Browser/Client cooperation, include both from the same reproduction. Neither
action stops an active share or uploads anything. Browser export also remains
available as `await window.__SCREENER_DEBUG__.export()` in DevTools.
available as `await window.__PIIK_DEBUG__.export()` in DevTools.
Client logs go to `logs` beside the executable, falling back to `Screener/logs`
Client logs go to `logs` beside the executable, falling back to `Piik/logs`
in the OS user-cache directory when that default is unwritable. The TUI shows
the actual path and the exported ZIP. `--log-dir` overrides
`SCREENER_LOG_DIR`; an explicit directory must be writable. Choosing a
`PIIK_LOG_DIR`; an explicit directory must be writable. Choosing a
directory alone does not enable collection. Non-interactive Clients export at
orderly shutdown. On Client, `SCREENER_DEBUG=route` alone retains console route
orderly shutdown. On Client, `PIIK_DEBUG=route` alone retains console route
tracing; use `--debug` for file collection and the `D` action.
Hosted logs use `SCREENER_LOG_DIR`, otherwise systemd `LOGS_DIRECTORY`,
Hosted logs use `PIIK_LOG_DIR`, otherwise systemd `LOGS_DIRECTORY`,
otherwise `logs` under the working directory. The service unit supplies
`/var/log/screener` with mode `0700`. Normal service notices remain in the
`/var/log/piik` with mode `0700`. Normal service notices remain in the
journal; detailed dependency records go into the report. Windows Server has no
Unix signal trigger; forced process termination cannot create a final snapshot.
@@ -129,10 +129,10 @@ project references and the reasons for this collection boundary.
| Port | Scope | Owner |
| ---: | --- | --- |
| TCP 80/443 | public | HTTP redirect and HTTPS/WSS reverse proxy |
| UDP 3478 | public | in-process STUN-only Screener listener |
| UDP 3479/3480 | public when NAT prediction is enabled | in-process auxiliary STUN-only Screener listeners |
| UDP 7882 (or `SFU_UDP_PORT`) | public when SFU enabled | in-process Screener WebRTC media |
| TCP 8787 | private | Screener application |
| UDP 3478 | public | in-process STUN-only Piik listener |
| UDP 3479/3480 | public when NAT prediction is enabled | in-process auxiliary STUN-only Piik listeners |
| UDP 7882 (or `SFU_UDP_PORT`) | public when SFU enabled | in-process Piik WebRTC media |
| TCP 8787 | private | Piik application |
TCP 3478, TCP/TLS 5349, TURN relay ranges, media TCP, and other media
ports remain closed. HTTPS/WSS transport is independent of the UDP-only media
@@ -142,7 +142,7 @@ When `NAT_PREDICTION_ENABLED=true`, the server derives
`stun:<same-hostname>:3479` and `:3480` from the first ordinary STUN authority
on UDP 3478. The Host sees a pre-share switch that defaults on and may disable
it. The capability adds no media route or third-party service. It needs both
cloud security-group rules and the host's `/etc/nftables.conf` rule. Screener
cloud security-group rules and the host's `/etc/nftables.conf` rule. Piik
binds every required UDP listener before opening room persistence or accepting
signaling; a bind failure rolls back all newly owned sockets. Close and End
retire these listeners with the application. Existing coturn listeners must
+1 -1
View File
@@ -136,7 +136,7 @@ It has three concrete failures/limits:
- The secondary sender's `framesSent` stayed zero despite sending the complete
payload. Its `qualityLimitationReason=none` is not useful proof that the
borrowed encoding fits that connection. Existing Screener sender evidence
borrowed encoding fits that connection. Existing Piik sender evidence
cannot consume those counters unchanged.
- In a VP8 late-join arm, the secondary received bytes but decoded zero frames
and sent 34 PLI requests. Those requests did not make the primary encoder
+4 -4
View File
@@ -11,7 +11,7 @@
OpenAI's agent-first repository treats `AGENTS.md` as a map into a structured,
versioned knowledge base. Its reported failure mode for one large instruction
file is the same one observed in Screener: scarce context is crowded out, every
file is the same one observed in Piik: scarce context is crowded out, every
rule appears equally important, stale guidance accumulates, and ownership is
hard to verify. The recommended corrective structure is progressive disclosure,
cross-links, and mechanical freshness/structure checks. Codex concatenates the
@@ -34,7 +34,7 @@ below that value and using nested context for genuinely distinct subtrees. Its
bounded-memory guidance starts consolidation at 80% rather than treating the
hard limit as the normal operating target.
Screener therefore warns at the published recommendation or 80% of a loader
Piik therefore warns at the published recommendation or 80% of a loader
limit, then fails only at a real loading boundary or a small explicit buffer:
32 KiB for the Codex chain, 20,000 characters for Hermes, and 250 effective
Claude lines above the 200-line recommendation. The same envelope applies to the
@@ -55,7 +55,7 @@ Sources:
### Documentation Shape
Diataxis separates tutorial, how-to, reference, and explanation because mixing
reader goals makes each form harder to use. Screener's internal product modules
reader goals makes each form harder to use. Piik's internal product modules
are compact reference; ADRs are explanation of a decision; deployment is a
how-to/reference boundary; research is evidence and explanation. The framework
does not require creating all four categories when there is no reader need.
@@ -76,7 +76,7 @@ Sources:
## Applicability
Screener is small enough that a vector database, memory provider, generated code
Piik is small enough that a vector database, memory provider, generated code
map, parallel agent-context format, or separate RFC repository would add more
ownership than it removes. The useful practices are bounded always-on context,
one owner per fact, domain-shaped reference, evidence on demand, short ADRs,
+1 -1
View File
@@ -49,7 +49,7 @@ bounded graph controller; absent LiveKit configuration still means P2P-only
service. Room privacy and topology-optimization controls remain independent.
The old environment variable must be removed before the next deployment.
There is no Host-star compatibility path to port. Removing the unused
authentication fields requires the coordinated private `screener-v21` release;
authentication fields required the coordinated private signaling v21 release;
rollback to the prior server must also restore its environment configuration.
[ADR-0009](../adr/0009-optional-nat-prediction.md) distinguishes server-owned
+1 -1
View File
@@ -208,4 +208,4 @@ copies no source code and adds no dependency. W3C material uses the
[W3C Document License](https://www.w3.org/copyright/document-license-2023/).
Chromium and WebRTC source files carry BSD-style licenses. Chrome for Developers
prose is CC BY 4.0, with samples under Apache 2.0. These sources are evidence
only; their implementations are not incorporated into Screener.
only; their implementations are not incorporated into Piik.
@@ -23,7 +23,7 @@ constraint. GameViewer separates its UI, supervised media service, codec
capability detector, streamer, health process, and updater. Discord keeps its
Chromium UI separate from a native voice/media module and updater. Oopz ships
Flutter, a second WebView, FFmpeg, Agora RTC, and Tencent LiteAV together; that
stack is substantially larger and duplicates media ownership. Screener follows
stack is substantially larger and duplicates media ownership. Piik follows
the first two products' process separation but not their UI runtimes, and avoids
Oopz's parallel RTC stacks: one Go process, one Pion media core, and one thin
system capture sidecar per platform remain sufficient. No binary code or private
@@ -49,9 +49,9 @@ credentials or route policy; those stay in the Browser/server protocol.
## Client Composition
The Client and Hosted Screener run the same server core, so the local package is
The Client and Hosted Piik run the same server core, so the local package is
one executable carrying the built Browser assets plus the optional capture and
tunnel sidecars; [the Client README](../../cmd/screener-client/README.md) owns
tunnel sidecars; [the Client README](../../cmd/piik-client/README.md) owns
that layout. In Local mode the Client runs the server in its own process with
explicit local configuration. In Site mode it starts no server, and the system
Browser opens the saved Site while the same process remains its loopback
@@ -67,7 +67,7 @@ Linux remain package gates.
## Local And Hosted Reachability
A local page completed discovery and the full control exchange in Chromium
151.0.7922.138. The hosted Screener origin completed it after Chromium received
151.0.7922.138. The hosted Piik origin completed it after Chromium received
its `loopback-network` permission; without that permission the cross-origin
request was blocked. The gate checks both expected outcomes.
@@ -97,7 +97,7 @@ Current WebView2 exposes `getDisplayMedia` screen-capture events and its
Evergreen runtime updates independently; WebKitGTK exposes display capture and
uses GStreamer for WebRTC. Electron instead bundles one Chromium and Node but
makes the Client responsible for shipping those updates and adds substantial
package size. These facts do not prove equivalent Screener media behavior.
package size. These facts do not prove equivalent Piik media behavior.
Only a reproduced Browser-launch failure should open a comparative physical
gate for Electron, Tauri, Wails, Neutralino, or another shell. That gate must
@@ -147,7 +147,7 @@ The sidecar remains an explicit package input rather than a linked Client
dependency.
The peer topology gate now treats native `getStats()` RTP identity and frame
totals as its portable core proof. Detailed Screener quality metrics enrich a
totals as its portable core proof. Detailed Piik quality metrics enrich a
development run when its source module is served, but their absence on a static
deployment no longer erases valid native RTCStats.
+4 -4
View File
@@ -5,7 +5,7 @@ Baseline: `3da8ecd22a3499e660f498e527ad4ffbaef08c82`.
## Goal
The owner requests source/library-level STUN and SFU inside the Screener Go
The owner requests source/library-level STUN and SFU inside the Piik Go
process. Bundled coturn/LiveKit executables, automatic installers, subprocess
supervision and embedding the full LiveKit service do not meet this goal.
The owner has also agreed to [node-local adaptive reuse](./node-local-adaptation.md):
@@ -25,7 +25,7 @@ meet this requirement and is not an SFU replacement candidate.
Retain H264/VP8, Opus, per-subscriber adaptation, retransmission, recovery and
the existing three STUN destinations. Do not trade weak-Viewer experience for a
smaller package, invent an application quality score or add a media ladder.
The media library, not Screener, must own congestion and layer decisions.
The media library, not Piik, must own congestion and layer decisions.
No recording, mixing, TURN relay, public port scanning or production test.
Selective local transcoding is now in research scope under the agreed model;
the existing fanout-only probe does not establish that capability.
@@ -53,7 +53,7 @@ because its example compiles. Keep test executables under the worktree's stable
v3 and ICE v2. Do not introduce another older media stack by default.
- inlivedev/sfu uses Pion WebRTC v4 and exposes room/client APIs. Its README
explicitly warns that its API is early-stage; listed features are claims to
verify, not proof of Screener's required behavior.
verify, not proof of Piik's required behavior.
- Galene is useful architectural reference, not yet an accepted library adapter.
## Current Evidence
@@ -91,7 +91,7 @@ Do not add that second transport merely to change the service's name.
WHIP is HTTP signaling for WebRTC ingest, not a replacement media transport.
Its no-renegotiation constraint must be checked against live audio/source changes;
it is not adopted merely because the request/response looks simpler. The current
Screener signaling owner remains authoritative during this evaluation.
Piik signaling owner remains authoritative during this evaluation.
## Sources
+3 -3
View File
@@ -140,7 +140,7 @@ that changes this boundary.
Native libp2p, Tailscale, and mobile studies are useful operational evidence,
but their relay reservation, raw-socket control, address discovery, native
coordination, and sampled networks differ from browser WebRTC. Their success
rates are not Screener coverage estimates.
rates are not Piik coverage estimates.
## TURN Screening And No-Go
@@ -171,11 +171,11 @@ measured `B_pub` and `sum(B_s)`, so the SFU advantage is not assumed.
This no-go is an application-scope decision, not a claim that TURN is broken.
Coturn remains a valid BSD-3-Clause STUN/TURN implementation, and LiveKit
supports TCP and embedded/external TURN options. Framework capability alone
does not require enabling every transport in Screener.
does not require enabling every transport in Piik.
## SFU Admission Evidence
LiveKit controls do not directly express Screener's resource dimensions:
LiveKit controls do not directly express Piik's resource dimensions:
- room `maxParticipants` limits participant count;
- `canPublishSources` is an authorization allowlist;
+1 -1
View File
@@ -208,7 +208,7 @@ extension forcibly disabled **Broadcast IP for Best WebRTC Performance**;
correcting the setting/extension restored Native sharing. The confirmed cause
is that extension's WebRTC policy, not unsupported Windows capture. VPN
extensions may reapply the override; the
[Chromium WebRTC FAQ](../../cmd/screener-client/README.md#chromium-webrtc-connections)
[Chromium WebRTC FAQ](../../cmd/piik-client/README.md#chromium-webrtc-connections)
owns the user steps. This does not authorize a Browser-specific transport fallback.
An isolated Chrome 152.0.7977.82 data-channel A/B verifies the policy
+1 -1
View File
@@ -8,7 +8,7 @@
## Result
Screener Client can own one process-isolated Windows capture and hardware H.264
Piik Client can own one process-isolated Windows capture and hardware H.264
encoder, feed its Annex-B access units into one Pion source, and deliver that
source through independent WebRTC transports to unmodified Chrome receivers.
The Browser remains the room, route, and signaling authority.
+1 -1
View File
@@ -129,7 +129,7 @@ Before native product consideration, one current-wire candidate must prove:
and
8. project, dependency, H.264 patent, and distribution license decisions.
No native experiment may preserve an obsolete Screener wire, create another
No native experiment may preserve an obsolete Piik wire, create another
room/topology model, loosen endpoint capacity, or become production merely to
make a diagnostic gate pass.
+3 -3
View File
@@ -16,7 +16,7 @@ alongside an identical `high` encoding.
## Two Workstreams
- Self-contained Screener Server: library-level STUN/media components inside the
- Self-contained Piik Server: library-level STUN/media components inside the
executable, not bundled external service processes or an automatic installer.
- Adaptive reuse: local derivation, shared lower outputs, independent child
delivery and quality recovery, usable by Native Client and the server.
@@ -74,7 +74,7 @@ not inferred from the words "automatic" or "simulcast":
counting. Do not claim every unused individual lower layer is always stopped.
- A higher quality request is not subject to downgrade-only debounce. Reduced
maximum demand is debounced; the pinned server default is five seconds. This
is an upstream implementation fact, not a proposed Screener timing constant.
is an upstream implementation fact, not a proposed Piik timing constant.
Publisher deactivation debounce is not a five-second delay before reducing
traffic to a congested subscriber.
- The SDK applies the selected active flags through `RTCRtpSender.setParameters`
@@ -137,7 +137,7 @@ retire their resources immediately; warm retention cannot revive a retired sourc
| Component | Useful capability | Boundary |
| --- | --- | --- |
| Existing `mediaedge.Source` and Pion RTP/WebRTC/interceptors | Encoded fanout, per-edge transport and feedback | No decoder/transcoder or shared-variant policy is supplied automatically |
| Existing Native encoders and generation/keyframe ownership | Platform encode paths already integrated with Screener | Received-source decode/scale and shared derived outputs still need design |
| Existing Native encoders and generation/keyframe ownership | Platform encode paths already integrated with Piik | Received-source decode/scale and shared derived outputs still need design |
| LiveKit Dynacast, layer selector and prober sources | Reference for demand aggregation, safe switching and recovery probes | Apache-2.0 code; inspect imports/ownership before any extraction, not whole-service embedding |
| libvpx multi-resolution VP8 API/example | Multiple spatial encodes through one coordinated API; libvpx is already used | VP8-specific; no promise that H264 hardware sessions have the same cost curve |
| OpenH264 AVC simulcast API | Library H264 encode/decode and per-spatial-layer configuration | Software codec; inspect realtime cost and actual source/binary distribution terms before selection |
+9 -9
View File
@@ -93,7 +93,7 @@ all layers are distinct costs.
each transport still owns its packet headers and recovery.
5. Reduced demand stops unused upper outputs through the framework's normal
deactivation semantics. Any debounce belongs to that owner, not another
Screener timer beside it. No application-selected timer values in this draft.
Piik timer beside it. No application-selected timer values in this draft.
6. Source replacement/failure/stop invalidates the same generation. No dormant
warm pool survives and no old callback can resurrect an encoder.
@@ -127,8 +127,8 @@ an explicitly controlled input-frame boundary is required. In particular,
| Existing module / primary source | Reuse or learn | Integration limit |
| --- | --- | --- |
| Screener `mediaedge.Source`, `Edge`, Native capture generations | Encoded-source ownership, Pion connections, existing share/route identity | Static RTP broadcast alone does not solve switching sequence spaces or codec descriptors |
| [Pion GCC](https://github.com/pion/interceptor/blob/main/pkg/gcc/send_side_bwe.go) and RTP/RTCP interceptors | Existing mature feedback and bandwidth estimate | Current Screener path only observes GCC and uses a no-queue pacer; selection/pacing is not already implemented |
| Piik `mediaedge.Source`, `Edge`, Native capture generations | Encoded-source ownership, Pion connections, existing share/route identity | Static RTP broadcast alone does not solve switching sequence spaces or codec descriptors |
| [Pion GCC](https://github.com/pion/interceptor/blob/main/pkg/gcc/send_side_bwe.go) and RTP/RTCP interceptors | Existing mature feedback and bandwidth estimate | Current Piik path only observes GCC and uses a no-queue pacer; selection/pacing is not already implemented |
| [LiveKit Dynacast manager](https://github.com/livekit/livekit/blob/v1.13.6/pkg/rtc/dynacast/dynacastmanagervideo.go) | Highest-needed activation and asymmetric demand handling | Apache-2.0; reference, not import the entire service or its downstream-node aggregation |
| [LiveKit Simulcast selector](https://github.com/livekit/livekit/blob/v1.13.6/pkg/sfu/videolayerselector/simulcast.go), [RTPMunger](https://github.com/livekit/livekit/blob/v1.13.6/pkg/sfu/rtpmunger.go), [prober](https://github.com/livekit/livekit/blob/v1.13.6/pkg/sfu/ccutils/prober.go) | Keyframe switching, packet continuity, bounded recovery probes | Valuable source, but imports protocol/buffer/logger infrastructure; not a demonstrated tiny drop-in |
| [libwebrtc VP8 adapter](https://github.com/webrtc-mirror/webrtc/blob/main/modules/video_coding/codecs/vp8/libvpx_vp8_encoder.cc) | Coordinated libvpx encoding, SetRates, active-stream keyframes, prepared scale buffers, separate codec and media timestamps | BSD-style; references many WebRTC types. Reuse the underlying libvpx API before copying a large wrapper |
@@ -141,7 +141,7 @@ or a proved adaptation replacement. Its inspected layer sender is coupled to
Client, track, bitrate-controller and packet-map owners; don't copy it as an
independent universal selector. No new dependency or upstream implementation
source was copied in this design pass. Preserve upstream licenses/notices when
an actual module is chosen; Screener's MIT does not replace them.
an actual module is chosen; Piik's MIT does not replace them.
Parallel inLive API inspection found work to validate before selecting it:
publisher-demand change notification is not public; `AddRelayTrack` installs a
@@ -149,7 +149,7 @@ no-op PLI callback; the layer sender has its own packet mapping rather than a
standalone codec-neutral switch API. Its inspected timestamp expression subtracts
the layer origin twice. Manager room removal/locking, failed-PC closing and
logger injection also need focused reproductions. These are candidate-library
risks, not claims about deployed Screener bugs or reasons to expand the Native
risks, not claims about deployed Piik bugs or reasons to expand the Native
module into an SFU fork.
## Required Before Production
@@ -354,7 +354,7 @@ compare the selected complete media adapter against the same release trace.
Run the probe explicitly from the repository root, optionally with
`-frame-bursts`; it writes only ignored `build/embedded-media` diagnostics. It is
not a default unit/CI task. The observed cadence sensitivity is a reason to test
the real packet path, not to introduce a Screener congestion score.
the real packet path, not to introduce a Piik congestion score.
### Embedded Service Composition
@@ -372,7 +372,7 @@ the existing Close/End owner retires STUN too. No service supervisor or second
availability flag is needed. Deployment removes coturn in the final coordinated
cutover, not while the media implementation is still under acceptance.
The implementation candidate uses a thin Screener SFU with LiveKit **media-core
The implementation candidate uses a thin Piik SFU with LiveKit **media-core
components**, without its RTC room or service. A stock-Pion compile and actual
ReceiverBase/DownTrack projection pass; two receiving PCs also demonstrate an
unaffected high-output sibling and high/low/high allocation under forced budgets.
@@ -463,8 +463,8 @@ and the existing pinned libvpx build, using the same MSVC flags as the earlier
CPU probe. Generate its JSONL under `build/embedded-media`, then run:
```powershell
$env:SCREENER_ENCODED_FIXTURE = 'build/embedded-media/encoded-group.fixture.jsonl'
$env:SCREENER_ENCODED_OUTPUT = 'build/embedded-media/encoded-group.received.json'
$env:PIIK_ENCODED_FIXTURE = 'build/embedded-media/encoded-group.fixture.jsonl'
$env:PIIK_ENCODED_OUTPUT = 'build/embedded-media/encoded-group.received.json'
& .\build\embedded-media\mediaedge.test.exe '-test.run=TestEncodedGroupVP8Fixture' '-test.v' '-test.timeout=30s'
node scripts/encoded-group-decode.mjs
```
+141
View File
@@ -0,0 +1,141 @@
# Piik rename
Status: accepted rename and coordinated cutover plan.
Last reviewed: 2026-09-10.
## Accepted identity
Display `Piik`; use `piik` for executable, package, environment and service
identifiers. GitHub is `TNTcraftHIM/Piik`; the Go module follows that exact case.
The native macOS bundle identifier is `tv.piik.client`.
The product keeps its TV mascot. The wordmark and morph animation remain in the
[brand study](../design/piik-brand.html) for the future public website.
This is a private pre-release rename. It changes names and deployment identity,
not media policy, routing, capture behavior or SQLite room semantics.
## Recovery and local metadata
The external `Piik-recovery/rename-20260910` directory contains a verified
`repository.bundle` and a complete `git-metadata` copy. The latter preserves
reflogs and all ten stash entries, which a bundle of ordinary refs alone does
not capture. Earlier recovery bundles also remain outside the repository.
Backups keep their original names and contents so they remain identifiable.
The main directory is `Piik`; preparation worktrees are `Piik-appearance` and
`Piik-rename`, with `Piik-external-audit` retained separately. Their private Git
registration names, backlinks and shared remote are updated. The hook path is
relative `.githooks`. Retire integrated preparation worktrees after cutover.
The audit worktree's branch and source revision remain untouched. Integration
keeps one complete phase commit on main.
On Windows, a terminal can hold the repository directory open. Move the entries
into an empty verified destination and run `git worktree repair` from the new
main directory. Do not terminate the user's terminal to release a path.
Existing firewall rules name executable paths, so moved build binaries require
their corresponding path rules to be reviewed before network acceptance.
## Runtime and data boundary
`piik-v23` and `piik-client-v9` are new exact protocol labels. Their message
schemas retain versions 23 and 9; the full label, not only its numeric suffix,
is checked. Native capture remains v7. Release Web, Server, Client and capture
artifacts together; old pages and installed Clients cannot interoperate across
the renamed control boundary. No alternate protocol reader is introduced.
SQLite schema 2, its application ID, room codes, token/grant digests and password
material remain unchanged. The four-byte database file identity is a format
constant, not display branding. Copy the database only while the old process is
stopped, verify integrity and retained columns, and preserve the original for
rollback. Do not recreate rooms or repeat the earlier schema-1 cutover.
The implementation renames Browser storage prefixes, the site-access cookie
and the Client configuration/cache directory. The owner accepts a clean break:
copy directly compatible, accessible settings once during cutover; discard
unavailable or incompatible settings. The product reads only Piik names, with
no migration code, alternate reader or old-name alias.
The local Client configuration is unchanged JSON version 1 and can be copied
byte-for-byte into the Piik directory without replacing an existing new config.
Browser credentials and settings are origin/session-owned. Transfer them only
through an available original Browser session; do not modify a live Browser's
storage database or introduce a product bridge to recover them. Otherwise the
new namespace starts empty, requiring login, room creation and preference setup.
Old room authority remains in SQLite, but an unavailable Host credential cannot
be reconstructed from its stored digest. Site-access cookies and remembered
Client activation are renewed normally under the new contract.
## Git history
The owner authorized replacing the old name in Git history. Use the maintained
`git-filter-repo` tool in a fresh, separate bare clone, never in the working
repository. Scope this to commit/tag **messages**: historical source snapshots,
authorship, dates and topology remain factual. Current tracked prose and code
use the accepted identity; old snapshots remain recoverable at their mapped
commits and in the original archive.
The initial inventory found two matching commit bodies and no matching commit
subjects or tag names. A small text change still changes descendant SHAs and
invalidates their commit signatures. Preserve `commit-map` and `ref-map`; verify
equal commit counts, identical source trees, mapped parents, authors and dates,
no dropped commits and a clean repository integrity check. No replacement refs,
duplicate compatibility branches or history-rewriting tool enter the product.
The rehearsal verified all 546 reachable branch/tag commits: exactly two messages
changed, all file trees and mapped parent relationships match, and authors/dates
are preserved. Standard filtering rewrote 545 IDs and removed 374 signatures.
Those signatures remain verifiable only in the original archive; a rewritten
commit must not be presented as preserving its earlier GitHub verification.
Apply a reviewed message rewrite only with the coordinated brand integration,
using explicit ref targets and old-SHA leases. Do not use `push --mirror` from
the rehearsal: it could overwrite unrelated refs. Preserve the external audit
branch and stash history in the original repository. Old audit/deployment SHAs
remain evidence identifiers, interpreted through the archive and commit map.
## Operational cutover
The following old identifiers are retained here only as operator recovery
inputs, not as runtime aliases:
| Surface | Before | After |
| --- | --- | --- |
| Service, user and group | `screener` | `piik` |
| Installation root | `/opt/screener` | `/opt/piik` |
| Environment file | `/etc/screener/screener.env` | `/etc/piik/piik.env` |
| Branded environment prefix | `SCREENER_` | `PIIK_` |
| Browser storage prefix | `screener:` | `piik:` |
| Client config directory | `Screener` | `Piik` |
Prepare the new immutable artifacts, service/environment/proxy configuration,
permissions and database path before stopping the old service. Record original
configuration and ownership for recovery. Do not run two room authorities on
the same database or two media services on the same UDP ports.
The routine `deploy/release-app.sh` assumes an existing **Piik** installation.
It is not the first brand migration tool: its old-release checks and recovery
target the Piik service/binary. Perform the first name cutover as one explicit
infrastructure operation, then use the ordinary wrapper for future releases.
On failure, stop the candidate and restore the old binary, service, environment,
proxy and data together before reopening ingress.
GitHub is already renamed and the remote works. There are no published releases
or Pages site to migrate. Keep repository visibility private; `piik.tv` DNS and
Pages publication remain a separate website task. Old GitHub deployment/PR
records describe earlier operations and are not rewritten as new releases.
## Validation and references
Acceptance covers the tracked-name inventory, Web type/build checks, relevant
contract/storage tests, native compilation, Client/Server packaging and a
controlled local launch. Production release additionally requires the accepted
best-effort settings transfer, configuration/database recovery and postflight.
Primary references, checked 2026-09-10:
- [Git worktree repair](https://git-scm.com/docs/git-worktree): repair both
directions after moving the main repository or linked worktrees.
- [git-filter-repo](https://github.com/newren/git-filter-repo/blob/main/Documentation/git-filter-repo.txt):
message replacement, fresh clones and old/new commit maps.
- [GitHub repository rename](https://docs.github.com/en/repositories/creating-and-managing-repositories/renaming-a-repository):
ordinary URL redirects do not cover repository-hosted Actions or project Pages.
+6 -6
View File
@@ -2,18 +2,18 @@
Last reviewed: 2026-08-31
This document owns evidence for showing a live Screener session on an AirPlay,
This document owns evidence for showing a live Piik session on an AirPlay,
Cast, or Presentation API display. It does not add a product output surface.
## Current Decision
Do not add an application Cast or AirPlay button yet. Browser or operating-system
tab/screen mirroring is already usable without Screener integration. The Web
tab/screen mirroring is already usable without Piik integration. The Web
platform does not give the current `video.srcObject = MediaStream` a portable
remote-playback contract.
The only application design worth a physical prototype is a custom Cast
receiver acting as another authenticated Screener Viewer. It would receive its
receiver acting as another authenticated Piik Viewer. It would receive its
own WebRTC route and count toward room and endpoint capacity; it would not reuse
or fling the controlling Browser's decoded video element.
@@ -24,7 +24,7 @@ or fling the controlling Browser's decoded video element.
Browser and operating-system UI can mirror a tab, window, screen, or media
element. The W3C Remote Playback model explicitly permits this implementation
shape but does not standardize its transport, latency, quality, lifecycle, or
device coverage. Screener needs no adapter for it and cannot claim its result.
device coverage. Piik needs no adapter for it and cannot claim its result.
### Media Remoting Or Flinging
@@ -55,12 +55,12 @@ application with custom messaging.
That makes a narrow architecture possible: launch a receiver page, deliver a
short-lived room-scoped Viewer grant over the established control channel, and
let that page join through the normal Screener Viewer and routing contracts. The
let that page join through the normal Piik Viewer and routing contracts. The
grant must not appear in the presentation URL, logs, storage, or device metadata.
This is not accepted product behavior. Google's documented media matrix does
not promise WebRTC in a Custom Web Receiver, the Presentation API is not a
cross-Browser surface, and no target receiver has proved Screener's codec,
cross-Browser surface, and no target receiver has proved Piik's codec,
autoplay, audio, network, or lifecycle requirements.
## Physical Acceptance Gate
+2 -2
View File
@@ -230,7 +230,7 @@ default screen-share publication can expose original and lower representations,
and server BWE can forward a lower representation to a constrained subscriber
while another receives the highest available representation.
Calling the pinned client's own encoding calculator with Screener's exact
Calling the pinned client's own encoding calculator with Piik's exact
publish options produces two VP8 encodings for every accepted profile:
| Profile | Lower representation | Original representation |
@@ -239,7 +239,7 @@ publish options produces two VP8 encodings for every accepted profile:
| 1080p30 | 1/2 scale, 1.25 Mbps, 30 fps | 5 Mbps, 30 fps |
| 1080p60 | 1/2 scale, 2 Mbps, 60 fps | 8 Mbps, 60 fps |
Both encodings start without an explicit RTP `priority` because Screener's
Both encodings start without an explicit RTP `priority` because Piik's
custom `screenShareEncoding` replaces LiveKit's priority-bearing preset while
retaining its default `simulcast: true`. The audio encoding is `high` priority.
Priority can only redistribute bandwidth within that PeerConnection and request
+6 -6
View File
@@ -7,7 +7,7 @@
## Current Ownership
`cmd/screener-server` (Hosted) and `cmd/screener-client` (Client) compose the
`cmd/piik-server` (Hosted) and `cmd/piik-client` (Client) compose the
same application from `internal/server` and own only their defaults,
reachability, and lifecycle policy. Within that scope `protocol` owns wire types,
strict decoding, and shared scalars; `config` owns environment validation plus
@@ -46,8 +46,8 @@ share one route authority and LiveKit remains an optional fallback of that graph
```text
one Go module
cmd/screener-server -> shared server application
cmd/screener-client -> shared server application + launcher + native media
cmd/piik-server -> shared server application
cmd/piik-client -> shared server application + launcher + native media
internal/server -> rooms, routing, HTTP/signaling, persistence, SFU adapters
internal/client -> current native and platform boundaries
one React/Vite static artifact -> embedded in both binaries
@@ -106,7 +106,7 @@ origin, site-access password, one STUN URL, loopback listen host), polled
`/healthz` every 10 ms until `{"status":"ok"}`, waited 5 s idle, sampled
resident memory through PowerShell `WorkingSet64`, then killed the process.
Five runs per side; the median is reported. Before is the Node baseline at tag
`baseline-b20fd88`; after is `screener-server` under the same script.
`baseline-b20fd88`; after is `piik-server` under the same script.
| metric | before | after |
| --- | ---: | ---: |
@@ -117,9 +117,9 @@ Five runs per side; the median is reported. Before is the Node baseline at tag
The before total is the Node runtime (91,694,408 B), the production
`node_modules` tree (12,640,469 B in 1,513 files), `dist/client` (1,492,721 B)
and `dist/server` (856,898 B). The after total is the linux/amd64
`screener-server` built with `-s -w` and embedded assets (13,983,906 B) plus its
`piik-server` built with `-s -w` and embedded assets (13,983,906 B) plus its
notices and `REVISION`. For reference, the windows/amd64 binaries are
14,276,608 B (`screener-server`) and 22,703,104 B (`screener-client`, which also
14,276,608 B (`piik-server`) and 22,703,104 B (`piik-client`, which also
carries Pion and the terminal UI); the Client package additionally drops the
Node runtime and dependency tree it used to ship beside its Go executable.
+4 -4
View File
@@ -13,7 +13,7 @@ The installed Chrome is 152.0.7977.82. Its [DEPS][chromium-deps] pins libwebrtc
to `6f37672d358475cd17544121a12494da454d85fb`; WebRTC references below use that
revision, not a floating upstream branch. Source shows available mechanisms,
not which field trials or hardware encoder a particular runtime selected.
The Screener comparison uses the `3c192ddd` candidate, not production.
The Piik comparison uses the `3c192ddd` candidate, not production.
## Actual Control Ownership
@@ -45,7 +45,7 @@ These are connected but different owners:
For example, the adapter can request at most 3/5 of the current pixel count or
reduce FPS to 2/3 under the corresponding preference. These are upstream rules,
not proposed Screener constants. The default [encoder minimum][encoder-api] is
not proposed Piik constants. The default [encoder minimum][encoder-api] is
320x180 pixels and can be overridden; no universal 108p floor is established.
VP8's [libvpx adapter][vp8] disables libvpx internal resizing because WebRTC owns
that work outside the codec. Turning on libvpx resizing alone does not restore
@@ -53,7 +53,7 @@ the omitted control chain.
### Motion And Simulcast Are Not Synonyms
Screener's `motion` hint becomes `kFluid` in the [Chromium sink][chromium-hint].
Piik's `motion` hint becomes `kFluid` in the [Chromium sink][chromium-hint].
[VideoRtpSender][sender-hint] then sets `is_screencast=false`; this follows the
realtime-video path even for display capture. Explicit degradation preference
still has precedence over the hint's default. Do not assume all display capture
@@ -584,7 +584,7 @@ software encoder. VSE invokes `OnEncodeStarted` on its encoder queue just
before the codec call; raw arrival and `OnDiscardedFrame` do not supply CPU
samples. Replaying dropped inputs as encoded work would misrepresent feedback.
Screener's synchronous drain and latest-input mailbox reduce arrivals into VSE
Piik's synchronous drain and latest-input mailbox reduce arrivals into VSE
under this load, but the [same pinned VSE][vse] also skips queued frames before
calling the CPU observer. Therefore moving admission or reporting source drops
alone is not an established fix for multi-second encode gaps. Chromium's pinned
+12 -9
View File
@@ -23,21 +23,24 @@ and Git/PRs own completed history.
## Deployment
The owner authorized integration, coordinated cutover and cleanup before the
Piik appearance phase. Authorization is not deployment evidence: the immutable
release descriptor, runtime `REVISION` and operator deployment record own the
actual deployed identity and postflight result. The
[schema cutover procedure](./deployment.md#permanent-room-schema-cutover) preserves
existing room authority and Browser credential keys with a database/application
rollback boundary.
Source, package identity, GitHub repository and local worktree paths use Piik.
The strict wire labels and stored keys move together at the coordinated
[brand cutover](./research/piik-rename-plan.md). Directly compatible, accessible
settings are transferred once; unavailable settings start fresh. Runtime code
keeps only the Piik contract.
The immutable release descriptor, runtime `REVISION` and operator deployment
record own exact identity and postflight results. The
[schema cutover](./deployment.md#permanent-room-schema-cutover) preserved existing
room authority and Browser credential keys. Old active pages reload at the
signaling v23 boundary.
The existing deployment uses one Go process for Web, room authority/signaling,
Binding-only STUN on UDP 3478/3479/3480 and optional SFU on UDP 7882. nginx owns
HTTPS; external LiveKit/coturn services remain disabled. Participants, routes and
media remain process-only. This private service is not a public demonstration.
[TODO](./todo.md) owns closeout and subsequent appearance, renaming, distribution
and ablation work. No public site or bulk rename belongs to this release.
[TODO](./todo.md) owns rename acceptance, distribution and ablation work.
The public website is a later phase.
## Media Evidence And Limits
+9 -33
View File
@@ -7,43 +7,19 @@ and Git/PRs own completed history. A parked idea is not implementation authority
## Now
Finish the owner-authorized PR386 squash, matched Web/Client/Server cutover and
cleanup, then resume Piik appearance. Entry/Client lifecycle repairs, confirmed
reconnect ownership fixes and the permanent-room/Hosted SQLite-default revision
are implemented and verified. Product modules and
[ADR-0002](./adr/0002-memory-resident-protected-rooms.md) own that accepted behavior.
Use the [coordinated cutover](./deployment.md#permanent-room-schema-cutover) to
preserve existing SQLite authority and unchanged Browser credential keys; old
active pages reload at the signaling v23 boundary. Runtime release metadata and
the operator record own deployment completion. Do not recreate databases or
add a replacement garbage collector. Later manual feedback is nonblocking.
No further product implementation is queued for the accepted Piik rename.
Its coordinated Web/Client/Server and Git-history cutover follows the
[rename plan](./research/piik-rename-plan.md); the immutable release descriptor
and deployment record own rollout identity. Preserve the original repository
backup and external-audit worktree. Website work starts on owner direction.
## Next — Awaiting Owner Direction
1. Resume the [Piik brand study](./design/piik-brand.html) after the experience
repairs. Keep round i dots; the tiny-e experiment was rejected. Try using the
two outer stems as small headphones/earcups on the TV, retaining its familiar
silhouette and the k diagonals as antennae. Display `Piik` and technical
`piik` remain proposals. Settle identity before coordinated renaming or the
public-site implementation.
2. After the brand discussion, rename display copy, Go/npm source identity,
Client/Server packages and OS icons, strict protocol/service identifiers,
diagnostic redaction, configuration and deployment tooling in one coordinated
phase. Inventory is complete. Browser storage contains room authority/grants
and Client configuration contains a saved Site; decide preservation/reset
explicitly before renaming their keys or directories. Preserve SQLite room
authority when changing deployment paths. Use matching Web/Client/Server
artifacts and one current reader/writer, without compatibility aliases.
Keep third-party names/notices and historical evidence attribution accurate.
The owner owns **piik.tv**.
Agree on casing, TV mascot/wordmark treatment, repository and runtime identifiers,
release names and the GitHub Pages website before implementing the rename.
Keep one current internal contract and remove replaced names in the coordinated
change; no private compatibility aliases. Existing sketches are input, not an
accepted identity. Do not configure DNS/Pages or publish the brand before that
design discussion. The private production service is not a public demo.
Design the GitHub Pages website around the accepted brand study. Keep one
current internal contract; third-party names/notices remain accurate. DNS/Pages
and public publication are separate from the repository rename. The private
production service is not a public demo.
Include the owner-requested frontend/backend ablation review in that phase.
Review duplicated behavior/state, module boundaries, interface clarity, lifecycle
+1 -1
View File
@@ -1,4 +1,4 @@
module github.com/TNTcraftHIM/Screener
module github.com/TNTcraftHIM/Piik
go 1.26
+2 -2
View File
@@ -6,10 +6,10 @@
<meta name="theme-color" content="#141e29" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#e9f2ee" media="(prefers-color-scheme: light)" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Screener</title>
<title>Piik</title>
<script>
try {
var theme = localStorage.getItem("screener:ui-theme") ||
var theme = localStorage.getItem("piik:ui-theme") ||
(matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
document.documentElement.dataset.theme = theme;
} catch (e) {}
+43 -43
View File
@@ -16,18 +16,18 @@ import (
"syscall"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/browser"
"github.com/TNTcraftHIM/Screener/internal/client/clientconfig"
"github.com/TNTcraftHIM/Screener/internal/client/lan"
"github.com/TNTcraftHIM/Screener/internal/client/launcher"
"github.com/TNTcraftHIM/Screener/internal/client/loopback"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Screener/internal/client/nativecontrol"
"github.com/TNTcraftHIM/Screener/internal/client/publictunnel"
"github.com/TNTcraftHIM/Screener/internal/diagnostics"
serverapp "github.com/TNTcraftHIM/Screener/internal/server/app"
serverconfig "github.com/TNTcraftHIM/Screener/internal/server/config"
"github.com/TNTcraftHIM/Screener/internal/server/webassets"
"github.com/TNTcraftHIM/Piik/internal/client/browser"
"github.com/TNTcraftHIM/Piik/internal/client/clientconfig"
"github.com/TNTcraftHIM/Piik/internal/client/lan"
"github.com/TNTcraftHIM/Piik/internal/client/launcher"
"github.com/TNTcraftHIM/Piik/internal/client/loopback"
"github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
"github.com/TNTcraftHIM/Piik/internal/client/nativecontrol"
"github.com/TNTcraftHIM/Piik/internal/client/publictunnel"
"github.com/TNTcraftHIM/Piik/internal/diagnostics"
serverapp "github.com/TNTcraftHIM/Piik/internal/server/app"
serverconfig "github.com/TNTcraftHIM/Piik/internal/server/config"
"github.com/TNTcraftHIM/Piik/internal/server/webassets"
)
const (
@@ -63,14 +63,14 @@ type Options struct {
func Run(ctx context.Context, options Options) (returnedErr error) {
ctx, cancel := context.WithCancel(ctx)
options.Debug = options.Debug || clientDebugEnabled(os.Getenv("SCREENER_DEBUG"))
options.Debug = options.Debug || clientDebugEnabled(os.Getenv("PIIK_DEBUG"))
options.console = newClientConsole(cancel, options.DisableBrowser)
var recorder *diagnostics.Recorder
var restoreLogger func()
defer func() {
cancel()
if recorder != nil {
slog.Debug("screener-client", "event", "stopped", "failed", returnedErr != nil, diagnostics.Error(returnedErr))
slog.Debug("piik-client", "event", "stopped", "failed", returnedErr != nil, diagnostics.Error(returnedErr))
if options.console.program == nil {
path, err := recorder.Export()
options.console.send(consoleExportResult{path: path, err: err})
@@ -85,7 +85,7 @@ func Run(ctx context.Context, options Options) (returnedErr error) {
var err error
recorder, err = openClientDiagnostics(options.LogDir)
if err != nil {
return fmt.Errorf("Screener Client diagnostics are unavailable: %w", err)
return fmt.Errorf("Piik Client diagnostics are unavailable: %w", err)
}
previous, previousWriter, previousFlags := slog.Default(), log.Writer(), log.Flags()
var dependencyLog *diagnostics.LineWriter
@@ -102,7 +102,7 @@ func Run(ctx context.Context, options Options) (returnedErr error) {
log.SetFlags(previousFlags)
options.console.send(consoleDebug{logPath: recorder.LogPath(), export: recorder.Export})
}
slog.Debug("screener-client", "event", "start", "revision", BuildRevision)
slog.Debug("piik-client", "event", "start", "revision", BuildRevision)
options.console.show(consoleView{state: "starting"})
if err := validateMode(options); err != nil {
return err
@@ -115,21 +115,21 @@ func Run(ctx context.Context, options Options) (returnedErr error) {
var err error
configPath, err = clientconfig.DefaultPath()
if err != nil {
return errors.New("Screener Client configuration is unavailable")
return errors.New("Piik Client configuration is unavailable")
}
}
config, err := clientconfig.LoadOrCreate(configPath)
if err != nil {
return errors.New("Screener Client configuration is unavailable")
return errors.New("Piik Client configuration is unavailable")
}
config, err = applyMode(config, options)
if err != nil {
return err
}
slog.Debug("screener-client", "event", "configuration", "siteConfigured", config.Site != "", "localAccessProtected", config.LocalAccessPassword != "")
slog.Debug("piik-client", "event", "configuration", "siteConfigured", config.Site != "", "localAccessProtected", config.LocalAccessPassword != "")
if options.SiteSet || options.Local {
if err = clientconfig.Save(configPath, config); err != nil {
return errors.New("Screener Client configuration is unavailable")
return errors.New("Piik Client configuration is unavailable")
}
}
nativeMedia := discoverNativeMedia(ctx, options.CaptureProcess)
@@ -139,7 +139,7 @@ func Run(ctx context.Context, options Options) (returnedErr error) {
recorder.Context("capture", nativeMedia.capture)
recorder.Binary("captureExecutable", nativeMedia.captureProcess)
}
slog.Debug("screener-client", "event", "native-capabilities",
slog.Debug("piik-client", "event", "native-capabilities",
"video", nativeMedia.capabilities.Video, "processAudio", nativeMedia.capabilities.ProcessAudio,
"systemAudio", nativeMedia.capabilities.SystemAudio, "hardwareH264", nativeMedia.capabilities.HardwareH264,
"softwareVP8", nativeMedia.capabilities.SoftwareVP8)
@@ -150,10 +150,10 @@ func Run(ctx context.Context, options Options) (returnedErr error) {
Presentation: options.console.setLanguage,
})
if err != nil {
return errors.New("Screener Client could not start")
return errors.New("Piik Client could not start")
}
defer client.Close()
slog.Debug("screener-client", "event", "control-ready")
slog.Debug("piik-client", "event", "control-ready")
if options.console.machine {
if err = printEndpoint(client.Endpoint()); err != nil {
return err
@@ -209,7 +209,7 @@ func runLauncher(
))
options.console.show(consoleView{state: "setup", entry: launch.URL()})
if err = browser.Open(launch.URL()); err != nil {
return errors.New("Screener Client could not open its launcher")
return errors.New("Piik Client could not open its launcher")
}
var selection launcher.Selection
@@ -219,7 +219,7 @@ func runLauncher(
return nil
case err = <-launch.Done():
if err != nil {
return errors.New("Screener Client launcher stopped unexpectedly")
return errors.New("Piik Client launcher stopped unexpectedly")
}
return nil
}
@@ -232,7 +232,7 @@ func runLauncher(
if err = clientconfig.Save(configPath, config); err != nil {
launch.SetResult("", err)
<-launch.Handled()
return errors.New("Screener Client configuration is unavailable")
return errors.New("Piik Client configuration is unavailable")
}
client.SetAllowedOrigins(clientOrigins(config.Site, options.Port))
options.SiteSet = false
@@ -289,7 +289,7 @@ func applyMode(config clientconfig.Config, options Options) (clientconfig.Config
var err error
config.Site, err = clientconfig.NormalizeSite(options.Site)
if err != nil || config.Site == "" {
return clientconfig.Config{}, errors.New("Screener Site must be an HTTP or HTTPS origin")
return clientconfig.Config{}, errors.New("Piik Site must be an HTTP or HTTPS origin")
}
} else if options.Local {
config.Site = ""
@@ -315,7 +315,7 @@ func clientDebugEnabled(value string) bool {
func openClientDiagnostics(directory string) (*diagnostics.Recorder, error) {
if directory = strings.TrimSpace(directory); directory == "" {
directory = strings.TrimSpace(os.Getenv("SCREENER_LOG_DIR"))
directory = strings.TrimSpace(os.Getenv("PIIK_LOG_DIR"))
}
if directory != "" {
return diagnostics.Open(directory, "client", BuildRevision)
@@ -332,33 +332,33 @@ func openClientDiagnostics(directory string) (*diagnostics.Recorder, error) {
if cacheErr != nil {
return nil, errors.Join(err, cacheErr)
}
return diagnostics.Open(filepath.Join(cache, "Screener", "logs"), "client", BuildRevision)
return diagnostics.Open(filepath.Join(cache, "Piik", "logs"), "client", BuildRevision)
}
// runSite opens the configured Screener Site in the Browser and waits for the
// runSite opens the configured Piik Site in the Browser and waits for the
// loopback server, which is the only thing this mode owns. It takes no context:
// cancellation reaches it through client.Done().
func runSite(site string, options Options, client *loopback.Server) error {
var err error
slog.Debug("screener-client", "event", "mode", "mode", "site")
slog.Debug("piik-client", "event", "mode", "mode", "site")
launchURL := clientLaunchURL(site)
view := consoleView{mode: "site", state: "starting", entry: launchURL}
options.console.show(view)
if options.console.machine {
fmt.Printf("Screener Site: %s\n", site)
fmt.Printf("Piik Site: %s\n", site)
}
if !options.DisableBrowser {
if err = browser.Open(launchURL); err != nil {
return errors.New("Screener Client could not open the Site")
return errors.New("Piik Client could not open the Site")
}
} else if options.Ready != nil {
options.Ready(launchURL)
}
view.state = "ready"
slog.Debug("screener-client", "event", "site-ready")
slog.Debug("piik-client", "event", "site-ready")
options.console.show(view)
if err = <-client.Done(); err != nil {
return errors.New("Screener Client stopped unexpectedly")
return errors.New("Piik Client stopped unexpectedly")
}
return nil
}
@@ -370,7 +370,7 @@ func runLocal(ctx context.Context, options Options, config clientconfig.Config,
if options.Link {
view.mode = "link"
}
slog.Debug("screener-client", "event", "mode", "mode", view.mode)
slog.Debug("piik-client", "event", "mode", "mode", view.mode)
options.console.show(view)
listener, err := net.ListenTCP("tcp4", &net.TCPAddr{IP: net.IPv4zero, Port: options.Port})
if err != nil {
@@ -402,7 +402,7 @@ func runLocal(ctx context.Context, options Options, config clientconfig.Config,
}
defer tunnel.Close()
publicOrigin = tunnel.Origin()
slog.Debug("screener-client", "event", "public-link-ready")
slog.Debug("piik-client", "event", "public-link-ready")
}
stunURLs, natPredictionStunURLs := localSTUNURLs(options.Link)
@@ -436,7 +436,7 @@ func runLocal(ctx context.Context, options Options, config clientconfig.Config,
if _, err = localServer.Listen(ctx); err != nil {
return err
}
slog.Debug("screener-client", "event", "local-server-ready", "port", options.Port, "publicLink", publicOrigin != "")
slog.Debug("piik-client", "event", "local-server-ready", "port", options.Port, "publicLink", publicOrigin != "")
// The readiness lines follow the listener, which the packaged smoke and the
// public-link gate both read from stdout before they probe the port.
@@ -458,7 +458,7 @@ func runLocal(ctx context.Context, options Options, config clientconfig.Config,
)
if !options.DisableBrowser {
if err = browser.Open(launchURL); err != nil {
return errors.New("Screener Client could not open the Local page")
return errors.New("Piik Client could not open the Local page")
}
} else if options.Ready != nil {
options.Ready(launchURL)
@@ -478,7 +478,7 @@ func runLocal(ctx context.Context, options Options, config clientconfig.Config,
return endLocalServer(localServer)
case err = <-client.Done():
if err != nil {
return errors.New("Screener Client runtime stopped unexpectedly")
return errors.New("Piik Client runtime stopped unexpectedly")
}
return nil
case <-tunnelDone:
@@ -495,7 +495,7 @@ func clientLaunchURL(raw string) string {
if err != nil {
return raw
}
fragment.Set("screener-client", "1")
fragment.Set("piik-client", "1")
parsed.Fragment = fragment.Encode()
return parsed.String()
}
@@ -513,7 +513,7 @@ func clientLaunchURLWithLocalAccess(raw, password string) string {
if password != "" {
fragment.Set("client-access", password)
}
fragment.Set("screener-client", "1")
fragment.Set("piik-client", "1")
parsed.Fragment = fragment.Encode()
return parsed.String()
}
@@ -604,7 +604,7 @@ func tunnelExecutable(configured string) string {
func printEndpoint(endpoint loopback.Endpoint) error {
payload, err := json.Marshal(endpoint)
if err != nil {
return errors.New("Screener Client endpoint is unavailable")
return errors.New("Piik Client endpoint is unavailable")
}
fmt.Println(string(payload))
return nil
+9 -9
View File
@@ -13,8 +13,8 @@ import (
"strings"
"testing"
"github.com/TNTcraftHIM/Screener/internal/client/clientconfig"
serverconfig "github.com/TNTcraftHIM/Screener/internal/server/config"
"github.com/TNTcraftHIM/Piik/internal/client/clientconfig"
serverconfig "github.com/TNTcraftHIM/Piik/internal/server/config"
)
func TestOccupiedPortRejectsLinkBeforeStartingATunnel(t *testing.T) {
@@ -41,7 +41,7 @@ func TestClientLaunchURLMarksThePageWithoutChangingOrigin(t *testing.T) {
t.Fatalf("launch URL = %q, %v", value, err)
}
fragment, err := url.ParseQuery(parsed.Fragment)
if err != nil || fragment.Get("screener-client") != "1" {
if err != nil || fragment.Get("piik-client") != "1" {
t.Fatalf("Client launch fragment = %q, %v", parsed.Fragment, err)
}
}
@@ -64,7 +64,7 @@ func TestClientDiagnosticsUseFilesOnlyWhenEnabled(t *testing.T) {
log.SetOutput(previousOutput)
slog.SetLogLoggerLevel(previousLevel)
})
t.Setenv("SCREENER_DEBUG", "")
t.Setenv("PIIK_DEBUG", "")
for _, enabled := range []bool{false, true} {
output.Reset()
directory := t.TempDir()
@@ -72,7 +72,7 @@ func TestClientDiagnosticsUseFilesOnlyWhenEnabled(t *testing.T) {
if err == nil {
t.Fatal("invalid mode must stop before starting capture or services")
}
if strings.Contains(output.String(), "screener-client") {
if strings.Contains(output.String(), "piik-client") {
t.Fatalf("Client diagnostics reached stderr: %s", output.String())
}
content, readErr := os.ReadFile(filepath.Join(directory, "client.log"))
@@ -92,7 +92,7 @@ func TestClientDiagnosticsUseFilesOnlyWhenEnabled(t *testing.T) {
func TestClientDiagnosticDirectoryHonorsExplicitSelection(t *testing.T) {
environmentDirectory := t.TempDir()
t.Setenv("SCREENER_LOG_DIR", environmentDirectory)
t.Setenv("PIIK_LOG_DIR", environmentDirectory)
for _, directory := range []string{"", t.TempDir()} {
recorder, err := openClientDiagnostics(directory)
if err != nil {
@@ -139,7 +139,7 @@ func TestLaunchURLPreservesLocalAccessInsideThePrivateFragment(t *testing.T) {
}
fragment, err := url.ParseQuery(parsed.Fragment)
if err != nil || fragment.Get("client-access") != "secret" ||
fragment.Get("screener-client") != "1" {
fragment.Get("piik-client") != "1" {
t.Fatalf("local native launch fragment = %q, %v", parsed.Fragment, err)
}
}
@@ -155,7 +155,7 @@ func TestLaunchURLEncodesAndClearsOptionalLocalAccess(t *testing.T) {
}
fragment, err := url.ParseQuery(parsed.Fragment)
if err != nil || fragment.Get("client-access") != "a+b&c?d=e" ||
fragment.Get("screener-client") != "1" || fragment.Get("retained") != "yes" {
fragment.Get("piik-client") != "1" || fragment.Get("retained") != "yes" {
t.Fatalf("encoded local launch fragment = %q, %v", parsed.Fragment, err)
}
@@ -165,7 +165,7 @@ func TestLaunchURLEncodesAndClearsOptionalLocalAccess(t *testing.T) {
t.Fatal(err)
}
fragment, err = url.ParseQuery(parsed.Fragment)
if err != nil || fragment.Get("client-access") != "" || fragment.Get("screener-client") != "1" {
if err != nil || fragment.Get("client-access") != "" || fragment.Get("piik-client") != "1" {
t.Fatalf("open local launch fragment = %q, %v", parsed.Fragment, err)
}
}
+6 -6
View File
@@ -14,7 +14,7 @@ import (
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"github.com/TNTcraftHIM/Screener/internal/client/browser"
"github.com/TNTcraftHIM/Piik/internal/client/browser"
"github.com/charmbracelet/colorprofile"
"github.com/charmbracelet/x/ansi"
"github.com/charmbracelet/x/term"
@@ -72,7 +72,7 @@ var consoleCopy = map[string][3]string{
"mode": {"Mode", "模式", ""},
"local": {"Local network", "局域网", "□"},
"link": {"Public link", "公网链接", "↗"},
"site": {"Screener Site", "Screener 站点", "@"},
"site": {"Piik Site", "Piik 站点", "@"},
"setup": {"Choose a mode in the browser", "在浏览器中选择模式", "?"},
"starting": {"Starting", "正在启动", "…"},
"ready": {"Ready", "已就绪", "✓"},
@@ -141,10 +141,10 @@ func (console *clientConsole) finish(err error) error {
if console.program != nil {
terminalErr := <-console.done
if err != nil {
fmt.Fprintln(os.Stderr, "Screener Client:", err)
fmt.Fprintln(os.Stderr, "Piik Client:", err)
}
if terminalErr != nil {
fmt.Fprintln(os.Stderr, "Screener Client:", terminalErr)
fmt.Fprintln(os.Stderr, "Piik Client:", terminalErr)
}
return terminalErr
}
@@ -260,7 +260,7 @@ func (model consoleModel) text(key string) string {
func (model consoleModel) View() tea.View {
view := tea.NewView(model.content(true))
view.WindowTitle = "Screener Client"
view.WindowTitle = "Piik Client"
view.AltScreen = true
return view
}
@@ -281,7 +281,7 @@ func (model consoleModel) content(styled bool) string {
revision = revision[:7]
}
var out strings.Builder
heading := "Screener\nClient\n" + muted.Render(revision)
heading := "Piik\nClient\n" + muted.Render(revision)
animated := styled && model.animating()
mascot := accent.Render(consoleTV(model.frame, animated, compact || width < 34))
if styled && model.colors && width >= 36 && !compactDebug {
+2 -2
View File
@@ -166,11 +166,11 @@ func TestConsoleDiagnosticsFitCompactTerminal(t *testing.T) {
model := consoleModel{
view: consoleView{mode: "local", state: "ready", entry: "http://localhost:8787/"},
width: 30, height: 18, colors: true,
debug: consoleDebug{logPath: "C:/Screener/logs/client.log", export: func() (string, error) { return "", nil }},
debug: consoleDebug{logPath: "C:/Piik/logs/client.log", export: func() (string, error) { return "", nil }},
}
for _, language := range []string{"en", "zh", "vis"} {
model.language = language
for _, path := range []string{"", "C:/Screener/logs/client-20260908T120000Z.zip", "C:/Users/Example/Documents/Workspaces/Screener-embedded-media/build/client-check/logs/client-diagnostics-20260908T120000Z-123456789.zip"} {
for _, path := range []string{"", "C:/Piik/logs/client-20260908T120000Z.zip", "C:/Users/Example/Documents/Workspaces/Piik-embedded-media/build/client-check/logs/client-diagnostics-20260908T120000Z-123456789.zip"} {
model.exportPath = path
content := model.content(true)
if lipgloss.Width(content) > model.width || lipgloss.Height(content) > model.height {
+2 -2
View File
@@ -32,7 +32,7 @@ func DefaultPath() (string, error) {
if err != nil {
return "", fmt.Errorf("find user configuration directory: %w", err)
}
return filepath.Join(directory, "Screener", "client.json"), nil
return filepath.Join(directory, "Piik", "client.json"), nil
}
func LoadOrCreate(path string) (Config, error) {
@@ -99,7 +99,7 @@ func NormalizeSite(value string) (string, error) {
parsed.Host == "" || parsed.User != nil ||
(parsed.Path != "" && parsed.Path != "/") ||
parsed.RawQuery != "" || parsed.Fragment != "" {
return "", errors.New("Screener Site must be an HTTP or HTTPS origin")
return "", errors.New("Piik Site must be an HTTP or HTTPS origin")
}
return parsed.Scheme + "://" + parsed.Host, nil
}
+2 -2
View File
@@ -13,7 +13,7 @@ import (
"sync"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/clientconfig"
"github.com/TNTcraftHIM/Piik/internal/client/clientconfig"
)
const (
@@ -255,7 +255,7 @@ func (server *Server) handleLaunch(response http.ResponseWriter, request *http.R
server.mu.Unlock()
if !resultSet || resultErr != nil {
writeJSON(response, http.StatusServiceUnavailable, map[string]string{
"error": "Screener Client could not start",
"error": "Piik Client could not start",
})
server.markHandled()
return
+3 -3
View File
@@ -59,7 +59,7 @@ func TestLauncherServesStateAndCompletesOneSelection(t *testing.T) {
if selection != (Selection{Mode: ModeLink, Language: "vis"}) {
t.Fatalf("selection = %+v", selection)
}
server.SetResult("http://localhost:8787/#screener-client=1", nil)
server.SetResult("http://localhost:8787/#piik-client=1", nil)
case <-time.After(time.Second):
t.Fatal("launcher did not emit a selection")
}
@@ -72,7 +72,7 @@ func TestLauncherServesStateAndCompletesOneSelection(t *testing.T) {
Target string `json:"target"`
}
if response.StatusCode != http.StatusOK || json.NewDecoder(response.Body).Decode(&launch) != nil ||
launch.Target != "http://localhost:8787/#screener-client=1" {
launch.Target != "http://localhost:8787/#piik-client=1" {
t.Fatalf("launcher result = %d, %+v", response.StatusCode, launch)
}
case <-time.After(time.Second):
@@ -135,7 +135,7 @@ func TestLauncherCarriesAndNormalizesAnOptionalLocalPassword(t *testing.T) {
if selection.Mode != ModeLocal || selection.Language != "zh" || selection.LocalAccessPassword != "new-local-pass" {
t.Fatalf("selection = %+v", selection)
}
server.SetResult("http://localhost:8787/#screener-client=1", nil)
server.SetResult("http://localhost:8787/#piik-client=1", nil)
case <-time.After(time.Second):
t.Fatal("launcher did not emit a selection")
}
+2 -2
View File
@@ -10,8 +10,8 @@ import (
const (
ProtocolVersion = 9
ServiceName = "screener-client"
ControlSubprotocol = "screener-client-v9"
ServiceName = "piik-client"
ControlSubprotocol = "piik-client-v9"
MaxControlMessageBytes = 256 << 10
DefaultPortStart = 39721
DefaultPortEnd = 39730
+3 -3
View File
@@ -6,7 +6,7 @@ import (
"sync/atomic"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/nativeaudio"
"github.com/TNTcraftHIM/Piik/internal/client/nativeaudio"
"github.com/pion/rtp"
"github.com/pion/webrtc/v4"
"github.com/pion/webrtc/v4/pkg/media"
@@ -59,7 +59,7 @@ func (engine *Engine) NewAudioSource(capacity, bitrate int) (*AudioSource, error
track, err := webrtc.NewTrackLocalStaticSample(
opusCapability,
"audio",
"screener-native",
"piik-native",
)
if err != nil {
return nil, err
@@ -82,7 +82,7 @@ func (engine *Engine) NewRelayedAudioSource(capacity int) (*AudioSource, error)
track, err := webrtc.NewTrackLocalStaticRTP(
opusCapability,
"audio",
"screener-native",
"piik-native",
)
if err != nil {
return nil, err
+8 -8
View File
@@ -8,8 +8,8 @@ import (
"strings"
"sync"
"github.com/TNTcraftHIM/Screener/internal/diagnostics"
"github.com/TNTcraftHIM/Screener/internal/media/forwarding"
"github.com/TNTcraftHIM/Piik/internal/diagnostics"
"github.com/TNTcraftHIM/Piik/internal/media/forwarding"
"github.com/pion/ice/v4"
"github.com/pion/webrtc/v4"
)
@@ -138,22 +138,22 @@ func (engine *Engine) NewEdge(source *Source, options EdgeOptions) (*Edge, error
edge.localCandidates.addPion(candidate)
})
connection.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
log.Debug("screener-client", "event", "media-ice-state", "direction", "outbound", "local", options.Local, "state", state.String())
log.Debug("piik-client", "event", "media-ice-state", "direction", "outbound", "local", options.Local, "state", state.String())
})
if dtls := edge.sender.Transport(); dtls != nil {
dtls.OnStateChange(func(state webrtc.DTLSTransportState) {
log.Debug("screener-client", "event", "media-dtls-state", "direction", "outbound", "local", options.Local, "state", state.String())
log.Debug("piik-client", "event", "media-dtls-state", "direction", "outbound", "local", options.Local, "state", state.String())
})
}
connection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
debug := slog.Default().Enabled(engine.ctx, slog.LevelDebug)
if debug {
log.Debug("screener-client", "event", "media-connection-state", "direction", "outbound", "local", options.Local, "state", state.String())
log.Debug("piik-client", "event", "media-connection-state", "direction", "outbound", "local", options.Local, "state", state.String())
}
if state == webrtc.PeerConnectionStateConnected {
if err := edge.transport.SetConnected(); err != nil {
if debug {
log.Debug("screener-client", "event", "media-activation-failed", diagnostics.Error(err))
log.Debug("piik-client", "event", "media-activation-failed", diagnostics.Error(err))
}
_ = connection.Close()
return
@@ -165,7 +165,7 @@ func (engine *Engine) NewEdge(source *Source, options EdgeOptions) (*Edge, error
if pair, pairErr := edge.SelectedPair(); pairErr == nil {
selected = &pair
if debug {
log.Debug("screener-client", "event", "media-selected-path", "direction", "outbound", "local", options.Local,
log.Debug("piik-client", "event", "media-selected-path", "direction", "outbound", "local", options.Local,
"localType", pair.Local.String(), "remoteType", pair.Remote.String(), "natTraversalPath", pair.NatTraversalPath)
}
}
@@ -387,7 +387,7 @@ func logLocalMediaCandidateCounts(report webrtc.StatsReport) {
tcp++
}
}
slog.Debug("screener-client", "event", "media-candidate-counts", "local", true,
slog.Debug("piik-client", "event", "media-candidate-counts", "local", true,
"direction", string(direction), "type", kind.String(), "udp", udp, "tcp", tcp)
}
}
+3 -3
View File
@@ -10,9 +10,9 @@ import (
"testing"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/nativeaudio"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/client/nativeaudio"
"github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
"github.com/livekit/livekit-server/pkg/sfu"
"github.com/livekit/livekit-server/pkg/sfu/streamtracker"
"github.com/pion/interceptor"
+6 -6
View File
@@ -12,10 +12,10 @@ import (
"sync/atomic"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/portmapping"
"github.com/TNTcraftHIM/Screener/internal/diagnostics"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Screener/internal/media/forwarding"
"github.com/TNTcraftHIM/Piik/internal/client/portmapping"
"github.com/TNTcraftHIM/Piik/internal/diagnostics"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/media/forwarding"
"github.com/pion/ice/v4"
"github.com/pion/interceptor"
"github.com/pion/rtcp"
@@ -90,7 +90,7 @@ func NewEngine(options EngineOptions) (*Engine, error) {
loggerFactory := diagnostics.PionLoggerFactory()
ready := make(chan struct{})
mux := ice.NewUniversalUDPMuxDefault(ice.UniversalUDPMuxParams{
Logger: loggerFactory.NewLogger("screener-ice"),
Logger: loggerFactory.NewLogger("piik-ice"),
UDPConn: &initializingUDPConn{UDPConn: connection, ready: ready},
})
close(ready)
@@ -288,7 +288,7 @@ func (engine *Engine) NewSource(codec string, capacity, layers int, requestKeyFr
publications: make(map[*Publication]struct{}),
}
media, err := forwarding.NewEncodedSource(forwarding.SourceOptions{
ID: "screen", StreamID: "screener-native", Codec: videoCodecs[codec],
ID: "screen", StreamID: "piik-native", Codec: videoCodecs[codec],
Formats: make([]forwarding.LayerFormat, layers), MaxPackets: encoded.MaxPacketWindow,
OnRTCP: func(layer int, packets []rtcp.Packet) {
for _, packet := range packets {
@@ -9,16 +9,16 @@ import (
"testing"
"time"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
)
// Explicit codec gate input; ordinary unit checks do not build/run a native codec.
func TestEncodedGroupVP8Fixture(t *testing.T) {
inputPath := os.Getenv("SCREENER_ENCODED_FIXTURE")
inputPath := os.Getenv("PIIK_ENCODED_FIXTURE")
if inputPath == "" {
t.Skip("set SCREENER_ENCODED_FIXTURE to the bounded VP8 fixture JSONL")
t.Skip("set PIIK_ENCODED_FIXTURE to the bounded VP8 fixture JSONL")
}
input, err := os.Open(inputPath)
if err != nil {
@@ -228,7 +228,7 @@ func TestEncodedGroupVP8Fixture(t *testing.T) {
t.Fatal("sender evidence counted unused-layer bytes")
}
}
if outputPath := os.Getenv("SCREENER_ENCODED_OUTPUT"); outputPath != "" {
if outputPath := os.Getenv("PIIK_ENCODED_OUTPUT"); outputPath != "" {
data, err := json.Marshal(delivered)
if err != nil {
t.Fatal(err)
+3 -3
View File
@@ -5,9 +5,9 @@ import (
"log/slog"
"time"
"github.com/TNTcraftHIM/Screener/internal/diagnostics"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Screener/internal/media/forwarding"
"github.com/TNTcraftHIM/Piik/internal/diagnostics"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/media/forwarding"
"github.com/pion/rtcp"
"github.com/pion/webrtc/v4"
)
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"testing"
"time"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Screener/internal/media/forwarding"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/media/forwarding"
"github.com/livekit/livekit-server/pkg/sfu"
"github.com/pion/rtcp"
)
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"sync"
"time"
"github.com/TNTcraftHIM/Screener/internal/media/forwarding"
"github.com/TNTcraftHIM/Piik/internal/media/forwarding"
"github.com/pion/webrtc/v4"
)
@@ -5,8 +5,8 @@ import (
"testing"
"time"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Screener/internal/media/forwarding"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/media/forwarding"
"github.com/livekit/livekit-server/pkg/sfu"
"github.com/pion/interceptor"
"github.com/pion/webrtc/v4"
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"log/slog"
"time"
"github.com/TNTcraftHIM/Screener/internal/diagnostics"
"github.com/TNTcraftHIM/Screener/internal/media/forwarding"
"github.com/TNTcraftHIM/Piik/internal/diagnostics"
"github.com/TNTcraftHIM/Piik/internal/media/forwarding"
"github.com/pion/webrtc/v4"
)
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"testing"
"time"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Screener/internal/media/forwarding"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/media/forwarding"
"github.com/livekit/livekit-server/pkg/sfu"
)
+2 -2
View File
@@ -99,14 +99,14 @@ func (engine *Engine) NewReceiver(options ReceiverOptions) (*Receiver, webrtc.Se
connection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
debug := slog.Default().Enabled(engine.ctx, slog.LevelDebug)
if debug {
slog.Debug("screener-client", "event", "media-connection-state", "direction", "inbound", "state", state.String())
slog.Debug("piik-client", "event", "media-connection-state", "direction", "inbound", "state", state.String())
}
var selected *SelectedPair
if state == webrtc.PeerConnectionStateConnected && (receiver.events.ConnectionState != nil || debug) {
if pair, pairErr := receiver.SelectedPair(); pairErr == nil {
selected = &pair
if debug {
slog.Debug("screener-client", "event", "media-selected-path", "direction", "inbound",
slog.Debug("piik-client", "event", "media-selected-path", "direction", "inbound",
"localType", pair.Local.String(), "remoteType", pair.Remote.String(), "natTraversalPath", pair.NatTraversalPath)
}
}
@@ -11,17 +11,17 @@ import (
"testing"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
"github.com/pion/webrtc/v4"
)
// Replays actual SDK encoder output through independent Pion connections. The
// relays retain derivation capability, but healthy children need only raw reuse.
func TestRelayChainEncodedFixture(t *testing.T) {
fixture := os.Getenv("SCREENER_POOL_CHAIN_FIXTURE")
fixture := os.Getenv("PIIK_POOL_CHAIN_FIXTURE")
if fixture == "" {
t.Skip("set SCREENER_POOL_CHAIN_FIXTURE and SCREENER_NATIVE_CAPTURE")
t.Skip("set PIIK_POOL_CHAIN_FIXTURE and PIIK_NATIVE_CAPTURE")
}
check := func(err error) {
t.Helper()
@@ -29,11 +29,11 @@ func TestRelayChainEncodedFixture(t *testing.T) {
t.Fatal(err)
}
}
executable := os.Getenv("SCREENER_NATIVE_CAPTURE")
executable := os.Getenv("PIIK_NATIVE_CAPTURE")
info, err := os.Stat(executable)
check(err)
if info.IsDir() {
t.Fatal("SCREENER_NATIVE_CAPTURE must name the existing capture executable")
t.Fatal("PIIK_NATIVE_CAPTURE must name the existing capture executable")
}
input, err := os.Open(fixture)
check(err)
@@ -293,7 +293,7 @@ func TestRelayChainEncodedFixture(t *testing.T) {
}{len(frames), 2, actual, false, true})
check(err)
t.Log(string(summary))
if output := os.Getenv("SCREENER_POOL_CHAIN_OUTPUT"); output != "" {
if output := os.Getenv("PIIK_POOL_CHAIN_OUTPUT"); output != "" {
check(os.WriteFile(output, append(summary, '\n'), 0o600))
}
}
@@ -7,9 +7,9 @@ import (
"sync"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Screener/internal/media/forwarding"
"github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/media/forwarding"
"github.com/livekit/mediatransportutil/pkg/utils"
"github.com/pion/rtp"
)
@@ -9,7 +9,7 @@ import (
"testing"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
"github.com/livekit/mediatransportutil"
mediacodec "github.com/livekit/mediatransportutil/pkg/codec"
"github.com/pion/rtcp"
@@ -20,9 +20,9 @@ import (
// Explicit small native-codec acceptance, never launched by the ordinary suite.
// Forced layer demand tests derivation ownership, not network adaptation.
func TestRelayDerivationFixture(t *testing.T) {
executable, fixture := os.Getenv("SCREENER_NATIVE_CAPTURE"), os.Getenv("SCREENER_ENCODED_FIXTURE")
executable, fixture := os.Getenv("PIIK_NATIVE_CAPTURE"), os.Getenv("PIIK_ENCODED_FIXTURE")
if executable == "" || fixture == "" {
t.Skip("set SCREENER_NATIVE_CAPTURE and SCREENER_ENCODED_FIXTURE for native relay acceptance")
t.Skip("set PIIK_NATIVE_CAPTURE and PIIK_ENCODED_FIXTURE for native relay acceptance")
}
input, err := os.Open(fixture)
if err != nil {
@@ -291,9 +291,9 @@ func TestRelayDerivationFixture(t *testing.T) {
// Forced direct-child budgets exercise real per-group WebRTC spatial adaptation;
// they do not simulate transport loss or prove bandwidth-estimator behavior.
func TestRelayIndependentSpatialFixture(t *testing.T) {
executable, fixture := os.Getenv("SCREENER_NATIVE_CAPTURE"), os.Getenv("SCREENER_GROUP_MOTION_INPUT")
executable, fixture := os.Getenv("PIIK_NATIVE_CAPTURE"), os.Getenv("PIIK_GROUP_MOTION_INPUT")
if executable == "" || fixture == "" {
t.Skip("set SCREENER_NATIVE_CAPTURE and SCREENER_GROUP_MOTION_INPUT")
t.Skip("set PIIK_NATIVE_CAPTURE and PIIK_GROUP_MOTION_INPUT")
}
check := func(err error) {
t.Helper()
@@ -457,7 +457,7 @@ func TestRelayIndependentSpatialFixture(t *testing.T) {
default:
}
}
if path := os.Getenv("SCREENER_GROUP_OUTPUT"); path != "" {
if path := os.Getenv("PIIK_GROUP_OUTPUT"); path != "" {
file, err := os.Create(path)
check(err)
check(json.NewEncoder(file).Encode(output))
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"errors"
"io"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
mediacodec "github.com/livekit/mediatransportutil/pkg/codec"
"github.com/livekit/server-sdk-go/v2/pkg/samplebuilder"
"github.com/pion/rtp"
@@ -7,7 +7,7 @@ import (
"testing"
"time"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
"github.com/livekit/livekit-server/pkg/sfu"
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
@@ -11,7 +11,7 @@ import (
"testing"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
"github.com/livekit/mediatransportutil"
"github.com/pion/ice/v4"
"github.com/pion/interceptor"
@@ -26,8 +26,8 @@ import (
// One real VP8 derived process, no desktop capture. The link changes capacity;
// production BWE, output admission and codec controls supply every adaptation.
func TestRelayNetworkVP8Fixture(t *testing.T) {
if os.Getenv("SCREENER_RELAY_NETWORK") == "" {
t.Skip("set SCREENER_RELAY_NETWORK=1, SCREENER_NATIVE_CAPTURE and SCREENER_ENCODED_FIXTURE")
if os.Getenv("PIIK_RELAY_NETWORK") == "" {
t.Skip("set PIIK_RELAY_NETWORK=1, PIIK_NATIVE_CAPTURE and PIIK_ENCODED_FIXTURE")
}
check := func(err error) {
t.Helper()
@@ -35,7 +35,7 @@ func TestRelayNetworkVP8Fixture(t *testing.T) {
t.Fatal(err)
}
}
input, err := os.Open(os.Getenv("SCREENER_ENCODED_FIXTURE"))
input, err := os.Open(os.Getenv("PIIK_ENCODED_FIXTURE"))
check(err)
defer input.Close()
var motion [][]byte
@@ -70,7 +70,7 @@ func TestRelayNetworkVP8Fixture(t *testing.T) {
check(err)
defer source.Close()
source.relay = &relayDerivation{source: source, options: RelayOptions{
CaptureProcess: os.Getenv("SCREENER_NATIVE_CAPTURE"),
CaptureProcess: os.Getenv("PIIK_NATIVE_CAPTURE"),
Capabilities: nativecapture.Capabilities{SoftwareVP8: true, Adapters: []nativecapture.Adapter{{Index: 0}}},
}}
check(source.SetRelayProfile(nativecapture.VideoProfile{Width: 1280, Height: 720,
@@ -332,9 +332,9 @@ func TestRelayNetworkVP8Fixture(t *testing.T) {
recovered = true
}
}
outputPath := os.Getenv("SCREENER_ENCODED_OUTPUT")
outputPath := os.Getenv("PIIK_ENCODED_OUTPUT")
if outputPath == "" {
t.Fatal("SCREENER_ENCODED_OUTPUT must name the ignored received-frame artifact")
t.Fatal("PIIK_ENCODED_OUTPUT must name the ignored received-frame artifact")
}
data, err := json.Marshal(delivered)
check(err)
+3 -3
View File
@@ -7,9 +7,9 @@ import (
"sync/atomic"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Screener/internal/media/forwarding"
"github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/media/forwarding"
mediacodec "github.com/livekit/mediatransportutil/pkg/codec"
"github.com/pion/rtcp"
"github.com/pion/rtp"
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
"github.com/livekit/livekit-server/pkg/sfu"
"github.com/livekit/livekit-server/pkg/sfu/streamtracker"
"google.golang.org/protobuf/proto"
@@ -13,11 +13,11 @@ import (
// Opt-in real capture-process check; normal tests never start a codec or GPU.
func TestAdaptiveOutputFixture(t *testing.T) {
executable, fixture := os.Getenv("SCREENER_NATIVE_CAPTURE"), os.Getenv("SCREENER_ADAPTIVE_INPUT")
executable, fixture := os.Getenv("PIIK_NATIVE_CAPTURE"), os.Getenv("PIIK_ADAPTIVE_INPUT")
if executable == "" || fixture == "" {
t.Skip("set SCREENER_NATIVE_CAPTURE and SCREENER_ADAPTIVE_INPUT")
t.Skip("set PIIK_NATIVE_CAPTURE and PIIK_ADAPTIVE_INPUT")
}
codec := os.Getenv("SCREENER_ADAPTIVE_CODEC")
codec := os.Getenv("PIIK_ADAPTIVE_CODEC")
if codec == "" {
codec = "vp8"
}
+5 -5
View File
@@ -15,7 +15,7 @@ import (
"strings"
"time"
"github.com/TNTcraftHIM/Screener/internal/diagnostics"
"github.com/TNTcraftHIM/Piik/internal/diagnostics"
)
const (
@@ -103,7 +103,7 @@ func Discover(parent context.Context, executable string) (Capabilities, error) {
command.Stderr = stderr
hideWindow(command)
if err := command.Run(); err != nil {
slog.DebugContext(ctx, "screener-client", "event", "capture-probe-failed", diagnostics.Error(err), "canceled", ctx.Err() != nil)
slog.DebugContext(ctx, "piik-client", "event", "capture-probe-failed", diagnostics.Error(err), "canceled", ctx.Err() != nil)
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return Capabilities{}, errors.New("native capture probe timed out")
}
@@ -127,11 +127,11 @@ func PackagedExecutable() string {
name := ""
switch runtime.GOOS {
case "windows":
name = "screener-client-capture.exe"
name = "piik-client-capture.exe"
case "darwin":
name = "screener-client-capture"
name = "piik-client-capture"
case "linux":
name = "screener-client-capture"
name = "piik-client-capture"
default:
return ""
}
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"time"
"unicode/utf8"
"github.com/TNTcraftHIM/Screener/internal/media/encoded"
"github.com/TNTcraftHIM/Piik/internal/media/encoded"
)
const (
+10 -10
View File
@@ -15,7 +15,7 @@ import (
"time"
"unicode/utf8"
"github.com/TNTcraftHIM/Screener/internal/diagnostics"
"github.com/TNTcraftHIM/Piik/internal/diagnostics"
)
const (
@@ -143,7 +143,7 @@ func ListSources(parent context.Context, executable string) ([]CaptureTarget, er
command.Stderr = stderr
hideWindow(command)
if err := command.Run(); err != nil {
slog.DebugContext(ctx, "screener-client", "event", "capture-source-list-failed", diagnostics.Error(err), "canceled", ctx.Err() != nil)
slog.DebugContext(ctx, "piik-client", "event", "capture-source-list-failed", diagnostics.Error(err), "canceled", ctx.Err() != nil)
return nil, errors.New("native capture source list is unavailable")
}
var targets []CaptureTarget
@@ -178,7 +178,7 @@ func PreviewSource(parent context.Context, executable string, target CaptureTarg
command.Stderr = stderr
hideWindow(command)
if err := command.Run(); err != nil {
slog.DebugContext(ctx, "screener-client", "event", "capture-source-preview-failed", diagnostics.Error(err), "canceled", ctx.Err() != nil)
slog.DebugContext(ctx, "piik-client", "event", "capture-source-preview-failed", diagnostics.Error(err), "canceled", ctx.Err() != nil)
return nil, errors.New("native capture preview is unavailable")
}
preview := stdout.Bytes()
@@ -198,7 +198,7 @@ func StartVideo(parent context.Context, executable string, options VideoOptions)
}
environment := []string(nil)
if options.RestoreToken != "" {
environment = []string{"SCREENER_XDP_RESTORE_TOKEN=" + options.RestoreToken}
environment = []string{"PIIK_XDP_RESTORE_TOKEN=" + options.RestoreToken}
}
arguments := []string{
"--capture-video",
@@ -398,7 +398,7 @@ func startStreamWithEnvironment(
logger := slog.Default().With("capture", captureID)
command := exec.CommandContext(ctx, executable, arguments...)
if logger.Enabled(ctx, slog.LevelDebug) {
environment = append(environment, "SCREENER_CAPTURE_DEBUG=1")
environment = append(environment, "PIIK_CAPTURE_DEBUG=1")
}
if len(environment) > 0 {
command.Env = append(os.Environ(), environment...)
@@ -420,7 +420,7 @@ func startStreamWithEnvironment(
if err = command.Start(); err != nil {
_ = trace.Close()
cancel()
logger.DebugContext(ctx, "screener-client", "event", "capture-process-start-failed", diagnostics.Error(err))
logger.DebugContext(ctx, "piik-client", "event", "capture-process-start-failed", diagnostics.Error(err))
return nil, fmt.Errorf("start native capture process: %w", err)
}
logger = logger.With("pid", command.Process.Pid)
@@ -431,7 +431,7 @@ func startStreamWithEnvironment(
mode = strings.TrimPrefix(arguments[0], "--")
}
}
logger.DebugContext(ctx, "screener-client", "event", "capture-process-started", "mode", mode)
logger.DebugContext(ctx, "piik-client", "event", "capture-process-started", "mode", mode)
stream := &Stream{
cancel: cancel,
input: stdout,
@@ -444,7 +444,7 @@ func startStreamWithEnvironment(
waitErr := command.Wait()
_ = trace.Close()
logCaptureFailure(ctx, waitErr, command.ProcessState.ExitCode(), stderr.Bytes())
logger.DebugContext(ctx, "screener-client", "event", "capture-process-ended", "mode", mode,
logger.DebugContext(ctx, "piik-client", "event", "capture-process-ended", "mode", mode,
"durationMs", time.Since(started).Milliseconds(), "exitCode", command.ProcessState.ExitCode(),
"canceled", ctx.Err() != nil, "cancelReason", diagnostics.SafeText(fmt.Sprint(context.Cause(ctx))), diagnostics.Error(waitErr))
stream.done <- waitErr
@@ -473,7 +473,7 @@ func logCaptureFailure(ctx context.Context, err error, exitCode int, stderr []by
if canceled && stage == "" && hresult == "" {
return
}
slog.DebugContext(ctx, "screener-client", "event", "capture-process-failed",
slog.DebugContext(ctx, "piik-client", "event", "capture-process-failed",
"exitCode", exitCode, "stage", stage, "hresult", hresult, "canceled", canceled)
}
@@ -481,7 +481,7 @@ func (stream *Stream) Read() (Frame, error) {
frame, err := readFrame(stream.input)
if err != nil && stream.logger != nil {
stream.readEnd.Do(func() {
stream.logger.DebugContext(stream.ctx, "screener-client", "event", "capture-stream-ended",
stream.logger.DebugContext(stream.ctx, "piik-client", "event", "capture-stream-ended",
"eof", errors.Is(err, io.EOF), "canceled", stream.ctx.Err() != nil, diagnostics.Error(err))
})
}
+1 -1
View File
@@ -86,7 +86,7 @@ func TestCaptureFailureDebugUsesOnlyFixedFields(t *testing.T) {
if err := json.Unmarshal(output.Bytes(), &record); err != nil {
t.Fatal(err)
}
if len(record) != 8 || record["event"] != "capture-process-failed" || record["msg"] != "screener-client" ||
if len(record) != 8 || record["event"] != "capture-process-failed" || record["msg"] != "piik-client" ||
record["level"] != "DEBUG" || record["exitCode"] != float64(2) || record["stage"] != check.stage ||
record["hresult"] != check.hresult || record["canceled"] != check.canceled || strings.Contains(output.String(), "private") {
t.Fatalf("diagnostic leaked or changed fields: %s", output.String())
+16 -16
View File
@@ -16,12 +16,12 @@ import (
"time"
"unicode"
"github.com/TNTcraftHIM/Screener/internal/client/loopback"
"github.com/TNTcraftHIM/Screener/internal/client/mediaedge"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Screener/internal/client/nativehost"
"github.com/TNTcraftHIM/Screener/internal/client/nativeviewer"
"github.com/TNTcraftHIM/Screener/internal/diagnostics"
"github.com/TNTcraftHIM/Piik/internal/client/loopback"
"github.com/TNTcraftHIM/Piik/internal/client/mediaedge"
"github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
"github.com/TNTcraftHIM/Piik/internal/client/nativehost"
"github.com/TNTcraftHIM/Piik/internal/client/nativeviewer"
"github.com/TNTcraftHIM/Piik/internal/diagnostics"
"github.com/pion/webrtc/v4"
)
@@ -153,7 +153,7 @@ func (session *Session) Handle(ctx context.Context, payload []byte) (result any,
!validQualitySettings(request.Profile) {
return nil, errors.New("native start-share request is invalid")
}
slog.Debug("screener-client", "event", "native-profile-requested", "requestId", envelope.ID,
slog.Debug("piik-client", "event", "native-profile-requested", "requestId", envelope.ID,
"share", diagnostics.ID(request.ShareID), "profile", nativeQualityProfile(request.Profile), "codec", request.Codec, "audio", request.Audio,
"sourceKind", request.Source.Kind, "adapterIndex", request.AdapterIndex, "encoderIndex", request.EncoderIndex)
return session.startShare(ctx, envelope, request)
@@ -166,7 +166,7 @@ func (session *Session) Handle(ctx context.Context, payload []byte) (result any,
return nil, errors.New("native update-share request is invalid")
}
profile := nativeQualityProfile(request.Profile)
slog.Debug("screener-client", "event", "native-profile-requested", "requestId", envelope.ID,
slog.Debug("piik-client", "event", "native-profile-requested", "requestId", envelope.ID,
"share", diagnostics.ID(request.ShareID), "profile", profile)
var err error
if host := session.current(request.ShareID); host != nil {
@@ -199,7 +199,7 @@ func (session *Session) Handle(ctx context.Context, payload []byte) (result any,
audio := request.Audio && session.capabilities.Summary().AudioFor(
request.Source.Kind,
)
slog.Debug("screener-client", "event", "native-source-requested", "requestId", envelope.ID,
slog.Debug("piik-client", "event", "native-source-requested", "requestId", envelope.ID,
"share", diagnostics.ID(request.ShareID), "sourceKind", request.Source.Kind, "audio", audio,
"adapterIndex", request.AdapterIndex, "encoderIndex", request.EncoderIndex)
err := host.ReplaceSource(ctx, nativecapture.VideoOptions{
@@ -210,7 +210,7 @@ func (session *Session) Handle(ctx context.Context, payload []byte) (result any,
if err != nil {
return operationFailure(envelope, err), nil
}
slog.Debug("screener-client", "event", "native-source-applied", "requestId", envelope.ID,
slog.Debug("piik-client", "event", "native-source-applied", "requestId", envelope.ID,
"share", diagnostics.ID(request.ShareID), "sourceKind", request.Source.Kind, "audio", audio)
return shareSourceReplacedResponse{
responseEnvelope: response(envelope, "share-source-replaced"),
@@ -347,7 +347,7 @@ func (session *Session) Handle(ctx context.Context, payload []byte) (result any,
return nil, errors.New("native share does not exist")
}
host.SetPaused(request.Paused)
slog.Debug("screener-client", "event", "native-share-paused", "requestId", envelope.ID,
slog.Debug("piik-client", "event", "native-share-paused", "requestId", envelope.ID,
"share", diagnostics.ID(request.ShareID), "paused", request.Paused)
return response(envelope, "share-paused"), nil
default:
@@ -429,7 +429,7 @@ func (session *Session) shareUpdateResult(request requestEnvelope, shareID strin
if err != nil {
return operationFailure(request, err)
}
slog.Debug("screener-client", "event", "native-profile-applied", "requestId", request.ID,
slog.Debug("piik-client", "event", "native-profile-applied", "requestId", request.ID,
"share", diagnostics.ID(shareID), "profile", profile)
return shareUpdatedResponse{responseEnvelope: response(request, "share-updated"), ShareID: shareID}
}
@@ -480,7 +480,7 @@ func (session *Session) startShare(
session.host = host
session.mu.Unlock()
codec := host.Codec()
slog.Debug("screener-client", "event", "native-profile-applied", "requestId", envelope.ID,
slog.Debug("piik-client", "event", "native-profile-applied", "requestId", envelope.ID,
"share", diagnostics.ID(request.ShareID), "profile", profile, "codec", codec, "audio", host.HasAudio())
go session.watchHost(host)
return shareStartedResponse{
@@ -940,7 +940,7 @@ func response(request requestEnvelope, responseType string) responseEnvelope {
func operationFailure(request requestEnvelope, causes ...error) requestFailedResponse {
if len(causes) > 0 {
slog.Debug("screener-client", "event", "native-operation-failed", "requestId", request.ID,
slog.Debug("piik-client", "event", "native-operation-failed", "requestId", request.ID,
"requestType", diagnostics.SafeText(request.Type), diagnostics.Error(causes[0]))
}
return requestFailedResponse{
@@ -965,10 +965,10 @@ func (session *Session) traceRequest(request requestEnvelope, payload []byte) fu
"share", diagnostics.ID(identity.ShareID), "connection", diagnostics.ID(identity.ConnectionID),
"sourceConnection", diagnostics.ID(identity.SourceConnectionID), "publication", diagnostics.ID(identity.PublicationGeneration))
started := time.Now()
logger.Debug("screener-client", "event", "native-request-started")
logger.Debug("piik-client", "event", "native-request-started")
return func(result any, err error) {
failure, rejected := result.(requestFailedResponse)
logger.Debug("screener-client", "event", "native-request-ended", "durationMs", time.Since(started).Milliseconds(),
logger.Debug("piik-client", "event", "native-request-ended", "durationMs", time.Since(started).Milliseconds(),
"failed", err != nil || rejected, "code", failure.Code, diagnostics.Error(err))
}
}
@@ -14,13 +14,13 @@ import (
"testing"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/loopback"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Screener/internal/client/nativehost"
"github.com/TNTcraftHIM/Piik/internal/client/loopback"
"github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
"github.com/TNTcraftHIM/Piik/internal/client/nativehost"
)
func TestMain(tests *testing.M) {
if directory := os.Getenv("SCREENER_QUIET_CAPTURE_FIXTURE"); directory != "" {
if directory := os.Getenv("PIIK_QUIET_CAPTURE_FIXTURE"); directory != "" {
runQuietCaptureFixture(directory)
os.Exit(0)
}
@@ -103,7 +103,7 @@ func TestQuietHostUpdatePreservesControlAndCancelsCleanly(t *testing.T) {
slog.SetDefault(slog.New(slog.NewJSONHandler(&trace, &slog.HandlerOptions{Level: slog.LevelDebug})))
}
directory := t.TempDir()
t.Setenv("SCREENER_QUIET_CAPTURE_FIXTURE", directory)
t.Setenv("PIIK_QUIET_CAPTURE_FIXTURE", directory)
executable, err := os.Executable()
if err != nil {
t.Fatal(err)
+2 -2
View File
@@ -1,8 +1,8 @@
package nativecontrol
import (
"github.com/TNTcraftHIM/Screener/internal/client/mediaedge"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Piik/internal/client/mediaedge"
"github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
"github.com/pion/webrtc/v4"
)
+3 -3
View File
@@ -10,8 +10,8 @@ import (
"testing"
"time"
"github.com/TNTcraftHIM/Screener/internal/client/nativeaudio"
"github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
"github.com/TNTcraftHIM/Piik/internal/client/nativeaudio"
"github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
"github.com/livekit/livekit-server/pkg/sfu"
"github.com/pion/webrtc/v4"
)
@@ -76,7 +76,7 @@ func runAudioRecoveryCapture() {
}
func TestAudioEOFWaitsForExplicitSourceReplacement(t *testing.T) {
t.Setenv("SCREENER_NATIVEHOST_PIPE_FIXTURE", "audio-recovery")
t.Setenv("PIIK_NATIVEHOST_PIPE_FIXTURE", "audio-recovery")
check := func(err error) {
t.Helper()
if err != nil {
+1 -1
View File
@@ -1,6 +1,6 @@
package nativehost
import "github.com/TNTcraftHIM/Screener/internal/client/nativecapture"
import "github.com/TNTcraftHIM/Piik/internal/client/nativecapture"
type QualityProfile struct {
Video nativecapture.VideoProfile
Loaded 100 of 254 files, more files were not shown because too many files have changed in this diff. Show more