config

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 3, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package config owns the per-OS file layout (PLAN §4 — the single table every other module defers to), the Config struct, config read/write (with the macOS no-write rule §4.1a), the token-storage interface (§6.4), and the alias store (§4.3/§5.6). Linux uses XDG; macOS uses ~/.clipbeam parity with ClipBeam.app.

Index

Constants

View Source
const KeychainAccount = "shared-token"

KeychainAccount is the FROZEN Keychain account for the shared token.

View Source
const KeychainService = "com.sani.clipbeam"

KeychainService is the FROZEN macOS Keychain service name, shared with ClipBeam.app so a Mac running both shares one token (PLAN §6.4, Keychain.swift).

Variables

View Source
var ErrMacOSConfigReadOnly = errors.New("clipbeam: config.json is owned by ClipBeam.app on macOS and must not be rewritten by the CLI (PLAN §4.1a)")

ErrMacOSConfigReadOnly is returned by Save on macOS: the app owns config.json and a Go rewrite that drops the non-optional hotkey/peers[].name would make Swift's strict JSONDecoder throw and silently reset the peer IP (PLAN §4.1a).

Functions

func ResolvedSaveDir

func ResolvedSaveDir(c Config) (string, error)

ResolvedSaveDir expands the tilde saveDir (or the per-OS default from Paths when empty) to an absolute path, ensuring the directory exists 0700 and self-heals if it vanished (PLAN §4, mirrors Swift ConfigStore.resolvedSaveDir).

func Save

func Save(c Config) error

Save persists config.json. On macOS it REFUSES (ErrMacOSConfigReadOnly): the app owns the file and a Go rewrite that drops the non-optional hotkey/peers[].name makes Swift's strict JSONDecoder throw and reset the peer IP (PLAN §4.1a). On Linux Go owns the file fully and writes pretty-printed encoding/json (space-less colons) at 0600 atomically, parent dir 0700.

func SaveAliases

func SaveAliases(p Paths, s AliasStore) error

SaveAliases persists the alias store at 0600 atomically (parent dir 0700). It writes the dedicated CLI-owned file on every OS, so it never touches the app's config.json even on macOS (PLAN §4.3).

Types

type Alias

type Alias struct {
	Name           string `json:"name"`
	Transport      string `json:"transport"` // "ssh" | "tailscale"
	SSHUser        string `json:"sshUser,omitempty"`
	SSHHost        string `json:"sshHost,omitempty"`
	SSHPort        int    `json:"sshPort,omitempty"`
	SSHConfigAlias string `json:"sshConfigAlias,omitempty"` // optional ~/.ssh/config Host name
	RemoteBinPath  string `json:"remoteBinPath,omitempty"`  // ABSOLUTE path setup recorded (§5.1)
	Serve          string `json:"serve,omitempty"`          // "exec" | "socket" | "tcp" | "tailscale"
	PeerIP         string `json:"peerIP,omitempty"`         // tailscale; empty otherwise
	Default        bool   `json:"default,omitempty"`
}

Alias is one saved transport target written by `clipbeam setup` (PLAN §5.6). It lives in the CLI-owned alias store, NEVER in the app's config.json (PLAN §4.3). The token is never stored here (Keychain on macOS, the 0600 file otherwise).

type AliasStore

type AliasStore struct {
	Aliases      []Alias `json:"aliases"`
	DefaultAlias string  `json:"defaultAlias"`
}

AliasStore is the on-disk CLI-owned alias store (PLAN §4.3/§5.6). Its JSON shape:

{ "aliases": [ {Alias…} ], "defaultAlias": "<name>" }

func LoadAliases

func LoadAliases(p Paths) (AliasStore, error)

LoadAliases reads the per-OS alias store (PLAN §4.3/§5.6), returning an empty store if the file is absent. A corrupt file is a hard error (a setup verb can choose to overwrite, but a data verb must not silently lose a saved default). The store lives in the CLI-owned location (~/.clipbeam/cli-aliases.json on macOS, $XDG_CONFIG_HOME/clipbeam/aliases.json on Linux) — never in the app's config.json.

func (AliasStore) Lookup

func (s AliasStore) Lookup(name string) (Alias, bool)

Lookup returns the alias with the given name, or (Alias{}, false). An empty name returns the default alias (PLAN §5.5: a verb with no target uses defaultAlias).

func (AliasStore) LookupSpec added in v0.1.1

func (s AliasStore) LookupSpec(spec string) (Alias, bool)

LookupSpec resolves a data-verb target spec to a saved alias (fix [D] completeness). It first tries the exact alias NAME (the contract Lookup guarantees: empty ⇒ default, else the named alias). When that misses, it falls back to matching the spec parsed as a literal user@host[:port] / bare host against a saved SSH alias's recorded host (and user/port when the spec carries them) — so a user who ran `clipbeam setup root@box` and then `clipbeam send file root@box` (the SAME spec) resolves to that alias and uses its recorded absolute remoteBinPath, instead of falling through to a bare `clipbeam ingest` that fails 127 under a minimal non-login SSH-exec PATH (PLAN §5.1).

Only SSH aliases participate in the host fallback (a tailscale alias has no SSHHost), so a literal tailnet 100.x target is unaffected and still routes via the tailscale probe.

type Config

type Config struct {
	Port              uint16       `json:"port"`
	Peers             []Peer       `json:"peers"`
	DefaultPeerID     string       `json:"defaultPeerId"`
	SaveDir           string       `json:"saveDir"`
	MaxBytes          int          `json:"maxBytes"`
	Hotkey            HotkeyConfig `json:"hotkey"`
	SaveTextToDisk    bool         `json:"saveTextToDisk"`
	LongTextThreshold int          `json:"longTextThreshold"`
	Notify            bool         `json:"notify"`

	// LinuxClipboard is a CLI-owned persistent opt-in for the best-effort
	// X/Wayland clipboard set on Linux (PLAN §7.3). Off by default; absent in the
	// app's config.json (omitempty keeps the macOS byte-shape unchanged).
	LinuxClipboard bool `json:"linuxClipboard,omitempty"`
}

