The list reached the bus only through a want of the bus module, which nobody writes (ADR 0539 §15): on 2026-10-25 it stood from 11:23 to 20:50 and was put back by hand. The node agent now follows its composition bucket for the newest signed composition carrying the user list, writes that file alone through the apply's file step when it differs (mode 0600, read back, recorded as the module's), moves nothing that reads it (the bus module's entrypoint reloads the server in place), never goes back to an older list, looks again after every apply of the bus module, and says source, revision, wanted and written digests, users and any error in the bus module's have (mesh-sdk UserListWritten). Read with mesh-delivery-view.node_haves (module nats); the bus's own nats_user_can says in_force once it reloaded.
83 lines
3.1 KiB
Go
83 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ed25519"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/declaration"
|
|
"github.com/novox/mesh-host/internal/apply"
|
|
"github.com/novox/mesh-host/internal/link"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
"github.com/novox/mesh-host/internal/system"
|
|
"github.com/novox/mesh-host/internal/userlist"
|
|
)
|
|
|
|
// The bridge of novox/hq ADR 0547 §8, wired: the bus node's engine writes the bus's user list alone from the newest
|
|
// composition of the bus module, under the apply's two locks as a filled file is written (filling.go), and says what
|
|
// it wrote in the bus module's have.
|
|
|
|
// userListFollower is the follower of the bus's user list for one link: the node's composition bucket, the keys the
|
|
// agent trusts, the write alone, and the have.
|
|
func userListFollower(opts options, session *link.Session, composedBucket string, trusted func() []ed25519.PublicKey,
|
|
say link.Announce) *userlist.Follower {
|
|
return userlist.New(userlist.Env{
|
|
Node: session.Membership.Node,
|
|
Composed: bucketWatch{session, composedBucket},
|
|
Trusted: trusted,
|
|
Write: func(ctx context.Context, module string, raw json.RawMessage) (string, bool, error) {
|
|
return writeUserListAlone(session.WithLink(ctx), opts, raw, say)
|
|
},
|
|
Report: func(ctx context.Context, module string, w userlist.Written) {
|
|
session.NoteUserList(ctx, module, w)
|
|
},
|
|
Say: announceOr(say),
|
|
})
|
|
}
|
|
|
|
// writeUserListAlone writes one user-list resource alone (apply.WriteAlone): its content had by digest when it is
|
|
// given so, the node's state read and saved under the apply's locks, and the digest of the file on the machine after.
|
|
func writeUserListAlone(ctx context.Context, opts options, raw []byte, say link.Announce) (string, bool, error) {
|
|
r, err := declaration.ParseResource(raw)
|
|
if err != nil {
|
|
return "", false, fmt.Errorf("the user list is not a resource: %w", err)
|
|
}
|
|
if _, ok := r.(*declaration.File); !ok {
|
|
return "", false, fmt.Errorf("the user list is a %s, not a file", r.Kind())
|
|
}
|
|
d := &declaration.Declaration{Version: declaration.Version, Resources: []declaration.Resource{r}}
|
|
if err := apply.ResolveFileReferences(ctx, d, link.FilesFrom(ctx)); err != nil {
|
|
return "", false, fmt.Errorf("the user list could not be had by its digest: %w", err)
|
|
}
|
|
file := d.Resources[0].(*declaration.File)
|
|
|
|
applying.Lock()
|
|
defer applying.Unlock()
|
|
unlock, err := store.Lock(opts.state, nil)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
defer unlock()
|
|
known, err := store.Load(opts.state)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
built, err := system.For(builtFor)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
out, updated, writeErr := apply.WriteAlone(ctx, built, file, known, apply.ExecRunner, announceOr(say))
|
|
// Saved whichever way it went, as an apply's: what was written is on the machine either way.
|
|
if err := store.Save(opts.state, updated); err != nil {
|
|
writeErr = errors.Join(writeErr, errors.New("written, and the node's state could not be saved: "+err.Error()))
|
|
}
|
|
digest := ""
|
|
if body, err := os.ReadFile(file.Path); err == nil {
|
|
digest = userlist.Digest(body)
|
|
}
|
|
return digest, writeErr == nil && out.Action != "unchanged", writeErr
|
|
}
|