Version 2026.07.08 release. First public tagged version.

This commit is contained in:
Storm Dragon
2026-07-08 22:38:29 -04:00
parent 8696c98d6d
commit 9a9742e066
14 changed files with 19 additions and 817 deletions
-444
View File
@@ -1,444 +0,0 @@
Skald 0.1 (unreleased):
* Forked from Galene after commit f718385.
* Renamed the project, module, command, control tool, static assets, and
documentation entrypoints from Galene to Skald.
Galene 1.1 (unreleased):
* Implemented "galenectl initial-setup".
* Implemented global tokens.
* Added options "-unrestricted-tokens" and "-auto-subgroups" to
galenectl group commands.
* Reworked the documentation.
* Fixed a bug that could cause multiple reads of the token file.
* Explicitly use blobs for file transfer, this should allow
transferring files larger than available memory. This was the intent
all along, but the spec changed behind our back.
* Implemented support for RSA tokens. Thanks to Jean Dupouy.
* Added support for include-inferiors in cryptographic tokens. Thanks
to izeau.
* Fixed an issue with the chat window and the users' list disappearing
on mobile. Thanks to Kirill smelkov.
* Fixed an issue that made it impossible to add a wildcard user to a
group with no users. Thanks to Andrea Zucchelli.
* Added some more protection against denial of service attacks. Thanks
to Vinayak Mishra.
9 August 2025: Galene 1.0
* Upgrade to Pion v4.
* Add support for ETags and ICE restarts to the WHIP server.
* Implement UDP multiplexing.
* Implement hierarchical tokens.
* Add rotate filter; this is useful when using a mobile phone that is
upside down as a webcam.
* Implement /sharescreen, rename /stopshare to /unsharescreen.
* Implement JSON templates in galenectl.
* Make the TURN server less verbose.
* Replace the configuration field "publicServer" with "allowOrigin" and
"allowAdminOrigin". This is an incompatible change.
* Fix a race condtion in the setdata request. Thanks to git-lui.
8 March 2025: Galene 0.96.3
* Change username validation to validate the effectively used username,
which is not necessarily the same as the one typed by the user.
Thanks to K900.
* Allow empty usernames again, they were mistakenly disallowed in 0.96.1.
* Reverted a change that caused the chat button to disappear on mobile.
21 January 2025: Galene 0.96.2
* Fix a bug, introduced in 0.96.1, that could cause plaintext passwords
to be erroneously accepted even when incorrect.
* Implement background blur on Safari.
* Set the minimum TLS version to 1.2.
* Change the default hashing algorithm to bcrypt. We use a cost of 8 in
order to keep hashing times under 25ms, even though 10 is currently
the recommended minimum.
* Limit the number of concurrent password hashing operations to the
number of CPUs. This avoids using excessive memory when many users
log in at the same time.
18 January 2025: Galene 0.96.1
* Avoid path traversal in disk writer.
* Forbid backslashes in group names on all systesm (we used to only
forbid them on Windows). This is an incompatible change.
* Minor restrictions on usernames: forbid usernames starting with
a slash, forbid usernames containing the string "/.", and forbid
backslashes. This is an incompatible change.
* Ensure cleartext passwords are veryfied in constant time.
* Remove "data:" from allowed image sources in CSP header.
* Disable the unmute button before presenting. Thanks to Sacha Chua.
* Fix PCMA audio (don't ask).
* Update fontawesome to version 6.7.2.
* Delay requesting microphone permissions on Safari until after
successful login.
1 January 2025: Galene 0.96
* Implemented asynchronous filters.
* Implemented background blur.
* More fixes to RTCP feedback signalling (thanks to Sean DuBois).
4 December 2024: Galene 0.95
* Galene now requires Go 1.20.
* Added "galenectl" utility and removed "galene-password-generator".
This is an incompatible change.
* Fixed a number of bugs in the management protocol.
* Implemented captions for the galene-stt tool.
* Reworked the peer-to-peer file transfer protocol. This is an
incompatible change.
* Added an id to every chat message, and made it possible to remove
a single chat message.
* Fixed a race condition that prevented operators from joining an
AutoKick group.
* Fixed a bug that could cause us to signal our ability to do RTCP
feedback incorrectly.
* Fixed a bug that would trigger JavaScript errors when a user attempted
to send a file to themself.
15 July 2024: Galene 0.9.1
* Added an example client with minimal functionality under static/example.
* Changed the token login to force a user interaction in order to avoid
issues with autoplay.
* Fixed a race condition that would cause the client to run with
undefined settings if the initial connection was extremely fast.
* Implemented client-side timeouts (we already had one on the server side).
* Reduced the server-side timeout.
* Don't attempt to set the file descriptor limit, since recent versions
of the Go runtime do it automatically.
26 May 2024: Galene 0.9
* Reworked the group definition format to be more modular. There are
some incompatible changes: multiple users with the same username but
distinct passords are no longer supported, and the field
"allow-anonymous" is ignored.
* Reworked the definition of administrator passwords in config.json.
The old field "admin" is ignored, a new field "users" is required.
This is an incompatible change.
* Implemented an administrative API over HTTP. Set the field
"writableGroups" in config.json to enable it. The API is described in
the file "README.API", a JavaScript library is in management.js.
* Implemented a form to allow users to change their password. If the
HTTP API is enabled, a link will appear in the side menu.
* Implemented a new command "/identify" (also availble in the users
menu) that discloses the IP address from which a user connected.
* Implemented a contextual menu that triggers on a double click on
a chat entry.
* Added a new command "/stopshare".
* Added a new permission "message" and new commands "shutup" and "unshutup".
* Changed stateless token validation to attempt all keys rather than
just the first matching one.
26 May 2024: Galene 0.8.3
* Fixed a bug that could allow any user to clear the chat.
14 April 2024: Galene 0.8.2
* Fixed possible crashes in the VP8 and AV1 parsers.
* Fixed keyframe detection for VP8 and VP9.
9 March 2024: Galene 0.8.1
* Security fixes to WHIP ingress.
* Improved error messages for authentication failures.
* Added "expires" and "not-before" to group descriptions.
* Allow proxyURL to contain a path, for people who run Galene at
non-root URLs. This requires manually modifying the files under
'static/' in order to point at the right locations.
25 November 2023: Galene 0.8
* Added a new configuration option "publicServer".
* Implemented the WHIP protocol (draft-ietf-wish-whip).
* Disabled simulcast on Firefox by default.
* Removed support for protocol version 1 (used before version 0.7)
* Worked around clients that don't set the VP9 and H.264 profiles.
* Updated the activity detection code to work with newer browsers.
Thanks to Jackson Vieira.
* Constrained videos sent from a browser to use 4:3 aspect.
* Implemented BCrypt as an alternate password encryption algorithm.
9 July 2023: Galene 0.7.2
* Improved workaround for Safari's autoplay-related mis-features.
* Fixed minor bugs related to stateful tokens.
14 May 2023: Galene 0.7.1
* Fixed incorrect error-handling in token parsing, which could cause
Galene to crash after receiving a mis-formatted token.
* Implement a dialog for creating tokens.
* Tweaked peer-to-peer file transfer to more reliably return an error
message to the user.
* Fixed the workaround for non-standard simulcast in older Firefox
versions.
10 April 2023: Galene 0.7.0
* Updated the protocol version to 2. Dates are now represented in
ISO 8601 format, and there is a new "error" field in error messages
that contains a machine-readable identifier.
* Implemented stateful tokens ("invitations").
* Made more errors non-fatal, which makes it possible for the client
to recover in more situations.
* Reworked the progress indicator used during file transfer.
* Added Referrer-Policy and X-Content-Type-Options headers to HTTP
responses (Galene was already generating HTML links with the
"noreferrer" option). Thanks to Alexandre Iooss.
* Fixed a bug in the handling of leave messages. (The bundled client
never sends leave, it just disconnects, so the bug never triggerred.)
* Changed the way simulcast tracks are selected to not depend on the rid.
11 January 2023: Galene 0.6.2
* Disable simulcast for screensharing; this didn't work well with many
browsers.
* Fix parsing of VP8 packets with degenerate headers.
* Fix computation of group URL when running in insecure mode.
* Add configuration option "proxyURL", which makes it easier to run
Galene behind a reverse proxy.
* Disable ulimit checking on BSD systems, where our code doesn't compile.
7 October 2022: Galene 0.6.1
* Add a camera/microphone indicator in the users list, and hide
audio-only peers by default. They can be reenabled in the side menu.
* Ensure that autolocked groups are locked on creation. Thanks to
Michael Ströder.
* Don't send chat history multiple times. Thanks to Rémy Nollet.
* Fix status display for remote (redirected) groups.
10 September 2022: Galene 0.6
* Version the protocol: the handshake message now contains a version
number, and the server will warn if it is incorrect. This will become
a hard error in the future.
* Rework the peer-to-peer file transfer protocol. It now lives in
protocol.js, which makes it easy to use by third-party clients.
* Extend the authorization protocol to allow the authorization server to
request that the client fall back to password authorization.
* Make it possible to run the web server on a Unix domain socket.
Thanks to Martin Pépin.
* Check that a given group exists before redirecting from the landing
page. This avoids an unsightly 404 error when the user makes a typo.
* Publish the group name end websocket endpoint in the group status.
This avoids hardwirint the server structure in the client.
* Fix a race condition that could cause chathistory messages to be sent
before the joined status.
23 July 2022: Galene 0.5.5
* Fix a longstanding bug with time conversion that would occasionally
cause us to miscalculate rates and send incorrect sender reports.
Thanks to lamhai1401.
* Update srtp to a version that includes Adrian Cable's optimisations.
This should yield a 10% to 20% improvement for AES-CTR (but modern
browsers use GCM).
* Reword the Safari warning to be more frightening, since screensharing
on Safari tends to get stuck.
5 May 2022: Galene 0.5.4
* Fix username check when a token is present.
* Improve the performance of AES in CTR mode.
* Tweak detection of keyframes with H.264.
25 April 2022: Galene 0.5.3
* Remove a forgotten "replace" that prevented 0.5.2 from compiling
unless "pion/ice" was downloaded manually.
* Fix a race condition in the rate estimator.
* Update pion/ice to a version that handles aggressive nomination; this
avoids broken connection in some (fairly rare) cases.
20 April 2022: Galene 0.5.2
* Implement audio-video sync in the diskwriter.
* Fix setting of the keyframe flag in audio samples in diskwriter.
* Don't use high-quality audio for screenshare by default, obey the
hqaudio preference in all cases.
* Maintain user information for oneself, not just other users.
* Call the onuser callback when a user deletes a stream.
25 March 2022: Galene 0.5.1
* Enable simulcast on Firefox.
* Fix rate control on Firefox.
* Fix corruption after packet reordering when saving to disk.
* Fix the H.264 profile for compatibility with older iPhones.
* Display a warning when joining a locked group.
* Add a hook that allows the client to influence the ICE policy.
* Fix error handling in peer-to-peer file transfer.
8 March 2022: Galene 0.5
* Implement third-party (token-based) authentication.
* Implement peer-to-peer file transfer (/sendfile).
* Add a contextual menu to the users list.
* Removed the "Broadcast file" entry in the side menu, it is now
available as a context menu entry and a chat command (/presentfile).
* Add side-menu options for high-quality audio (Opus at 128kbps) and
disabling audio preprocessing.
* Send audio in addition to video when sharing browser tabs.
1 February 2022: Galene 0.4.4
* Update dependencies. Pion WebRTC v3.1.19 fixes a bug that could cause
audio tracks to be discarded under some circumstances.
* Fix a bug that could cause user statuses (raisehand) to be discarded.
* Fix incorrect maxRate value in statistics display.
27 January 2022: Galène 0.4.3
* Fix a locking issue that could cause a deadlock when massive numbers
of clients join at the same time.
* Add support for displaying raised hands (thanks to Sascha Silbe).
* Update the MIME type for AV1 to track recent browsers.
* Avoid overflow during rate estimation.
* More fixes to the disk writer.
1 December 2021: Galène 0.4.2
* Update the samplebuilder to avoid a crash when a single frame is so
large that it overflows the buffer.
* Various other changes that improve the quality of disk recordings.
4 November 2021: Galène 0.4.1
* Create a new file data/config.json with global configuration.
* Remove data/passwd and the -redirect option with entries in config.json;
these are incompatible changes.
* Change the group URL from /group/name to /group/name/, which allows
using relative links. The old URL is redirected to the new one.
* Extend the protocol with the ability to consult the group status before
joining; this allows using the group's displayName before the user has
logged in, and will be required for new authorisation schemes.
* Allow scrolling of the login dialog, useful on small devices.
* Fixed a typo that prevented the group name from being displayed.
* Made failed videos more visible.
* No longer attempt to save passwords in browser JavaScript, it's
insecure and not very useful.
5 September 2021: Galène 0.4
* Implemented simulcast.
* Implemented scalable video coding (SVC) for VP8 and VP9.
* Preliminary support for the AV1 codec.
* Fixed the profile announced for VP9.
* Reworked the disk writer, which should no longer cause corruption
after packet loss.
* Added support for recording H.264 tracks.
* Reworked the signalling protocol, it is now possible to request
different tracks for distinct streams, and there are provisions to
carry user permissions and statuses; this is not yet used by the user
interface.
* Reworked internal signalling; this will hopefully fix issues with
losing tracks under load.
* Added an option to limit the range of UDP ports used by the server.
* Added a close button in every locally-generated video; this allows
closing individual screen shares and simplifies the UI by not creating
close buttons in the top bar.
* The server will now attempt to increase the file descriptor limit at
startup.
* Stats are now exported as JSON and formatted on the client side; the
stats URL is now /stats.html rather than just /stats.
27 April 2021: Galène 0.3.4
* Fixed a bug in the loss estimator. We will now reduce the rate more
aggressively when there is loss in the client->serer direction.
* Fixed display of the chat on some devices.
* Exported the group.Description data structure for the sake of external
configuration tools.
16 April 2021: Galène 0.3.3
* Fixed an off-by-one error that could occasionally lead to a server
crash. Thanks to Louis Gesbert.
* Attempt to negotiate stereo Opus audio.
3 March 2021: Galène 0.3.2
* Fixed compatibility with versions of Go earlier than 1.15 (thanks to
Michael Ströder).
* Fixed a race condition in the disk writer.
26 February 2021: Galène 0.3.1
* We now notice when the TLS certificate has changed on disk; the
certificate can therefore be rotated without a restart.
* We now generate a self-signed certificate if none is found on disk.
18 February 2021: Galène 0.3
* Implemented the command /muteall. This allows muting all users except
yourself.
* Implemented the autolock and autokick user group options, that cause,
a group to be locked or all users to be kicked out when the last
administrator leaves.
* Implemented client-side filters that are run over the video before it
is sent to the server. A pair of mirroring filters are provided,
which are useful when using a mirror in front of a webcam.
* Included a built-in TURN server, which drastically simplifies
deployment in common cases.
* Implemented testing of TURN servers, on both the client and the server
side. Connectivity will be checked by the server every 12 hours, and
by the client whenever a user uses the /relay-test command.
* Group configurations are now checked at startup, and any failures are
logged. This implies that it is no longer possible to use undefined
keys in group definition files (this is a breaking change).
* Reworked the client API, which is now simpler and more uniform.
* Subgroups can now be public.
* Added an option to disable the mirror view, which is useful when writing.
* Ported file playback to Firefox.
* We now display a friendly error message when the user has disabled
WebRTC under Firefox.
* Implemented renegotiation of down streams, which reduces the amount of
flicker in the user interface.
* Fixed a number of race conditions that could cause audio tracks to
become muted or video tracks to freeze.
9 January 2021: Galène 0.2
* We now work around a limitation of the Go runtime that used to cause
a crash on 32-bit architectures.
* Recording to disk now works on Windows too.
* Error handling has been improved. In particular, group operators now
receive a friendly warning when recording to disk fails for some reason.
* It is now possible to configure a group to use codecs such as VP9 and
H.264. The default is still to use VP8 and Opus, as this yields the
best compatibility across browsers. Only VP8, VP9 and Opus can be
saved to disk.
* The command-line option "-relay-only" can be used to specify that all
media traffic should go through a TURN server.
* It is now possible to generate TURN passwords automatically, which
avoids leaking long-term TURN credentials. See the "credentialType"
value "hmac-sha1" described in README.
* The ICE configuration will now be checked periodically, and new
clients will use an updated configuration.
* The client-server protocol has been reworked to be more symmetric
and more secure. It is now documented in the file README.PROTOCOL.
* The stream abort protocol message is now supported in the client->server
direction, making it possible for the client to reject a stream.
* The delay before sending NACKs has been reduced, which gives a major
boost in video quality.
* The play from disk button is not available when the browser doesn't
support the required API. We used to display a cryptic error message
in that case.
* The command-line option -insecure allows using plain HTTP, which is
useful when running behind a reverse proxy.
* When saving to disk to disk, the keyframe interval has been reduced to 4s.
* We now send keepalives after 55s at most, which should fix issues with
reverse proxies timing out the WebSocket.
19 December 2020: Galène 0.1
* Initial release.
+2 -2
View File
@@ -1,7 +1,7 @@
# Skald
Skald is a hard fork of Galene that is being turned into an audio-only
hall conferencing server. The current development repository is
Skald is a hard fork of Galene into an audio-only hall conferencing
server. The current development repository is
<https://git.stormux.org/storm/skald>.
Skald is not protocol-compatible or API-compatible with Galene. Its public
@@ -2,7 +2,7 @@
# shellcheck shell=bash disable=SC2034,SC2154
pkgname=skald-git
pkgver=0.0.0.r1553.g0c852e8
pkgver=2026.07.08
pkgrel=1
pkgdesc='Audio-only hall-based conferencing server'
arch=('x86_64' 'aarch64')
@@ -28,10 +28,10 @@ sha256sums=(
pkgver() {
cd skald || return
local describe
if describe="$(git describe --long --tags 2>/dev/null)"; then
if describe="$(git describe --long --tags --match '[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9]' 2>/dev/null)"; then
printf '%s\n' "$describe" | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g'
else
printf '0.0.0.r%s.g%s' "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
printf '2026.07.08.r%s.g%s' "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
fi
}
-1
View File
@@ -1 +0,0 @@
@@ -1,24 +0,0 @@
# Void Linux packaging
This directory contains a local `xbps-src` template for Skald.
Copy `skald-git/` into `srcpkgs/skald-git/` in a Void packages checkout,
then build it with:
```sh
./xbps-src pkg skald-git
```
The package installs both `skald` and `skaldctl`, creates a `skald` system
account, creates `/var/lib/skald/{data,halls,recordings}`, and provides a
runit service named `skald`.
To use a private repository, prefer SSH authentication by changing
`repository` in the template, for example:
```sh
repository="ssh://git@git.stormux.org/storm/skald.git"
```
Make sure the build environment has access to the needed SSH key or Git
credential helper. Do not put passwords in the template.
@@ -1,10 +0,0 @@
#!/bin/sh
# shellcheck disable=SC3045
exec 2>&1
cd /var/lib/skald || exit 1
ulimit -n 65536
exec chpst -u skald:skald /usr/bin/skald \
-static /usr/share/skald/static \
-data /var/lib/skald/data \
-halls /var/lib/skald/halls \
-recordings /var/lib/skald/recordings
@@ -1,60 +0,0 @@
# Template file for 'skald-git'
# shellcheck shell=bash disable=SC2034,SC2154
pkgname=skald-git
version=0.0.0
revision=1
hostmakedepends="git go"
depends="ffmpeg"
short_desc="Audio-only hall-based conferencing server"
maintainer="Stormux <storm_dragon@stormux.org>"
license="MIT"
homepage="https://git.stormux.org/storm/skald"
repository="https://git.stormux.org/storm/skald.git"
provides="skald-${version}_${revision}"
conflicts="skald>=0"
wrksrc="skald"
system_accounts="skald"
skald_homedir="/var/lib/skald"
make_dirs="
/var/lib/skald 0750 skald skald
/var/lib/skald/data 0750 skald skald
/var/lib/skald/halls 0750 skald skald
/var/lib/skald/recordings 0750 skald skald"
do_fetch() {
if [ -d "${XBPS_BUILDDIR}/${wrksrc}/.git" ]; then
git -C "${XBPS_BUILDDIR}/${wrksrc}" fetch --depth=1 origin "${SKALD_GIT_BRANCH:-master}"
git -C "${XBPS_BUILDDIR}/${wrksrc}" checkout FETCH_HEAD
else
git clone --depth=1 --branch "${SKALD_GIT_BRANCH:-master}" "$repository" "${XBPS_BUILDDIR}/${wrksrc}"
fi
}
do_build() {
export CGO_ENABLED=0
export GOFLAGS="-buildmode=pie -trimpath -mod=readonly -modcacherw"
mkdir -p build
go build -ldflags='-s -w' -o build/skald .
go build -ldflags='-s -w' -o build/skaldctl ./skaldctl
}
do_check() {
go test ./...
}
do_install() {
vbin build/skald
vbin build/skaldctl
vmkdir usr/share/skald/static
vcopy "static/*" usr/share/skald/static
vdoc README.md
vdoc skald.md
vdoc skald-api.md
vdoc skald-client.md
vdoc skald-install.md
vdoc skald-protocol.md
vsv skald
}
-91
View File
@@ -1,91 +0,0 @@
# Skald Fork Inventory
This file records the baseline surfaces found during the early hard-fork
inventory. It is intentionally short; implementation work should update the
main docs and tests, not expand this into a second task list.
## Static And Generated Assets
The served static tree is `static/`. Current first-party static files are:
- `404.css`, `404.html`
- `change-password.css`, `change-password.html`, `change-password.js`
- `common.css`
- `index.html`
- `mainpage.css`, `mainpage.js`
- `management.js`
- `protocol.js`
- `skald.css`, `skald.html`, `skald.js`
- `stats.html`, `stats.js`
- `tsconfig.json`
The static tree still includes `static/background-blur-worker.js`, which is a
video-era asset to remove during the audio-only frontend/backend work.
The `static/example/` directory contains `example.html` and `example.js`.
Third-party static assets are vendored under `static/third-party/`:
- Font Awesome CSS, fonts, and license files.
- `contextual` CSS/JS and license.
- `toastify` CSS/JS and license.
No generated static output directory is currently present in the checkout.
## Public URL Surfaces
Current active Skald URL surfaces found in the server and browser code:
- `/hall/name/`
- `/public-halls.json`
- `/recordings/`
- `/skald-api/v0/`
- `/skald-api/v0/.halls/`
- `/skald-api/v0/.stats`
Old Galene URL surfaces from the planning checklist are not active routes in
the current server code:
- `/group/`
- `/public-groups.json`
- `/galene-api/`
- `.groups`
The old `/group/name/` spelling remains in historical `CHANGES` text only.
## Config And Data Paths
Current runtime defaults and config paths:
- `-static ./static/`
- `-data ./data/`
- `-halls ./halls/`
- `-recordings ./recordings/`
- `data/config.json`
- `data/ice-servers.json`
- `data/var/tokens.jsonl`
- `halls/hallname.json`
The old `groups/`, `galenectl.json`, and `skaldctl.json` paths are not current
runtime defaults.
Existing Galene installations should be treated as incompatible unless a later
task explicitly adds a one-shot manual migration tool or documented manual
conversion procedure.
## Binary, Service, And Install Names
Current binary and service surfaces found in active docs/code:
- Server binary: `skald`
- Control tool binary: `skaldctl`
- Service example: `skald.service`
- Service user and group examples: `skald`
Remaining `galene`, `Galene`, and `galenectl` references are historical
changelog entries, fork attribution, or the master todo's record of completed
rename work.
## License And Attribution
The upstream `LICENCE` file remains in place. Skald docs also keep explicit
fork attribution to Galene where appropriate.
+2 -2
View File
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Skald</title>
<title>Skald Version 2026.07.08</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/common.css">
@@ -13,7 +13,7 @@
<body>
<div class="home">
<h1 id="title" class="navbar-brand">Skald</h1>
<h1 id="title" class="navbar-brand">Skald Version 2026.07.08</h1>
<form id="hallform">
<label for="hall">Hall:</label>
+3 -3
View File
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Skald</title>
<title>Skald Version 2026.07.08</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="ScreenOrientation" content="autoRotate:disabled">
@@ -19,7 +19,7 @@
<div class="row full-height">
<nav id="left-sidebar" class="connected-only invisible" aria-hidden="true">
<div class="users-header">
<div class="skald-header">Skald</div>
<div class="skald-header">Skald Version 2026.07.08</div>
</div>
<div class="header-sep"></div>
<div id="users"></div>
@@ -31,7 +31,7 @@
<button class="collapse" aria-label="Toggle left panel" aria-expanded="true" aria-controls="left-sidebar" id="sidebarCollapse">
<i class="fas fa-align-left" aria-hidden="true"></i>
</button>
<h1 id="title" class="header-title" tabindex="-1">Skald</h1>
<h1 id="title" class="header-title" tabindex="-1">Skald Version 2026.07.08</h1>
</div>
<ul class="nav-menu">
+3 -2
View File
@@ -2033,6 +2033,7 @@ function displayUsername() {
}
let presentRequested = null;
const skaldVersionLabel = 'Skald Version 2026.07.08';
/**
* @param {string} s
@@ -2047,12 +2048,12 @@ function capitalise(s) {
* @param {string} title
*/
function setTitle(title) {
let displayTitle = 'Skald';
let displayTitle = skaldVersionLabel;
if(title) {
let status = hallStatus.locked ? 'locked' : 'unlocked';
if(hallRecording)
status += ', recording';
displayTitle = `Hall: ${title} (${status})`;
displayTitle = `Hall: ${title} (${status}) - ${skaldVersionLabel}`;
}
document.title = displayTitle;
document.getElementById('title').textContent = displayTitle;
-172
View File
@@ -1,172 +0,0 @@
# Skald Recording Quality Roadmap
#
# Current state:
# The original hard-fork checklist is retired. Skald is live on the user's
# server, Galene rooms have been migrated as Skald halls, and basic recording
# has been verified with real users.
#
# Goal:
# Make Skald's single mixed Ogg Opus recordings sound good, stay reliable
# under larger audio-only halls, and avoid avoidable CPU/process overhead.
#
# Legend:
# - item = needs doing.
# + item = implemented, needs testing.
# X item = tested and done.
#
# Constraints:
# - Keep recordings as one mixed Ogg Opus file per hall recording session.
# - Preserve clear recording start/stop announcements and operator feedback.
# - Do not block WebRTC packet handling on recording work.
# - Favor small, testable stages over one large rewrite.
# - Keep live-server safety in mind; staged improvements should be deployable
# independently.
# ============================================================================
# PHASE 0: BASELINE AND MEASUREMENT
# ============================================================================
X Confirm the current live deployment can start and stop a Skald recording.
X Confirm migrated Galene rooms work as Skald halls on meet.stormux.org.
X Document the current recording pipeline in skald.md for maintainers:
incoming Opus RTP -> per-source ffmpeg decoder -> PCM mixer -> ffmpeg Opus
encoder -> Ogg Opus file.
X Add lightweight recording metrics in logs or debug output:
active recorded sources, dropped source frames, mixer write errors, ffmpeg
decoder starts/stops, and encoder failures.
X Add a synthetic recording smoke tool that can simulate multiple decoded PCM
sources without needing browser clients.
- Add a synthetic RTP/Opus recording benchmark that exercises per-source
decoder subprocesses without needing browser clients.
- Record baseline CPU, process count, and output quality for 1, 5, 10, and 25
active sources on a representative server.
- Define practical target limits for "normal hall", "large moderated hall",
and "stress test" recording scenarios.
# ============================================================================
# PHASE 1: MIX QUALITY BEFORE PIPELINE REWRITE
# ============================================================================
X Add mixer tests for multiple simultaneous PCM sources, including clipping
cases and quiet/loud source combinations.
X Replace raw summing with a safer mix strategy:
per-source attenuation, headroom, or another deterministic approach that
reduces clipping when multiple users speak.
X Add a simple limiter to prevent harsh clipping in the final mixed PCM.
X Add tests proving the limiter preserves normal speech-level samples and
bounds overloaded samples.
- Add optional per-source level tracking so logs or future UI can identify
sources that dominate the recording.
- Verify recordings with 2-5 simultaneous speakers sound cleaner than the
current clamped sum.
- Verify the limiter does not create pumping, distortion, or excessive volume
loss for a single speaker.
# ============================================================================
# PHASE 2: SOURCE LIFECYCLE AND SILENCE HANDLING
# ============================================================================
X Track whether each recording source is active, silent, stalled, or closed.
X Treat missing frames as silence without blocking the mixer.
- Drop or de-prioritize stale silent sources after a short timeout.
- Ensure mute/unmute transitions do not leave stale audio in the mixed output.
- Ensure participant join/leave during recording never stops the recording file.
- Add tests for source join, leave, mute, unmute, stall, and recovery during an
active recording.
- Verify a long recording remains valid when sources repeatedly join and leave.
# ============================================================================
# PHASE 3: REDUCE PROCESS OVERHEAD
# ============================================================================
- Evaluate Go-native Opus decode options against the current ffmpeg decoder
subprocesses:
sound quality, maintenance risk, packaging impact, CGO requirements, and CPU.
- Decide whether in-process Opus decoding is worth the dependency and packaging
tradeoff.
- If keeping ffmpeg decoders, pool or supervise decoder processes more
deliberately and improve failure recovery.
- If replacing ffmpeg decoders, implement one in-process decoder path behind
tests before removing the subprocess path.
- Verify recording still handles dynamic payload type, RTP sequence behavior,
packet loss, and source restarts.
- Measure CPU and process-count improvement with 10+ active sources.
- Keep one final encoder path to Ogg Opus unless there is a proven reason to
replace it too.
# ============================================================================
# PHASE 4: ENCODER AND CONTAINER HARDENING
# ============================================================================
- Decide whether the final Ogg Opus encoder should remain ffmpeg or move
in-process later.
- Make encoder startup errors operator-visible and log the exact ffmpeg error.
X Ensure encoder stdin writes cannot block the mixer indefinitely.
- Ensure recorder close handles encoder failure, stdin close failure, and wait
failure without leaving misleading successful output.
- Verify interrupted or failed recordings are either clearly marked as failed or
produce a playable partial file.
X Verify completed files with ffprobe or equivalent in tests where available.
- Verify completed files play in common players after long recordings.
# ============================================================================
# PHASE 5: LARGE-HALL OPERATIONS
# ============================================================================
- Add documentation for recommended large-hall permissions:
most users observe/message, only presenters publish audio, operators control
recording.
- Document expected scaling limits in terms of active speakers, not just total
connected users.
- Add guidance for max-clients, TURN bandwidth, CPU monitoring, and recording
expectations for 50, 100, and 250 user events.
- Add an operator-facing warning or log message when active recorded sources
exceed a conservative threshold.
- Test a large moderated hall with many listeners and a few active speakers.
- Test a stress hall with many active speakers and document the observed limit.
# ============================================================================
# PHASE 6: FINAL CLEANUP AND RELEASE READINESS
# ============================================================================
- Update skald.md and skald-install.md with the final recording architecture.
- Update package dependencies if recording dependency choices change.
- Run focused recording tests and webserver recording-control tests.
- Run `go test ./...` in a clean tree without distro package build artifacts.
- Run live browser testing for recording start, stop, join, leave, mute, and
playback.
- Do a final review for accidental regressions in recording announcements,
hall permissions, and migrated-hall compatibility.
# ============================================================================
# PHASE 7: WEB UI AUDIO DEFAULTS AND EVENT SOUNDS
# ============================================================================
+ Default new sessions to high-quality audio.
+ Default new sessions to no browser noise suppression, echo cancellation, or
automatic gain control.
+ Ensure the default microphone path also receives explicit audio constraints.
+ Add generated browser notification sounds without binary sound assets.
+ Add a user-facing Notification sounds setting.
+ Play generated sounds for recording start, recording stop, raised hand,
lowered hand, user join, user leave, and incoming chat message.
- Verify sounds play after login in Chromium.
- Verify sounds play after login in Firefox.
- Verify sounds never replace screen-reader live-region announcements.
- Tune sound levels and shapes with live users.
# ============================================================================
# PHASE 8: LOCAL SKALDCTL ADMINISTRATION
# ============================================================================
X Remove skaldctl's remote HTTPS administration path and operate directly on
the server's local data, hall, and token files.
X Add `create-hall -interactive` for guided hall settings, operator accounts,
and an optional shared participant password.
X Document that skaldctl runs on the Skald server with filesystem access.
# ============================================================================
# PHASE 9: HALL HEADING STATUS
# ============================================================================
X Show lock and recording state in the browser's hall heading.
+2 -3
View File
@@ -184,9 +184,8 @@ func (state *state) load() (string, error) {
state.reset()
return "", err
}
// the "message" permission was introduced in Skald 0.9,
// so add it to tokens read from disk. We can remove this
// hack in late 2024.
// The "message" permission was introduced before the
// Skald fork, so add it to older tokens read from disk.
if !member("message", t.Permissions) {
t.Permissions = append(t.Permissions, "message")
}
+4
View File
@@ -70,10 +70,14 @@ func TestStaticLiveRegionAnnouncementsStayWired(t *testing.T) {
}
func TestHallHeadingIncludesCurrentStatus(t *testing.T) {
html := readStaticFile(t, "skald.html")
js := readStaticFile(t, "skald.js")
requireContains(t, html, "Skald Version 2026.07.08", "hall page title")
requireContains(t, js, "const skaldVersionLabel = 'Skald Version 2026.07.08'", "version label")
requireFunctionContains(t, js, "setTitle", "hallStatus.locked ? 'locked' : 'unlocked'")
requireFunctionContains(t, js, "setTitle", "status += ', recording'")
requireFunctionContains(t, js, "setTitle", "${skaldVersionLabel}")
requireFunctionContains(t, js, "gotJoined", "hallStatus.locked = false")
requireFunctionContains(t, js, "gotJoined", "hallStatus.recording = false")
requireFunctionContains(t, js, "gotJoined", "hallRecording = !!hallStatus.recording")