Rebrand project as Skald

This commit is contained in:
Storm Dragon
2026-05-17 22:16:08 -04:00
parent 77edf0539c
commit bda0e548d3
53 changed files with 479 additions and 433 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
*~
/galene
/galenectl/galenectl
/skald
/skaldctl/skaldctl
/data
/groups/**/*.json
/static/**/*.d.ts
+6
View File
@@ -1,3 +1,9 @@
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".
+22 -34
View File
@@ -1,59 +1,47 @@
# The Galene videoconferencing system
# Skald
Galene is a fully-features videoconferencing system that is easy to deploy
and requires very moderate server resources. It is described at
<https://galene.org>.
Skald is a hard fork of Galene that is being turned into an audio-only
hall conferencing server. The current development repository is
<https://git.stormux.org/storm/skald>.
## Quick start
```sh
git clone https://github.com/jech/galene
cd galene
git clone https://git.stormux.org/storm/skald
cd skald
CGO_ENABLED=0 go build -ldflags='-s -w'
mkdir groups
echo '{"users": {"vimes": {"password":"sybil", "permissions":"op"}}}' > groups/night-watch.json
./galene &
./skald &
```
Point your browser at <https://localhost:8443/group/night-watch/>, ignore
the unknown certificate warning, and log in with username *vimes* and
password *sybil*.
For full installation instructions, please see the file [galene-install.md][1]
For full installation instructions, please see the file [skald-install.md][1]
in this directory.
## Documentation
* [galene-install.md][1]: full installation instructions
* [galene.md][2]: usage and administration;
* [galene-client.md][3]: writing clients;
* [galene-protocol.md][4]: the client protocol;
* [galene-api.md][5]: Galene's administrative API.
* [skald-install.md][1]: full installation instructions
* [skald.md][2]: usage and administration;
* [skald-client.md][3]: writing clients;
* [skald-protocol.md][4]: the client protocol;
* [skald-api.md][5]: Skald's administrative API.
## Contributing
In order to contribute to Galene, you may:
* send patches to [the Galene mailing list][6] by sending mail to
<galene@lists.galene.org>;
* submit pull requests on GitHub.
For general discussion, please use the [Galene mailing list][6] (feel free
to send mail without subscribing). Please do not use Github for general
discussion.
Skald is currently being developed at
<https://git.stormux.org/storm/skald>.
## Further information
Galène's web page is at <https://galene.org>.
Skald is derived from Galene by Juliusz Chroboczek. Galene's web page is at
<https://galene.org>.
Answers to common questions and issues are at <https://galene.org/faq.html>.
-- Juliusz Chroboczek <https://www.irif.fr/~jch/>
[1]: <galene-install.md>
[2]: <galene.md>
[3]: <galene-client.md>
[4]: <galene-protocol.md>
[5]: <galene-api.md>
[6]: <https://lists.galene.org/>
[1]: <skald-install.md>
[2]: <skald.md>
[3]: <skald-client.md>
[4]: <skald-protocol.md>
[5]: <skald-api.md>
+4 -4
View File
@@ -22,10 +22,10 @@ import (
"github.com/jech/samplebuilder"
gcodecs "github.com/jech/galene/codecs"
"github.com/jech/galene/conn"
"github.com/jech/galene/group"
"github.com/jech/galene/rtptime"
gcodecs "git.stormux.org/storm/skald/codecs"
"git.stormux.org/storm/skald/conn"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/rtptime"
)
const (
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"testing"
"time"
"github.com/jech/galene/rtptime"
"git.stormux.org/storm/skald/rtptime"
)
func TestSanitise(t *testing.T) {
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"sync"
"time"
"github.com/jech/galene/rtptime"
"git.stormux.org/storm/skald/rtptime"
)
type Estimator struct {
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/jech/galene/rtptime"
"git.stormux.org/storm/skald/rtptime"
)
func TestEstimator(t *testing.T) {
+1 -1
View File
@@ -1,4 +1,4 @@
module github.com/jech/galene
module git.stormux.org/storm/skald
go 1.21
+1 -1
View File
@@ -14,7 +14,7 @@ import (
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2"
"github.com/jech/galene/conn"
"git.stormux.org/storm/skald/conn"
)
type RawPassword struct {
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"strings"
"time"
"github.com/jech/galene/token"
"git.stormux.org/storm/skald/token"
)
var ErrTagMismatch = errors.New("tag mismatch")
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"github.com/pion/interceptor"
"github.com/pion/webrtc/v4"
"github.com/jech/galene/token"
"git.stormux.org/storm/skald/token"
)
var Directory, DataDirectory string
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"github.com/pion/webrtc/v4"
"github.com/jech/galene/turnserver"
"git.stormux.org/storm/skald/turnserver"
)
type timeoutError struct{}
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"github.com/pion/webrtc/v4"
"github.com/jech/galene/turnserver"
"git.stormux.org/storm/skald/turnserver"
)
func TestPassword(t *testing.T) {
+1 -1
View File
@@ -4,7 +4,7 @@ package jitter
import (
"sync/atomic"
"github.com/jech/galene/rtptime"
"git.stormux.org/storm/skald/rtptime"
)
type Estimator struct {
+10 -10
View File
@@ -14,16 +14,16 @@ import (
"github.com/pion/sdp/v3"
"github.com/pion/webrtc/v4"
"github.com/jech/galene/codecs"
"github.com/jech/galene/conn"
"github.com/jech/galene/estimator"
"github.com/jech/galene/group"
"github.com/jech/galene/ice"
"github.com/jech/galene/jitter"
"github.com/jech/galene/packetcache"
"github.com/jech/galene/packetmap"
"github.com/jech/galene/rtptime"
"github.com/jech/galene/unbounded"
"git.stormux.org/storm/skald/codecs"
"git.stormux.org/storm/skald/conn"
"git.stormux.org/storm/skald/estimator"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/ice"
"git.stormux.org/storm/skald/jitter"
"git.stormux.org/storm/skald/packetcache"
"git.stormux.org/storm/skald/packetmap"
"git.stormux.org/storm/skald/rtptime"
"git.stormux.org/storm/skald/unbounded"
)
type bitrate struct {
+1 -1
View File
@@ -3,7 +3,7 @@ package rtpconn
import (
"testing"
"github.com/jech/galene/rtptime"
"git.stormux.org/storm/skald/rtptime"
)
func TestDownTrackAtomics(t *testing.T) {
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"github.com/pion/rtp"
"github.com/pion/webrtc/v4"
"github.com/jech/galene/codecs"
"github.com/jech/galene/packetcache"
"github.com/jech/galene/rtptime"
"git.stormux.org/storm/skald/codecs"
"git.stormux.org/storm/skald/packetcache"
"git.stormux.org/storm/skald/rtptime"
)
func readLoop(track *rtpUpTrack) {
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"sort"
"time"
"github.com/jech/galene/rtptime"
"github.com/jech/galene/stats"
"git.stormux.org/storm/skald/rtptime"
"git.stormux.org/storm/skald/stats"
)
func (c *webClient) GetStats() *stats.Client {
+3 -3
View File
@@ -6,9 +6,9 @@ import (
"sort"
"time"
"github.com/jech/galene/conn"
"github.com/jech/galene/packetcache"
"github.com/jech/galene/rtptime"
"git.stormux.org/storm/skald/conn"
"git.stormux.org/storm/skald/packetcache"
"git.stormux.org/storm/skald/rtptime"
)
// packetIndex is a request to send a packet from the cache.
+7 -7
View File
@@ -17,13 +17,13 @@ import (
"github.com/gorilla/websocket"
"github.com/pion/webrtc/v4"
"github.com/jech/galene/conn"
"github.com/jech/galene/diskwriter"
"github.com/jech/galene/estimator"
"github.com/jech/galene/group"
"github.com/jech/galene/ice"
"github.com/jech/galene/token"
"github.com/jech/galene/unbounded"
"git.stormux.org/storm/skald/conn"
"git.stormux.org/storm/skald/diskwriter"
"git.stormux.org/storm/skald/estimator"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/ice"
"git.stormux.org/storm/skald/token"
"git.stormux.org/storm/skald/unbounded"
)
func errorToWSCloseMessage(id string, err error) (*clientMessage, []byte) {
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"reflect"
"testing"
"github.com/jech/galene/token"
"git.stormux.org/storm/skald/token"
)
var tokens = []string{
+3 -3
View File
@@ -6,9 +6,9 @@ import (
"net"
"sync"
"github.com/jech/galene/conn"
"github.com/jech/galene/group"
"github.com/jech/galene/sdpfrag"
"git.stormux.org/storm/skald/conn"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/sdpfrag"
"github.com/pion/sdp/v3"
"github.com/pion/webrtc/v4"
+21 -21
View File
@@ -1,9 +1,9 @@
# Galene's administrative API
# Skald's administrative API
Galene provides an HTTP-based API that can be used to create groups and
Skald provides an HTTP-based API that can be used to create groups and
users. For example, in order to create a group, a client may do
PUT /galene-api/v0/.groups/groupname/
PUT /skald-api/v0/.groups/groupname/
Content-Type: application/json
If-None-Match: *
@@ -11,12 +11,12 @@ The `If-None-Match` header avoids overwriting an existing group.
In order to edit a group definition, a client first does
GET /galene-api/v0/.groups/groupname/
GET /skald-api/v0/.groups/groupname/
This yields the group definition and an entity tag (in the ETag header).
The client then modifies the group defintion, and does
PUT /galene-api/v0/.groups/groupname/
PUT /skald-api/v0/.groups/groupname/
If-Match: "abcd"
where "abcd" is the entity tag returned by the GET request. If the group
@@ -27,13 +27,13 @@ in the case of a concurrent modification.
## Endpoints
The API is located under `/galene-api/v0/`. The `/v0/` is a version number,
The API is located under `/skald-api/v0/`. The `/v0/` is a version number,
and will be incremented if we ever find out that the current API cannot be
extended in a backwards compatible manner.
### Statistics
/galene-api/v0/.stats
/skald-api/v0/.stats
Provides a number of statistics about the running server, in JSON. The
exact format is undocumented, and may change between versions. The only
@@ -41,14 +41,14 @@ allowed methods are HEAD and GET.
### List of groups
/galene-api/v0/.groups/
/skald-api/v0/.groups/
Returns a list of groups, as a JSON array. The only allowed methods are
HEAD and GET.
### Group definition
/galene-api/v0/.groups/groupname
/skald-api/v0/.groups/groupname
Contains a "sanitised" group definition in JSON format, analogous to the
on-disk format but without any user definitions or cryptographic keys.
@@ -57,7 +57,7 @@ content-type is `application/json`.
### Authentication keys
/galene-api/v0/.groups/groupname/.keys
/skald-api/v0/.groups/groupname/.keys
Contains the keys used for validation of stateless tokens, encoded as
a JSON key set (RFC 7517). Allowed methods are PUT and DELETE. The only
@@ -65,16 +65,16 @@ accepted content-type is `application/jwk-set+json`.
### List of users
/galene-api/v0/.groups/groupname/.users/
/skald-api/v0/.groups/groupname/.users/
Returns a list of users, as a JSON array. The only allowed methods are
HEAD and GET.
### User definitions
/galene-api/v0/.groups/groupname/.users/username
/galene-api/v0/.groups/groupname/.empty-user
/galene-api/v0/.groups/groupname/.wildcard-user
/skald-api/v0/.groups/groupname/.users/username
/skald-api/v0/.groups/groupname/.empty-user
/skald-api/v0/.groups/groupname/.wildcard-user
Contains a "sanitised" user definition (without any passwords), a JSON
object with a single field `permissions`. The entries `.empty-user` and
@@ -84,9 +84,9 @@ only accepted content-type is `application/json`.
### Passwords
/galene-api/v0/.groups/groupname/.users/username/.password
/galene-api/v0/.groups/groupname/.empty-user/.password
/galene-api/v0/.groups/groupname/.wildcard-user/.password
/skald-api/v0/.groups/groupname/.users/username/.password
/skald-api/v0/.groups/groupname/.empty-user/.password
/skald-api/v0/.groups/groupname/.wildcard-user/.password
Contains the password of a given user. The PUT method takes a full
password definition, identical to what can appear in the `"password"`
@@ -97,7 +97,7 @@ POST.
### Wildcard user
/galene-api/v0/.groups/groupname/.wildcard-user
/skald-api/v0/.groups/groupname/.wildcard-user
Contains a dictionary defining the wildcard user, in the same format as
the dictionary defining an ordinary user. Allowed methods are HEAD, GET,
@@ -105,14 +105,14 @@ PUT and DELETE.
### Wildcard user password
/galene-api/v0/.groups/groupname/.wildcard-user/.password
/skald-api/v0/.groups/groupname/.wildcard-user/.password
This is analogous to the password of an ordinary user. Allowed methods
are PUT, POST and DELETE.
### List of stateful tokens
/galene-api/v0/.groups/groupname/.users/username/.tokens/
/skald-api/v0/.groups/groupname/.users/username/.tokens/
GET returns the list of stateful tokens, as a JSON array. POST creates
a new token, and returns its name in the `Location` header. Allowed
@@ -120,7 +120,7 @@ methods are HEAD, GET and POST.
### Stateful token
/galene-api/v0/.groups/groupname/.users/username/.tokens/token
/skald-api/v0/.groups/groupname/.users/username/.tokens/token
The full contents of a single token, in JSON. The exact format may change
between versions, so a client should first GET a token, update one or more
+3 -3
View File
@@ -4,11 +4,11 @@ The frontend is written in JavaScript and is split into two files:
- `protocol.js` contains the low-level functions that interact with the
server;
- `galene.js` contains the user interface.
- `skald.js` contains the user interface.
A simpler example client can be found in the directory `static/example`.
A new frontend may either implement Galène's client-server protocol from
A new frontend may either implement Skald's client-server protocol from
scratch, or it may use the functionality of `protocol.js`. This document
documents the latter approach.
@@ -197,7 +197,7 @@ audio energy (the square of the volume) for streams in the down direction.
# Peer-to-peer file transfer
Galene's client allows users to transfer files during a meeting. The
Skald's client allows users to transfer files during a meeting. The
protocol is peer-to-peer: the clients exchange network parameters and
cryptographic keys through the server (over messages of type
`usermessage`), but all file transfer is performed directly between the
+62 -62
View File
@@ -1,8 +1,8 @@
# Galene installation instructions
# Skald installation instructions
## Basic installation
### Build the Galene binary
### Build the Skald binary
Say:
@@ -28,8 +28,8 @@ CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags='-s -w'
### Optional: install libraries for background blur
Galene's client uses Google's MediaPipe library to implement background
blur. This library is optional, and if it is absent, Galene will
Skald's client uses Google's MediaPipe library to implement background
blur. This library is optional, and if it is absent, Skald will
disable the menu entries for background blur.
Optionally install Google's MediaPipe library:
@@ -53,8 +53,8 @@ If you don't have `wget` on your system, try using `curl -O` instead.
### Deploy to your server
The following instructions assume that your server is called
`galene.example.org` and that you have already created a dedicated user
called `galene`.
`skald.example.org` and that you have already created a dedicated user
called `skald`.
First, make sure that the `groups` and `data` directories exist:
@@ -62,37 +62,37 @@ First, make sure that the `groups` and `data` directories exist:
mkdir groups data
```
Now copy the `galene` binary, and the directories `static`, `data` and
Now copy the `skald` binary, and the directories `static`, `data` and
`groups` to the server:
```sh
rsync -a galene static data groups galene@galene.example.org:
rsync -a skald static data groups skald@skald.example.org:
```
If you don't have a TLS certificate, Galene will generate a self-signed
If you don't have a TLS certificate, Skald will generate a self-signed
certificate (and print a warning to the logs). If you have a certificate,
install it in the files `data/cert.pem` and `data/key.pem`:
```sh
ssh galene@galene.example.org
sudo cp /etc/letsencrypt/live/galene.example.org/fullchain.pem data/cert.pem
sudo cp /etc/letsencrypt/live/galene.example.org/privkey.pem data/key.pem
sudo chown galene:galene data/*.pem
ssh skald@skald.example.org
sudo cp /etc/letsencrypt/live/skald.example.org/fullchain.pem data/cert.pem
sudo cp /etc/letsencrypt/live/skald.example.org/privkey.pem data/key.pem
sudo chown skald:skald data/*.pem
chmod go-rw data/key.pem
```
Since certificates are regularly rotated, this should be done in a monthly
cron job (or a *SystemD* timer unit, if you're feeling particularly kinky).
### Run Galene on the server
### Run Skald on the server
Arrange to run the binary on the server. If you never reboot your server,
just do:
```sh
ssh galene@galene.example.org
ssh skald@skald.example.org
ulimit -n 65536
nohup ./galene &
nohup ./skald &
```
If you are using *runit*, use a script like the following:
@@ -100,58 +100,58 @@ If you are using *runit*, use a script like the following:
```sh
#!/bin/sh
exec 2>&1
cd ~galene
cd ~skald
ulimit -n 65536
exec setuidgid galene ./galene
exec setuidgid skald ./skald
```
If you are using *SystemD*, put the following in
`/etc/systemd/system/galene.service` (and then run `systemctl daemon-reload`):
`/etc/systemd/system/skald.service` (and then run `systemctl daemon-reload`):
```ini
[Unit]
Description=Galene
Description=Skald
After=network.target
[Service]
Type=simple
WorkingDirectory=/home/galene
User=galene
Group=galene
ExecStart=/home/galene/galene
WorkingDirectory=/home/skald
User=skald
Group=skald
ExecStart=/home/skald/skald
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
```
### Set up galenectl
### Set up skaldctl
There are two ways to administer a Galene instance: by manually editing
JSON files on the server, or by using the `galenectl` utility.
The `galenectl` utility is recommended, since it avoids issues with
There are two ways to administer a Skald instance: by manually editing
JSON files on the server, or by using the `skaldctl` utility.
The `skaldctl` utility is recommended, since it avoids issues with
concurrent modifications and is less error-prone than the alternative.
Build the `galenectl` utility, and copy it somewhere on your path:
Build the `skaldctl` utility, and copy it somewhere on your path:
```sh
cd galenectl
cd skaldctl
go build -ldflags='-s -w'
sudo cp galenectl /usr/local/bin/
sudo cp skaldctl /usr/local/bin/
```
Now create an administrator password, and set up galenectl:
Now create an administrator password, and set up skaldctl:
```sh
galenectl -admin-username admin initial-setup
skaldctl -admin-username admin initial-setup
```
This command creates two files: `galenectl.json` and `config.json`. The
This command creates two files: `skaldctl.json` and `config.json`. The
former is already at the right place, the latter must be copied to the
server's `data/` directory:
```sh
rsync config.json galene@galene.example.org:data/
rsync config.json skald@skald.example.org:data/
```
### Group setup
@@ -159,51 +159,51 @@ rsync config.json galene@galene.example.org:data/
Create a group:
```sh
galenectl create-group -group city-watch
skaldctl create-group -group city-watch
```
If you didn't install a TLS certificate above, you will need to run
`galenectl` with the flag `-insecure`:
`skaldctl` with the flag `-insecure`:
```sh
galenectl -insecure create-group -group city-watch
skaldctl -insecure create-group -group city-watch
```
Create an "op", a user with group moderation privileges:
```sh
galenectl create-user -group city-watch -user vimes -permissions op
skaldctl create-user -group city-watch -user vimes -permissions op
```
Set the new user's password:
```sh
galenectl set-password -group city-watch -user vimes
skaldctl set-password -group city-watch -user vimes
```
You should now be able to test your Galene installation by pointing a web
browser at <https://galene.example.org:8443/group/city-watch/>.
You should now be able to test your Skald installation by pointing a web
browser at <https://skald.example.org:8443/group/city-watch/>.
Create an ordinary user:
```sh
galenectl create-user -group city-watch -user fred
galenectl set-password -group city-watch -user fred
skaldctl create-user -group city-watch -user fred
skaldctl set-password -group city-watch -user fred
```
Check the results:
```sh
galenectl list-groups
galenectl list-users -l -group city-watch
skaldctl list-groups
skaldctl list-users -l -group city-watch
```
Type `galenectl -help`, `galenectl create-group -help`, etc. for more
Type `skaldctl -help`, `skaldctl create-group -help`, etc. for more
information.
## Advanced configuration
Galene is designed to be exposed directly to the Internet. If your server
Skald is designed to be exposed directly to the Internet. If your server
is behind a firewall or NAT router, some extra configuration is necessary.
### Running behind a firewall
@@ -229,7 +229,7 @@ of the following options:
and demultiplex the traffic in userspace.
At the time of writing, this mechanism is not quite complete, and you will
see Galene attempting to use other ports. Unless you see connection
see Skald attempting to use other ports. Unless you see connection
failures, this is nothing to worry about.
### Running behind NAT
@@ -238,23 +238,23 @@ If your server is behind NAT, then currently the only option is to use
a STUN, or, preferably, TURN server on a separate host, one that is not
behind NAT. See Section *Connectivity issues and ICE servers* below.
Galene has some support for running behind NAT without a helpful server,
Skald has some support for running behind NAT without a helpful server,
but this has not been exhaustively tested. Please see the section
"Connectivity issues and ICE servers" below.
### Running behind a reverse proxy
Galene is designed to be directly exposed to the Internet. In order to
run Galene behind a reverse proxy, you might need to make a number of
Skald is designed to be directly exposed to the Internet. In order to
run Skald behind a reverse proxy, you might need to make a number of
tweaks to your configuration.
First, you might need to inform Galene of the URL at which users connect
First, you might need to inform Skald of the URL at which users connect
(the reverse proxy's URL) by adding an entry `proxyURL` to your
`data/config.json` file:
```json
{
"proxyURL": "https://galene.example.org/"
"proxyURL": "https://skald.example.org/"
}
```
@@ -272,12 +272,12 @@ location /ws {
```
Finally, in order to avoid TLS termination issues, you may want to run
Galene over plain HTTP instead of HTTPS by using the command-line flag
Skald over plain HTTP instead of HTTPS by using the command-line flag
`-insecure`.
Note that even if you're using a reverse proxy, clients will attempt to
establish direct UDP flows with Galene and direct TCP connections to
Galene's TURN server; see the section *Configuring your firewall*
establish direct UDP flows with Skald and direct TCP connections to
Skald's TURN server; see the section *Configuring your firewall*
above.
## Connectivity issues and ICE servers
@@ -290,7 +290,7 @@ that help punching holes in well-behaved NATs, and TURN servers, that
serve as relays for traffic. TURN is a superset of STUN: no STUN server
is necessary if one or more TURN servers are available.
Galene includes an IPv4-only TURN server, which is controlled by the
Skald includes an IPv4-only TURN server, which is controlled by the
`-turn` command-line option. It has the following behaviour:
* if its value is set to the empty string `""`, then the built-in server
@@ -314,7 +314,7 @@ Galene includes an IPv4-only TURN server, which is controlled by the
If the server is not accessible from the Internet, e.g. because of NAT or
because it is behind a restrictive firewall, then you should configure
a TURN server that runs on a host that is accessible by both Galene and
a TURN server that runs on a host that is accessible by both Skald and
the clients. Disable the built-in TURN server (`-turn ""` or the default
`-turn auto`), and provide a working ICE configuration in the file
`data/ice-servers.json`. In the case of a single STUN server, it should
@@ -340,7 +340,7 @@ look like this:
"turn:turn.example.org:443",
"turn:turn.example.org:443?transport=tcp"
],
"username": "galene",
"username": "skald",
"credential": "secret"
}
]
@@ -356,7 +356,7 @@ that, then the `ice-servers.json` file should look like this:
"turn:turn.example.org:443",
"turn:turn.example.org:443?transport=tcp"
],
"username": "galene",
"username": "skald",
"credential": "secret",
"credentialType": "hmac-sha1"
}
@@ -365,5 +365,5 @@ that, then the `ice-servers.json` file should look like this:
For redundancy, you may set up multiple TURN servers, and ICE will use the
first one that works. If an `ice-servers.json` file is present and
Galene's built-in TURN server is enabled, then the external server will be
Skald's built-in TURN server is enabled, then the external server will be
used in preference to the built-in server.
+15 -15
View File
@@ -1,4 +1,4 @@
# Galène's protocol
# Skald's protocol
## Data structures
@@ -17,12 +17,12 @@ that do not originate messages (servers) do not need to be assigned an id.
### Stream
A stream is a set of related tracks. It is identified by an id, an opaque
string. Streams in Galène are unidirectional. A stream is carried by
string. Streams in Skald are unidirectional. A stream is carried by
exactly one peer connection (PC) (multiple streams in a single PC are not
allowed). The offerer is also the RTP sender (i.e. all tracks sent by the
offerer are of type `sendonly`).
Galène uses a symmetric, asynchronous protocol. In client-server
Skald uses a symmetric, asynchronous protocol. In client-server
usage, some messages are only sent in the client to server or in the
server to client direction.
@@ -53,7 +53,7 @@ All fields are optional except `name`, `location` and `endpoint`.
## Connecting
The client connects to the websocket at the URL obtained at the previous
step. Galene uses a symmetric, asynchronous protocol: there are no
step. Skald uses a symmetric, asynchronous protocol: there are no
requests and responses, and most messages may be sent by either peer.
## Message syntax
@@ -227,7 +227,7 @@ The field `label` is one of `camera`, `screenshare` or `video`, and will
be matched against the keys sent by the receiver in their `request` message.
The field `sdp` contains the raw SDP string (i.e. the `sdp` field of
a JSEP session description). Galène will interpret the `nack`,
a JSEP session description). Skald will interpret the `nack`,
`nack pli`, `ccm fir` and `goog-remb` RTCP feedback types, and act
accordingly.
@@ -541,13 +541,13 @@ receives the `cancel` message immediately tears down the peer connection
# Authorisation protocol
In addition to username/password authentication, Galene supports
In addition to username/password authentication, Skald supports
authentication using cryptographic tokens. Two flows are supported: using
an authentication server, where Galene's client requests a token from
an authentication server, where Skald's client requests a token from
a third-party server, and using an authentication portal, where
a third-party login portal redirects the user to Galene. Authentication
a third-party login portal redirects the user to Skald. Authentication
servers are somewhat simpler to implement, but authentication portals are
more flexible and avoid communicating the user's password to Galene's
more flexible and avoid communicating the user's password to Skald's
Javascript code.
## Authentication server
@@ -558,24 +558,24 @@ a POST request to the authorisation server URL containing in its body
a JSON dictionary of the following form:
```javascript
{
"location": "https://galene.example.org/group/groupname/",
"location": "https://skald.example.org/group/groupname/",
"username": username,
"password": password
}
```
If the user is not allowed to join the group, then the authorisation
server replies with a code of 403 ("not authorised"), and Galene will
server replies with a code of 403 ("not authorised"), and Skald will
reject the user. If the authentication server has no opinion about
whether the user is allowed to join, it replies with a code of 204 ("no
content"), and Galene will proceed with ordinary password authorisation.
content"), and Skald will proceed with ordinary password authorisation.
If the user is allowed to join, then the authorisation server replies with
a signed JWT (a "JWS") the body of which has the following form:
```javascript
{
"sub": username,
"aud": "https://galene.example.org/group/groupname/",
"aud": "https://skald.example.org/group/groupname/",
"permissions": ["present"],
"iat": now,
"exp": now + 30s,
@@ -593,10 +593,10 @@ a stateful token.
## Authentication portal
If a group's status dictionary has a non-empty `authPortal` field, Galene
If a group's status dictionary has a non-empty `authPortal` field, Skald
redirects the user agent to the URL indicated by `authPortal`. The
authentication portal performs authorisation, generates a token as above,
then redirects back to the group's URL with the token stores in a URL
query parameter named `token`:
https://galene.example.org/group/groupname/?token=eyJhbG...
https://skald.example.org/group/groupname/?token=eyJhbG...
+7 -7
View File
@@ -14,13 +14,13 @@ import (
"syscall"
"time"
"github.com/jech/galene/diskwriter"
"github.com/jech/galene/group"
"github.com/jech/galene/ice"
"github.com/jech/galene/limit"
"github.com/jech/galene/token"
"github.com/jech/galene/turnserver"
"github.com/jech/galene/webserver"
"git.stormux.org/storm/skald/diskwriter"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/ice"
"git.stormux.org/storm/skald/limit"
"git.stormux.org/storm/skald/token"
"git.stormux.org/storm/skald/turnserver"
"git.stormux.org/storm/skald/webserver"
)
func main() {
+59 -59
View File
@@ -1,15 +1,15 @@
# Galene manual
# Skald manual
Please see the file [galene-install.md][1] for installation instructions.
Please see the file [skald-install.md][1] for installation instructions.
Please see the section *Server administration* below for detailed
administration instructions.
## Usage
Galene includes a web server that, by default, listens on port 8443.
Galene is therefore accessed on URLs such as:
Skald includes a web server that, by default, listens on port 8443.
Skald is therefore accessed on URLs such as:
https://galene.example.org:8443/
https://skald.example.org:8443/
### The landing page
@@ -87,7 +87,7 @@ commands.
In order to generate an invitation link, choose the entry *Invite user* in
the group menu. This generates a link of the form
https://galene.example.org:8443/group/city-watch/?token=XXX
https://skald.example.org:8443/group/city-watch/?token=XXX
where the *XXX* part, known as the *token*, is a shared secret. Such
a link allows password-less login to the group, and may therefore be
@@ -102,7 +102,7 @@ Tokens can be created, modified, and expired using the `/invite`,
### File transfer
Galene includes a peer-to-peer, end-to-end encrypted file transfer protocol.
Skald includes a peer-to-peer, end-to-end encrypted file transfer protocol.
In order to transfer a file, click on the receiver's entry in the user
list and choose *Send file*.
@@ -131,7 +131,7 @@ file may look as follows:
```json
{
"users":{"vetinari": {"password":"lagniappe", "permissions": "admin"}},
"canonicalHost": "galene.example.org",
"canonicalHost": "skald.example.org",
"writableGroups": true
}
```
@@ -146,12 +146,12 @@ or, better, with a hashed password:
"permissions": "admin"
}
},
"canonicalHost": "galene.example.org",
"canonicalHost": "skald.example.org",
"writableGroups": true
}
```
The file is initially created using `galenectl initial-setup`, but may be
The file is initially created using `skaldctl initial-setup`, but may be
manually edited at any time (there is no need to restart the server). The
fields are as follows:
@@ -159,7 +159,7 @@ fields are as follows:
same syntax as user definitions in groups (see below), except that the
only meaningful permission is `"admin"`;
- `writableGroups`: if true, then the API used by `galenectl` can be used
- `writableGroups`: if true, then the API used by `skaldctl` can be used
to modify group definitions; if unset or false, then only read-only
access is allowed;
@@ -167,7 +167,7 @@ fields are as follows:
are allowed to access the server;
- `allowAdminOrigin` is like `allowOrigin`, but applies to the
administrative API (the one used by `galenectl`);
administrative API (the one used by `skaldctl`);
- `proxyURL`: if running behind a reverse proxy, this specifies the root
URL that will be visible outside the proxy;
@@ -180,28 +180,28 @@ fields are as follows:
## Group definitions
Groups are described by JSON files in the `groups/` directory. These
files are normally administered using the `galenectl` utility, but may
files are normally administered using the `skaldctl` utility, but may
also be edited manually (there is no need to restart the server).
### Managing groups using `galenectl`
### Managing groups using `skaldctl`
#### Creating, modifying, and deleting groups
A group is created using `galenectl create-group`:
A group is created using `skaldctl create-group`:
```sh
galenectl create-group -group city-watch
skaldctl create-group -group city-watch
```
There are a number of options to customise the behaviour of the group, see
`galenectl create-group -help` for a full list. For example, in order to
`skaldctl create-group -help` for a full list. For example, in order to
create a group that allows unrestricted creation of tokens, say:
```sh
galenectl create-group -group city-watch -unrestricted-tokens
skaldctl create-group -group city-watch -unrestricted-tokens
```
For more advanced configuration, `galenectl create-group` can be invoked
For more advanced configuration, `skaldctl create-group` can be invoked
with the `-json` flag, in which case it takes a JSON template from standard
input. The syntax of a JSON template is just like that of a group
definition file (see below), except that it must not contain the fields
@@ -209,35 +209,35 @@ definition file (see below), except that it must not contain the fields
(see the section *Group description reference* below):
```sh
echo '{"redirect": "https://galene.example.org:8443/group/city-watch/"}' | galenectl create-group -group amcw -json
echo '{"redirect": "https://skald.example.org:8443/group/city-watch/"}' | skaldctl create-group -group amcw -json
```
Groups are modified using `galenectl update-group`:
Groups are modified using `skaldctl update-group`:
```sh
galenectl update-group -group city-watch -unrestricted-tokens=false
skaldctl update-group -group city-watch -unrestricted-tokens=false
```
If a JSON template is provided to `galenectl update-group`, then it is
If a JSON template is provided to `skaldctl update-group`, then it is
merged with the existing group configuration. Entries may be deleted
by setting them to `null` in the template:
```sh
echo '{"redirect": null}' | galenectl update-group -group amcw
echo '{"redirect": null}' | skaldctl update-group -group amcw
```
A group is deleted using `galenectl delete-group`:
A group is deleted using `skaldctl delete-group`:
```sh
galenectl delete-group -group amcw
skaldctl delete-group -group amcw
```
#### Creating, modifying, and deleting users
A user entry is created with the `galenectl create-user` command:
A user entry is created with the `skaldctl create-user` command:
```sh
galenectl create-user -group city-watch -user vimes -permissions op
skaldctl create-user -group city-watch -user vimes -permissions op
```
If the `-permissions` flag is not specified, it defaults to `present`,
@@ -246,14 +246,14 @@ the group. Other useful values are `message`, which allows a user
to participate in the chat only, and `observe`, which doesn't allow any
active participation.
A user is modified using `galenectl update-user`, and deleted using
`galenectl delete-user`.
A user is modified using `skaldctl update-user`, and deleted using
`skaldctl delete-user`.
In order to be useful, a user entry needs to be assigned a password. This
is done with the `galenectl set-password` command:
is done with the `skaldctl set-password` command:
```sh
galenectl set-password -group city-watch -user vimes
skaldctl set-password -group city-watch -user vimes
```
#### The fallback user
@@ -262,15 +262,15 @@ It is sometimes useful to allow multiple users to log in using the same
password. This can be achieved by defining the *wildcard* user:
```sh
galenectl create-user -group city-watch -wildcard
galenectl set-password -group city-watch -wildcard
skaldctl create-user -group city-watch -wildcard
skaldctl set-password -group city-watch -wildcard
```
For open groups, where any user can login with any password, the wildcard
user's password is set to the password of type `wildcard`:
```sh
galenectl set-password -group city-watch -wildcard -type wildcard
skaldctl set-password -group city-watch -wildcard -type wildcard
```
See the section *Client authorisation* below for more information about
@@ -279,19 +279,19 @@ password types.
#### Automatic subgroups
It is sometimes necessary to create a large number of identical groups.
For example, the author has been using Galene to supervise computer
For example, the author has been using Skald to supervise computer
science practicals, where up to 40 students are working in groups of two.
While it is possible to automate the creation of groups by accessing
Galene's API, by scripting calls to galenectl, or by generating files
directly under `groups/`, Galene provides a facility called *automatic
Skald's API, by scripting calls to skaldctl, or by generating files
directly under `groups/`, Skald provides a facility called *automatic
subgroups* that can be used to generate groups on demand.
Automatic subgroups are enabled by setting the `"auto-subgroups"`
field in the group description:
```sh
galenectl create-group unseen-university -auto-subgroups
skaldctl create-group unseen-university -auto-subgroups
```
Whenever a user attempts to access a subgroup of `unseen-university`, for
@@ -303,13 +303,13 @@ group's operator can view the list of populated subgroups with the command
#### Managing tokens
Tokens are normally managed using the `/invite`, `/reinvite`, and `/expire`
commands in Galene's user interface, but they may also be managed using
the `galenectl` utility's `create-token`, `revoke-token`, `delete-token`
commands in Skald's user interface, but they may also be managed using
the `skaldctl` utility's `create-token`, `revoke-token`, `delete-token`
and `list-tokens` commands:
```sh
galenectl create-token -group city-watch
galenectl list-tokens -l -group city-watch
skaldctl create-token -group city-watch
skaldctl list-tokens -l -group city-watch
```
A token that is generated with the `-include-subgroups` flag applies to
@@ -317,14 +317,14 @@ the whole hierarchy rooted at the given group, including both ordinary
groups and automatically generated subgroups.
```sh
galenectl create-token -group city-watch -include-subgroups
skaldctl create-token -group city-watch -include-subgroups
```
Such a token can be attached to the root of the group hierarchy, and
therefore be valid for any group on the server:
```sh
galenectl create-token -group '' -include-subgroups
skaldctl create-token -group '' -include-subgroups
```
### Group description reference
@@ -393,7 +393,7 @@ are optional. The following fields are allowed:
A user definition is a dictionary with entries `password` and
`permission`. The value of the `password` field is either a plaintext
password, or a hashed password generated for example by the `galenectl
password, or a hashed password generated for example by the `skaldctl
hash-password` command. The value of the `permissions` field can either
be an array of individual permissions (not recommended), or one of the
following strings:
@@ -425,7 +425,7 @@ anything except Opus.
## Client Authorisation
Galene implements three authorisation methods: a username/password
Skald implements three authorisation methods: a username/password
authorisation scheme, a scheme using stateful tokens, and a mechanism
based on cryptographic tokens. The former two mechanisms are intended to
be used in standalone installations, while the cryptographic mechanism is
@@ -439,13 +439,13 @@ defined directly in the group configuration file, in the `users` and
`wildcard-user` entries. The `users` entry is a dictionary that maps user
names to user descriptions; the `wildcard-user` is a user description
that is used with usernames that don't appear in `users`. These two
entries are usually managed by the `galenectl` utility.
entries are usually managed by the `skaldctl` utility.
Every user description is a dictionary with fields `password` and
`permissions`. The `password` field may be a literal password string, or
a dictionary describing a hashed password or a wildcard. The
`permissions` field should be one of `op`, `present`, `message` or
`observe`. (An array of Galene's internal permissions is also allowed,
`observe`. (An array of Skald's internal permissions is also allowed,
but this is not recommended, since internal permissions may vary from
version to version.)
@@ -479,7 +479,7 @@ allows any username with any password.
### Hashed passwords
For security reasons, passwords are usually hashed before being stored in
group descriptions (in fact, the `galenectl` utility does not even support
group descriptions (in fact, the `skaldctl` utility does not even support
storing plaintext passwords). A hashed password is represented as a JSON
dictionary with a field `type` and a number of type-specific fields.
@@ -501,14 +501,14 @@ A user entry with a hashed password looks like this:
```
Hashed passwords are normally generated transparently to the user by the
`galenectl set-password` command. When editing group description files
manually, hashed passwords can be generated with the `galenectl hash-password`
`skaldctl set-password` command. When editing group description files
manually, hashed passwords can be generated with the `skaldctl hash-password`
utility.
### Stateful tokens
Stateful tokens are created by the `/invite` command in the Galene user
interface or by the `galenectl create-token` command; see the section
Stateful tokens are created by the `/invite` command in the Skald user
interface or by the `skaldctl create-token` command; see the section
*Managing tokens* above. They are stored in the file
`data/var/tokens.jsonl`, which, on most filesystems, can be safely backed
up without stopping the server.
@@ -516,7 +516,7 @@ up without stopping the server.
### Cryptographic tokens
In many cases, it is useful to delegate authorisation decisions to a third
party, such as an LDAP or OAuth2 client. Galene implements delegation of
party, such as an LDAP or OAuth2 client. Skald implements delegation of
authorisation decisions using cryptographic tokens generated by a third
party known as an *authorisation server*. Two authorisation servers are
available: an [LDAP client][2], and a [sample server written in Python][3].
@@ -564,9 +564,9 @@ Alternatively, the group file may specify an authorisation portal using
the `"authPortal"` key. If an authorisation portal is specified, then the
default client will redirect initial client connections to the
authorisation portal. The authorisation portal is expected to authorise
the client and then redirect it to Galene with the `username` and `token`
the client and then redirect it to Skald with the `username` and `token`
query parameters set.
[1]: <galene-install.md>
[2]: <https://github.com/jech/galene-imap/>
[3]: <https://github.com/jech/galene-sample-auth-server/>
[1]: <skald-install.md>
[2]: <https://git.stormux.org/storm/skald-imap/>
[3]: <https://git.stormux.org/storm/skald-sample-auth-server/>
+38 -38
View File
@@ -26,8 +26,8 @@ import (
"golang.org/x/crypto/pbkdf2"
"golang.org/x/term"
"github.com/jech/galene/group"
"github.com/jech/galene/token"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/token"
)
type configuration struct {
@@ -55,7 +55,7 @@ var commands = map[string]command{
},
"initial-setup": {
command: initialSetupCmd,
description: "initial setup of Galene and galenectl",
description: "initial setup of Skald and skaldctl",
},
"set-password": {
command: setPasswordCmd,
@@ -125,8 +125,8 @@ func main() {
log.Fatalf("UserConfigDir: %v", err)
}
configFile = filepath.Join(
filepath.Join(configdir, "galene"),
"galenectl.json",
filepath.Join(configdir, "skald"),
"skaldctl.json",
)
flag.Usage = func() {
@@ -332,15 +332,15 @@ func hashPasswordCmd(cmdname string, args []string) {
}
func initialSetupCmd(cmdname string, args []string) {
var galeneConfigFn string
var skaldConfigFn string
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname,
"%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.StringVar(&galeneConfigFn, "config", "config.json",
"Galene configuration `file`")
cmd.StringVar(&skaldConfigFn, "config", "config.json",
"Skald configuration `file`")
cmd.Parse(args)
if cmd.NArg() != 0 {
@@ -373,24 +373,24 @@ func initialSetupCmd(cmdname string, args []string) {
}
}
galeneConfig, err := os.OpenFile(galeneConfigFn,
skaldConfig, err := os.OpenFile(skaldConfigFn,
os.O_WRONLY|os.O_CREATE|os.O_CREATE|os.O_EXCL,
0600)
if err != nil {
log.Fatalf("Create %v: %v", galeneConfigFn, err)
log.Fatalf("Create %v: %v", skaldConfigFn, err)
}
galenectlConfig, err := os.OpenFile(configFile,
skaldctlConfig, err := os.OpenFile(configFile,
os.O_WRONLY|os.O_CREATE|os.O_CREATE|os.O_EXCL,
0600)
if err != nil {
galeneConfig.Close()
os.Remove(galeneConfigFn)
log.Fatalf("Create %v: %v", galeneConfigFn, err)
skaldConfig.Close()
os.Remove(skaldConfigFn)
log.Fatalf("Create %v: %v", skaldConfigFn, err)
}
defer galeneConfig.Close()
defer galenectlConfig.Close()
defer skaldConfig.Close()
defer skaldctlConfig.Close()
var users map[string]group.UserDescription
if adminPassword != "" {
@@ -416,11 +416,11 @@ func initialSetupCmd(cmdname string, args []string) {
Users: users,
}
encoder := json.NewEncoder(galeneConfig)
encoder := json.NewEncoder(skaldConfig)
encoder.SetIndent("", " ")
err = encoder.Encode(&config)
if err != nil {
log.Fatalf("Encode %v: %v", galeneConfigFn, err)
log.Fatalf("Encode %v: %v", skaldConfigFn, err)
}
ctlConfig := configuration{
@@ -430,14 +430,14 @@ func initialSetupCmd(cmdname string, args []string) {
AdminToken: adminToken,
}
ctlEncoder := json.NewEncoder(galenectlConfig)
ctlEncoder := json.NewEncoder(skaldctlConfig)
ctlEncoder.SetIndent("", " ")
err = ctlEncoder.Encode(&ctlConfig)
if err != nil {
log.Fatalf("Encode %v: %v", configFile, err)
}
fmt.Printf("The file %v has been created. ", galeneConfigFn)
fmt.Printf("The file %v has been created. ", skaldConfigFn)
fmt.Printf("Please copy it into your server's\n\"data/\" directory.\n")
fmt.Printf("The file %v has been created. ", configFile)
fmt.Printf("It contains the administrator's\npassword in cleartext, ")
@@ -655,12 +655,12 @@ func setPasswordCmd(cmdname string, args []string) {
var u string
if wildcard {
u, err = url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.groups", groupname,
".wildcard-user/.password",
)
} else {
u, err = url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.groups", groupname,
".users", username, ".password",
)
}
@@ -710,12 +710,12 @@ func deletePasswordCmd(cmdname string, args []string) {
var err error
if wildcard {
u, err = url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.groups", groupname,
".wildcard-user/.password",
)
} else {
u, err = url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.groups", groupname,
".users", username, ".password",
)
}
@@ -828,7 +828,7 @@ func createGroupCmd(cmdname string, args []string) {
}
u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.groups", groupname,
)
if err != nil {
log.Fatalf("Build URL: %v", err)
@@ -874,7 +874,7 @@ func deleteGroupCmd(cmdname string, args []string) {
}
u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.groups", groupname,
)
if err != nil {
log.Fatalf("Build URL: %v", err)
@@ -913,7 +913,7 @@ func updateGroupCmd(cmdname string, args []string) {
}
u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.groups", groupname,
)
if err != nil {
log.Fatalf("Build URL: %v", err)
@@ -1025,7 +1025,7 @@ func listUsersCmd(cmdname string, args []string) {
os.Exit(1)
}
u, err := url.JoinPath(serverURL, "/galene-api/v0/.groups/", groupname,
u, err := url.JoinPath(serverURL, "/skald-api/v0/.groups/", groupname,
".users/")
if err != nil {
log.Fatalf("Build URL: %v", err)
@@ -1072,12 +1072,12 @@ func listUsersCmd(cmdname string, args []string) {
func userURL(wildcard bool, groupname, username string) (string, error) {
if wildcard {
return url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.groups", groupname,
".wildcard-user",
)
}
return url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.groups", groupname,
".users", username,
)
}
@@ -1250,12 +1250,12 @@ func deleteUserCmd(cmdname string, args []string) {
var err error
if wildcard {
u, err = url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.groups", groupname,
".wildcard-user",
)
} else {
u, err = url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.groups", groupname,
".users", username,
)
}
@@ -1288,7 +1288,7 @@ func showGroupCmd(cmdname string, args []string) {
log.Fatal("Option \"-group\" is required.")
}
u, err := url.JoinPath(serverURL, "/galene-api/v0/.groups/", groupname)
u, err := url.JoinPath(serverURL, "/skald-api/v0/.groups/", groupname)
if err != nil {
log.Fatalf("Build URL: %v", err)
}
@@ -1315,7 +1315,7 @@ func listGroupsCmd(cmdname string, args []string) {
cmd.Parse(args)
patterns := cmd.Args()
u, err := url.JoinPath(serverURL, "/galene-api/v0/.groups/")
u, err := url.JoinPath(serverURL, "/skald-api/v0/.groups/")
if err != nil {
log.Fatalf("Build URL: %v", err)
}
@@ -1365,7 +1365,7 @@ func listTokensCmd(cmdname string, args []string) {
}
u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups/", groupname.value, ".tokens/",
serverURL, "/skald-api/v0/.groups/", groupname.value, ".tokens/",
)
if err != nil {
@@ -1469,7 +1469,7 @@ func createTokenCmd(cmdname string, args []string) {
}
u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups/", groupname.value, ".tokens/",
serverURL, "/skald-api/v0/.groups/", groupname.value, ".tokens/",
)
if err != nil {
log.Fatalf("Build URL: %v", err)
@@ -1505,7 +1505,7 @@ func revokeTokenCmd(cmdname string, args []string) {
}
u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups/", groupname.value,
serverURL, "/skald-api/v0/.groups/", groupname.value,
".tokens", token,
)
if err != nil {
@@ -1544,7 +1544,7 @@ func deleteTokenCmd(cmdname string, args []string) {
}
u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups/", groupname.value,
serverURL, "/skald-api/v0/.groups/", groupname.value,
".tokens", token,
)
if err != nil {
@@ -4,7 +4,7 @@ import (
"encoding/json"
"testing"
"github.com/jech/galene/group"
"git.stormux.org/storm/skald/group"
)
func TestMakePassword(t *testing.T) {
+1 -1
View File
@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/common.css">
<link rel="stylesheet" href="/change-password.css">
<link rel="stylesheet" type="text/css" href="/galene.css"/>
<link rel="stylesheet" type="text/css" href="/skald.css"/>
<link rel="author" href="https://www.irif.fr/~jch/"/>
</head>
+1 -1
View File
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Galène client example</title>
<title>Skald client example</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="author" href="https://www.irif.fr/~jch/"/>
</head>
+1 -1
View File
@@ -1,4 +1,4 @@
/* Galene client example. */
/* Skald client example. */
/**
* The main function.
+4 -4
View File
@@ -1,19 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Galène</title>
<title>Skald</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/common.css">
<link rel="stylesheet" href="/mainpage.css">
<link rel="stylesheet" type="text/css" href="/galene.css"/>
<link rel="stylesheet" type="text/css" href="/skald.css"/>
<link rel="author" href="https://www.irif.fr/~jch/"/>
</head>
<body>
<div class="home">
<h1 id="title" class="navbar-brand">Galène</h1>
<h1 id="title" class="navbar-brand">Skald</h1>
<form id="groupform">
<label for="group">Group:</label>
@@ -30,7 +30,7 @@
</div>
</div>
<footer class="signature">
<p><a href="https://galene.org/">Galène</a> by <a href="https://www.irif.fr/~jch/" rel="author">Juliusz Chroboczek</a></p>
<p><a href="https://skald.org/">Skald</a> by <a href="https://www.irif.fr/~jch/" rel="author">Juliusz Chroboczek</a></p>
</footer>
<script src="/mainpage.js" defer></script>
+13 -13
View File
@@ -159,7 +159,7 @@ async function updateObject(url, values, etag) {
* @returns {Promise<Array<string>>}
*/
async function listGroups() {
return await listObjects('/galene-api/v0/.groups/');
return await listObjects('/skald-api/v0/.groups/');
}
/**
@@ -170,7 +170,7 @@ async function listGroups() {
* @returns {Promise<Object>}
*/
async function getGroup(group, etag) {
return await getObject(`/galene-api/v0/.groups/${group}`, etag);
return await getObject(`/skald-api/v0/.groups/${group}`, etag);
}
/**
@@ -180,7 +180,7 @@ async function getGroup(group, etag) {
* @param {Object} [values]
*/
async function createGroup(group, values) {
return await createObject(`/galene-api/v0/.groups/${group}`, values);
return await createObject(`/skald-api/v0/.groups/${group}`, values);
}
/**
@@ -190,7 +190,7 @@ async function createGroup(group, values) {
* @param {string} [etag]
*/
async function deleteGroup(group, etag) {
return await deleteObject(`/galene-api/v0/.groups/${group}`, etag);
return await deleteObject(`/skald-api/v0/.groups/${group}`, etag);
}
/**
@@ -203,7 +203,7 @@ async function deleteGroup(group, etag) {
* @param {string} [etag]
*/
async function updateGroup(group, values, etag) {
return await updateObject(`/galene-api/v0/.groups/${group}`, values);
return await updateObject(`/skald-api/v0/.groups/${group}`, values);
}
/**
@@ -213,7 +213,7 @@ async function updateGroup(group, values, etag) {
* @returns {Promise<Array<string>>}
*/
async function listUsers(group) {
return await listObjects(`/galene-api/v0/.groups/${group}/.users/`);
return await listObjects(`/skald-api/v0/.groups/${group}/.users/`);
}
/**
@@ -225,11 +225,11 @@ async function listUsers(group) {
*/
function userURL(group, user, wildcard) {
if(wildcard)
return `/galene-api/v0/.groups/${group}/.wildcard-user`;
return `/skald-api/v0/.groups/${group}/.wildcard-user`;
else if(user === "")
return `/galene-api/v0/.groups/${group}/.empty-user`;
return `/skald-api/v0/.groups/${group}/.empty-user`;
else
return `/galene-api/v0/.groups/${group}/.users/${user}`
return `/skald-api/v0/.groups/${group}/.users/${user}`
}
/**
@@ -320,7 +320,7 @@ async function setPassword(group, user, wildcard, password, oldpassword) {
* @returns {Promise<Array<string>>}
*/
async function listTokens(group) {
return await listObjects(`/galene-api/v0/.groups/${group}/.tokens/`);
return await listObjects(`/skald-api/v0/.groups/${group}/.tokens/`);
}
/**
@@ -332,7 +332,7 @@ async function listTokens(group) {
* @returns {Promise<Object>}
*/
async function getToken(group, token, etag) {
return await getObject(`/galene-api/v0/.groups/${group}/.tokens/${token}`,
return await getObject(`/skald-api/v0/.groups/${group}/.tokens/${token}`,
etag);
}
@@ -353,7 +353,7 @@ async function createToken(group, template) {
}
let r = await fetch(
`/galene-api/v0/.groups/${group}/.tokens/`,
`/skald-api/v0/.groups/${group}/.tokens/`,
options);
if(!r.ok)
throw httpError(r);
@@ -373,6 +373,6 @@ async function updateToken(group, token, etag) {
if(!token.token)
throw new Error("Unnamed token");
return await updateObject(
`/galene-api/v0/.groups/${group}/.tokens/${token.token}`,
`/skald-api/v0/.groups/${group}/.tokens/${token.token}`,
token, etag);
}
+1 -1
View File
@@ -1619,7 +1619,7 @@ function TransferredFile(sc, userid, id, up, username, name, mimetype, size) {
* The negotiated file-transfer protocol version.
*
* This is the version of the file-transfer protocol, and is not
* necessarily equal to the version of the Galene protocol used by the
* necessarily equal to the version of the Skald protocol used by the
* server connection.
*
* @type {string}
+2 -2
View File
@@ -1109,7 +1109,7 @@ legend {
border-right: 1px solid #dcdcdc;
}
#left-sidebar .galene-header {
#left-sidebar .skald-header {
display: inline-block;
}
@@ -1127,7 +1127,7 @@ header .collapse:hover {
color: #c2a4e0;
}
.galene-header {
.skald-header {
font-size: 1.3rem;
font-weight: 900;
color: #dbd9d9;
+5 -5
View File
@@ -1,12 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Galène</title>
<title>Skald</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="ScreenOrientation" content="autoRotate:disabled">
<link rel="stylesheet" type="text/css" href="/common.css"/>
<link rel="stylesheet" type="text/css" href="/galene.css"/>
<link rel="stylesheet" type="text/css" href="/skald.css"/>
<link rel="author" href="https://www.irif.fr/~jch/"/>
<link rel="stylesheet" type="text/css" href="/third-party/fontawesome/css/fontawesome.min.css"/>
<link rel="stylesheet" type="text/css" href="/third-party/fontawesome/css/solid.min.css"/>
@@ -20,7 +20,7 @@
<div class="row full-height">
<nav id="left-sidebar">
<div class="users-header">
<div class="galene-header">Galène</div>
<div class="skald-header">Skald</div>
</div>
<div class="header-sep"></div>
<div id="users"></div>
@@ -32,7 +32,7 @@
<button class="collapse" aria-label="Toggle left panel" aria-expanded="true" id="sidebarCollapse">
<i class="fas fa-align-left" aria-hidden="true"></i>
</button>
<h1 id="title" class="header-title">Galène</h1>
<h1 id="title" class="header-title">Skald</h1>
</div>
<ul class="nav-menu">
@@ -312,6 +312,6 @@
<script src="/protocol.js" defer></script>
<script src="/third-party/toastify/toastify.js" defer></script>
<script src="/third-party/contextual/contextual.js" defer></script>
<script src="/galene.js" defer></script>
<script src="/skald.js" defer></script>
</body>
</html>
+2 -2
View File
@@ -2869,7 +2869,7 @@ function setTitle(title) {
if(title)
set(title);
else
set('Galène');
set('Skald');
}
/**
@@ -3245,7 +3245,7 @@ function gotUserMessage(id, dest, username, time, privileged, kind, error, messa
if('share' in navigator) {
try {
navigator.share({
title: `Invitation to Galene group ${message.group}`,
title: `Invitation to Skald group ${message.group}`,
text: f[0],
url: f[1],
});
+2 -2
View File
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Galène statistics</title>
<title>Skald statistics</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/common.css">
@@ -10,7 +10,7 @@
<body>
<h1 id="title" class="navbar-brand">Galène statistics</h1>
<h1 id="title" class="navbar-brand">Skald statistics</h1>
<table id="stats-table"></table>
<script src="/stats.js" defer></script>
</body>
+1 -1
View File
@@ -25,7 +25,7 @@ async function listStats() {
let l;
try {
let r = await fetch('/galene-api/v0/.stats');
let r = await fetch('/skald-api/v0/.stats');
if(!r.ok)
throw new Error(`${r.status} ${r.statusText}`);
l = await r.json();
+1 -1
View File
@@ -15,7 +15,7 @@
},
"files": [
"protocol.js",
"galene.js",
"skald.js",
"management.js",
"example/example.js"
]
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"sort"
"time"
"github.com/jech/galene/group"
"git.stormux.org/storm/skald/group"
)
type GroupStats struct {
+26 -26
View File
@@ -26,12 +26,12 @@
# PHASE 0: BASELINE INVENTORY AND GUARDRAILS
# ============================================================================
- Record the current upstream/fork point in CHANGES or a new fork note
- Capture current baseline: `go test ./...` result before behavior changes
- Capture current baseline: `go build ./...` result before behavior changes
X Record the current upstream/fork point in CHANGES or a new fork note
X Capture current baseline: `go test ./...` result before behavior changes
X Capture current baseline: `go build ./...` result before behavior changes
- Inventory generated/static assets, including static/example and third-party assets
- Inventory all public URLs: /group/, /public-groups.json, /recordings/, /galene-api/
- Inventory all config/data paths: groups/, data/, recordings/, galenectl.json
- Inventory all config/data paths: groups/, data/, recordings/, skaldctl.json
- Inventory all Galene binary/service names in docs, scripts, examples, and tests
- Decide whether existing stored data will be migrated manually or treated as incompatible
- Keep the old LICENSE intact and preserve required attribution
@@ -40,23 +40,23 @@
# PHASE 1: FOUNDATION AND TOTAL REBRANDING
# ============================================================================
- Rename Go module: github.com/jech/galene -> git.stormux.org/storm/skald
- Update all internal Go import paths across source and tests
- Rename galene.go -> skald.go and keep the main package entrypoint working
- Rename galenectl/ directory and binary to skaldctl/
- Rename static/galene.css -> static/skald.css
- Rename static/galene.js -> static/skald.js
- Rename static/galene.html -> static/skald.html
- Update webserver static serving to load skald.html and renamed assets
- Update all HTML, JS, CSS, docs, tests, and examples that reference renamed assets
- Rename galene-*.md docs to skald-*.md
- Replace "Galene", "Galène", and "galene" with "Skald" and "skald" where they refer to this project
- Keep historical upstream references only where they are attribution, changelog history, or license context
- Rename default binary, service, and install examples from galene to skald
- Rename galenectl config file to skaldctl.json
- Update CHANGES with Skald fork point and first Skald development section
- Verify `go test ./...` compiles after the pure project rename pass
- Search for remaining Galene names and classify each survivor as attribution/history or a bug
X Rename Go module: github.com/jech/galene -> git.stormux.org/storm/skald
X Update all internal Go import paths across source and tests
X Rename galene.go -> skald.go and keep the main package entrypoint working
X Rename galenectl/ directory and binary to skaldctl/
X Rename static/galene.css -> static/skald.css
X Rename static/galene.js -> static/skald.js
X Rename static/galene.html -> static/skald.html
X Update webserver static serving to load skald.html and renamed assets
X Update all HTML, JS, CSS, docs, tests, and examples that reference renamed assets
X Rename galene-*.md docs to skald-*.md
X Replace "Galene", "Galène", and "galene" with "Skald" and "skald" where they refer to this project
X Keep historical upstream references only where they are attribution, changelog history, or license context
X Rename default binary, service, and install examples from galene to skald
X Rename galenectl config file to skaldctl.json
X Update CHANGES with Skald fork point and first Skald development section
X Verify `go test ./...` compiles after the pure project rename pass
X Search for remaining Galene names and classify each survivor as attribution/history or a bug
# ============================================================================
# PHASE 2: HALL TERMINOLOGY AND DATA MODEL
@@ -92,13 +92,13 @@
- Update static/protocol.js public API: ServerConnection.join and related callbacks
- Update static/management.js to use /skald-api/v0/.halls/
- Update static/mainpage.js to open /hall/ URLs and fetch /public-halls.json
- Update stats endpoint naming only if it exposes Galene/group terminology
- Update stats endpoint naming only if it exposes Skald/group terminology
- Update skaldctl command names: create-hall, update-hall, delete-hall, list-halls
- Update skaldctl flags: -hall, -include-subhalls, -auto-subhalls, etc.
- Update skaldctl help text and error messages
- Update API and webserver tests for /hall/ and /skald-api/
- Verify old /group/, /public-groups.json, /galene-api/, and .groups paths fail clearly
- Verify protocol backward compatibility with Galene is intentionally broken
- Verify protocol backward compatibility with Skald is intentionally broken
# ============================================================================
# PHASE 4: BACKEND - STRIP VIDEO COMPLETELY
@@ -189,7 +189,7 @@
- Update TURN/STUN docs only where project name or URLs changed
- Update Docker/container or packaging files if present later
- Update .gitignore for renamed binaries, config files, and generated artifacts
- Verify there are no stale install commands that build or run galene/galenectl
- Verify there are no stale install commands that build or run skald/skaldctl
# ============================================================================
# PHASE 8: DOCUMENTATION
@@ -204,7 +204,7 @@
- Remove video, camera, screenshare, simulcast, SVC, and WebM recording instructions
- Add Ogg Opus recording documentation
- Document that incoming video is rejected by design
- Document that Galene protocol/API compatibility is intentionally broken
- Document that Skald protocol/API compatibility is intentionally broken
- Document any required external ffmpeg dependency if the recorder uses ffmpeg
- Check docs for old real-person sample usernames and replace with placeholders
@@ -235,4 +235,4 @@
- Test: play the recording in a standard audio player
- Test: restart server and verify halls/config load from the new paths
- Remove obsolete files that are no longer served or built
- Do a final diff review for accidental compatibility shims or stale Galene naming
- Do a final diff review for accidental compatibility shims or stale Skald naming
+64 -12
View File
@@ -6,6 +6,8 @@ import (
"encoding/json"
"reflect"
"testing"
"github.com/golang-jwt/jwt/v5"
)
func TestJWKHS256(t *testing.T) {
@@ -113,6 +115,17 @@ func TestMatchGroup(t *testing.T) {
}
}
func makeTestJWT(t *testing.T, key any, claims jwt.MapClaims) string {
t.Helper()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
value, err := token.SignedString(key)
if err != nil {
t.Fatalf("SignedString: %v", err)
}
return value
}
func TestJWT(t *testing.T) {
key := `{"alg":"HS256","k":"H7pCkktUl5KyPCZ7CKw09y1j460tfIv4dRcS1XstUKY","key_ops":["sign","verify"],"kty":"oct"}`
var k map[string]interface{}
@@ -122,17 +135,35 @@ func TestJWT(t *testing.T) {
}
keys := []map[string]interface{}{k}
signingKey, err := ParseKey(k)
if err != nil {
t.Fatalf("ParseKey: %v", err)
}
john := "john"
jack := "jack"
baseClaims := jwt.MapClaims{
"aud": "https://skald.org:8443/group/auth/",
"permissions": []string{"present"},
"iat": int64(1645310294),
"exp": int64(2906750294),
"iss": "http://localhost:1234/",
}
goodToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDI5NCwiZXhwIjoyOTA2NzUwMjk0LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0.6xXpgBkBMn4PSBpnwYHb-gRn_Q97Yq9DoKkAf2_6iwc"
goodToken := makeTestJWT(t, signingKey, jwt.MapClaims{
"sub": "john",
"aud": baseClaims["aud"],
"permissions": baseClaims["permissions"],
"iat": baseClaims["iat"],
"exp": baseClaims["exp"],
"iss": baseClaims["iss"],
})
tok, err := Parse(goodToken, keys)
if err != nil {
t.Errorf("Couldn't parse goodToken: %v", err)
}
username, perms, err := tok.Check("galene.org:8443", "auth", &john)
username, perms, err := tok.Check("skald.org:8443", "auth", &john)
if err != nil {
t.Errorf("goodToken is not valid: %v", err)
}
@@ -140,7 +171,7 @@ func TestJWT(t *testing.T) {
t.Errorf("Expected john, [present], got %v %v", username, perms)
}
username, perms, err = tok.Check("galene.org:8443", "auth", &jack)
username, perms, err = tok.Check("skald.org:8443", "auth", &jack)
if err != nil {
t.Errorf("goodToken is not valid: %v", err)
}
@@ -153,28 +184,35 @@ func TestJWT(t *testing.T) {
t.Errorf("goodToken is not valid: %v", err)
}
_, _, err = tok.Check("galene.org", "auth", &john)
_, _, err = tok.Check("skald.org", "auth", &john)
if err == nil {
t.Errorf("goodToken is valid for wrong hostname")
}
_, _, err = tok.Check("galene.org:8443", "not-auth", &john)
_, _, err = tok.Check("skald.org:8443", "not-auth", &john)
if err == nil {
t.Errorf("goodToken is valid for wrong group")
}
_, _, err = tok.Check("galene.org:8443", "auth/subgroup", &john)
_, _, err = tok.Check("skald.org:8443", "auth/subgroup", &john)
if err == nil {
t.Errorf("goodToken is valid for subgroup")
}
emptySubToken := "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.xwpHIRzKAIgiHKG1pVQyZlXcolmvRwNvBm6FN2gTwZw"
emptySubToken := makeTestJWT(t, signingKey, jwt.MapClaims{
"sub": "",
"aud": baseClaims["aud"],
"permissions": baseClaims["permissions"],
"iat": baseClaims["iat"],
"exp": baseClaims["exp"],
"iss": baseClaims["iss"],
})
tok, err = Parse(emptySubToken, keys)
if err != nil {
t.Errorf("Couldn't parse emptySubToken: %v", err)
}
username, perms, err = tok.Check("galene.org:8443", "auth", &jack)
username, perms, err = tok.Check("skald.org:8443", "auth", &jack)
if err != nil {
t.Errorf("anonymousToken is not valid: %v", err)
}
@@ -182,13 +220,19 @@ func TestJWT(t *testing.T) {
t.Errorf("Expected \"\", [present], got %v %v", username, perms)
}
noSubToken := "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.7LvoZEKPNVvsRe8SjLxmKa1TgjTA4ZQo2LMPJSXl-ro"
noSubToken := makeTestJWT(t, signingKey, jwt.MapClaims{
"aud": baseClaims["aud"],
"permissions": baseClaims["permissions"],
"iat": baseClaims["iat"],
"exp": baseClaims["exp"],
"iss": baseClaims["iss"],
})
tok, err = Parse(noSubToken, keys)
if err != nil {
t.Errorf("Couldn't parse noSubToken: %v", err)
}
username, perms, err = tok.Check("galene.org:8443", "auth", &jack)
username, perms, err = tok.Check("skald.org:8443", "auth", &jack)
if err != nil {
t.Errorf("noSubToken is not valid: %v", err)
}
@@ -216,13 +260,21 @@ func TestJWT(t *testing.T) {
t.Errorf("noneToken is good")
}
subgroupsToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJpbmNsdWRlLXN1Ymdyb3VwcyI6dHJ1ZSwicGVybWlzc2lvbnMiOlsicHJlc2VudCJdLCJpYXQiOjE2NDUzMTAyOTQsImV4cCI6MjkwNjc1MDI5NCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDoxMjM0LyJ9.ty4B7cJPCGJPMadwOCNMGQirIByj1L9AxWiDFnBm5wA"
subgroupsToken := makeTestJWT(t, signingKey, jwt.MapClaims{
"sub": "john",
"aud": baseClaims["aud"],
"include-subgroups": true,
"permissions": baseClaims["permissions"],
"iat": baseClaims["iat"],
"exp": baseClaims["exp"],
"iss": baseClaims["iss"],
})
tok, err = Parse(subgroupsToken, keys)
if err != nil {
t.Errorf("subgroupsToken is not valid: %v", err)
}
_, _, err = tok.Check("galene.org:8443", "auth/subgroup", &john)
_, _, err = tok.Check("skald.org:8443", "auth/subgroup", &john)
if err != nil {
t.Errorf("subgroupsToken is not valid for subgroup: %v", err)
}
+1 -1
View File
@@ -184,7 +184,7 @@ func (state *state) load() (string, error) {
state.reset()
return "", err
}
// the "message" permission was introduced in Galene 0.9,
// 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.
if !member("message", t.Permissions) {
+1 -1
View File
@@ -39,7 +39,7 @@ func TestToken(t *testing.T) {
t.Fatalf("Parse: %v", err)
}
_, _, err = token.Check("galene.org:8443", "group", &user)
_, _, err = token.Check("skald.org:8443", "group", &user)
if err != nil {
t.Errorf("Check: %v", err)
}
+3 -3
View File
@@ -113,7 +113,7 @@ func Start() error {
return err
}
username = "galene"
username = "skald"
buf := make([]byte, 6)
_, err = rand.Read(buf)
if err != nil {
@@ -187,9 +187,9 @@ func Start() error {
log.Printf("Starting built-in TURN server on %v", addr.String())
server.server, err = turn.NewServer(turn.ServerConfig{
Realm: "galene.org",
Realm: "skald.org",
AuthHandler: func(u, r string, src net.Addr) ([]byte, bool) {
if u != username || r != "galene.org" {
if u != username || r != "skald.org" {
return nil, false
}
return turn.GenerateAuthKey(u, r, password), true
+7 -7
View File
@@ -13,9 +13,9 @@ import (
"golang.org/x/crypto/bcrypt"
"github.com/jech/galene/group"
"github.com/jech/galene/stats"
"github.com/jech/galene/token"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/stats"
"git.stormux.org/storm/skald/token"
)
// checkAdmin checks whether the client authentifies as an administrator
@@ -25,7 +25,7 @@ func checkAdmin(w http.ResponseWriter, r *http.Request) bool {
ok, _ = adminMatch(username, password)
}
if !ok {
failAuthentication(w, "/galene-api/")
failAuthentication(w, "/skald-api/")
return false
}
return true
@@ -71,7 +71,7 @@ func checkPasswordAdmin(w http.ResponseWriter, r *http.Request, groupname, user
}
}
}
failAuthentication(w, "/galene-api/")
failAuthentication(w, "/skald-api/")
return false
}
@@ -141,12 +141,12 @@ func apiCORS(w http.ResponseWriter, r *http.Request, methods string) bool {
}
func apiHandler(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.URL.Path, "/galene-api/") {
if !strings.HasPrefix(r.URL.Path, "/skald-api/") {
http.NotFound(w, r)
return
}
first, kind, rest := splitPath(r.URL.Path[len("/galene-api"):])
first, kind, rest := splitPath(r.URL.Path[len("/skald-api"):])
if first != "/v0" {
http.NotFound(w, r)
return
+52 -52
View File
@@ -15,8 +15,8 @@ import (
"path/filepath"
"testing"
"github.com/jech/galene/group"
"github.com/jech/galene/token"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/token"
)
var setupOnce sync.Once
@@ -115,19 +115,19 @@ func TestApi(t *testing.T) {
}
var groups []string
err = getJSON("/galene-api/v0/.groups/", &groups)
err = getJSON("/skald-api/v0/.groups/", &groups)
if err != nil || len(groups) != 0 {
t.Errorf("Get groups: %v", err)
}
resp, err := do("PUT", "/galene-api/v0/.groups/test/",
resp, err := do("PUT", "/skald-api/v0/.groups/test/",
"application/json", "\"foo\"", "",
"{}")
if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Create group (bad ETag): %v %v", err, resp.StatusCode)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test/",
resp, err = do("PUT", "/skald-api/v0/.groups/test/",
"text/plain", "", "",
"Hello, world!")
if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType {
@@ -135,7 +135,7 @@ func TestApi(t *testing.T) {
err, resp.StatusCode)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test/",
resp, err = do("PUT", "/skald-api/v0/.groups/test/",
"application/json", "", "*",
"{}")
if err != nil || resp.StatusCode != http.StatusCreated {
@@ -143,30 +143,30 @@ func TestApi(t *testing.T) {
}
var desc *group.Description
err = getJSON("/galene-api/v0/.groups/test/", &desc)
err = getJSON("/skald-api/v0/.groups/test/", &desc)
if err != nil || len(desc.Users) != 0 {
t.Errorf("Get group: %v", err)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test/",
resp, err = do("PUT", "/skald-api/v0/.groups/test/",
"application/json", "", "*",
"{}")
if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Create group (bad ETag): %v %v", err, resp.StatusCode)
}
resp, err = do("DELETE", "/galene-api/v0/.groups/test/",
resp, err = do("DELETE", "/skald-api/v0/.groups/test/",
"", "", "*", "")
if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Delete group (bad ETag): %v %v", err, resp.StatusCode)
}
err = getJSON("/galene-api/v0/.groups/", &groups)
err = getJSON("/skald-api/v0/.groups/", &groups)
if err != nil || len(groups) != 1 || groups[0] != "test" {
t.Errorf("Get groups: %v %v", err, groups)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test/.keys",
resp, err = do("PUT", "/skald-api/v0/.groups/test/.keys",
"application/jwk-set+json", "", "",
`{"keys": [{
"kty": "oct", "alg": "HS256",
@@ -176,12 +176,12 @@ func TestApi(t *testing.T) {
t.Errorf("Set key: %v %v", err, resp.StatusCode)
}
err = getJSON("/galene-api/v0/.groups/test/.users/", &groups)
err = getJSON("/skald-api/v0/.groups/test/.users/", &groups)
if err != nil || len(groups) != 0 {
t.Errorf("Get users: %v", err)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch",
resp, err = do("PUT", "/skald-api/v0/.groups/test/.users/jch",
"text/plain", "", "*",
`hello, world!`)
if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType {
@@ -189,7 +189,7 @@ func TestApi(t *testing.T) {
err, resp.StatusCode)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch",
resp, err = do("PUT", "/skald-api/v0/.groups/test/.users/jch",
"application/json", "", "*",
`{"permissions": "present"}`)
if err != nil || resp.StatusCode != http.StatusCreated {
@@ -197,26 +197,26 @@ func TestApi(t *testing.T) {
}
var users []string
err = getJSON("/galene-api/v0/.groups/test/.users/", &users)
err = getJSON("/skald-api/v0/.groups/test/.users/", &users)
if err != nil || len(users) != 1 || users[0] != "jch" {
t.Errorf("Get users: %v %v", err, users)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch",
resp, err = do("PUT", "/skald-api/v0/.groups/test/.users/jch",
"application/json", "", "*",
`{"permissions": "present"}`)
if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Create user (bad ETag): %v %v", err, resp.StatusCode)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch/.password",
resp, err = do("PUT", "/skald-api/v0/.groups/test/.users/jch/.password",
"application/json", "", "",
`"toto"`)
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Set password (PUT): %v %v", err, resp.StatusCode)
}
resp, err = do("POST", "/galene-api/v0/.groups/test/.users/jch/.password",
resp, err = do("POST", "/skald-api/v0/.groups/test/.users/jch/.password",
"text/plain", "", "",
`toto`)
if err != nil || resp.StatusCode != http.StatusNoContent {
@@ -224,7 +224,7 @@ func TestApi(t *testing.T) {
}
var user group.UserDescription
err = getJSON("/galene-api/v0/.groups/test/.users/jch", &user)
err = getJSON("/skald-api/v0/.groups/test/.users/jch", &user)
if err != nil {
t.Errorf("Get user: %v", err)
}
@@ -245,7 +245,7 @@ func TestApi(t *testing.T) {
t.Errorf("Password.Type: %v", desc.Users["jch"].Password.Type)
}
resp, err = do("DELETE", "/galene-api/v0/.groups/test/.users/jch",
resp, err = do("DELETE", "/skald-api/v0/.groups/test/.users/jch",
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete group: %v %v", err, resp.StatusCode)
@@ -260,14 +260,14 @@ func TestApi(t *testing.T) {
t.Errorf("Users (after delete): %#v", desc.Users)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test/.wildcard-user",
resp, err = do("PUT", "/skald-api/v0/.groups/test/.wildcard-user",
"application/json", "", "*",
`{"permissions": "present"}`)
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create wildcard user: %v %v", err, resp.StatusCode)
}
err = getJSON("/galene-api/v0/.groups/test/.wildcard-user", &user)
err = getJSON("/skald-api/v0/.groups/test/.wildcard-user", &user)
if err != nil {
t.Errorf("Get wildcard user: %v", err)
}
@@ -281,7 +281,7 @@ func TestApi(t *testing.T) {
t.Errorf("Got %v, expected %v", desc.WildcardUser, user)
}
resp, err = do("DELETE", "/galene-api/v0/.groups/test/.wildcard-user",
resp, err = do("DELETE", "/skald-api/v0/.groups/test/.wildcard-user",
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete wildcard user: %v %v", err, resp.StatusCode)
@@ -300,33 +300,33 @@ func TestApi(t *testing.T) {
t.Errorf("Keys: %v", len(desc.AuthKeys))
}
resp, err = do("POST", "/galene-api/v0/.groups/test/.tokens/",
resp, err = do("POST", "/skald-api/v0/.groups/test/.tokens/",
"application/json", "", "", `{"group":"bad"}`)
if err != nil || resp.StatusCode != http.StatusBadRequest {
t.Errorf("Create token (bad group): %v %v", err, resp.StatusCode)
}
resp, err = do("POST", "/galene-api/v0/.groups/test/.tokens/",
resp, err = do("POST", "/skald-api/v0/.groups/test/.tokens/",
"application/json", "", "", "{}")
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create token: %v %v", err, resp.StatusCode)
}
resp, err = do("POST", "/galene-api/v0/.groups/.tokens/",
resp, err = do("POST", "/skald-api/v0/.groups/.tokens/",
"application/json", "", "", "{\"includeSubgroups\": true}")
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create global token: %v %v", err, resp.StatusCode)
}
var toknames []string
err = getJSON("/galene-api/v0/.groups/test/.tokens/", &toknames)
err = getJSON("/skald-api/v0/.groups/test/.tokens/", &toknames)
if err != nil || len(toknames) != 1 {
t.Errorf("Get tokens: %v %v", err, toknames)
}
tokname := toknames[0]
var globalToknames []string
err = getJSON("/galene-api/v0/.groups/test/.tokens/", &globalToknames)
err = getJSON("/skald-api/v0/.groups/test/.tokens/", &globalToknames)
if err != nil || len(globalToknames) != 1 {
t.Errorf("Get global tokens: %v %v", err, globalToknames)
}
@@ -336,7 +336,7 @@ func TestApi(t *testing.T) {
t.Errorf("token.List: %v %v", tokens, err)
}
tokenpath := "/galene-api/v0/.groups/test/.tokens/" + tokname
tokenpath := "/skald-api/v0/.groups/test/.tokens/" + tokname
var tok token.Stateful
err = getJSON(tokenpath, &tok)
if err != nil {
@@ -375,13 +375,13 @@ func TestApi(t *testing.T) {
t.Errorf("Got %v, expected %v (%v)", tok.Expires, e, err)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test2",
resp, err = do("PUT", "/skald-api/v0/.groups/test2",
"application/json", "", "*", "{}")
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create test2: %v %v", err, resp.StatusCode)
}
tokenpath2 := "/galene-api/v0/.groups/test2/.tokens/" + tokname
tokenpath2 := "/skald-api/v0/.groups/test2/.tokens/" + tokname
resp, err = do("GET", tokenpath2, "", "", "", "")
if err != nil || resp.StatusCode != http.StatusNotFound {
t.Errorf("Get token in bad group: %v %v", err, resp.StatusCode)
@@ -402,13 +402,13 @@ func TestApi(t *testing.T) {
t.Errorf("Token list: %v %v", tokens, err)
}
resp, err = do("DELETE", "/galene-api/v0/.groups/test/.keys",
resp, err = do("DELETE", "/skald-api/v0/.groups/test/.keys",
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete keys: %v %v", err, resp.StatusCode)
}
resp, err = do("DELETE", "/galene-api/v0/.groups/test/",
resp, err = do("DELETE", "/skald-api/v0/.groups/test/",
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete group: %v %v", err, resp.StatusCode)
@@ -447,9 +447,9 @@ func TestApiBadAuth(t *testing.T) {
}
}
do("GET", "/galene-api/v0/.stats")
do("GET", "/galene-api/v0/.groups/")
do("PUT", "/galene-api/v0/.groups/test/")
do("GET", "/skald-api/v0/.stats")
do("GET", "/skald-api/v0/.groups/")
do("PUT", "/skald-api/v0/.groups/test/")
f, err := os.Create(filepath.Join(group.Directory, "test.json"))
if err != nil {
@@ -460,20 +460,20 @@ func TestApiBadAuth(t *testing.T) {
}\n`)
f.Close()
do("PUT", "/galene-api/v0/.groups/test/")
do("DELETE", "/galene-api/v0/.groups/test/")
do("GET", "/galene-api/v0/.groups/test/.users/")
do("GET", "/galene-api/v0/.groups/test/.users/jch")
do("GET", "/galene-api/v0/.groups/test/.users/jch")
do("PUT", "/galene-api/v0/.groups/test/.users/jch")
do("DELETE", "/galene-api/v0/.groups/test/.users/jch")
do("GET", "/galene-api/v0/.groups/test/.users/not-jch")
do("PUT", "/galene-api/v0/.groups/test/.users/not-jch")
do("PUT", "/galene-api/v0/.groups/test/.users/jch/.password")
do("POST", "/galene-api/v0/.groups/test/.users/jch/.password")
do("GET", "/galene-api/v0/.groups/test/.tokens/")
do("POST", "/galene-api/v0/.groups/test/.tokens/")
do("GET", "/galene-api/v0/.groups/test/.tokens/token")
do("PUT", "/galene-api/v0/.groups/test/.tokens/token")
do("DELETE", "/galene-api/v0/.groups/test/.tokens/token")
do("PUT", "/skald-api/v0/.groups/test/")
do("DELETE", "/skald-api/v0/.groups/test/")
do("GET", "/skald-api/v0/.groups/test/.users/")
do("GET", "/skald-api/v0/.groups/test/.users/jch")
do("GET", "/skald-api/v0/.groups/test/.users/jch")
do("PUT", "/skald-api/v0/.groups/test/.users/jch")
do("DELETE", "/skald-api/v0/.groups/test/.users/jch")
do("GET", "/skald-api/v0/.groups/test/.users/not-jch")
do("PUT", "/skald-api/v0/.groups/test/.users/not-jch")
do("PUT", "/skald-api/v0/.groups/test/.users/jch/.password")
do("POST", "/skald-api/v0/.groups/test/.users/jch/.password")
do("GET", "/skald-api/v0/.groups/test/.tokens/")
do("POST", "/skald-api/v0/.groups/test/.tokens/")
do("GET", "/skald-api/v0/.groups/test/.tokens/token")
do("PUT", "/skald-api/v0/.groups/test/.tokens/token")
do("DELETE", "/skald-api/v0/.groups/test/.tokens/token")
}
+5 -5
View File
@@ -21,10 +21,10 @@ import (
"github.com/gorilla/websocket"
"git.stormux.org/storm/skald/diskwriter"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/rtpconn"
"github.com/jech/cert"
"github.com/jech/galene/diskwriter"
"github.com/jech/galene/group"
"github.com/jech/galene/rtpconn"
)
var server *http.Server
@@ -44,7 +44,7 @@ func Serve(address string, dataDir string) error {
http.HandleFunc("/recordings/", recordingsHandler)
http.HandleFunc("/ws", wsHandler)
http.HandleFunc("/public-groups.json", publicHandler)
http.HandleFunc("/galene-api/", apiHandler)
http.HandleFunc("/skald-api/", apiHandler)
s := &http.Server{
Addr: address,
@@ -371,7 +371,7 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
status := g.Status(false, nil)
cspHeader(w, status.AuthServer)
serveFile(w, r, filepath.Join(StaticRoot, "galene.html"))
serveFile(w, r, filepath.Join(StaticRoot, "skald.html"))
}
func baseURL(r *http.Request) (*url.URL, error) {
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"github.com/pion/webrtc/v4"
"github.com/jech/galene/group"
"git.stormux.org/storm/skald/group"
)
func TestParseGroupName(t *testing.T) {
+4 -4
View File
@@ -17,10 +17,10 @@ import (
"github.com/pion/webrtc/v4"
"github.com/jech/galene/group"
"github.com/jech/galene/ice"
"github.com/jech/galene/rtpconn"
"github.com/jech/galene/sdpfrag"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/ice"
"git.stormux.org/storm/skald/rtpconn"
"git.stormux.org/storm/skald/sdpfrag"
)
var idSecret []byte