Rebrand project as Skald
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
||||
*~
|
||||
/galene
|
||||
/galenectl/galenectl
|
||||
/skald
|
||||
/skaldctl/skaldctl
|
||||
/data
|
||||
/groups/**/*.json
|
||||
/static/**/*.d.ts
|
||||
|
||||
@@ -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".
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jech/galene/rtptime"
|
||||
"git.stormux.org/storm/skald/rtptime"
|
||||
)
|
||||
|
||||
func TestSanitise(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jech/galene/rtptime"
|
||||
"git.stormux.org/storm/skald/rtptime"
|
||||
)
|
||||
|
||||
type Estimator struct {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jech/galene/rtptime"
|
||||
"git.stormux.org/storm/skald/rtptime"
|
||||
)
|
||||
|
||||
func TestEstimator(t *testing.T) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module github.com/jech/galene
|
||||
module git.stormux.org/storm/skald
|
||||
|
||||
go 1.21
|
||||
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -3,7 +3,7 @@ package rtpconn
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jech/galene/rtptime"
|
||||
"git.stormux.org/storm/skald/rtptime"
|
||||
)
|
||||
|
||||
func TestDownTrackAtomics(t *testing.T) {
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jech/galene/token"
|
||||
"git.stormux.org/storm/skald/token"
|
||||
)
|
||||
|
||||
var tokens = []string{
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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...
|
||||
@@ -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
@@ -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/>
|
||||
@@ -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) {
|
||||
@@ -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,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,4 +1,4 @@
|
||||
/* Galene client example. */
|
||||
/* Skald client example. */
|
||||
|
||||
/**
|
||||
* The main function.
|
||||
|
||||
+4
-4
@@ -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
@@ -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
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"files": [
|
||||
"protocol.js",
|
||||
"galene.js",
|
||||
"skald.js",
|
||||
"management.js",
|
||||
"example/example.js"
|
||||
]
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/jech/galene/group"
|
||||
"git.stormux.org/storm/skald/group"
|
||||
)
|
||||
|
||||
type GroupStats struct {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user