Files
ak47 573d15d7b4 Add Remote session foundations and feasibility checks
Add opt-in Tentacle protocol 1.1, explicit remote grants, lifecycle IPC and expiring core leases in both SDKs. Revoke access before update preparation, including failed updates.

Add transient secret entries, shared Braille cell export and isolated Xorg recovery experiments with focused tests and documented limitations. Production consent, desktop services and live interoperability remain pending.
2026-11-03 22:33:26 -05:00

250 lines
8.3 KiB
Go

// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package tentacle
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"syscall"
"github.com/pelletier/go-toml/v2"
)
// Entry records an explicitly added local program or a verified package.
// Discovery never adds entries. Enablement is desired state; failures retain it.
type Entry struct {
SessionEvents bool `toml:"session-events,omitempty"`
RemoteGrants []string `toml:"remote-grants,omitempty"`
ID string `toml:"id"`
Program []string `toml:"program"`
Directory string `toml:"directory,omitempty"`
Enabled bool `toml:"enabled"`
Presentation bool `toml:"presentation"`
ReaderSettings bool `toml:"reader-settings,omitempty"`
Interaction bool `toml:"interaction,omitempty"`
Repository string `toml:"repository,omitempty"`
RepositoryID string `toml:"repository-id,omitempty"`
LocalID string `toml:"local-id,omitempty"`
Name string `toml:"name,omitempty"`
Version string `toml:"package-version,omitempty"`
PackageDirectory string `toml:"package-directory,omitempty"`
PackageSHA256 string `toml:"package-sha256,omitempty"`
RepositoryRevision string `toml:"repository-revision,omitempty"`
}
func (entry Entry) Spec() Spec {
return Spec{SessionEvents: entry.SessionEvents, RemoteGrants: append([]string(nil), entry.RemoteGrants...), ID: entry.ID, Program: append([]string(nil), entry.Program...), Directory: entry.Directory, Presentation: entry.Presentation, Interaction: entry.Interaction, ReaderSettings: entry.ReaderSettings}
}
type registryDocument struct {
Version int `toml:"version"`
Entries []Entry `toml:"tentacles"`
Repositories []Repository `toml:"repositories,omitempty"`
}
// Registry is owned by the reader lifecycle worker, independently of profiles.
// Its optimistic revision prevents overwriting another editor's changes.
type Registry struct {
Path string
original []byte
loaded bool
repositories []Repository
}
var ErrRegistrySync = errors.New("tentacle registry was replaced but its directory could not be synced")
func RegistryPath(settingsPath string) string {
return filepath.Join(filepath.Dir(settingsPath), "tentacles.toml")
}
func CloneEntries(entries []Entry) []Entry {
result := append([]Entry(nil), entries...)
for index := range result {
result[index].Program = append([]string(nil), result[index].Program...)
result[index].RemoteGrants = append([]string(nil), result[index].RemoteGrants...)
}
return result
}
func ValidateEntries(entries []Entry) error {
if len(entries) > DefaultLimits().Processes {
return ErrLimit
}
seen := make(map[string]bool)
packageDirectories := make(map[string]bool)
for _, entry := range entries {
if err := validate_spec(entry.Spec()); err != nil {
return err
}
if entry.Repository != "" {
origin, err := RepositoryURL(entry.Repository)
if err != nil || origin != entry.Repository || !metadata_id(entry.RepositoryID) || !metadata_id(entry.LocalID) {
return errors.New("invalid installed tentacle source identity")
}
} else if entry.RepositoryID != "" || entry.LocalID != "" {
return errors.New("tentacle source identity requires a repository origin")
}
if !metadata_text(entry.Name, false) || !metadata_text(entry.Version, false) {
return errors.New("invalid installed tentacle display metadata")
}
if seen[entry.ID] {
return errors.New("duplicate tentacle ID")
}
if entry.PackageDirectory != "" {
relativeProgram, err := filepath.Rel(entry.PackageDirectory, entry.Program[0])
if entry.Repository == "" || !filepath.IsAbs(entry.PackageDirectory) ||
filepath.Clean(entry.PackageDirectory) != entry.PackageDirectory || entry.Directory != entry.PackageDirectory ||
!valid_digest(entry.PackageSHA256) || !valid_revision(entry.RepositoryRevision) ||
entry.ID != PackageID(Repository{URL: entry.Repository, ID: entry.RepositoryID}, entry.LocalID) ||
err != nil || !package_path(filepath.ToSlash(relativeProgram)) || packageDirectories[entry.PackageDirectory] {
return errors.New("invalid installed package lock")
}
packageDirectories[entry.PackageDirectory] = true
} else if entry.PackageSHA256 != "" || entry.RepositoryRevision != "" {
return errors.New("package lock requires a package directory")
}
seen[entry.ID] = true
}
data, err := toml.Marshal(registryDocument{Version: 1, Entries: entries})
if err == nil && len(data) > MaxFrameBytes {
return ErrLimit
}
return err
}
func read_registry(path string) ([]byte, error) {
file, err := os.OpenFile(path, os.O_RDONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
if err != nil {
return nil, err
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return nil, err
}
if !info.Mode().IsRegular() {
return nil, errors.New("tentacle registry must be a regular file")
}
data, err := io.ReadAll(io.LimitReader(file, MaxFrameBytes+1))
if len(data) > MaxFrameBytes {
return nil, ErrLimit
}
return data, err
}
func (registry *Registry) Load() ([]Entry, error) {
registry.loaded = false
data, err := read_registry(registry.Path)
if err != nil {
return nil, err
}
var document registryDocument
if data != nil {
decoder := toml.NewDecoder(bytes.NewReader(data)).DisallowUnknownFields()
if err := decoder.Decode(&document); err != nil {
return nil, err
}
if document.Version != 1 {
return nil, errors.New("unsupported tentacle registry version")
}
if err := ValidateEntries(document.Entries); err != nil {
return nil, err
}
if err := ValidateRepositories(document.Repositories); err != nil {
return nil, err
}
}
registry.original, registry.loaded = data, true
registry.repositories = CloneRepositories(document.Repositories)
return CloneEntries(document.Entries), nil
}
// Save validates before touching disk, locks cooperating writers, and atomically
// replaces an owner-only file. A failed load can never be overwritten.
func (registry *Registry) Save(entries []Entry) error {
return registry.SaveState(entries, registry.repositories)
}
func (registry *Registry) Repositories() []Repository {
return CloneRepositories(registry.repositories)
}
func (registry *Registry) SaveState(entries []Entry, repositories []Repository) error {
if !registry.loaded {
return errors.New("tentacle registry has not been loaded successfully")
}
if err := ValidateEntries(entries); err != nil {
return err
}
if err := ValidateRepositories(repositories); err != nil {
return err
}
data, err := toml.Marshal(registryDocument{Version: 1, Entries: entries, Repositories: repositories})
if err != nil {
return err
}
if len(data) > MaxFrameBytes {
return ErrLimit
}
directory := filepath.Dir(registry.Path)
if err := os.MkdirAll(directory, 0700); err != nil {
return err
}
lock, err := os.OpenFile(registry.Path+".lock", os.O_CREATE|os.O_RDWR|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0600)
if err != nil {
return err
}
defer lock.Close()
if info, err := lock.Stat(); err != nil || !info.Mode().IsRegular() {
return errors.New("tentacle registry lock must be a regular file")
}
if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
return errors.New("tentacle registry is being edited")
}
defer syscall.Flock(int(lock.Fd()), syscall.LOCK_UN)
current, err := read_registry(registry.Path)
if err != nil {
return err
}
if !bytes.Equal(current, registry.original) || (current == nil) != (registry.original == nil) {
return errors.New("tentacle registry changed outside the reader; restart before editing")
}
temporary, err := os.CreateTemp(directory, ".tentacles-*.toml")
if err != nil {
return err
}
defer os.Remove(temporary.Name())
defer temporary.Close()
if _, err := temporary.Write(data); err != nil {
return err
}
if err := temporary.Sync(); err != nil {
return err
}
if err := temporary.Close(); err != nil {
return err
}
dir, err := os.Open(directory)
if err != nil {
return err
}
defer dir.Close()
if err := os.Rename(temporary.Name(), registry.Path); err != nil {
return err
}
registry.original = data
registry.repositories = CloneRepositories(repositories)
if err := dir.Sync(); err != nil {
return fmt.Errorf("%w: %w", ErrRegistrySync, err)
}
return nil
}