Config mirrors Swift Config (Config.swift). saveDir is a tilde path expanded only at use; maxBytes is the DECODED payload cap. On macOS Go reads but never rewrites this file (the app owns it, PLAN §4.1a). On Linux Go owns it fully.

Raw preserves every unknown key verbatim so a macOS rewrite (only if ever forced) round-trips the app's hotkey/peers[].name + any future keys (PLAN §4.1a).

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the defaults used when no config.json exists. On Linux these back the XDG-resolved saveDir; the app's hardcoded peer IP is NOT reproduced here (a fresh CLI config has no peer until setup pairs one).

func Load

func Load() (Config, error)

Load reads the per-OS config.json, applying defaults for any absent file. On macOS it parses the app's space-before-colon / escaped-slash byte-shape tolerantly (Go's encoding/json normalizes both transparently) and NEVER writes back (PLAN §4.1a). A genuinely corrupt file is a hard error; a missing file yields DefaultConfig (no implicit write — unlike the Swift app, the CLI does not persist defaults on first read).

type HotkeyConfig

type HotkeyConfig struct {
	KeyCode   uint32   `json:"keyCode"`
	Modifiers []string `json:"modifiers"`
}

HotkeyConfig mirrors Swift Config.HotkeyConfig. It is present and non-optional in the app's config.json; the Go reader preserves it verbatim and NEVER drops it on macOS (PLAN §4.1a).

type Paths

type Paths struct {
	// SaveDir is the clipboard-channel save dir (dir 0700).
	SaveDir string
	// AgentInbox is the agent-channel inbox dir (dir 0700, files 0600).
	AgentInbox string
	// LastPath is the last_path file.
	LastPath string
	// Recents is recents.json (0600).
	Recents string
	// Config is config.json (0600).
	Config string
	// Aliases is the CLI-owned alias store (0600), separate from the app config.
	Aliases string
	// Token is the 0600 Linux token file (empty on macOS, where the Keychain holds it).
	Token string
	// Log is the redacted, size-rotated log (0600).
	Log string
}

Paths resolves the per-OS file layout (PLAN §4). It is the single place that branches on GOOS: Linux uses XDG ($XDG_*_HOME honored only if absolute, else ~/.local|.config defaults); macOS uses ~/.clipbeam (and ~/Downloads/ClipBeam) for parity with ClipBeam.app and its shim. Every other module gets its paths from here.

func Resolve

func Resolve() (Paths, error)

Resolve computes the per-OS Paths. It does not create any directories — callers create dirs 0700 and files 0600 on demand (PLAN §4: self-heal + re-chmod on load).

type Peer

type Peer struct {
	ID   string `json:"id"`
	Host string `json:"host"`
	Name string `json:"name"`
}

Peer mirrors Swift Config.Peer. name is non-optional and present (load-bearing for the macOS strict-decode no-write rule, PLAN §4.1a).

type TokenStore

type TokenStore interface {
	// Load returns the stored token, or ("", false, nil) if none is stored. A
	// non-nil error indicates a backend failure (not a missing token).
	Load() (token string, ok bool, err error)
	// Save persists the token, replacing any existing value.
	Save(token string) error
	// Kind reports the concrete backend ("keychain" | "secret-service" | "file")
	// for doctor diagnostics.
	Kind() string
}

TokenStore is the cross-platform shared-secret storage boundary (PLAN §6.4). Implementations are auto-selected by --token-store keychain|secret-service|file:

macOS          → Keychain (service com.sani.clipbeam, account shared-token,
                 FROZEN + shared with ClipBeam.app)
Linux desktop  → Secret Service / libsecret (only when $DBUS_SESSION_BUS_ADDRESS
                 is set and the keyring is unlocked; never block on a locked one)
headless Linux → 0600 token file at $XDG_CONFIG_HOME/clipbeam/token (re-chmod'd
                 0600 on every load, PLAN §4.2)

The token is never in argv; CLIPBEAM_TOKEN is honored for CI only.

func OpenTokenStore

func OpenTokenStore(kind TokenStoreKind, p Paths) (TokenStore, error)

OpenTokenStore selects and returns a TokenStore for the given kind, using the per-OS Paths for the file backend (PLAN §6.4):

TokenStoreAuto          → macOS Keychain; else Linux Secret Service if a D-Bus
                          session is present and secret-tool exists; else 0600 file.
TokenStoreKeychain      → macOS Keychain (error off macOS).
TokenStoreSecretService → Linux Secret Service via secret-tool (error if absent).
TokenStoreFile          → the 0600 token file (always available).

The auto path NEVER blocks on a locked keyring — Secret Service is only auto-chosen when $DBUS_SESSION_BUS_ADDRESS is set and the secret-tool binary exists; any backend failure at use time still falls through to the caller as an error, never a hang.

type TokenStoreKind

type TokenStoreKind string

TokenStoreKind selects a concrete TokenStore backend.

const (
	// TokenStoreAuto auto-selects per-OS (PLAN §6.4).
	TokenStoreAuto TokenStoreKind = "auto"
	// TokenStoreKeychain forces the macOS login Keychain.
	TokenStoreKeychain TokenStoreKind = "keychain"
	// TokenStoreSecretService forces Linux Secret Service / libsecret.
	TokenStoreSecretService TokenStoreKind = "secret-service"
	// TokenStoreFile forces the 0600 token file.
	TokenStoreFile TokenStoreKind = "file"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL