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 ¶
const KeychainAccount = "shared-token"
KeychainAccount is the FROZEN Keychain account for the shared token.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
type Peer ¶
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" )