Help us shape the future of Go! Take the 2026 Go Developer Survey

visorconfig

package
v1.3.104-alpha1 Latest Latest
Warning

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

Go to latest
Published: Oct 30, 2026 License: AGPL-3.0 Imports: 42 Imported by: 8

Documentation

Overview

Package visorconfig pkg/visor/visorconfig/args.go c3-vis-core

Renders the per-app Args slice as a single shell-like string in the on-disk config file. The hand-edit experience is the goal:

"args": "skycoin daemon --port 6420 --data-dir /var/skycoin/sky"

is far easier to read and tweak than the array form. The Go internals (launcher, RPC, HTTP API) keep []string — only the JSON file representation changes. Old configs with array-form args still load.

The encoding/json-using halves (appsList.MarshalJSON, appsList.UnmarshalJSON, unmarshalAppConfig) live in args_native.go under //go:build !js. This file keeps the type definitions and the json-free shell-arg parser so the WASM build path can still reach SplitArgs, splitArgs, and joinArgs (none of which touch encoding/json).

Package visorconfig pkg/visor/visorconfig/args_native.go c3-vis-core

encoding/json-using halves of args.go. Build-tag-gated off the WASM path because encoding/json's reflect-based marshaller drags reflect.unsafe_New / mapassign / typedmemmove (which TinyGo's stdlib doesn't ship). The browser-side WASM never (de)serializes V1 apps lists; under js V1.Apps stays as raw appsList values and the type's json methods are absent from the build.

Package visorconfig pkg/visor/visorconfig/autoconnect.go c3-vis-core

Package visorconfig pkg/visor/visorconfig/common.go c3-vis-core

Package visorconfig pkg/visor/visorconfig/common_native.go c3-vis-core

Native (non-WASM) implementation of Common.flush — writes a JSON-encoded config back to its on-disk path. encoding/json imports the reflect runtime helpers TinyGo's stdlib doesn't provide, so this code path is tagged off the WASM build to keep `tinygo build -target wasm` viable for the install-page generator. The browser-side caller can't write files anyway.

Package visorconfig pkg/visor/visorconfig/config.go c3-vis-core

Package visorconfig pkg/visor/visorconfig/config_compat.go c3-vis-core backward-compat JSON unmarshaling for renamed config keys.

V1.Pty (canonical) corresponds to operator-config-json's "pty" key. The legacy key "dmsgpty" is still accepted on load so that existing operator config.json files don't break across the rename. Marshaling always emits the canonical "pty" key — V1's struct tag is `json:"pty,omitempty"`.

When both keys appear, the canonical "pty" wins. When neither appears, V1.Pty stays nil (the visor's init path treats nil as "pty subsystem disabled").

Package visorconfig pkg/visor/visorconfig/deployment_services.go c3-vis-core

Package visorconfig pkg/visor/visorconfig/hypervisorconfig.go c3-vis-core

The CookieConfig.SameSite() method (which returns http.SameSite) is the only net/http user in this file; it's moved to hypervisorconfig_native.go under //go:build !js so this file compiles cleanly for js/wasm. See services.go's package doc for the broader rationale.

Package visorconfig pkg/visor/visorconfig/hypervisorconfig_native.go c3-vis-core

Native (non-WASM) implementations for HypervisorConfig methods that touch disk or pull net/http / encoding/json. Kept off the js/wasm build graph so the install-page WASM (genvisor + autoconfigcmd) stays small and TinyGo-compatible.

  • CookieConfig.SameSite returns http.SameSite (net/http).
  • HypervisorConfig.Parse reads a config file from disk and decodes via encoding/json — useless in a browser, harmful for the WASM dep graph because encoding/json pulls reflect runtime helpers that TinyGo's stdlib lacks.

Package visorconfig pkg/visor/visorconfig/keyring.go c3-vis-core

Package visorconfig pkg/visor/visorconfig/network_watch.go c3-vis-core

Package visorconfig pkg/visor/visorconfig/parse.go c3-vis-core

Parse wraps Reader (from read.go, also !js-tagged) and adds version-compat checking via blang/semver. Both deps (encoding/ json transitively, and blang/semver which imports json itself) stay out of the WASM build. Browser-side WASM never parses an on-disk config; it constructs V1 in memory via genvisor.Generate.

Package visorconfig pkg/visor/visorconfig/preserve.go c3-vis-core

Unknown-key preservation for the config read-modify-write cycle.

The visor loads skywire-config.json into the typed V1 struct and later re-marshals that struct back over the same file (Common.flush). Anything in the file without a matching struct field never survives that round trip: it is not ignored, it is ERASED. Running an older build for a single restart — routine in the dev loop, and the whole of a downgrade — therefore deletes every setting newer than that binary, along with any third-party or experimental key an operator added by hand.

The fix: before overwriting, flush reads the file it is about to replace and walks that JSON against the Go type being marshaled, recording the RESIDUE — the subtrees, at any depth, that have no counterpart field. The marshaled struct is then written with the residue re-attached. Because residue keys are by construction keys the struct does not model, they can never shadow a known key, and clearing a known field still clears it on disk: the struct remains the sole authority for everything it does model.

Reading at flush rather than capturing at load is deliberate. The residue then belongs to the file actually being overwritten, so it survives the config being rebuilt in memory or handed a fresh Common (`skywire cli config update` does exactly that, which is where a load-time capture was observed to fall out), and an operator's hand-edit landing while the visor runs is no longer clobbered by the next flush.

The alternative — json.RawMessage catch-alls with hand-written Marshal/Unmarshal on V1, HypervisorConfig, WasmServeConf, … — was rejected: it needs a new method pair on every nested block anyone might ever extend, and each one is a fresh chance to get the catch-all wrong. This walk covers every block at once and needs no per-type maintenance.

Deliberate omissions:

  • retiredKeys. A load-time migration that RENAMES a key (V1's legacy "dmsgpty" → "pty", see config_compat.go) leaves the old key looking "unknown". Preserving it would resurrect a key the migration exists to retire, and leave two copies of the same block on disk. Such keys are listed below and dropped from the residue explicitly.

  • Types with their own json.Unmarshaler / encoding.TextUnmarshaler are opaque: their on-disk shape is not their Go shape, so a field-name walk cannot tell known from unknown inside them. (Duration, cipher.PubKey, and Launcher.Apps' appsList wrapper.)

  • Array elements re-attach by index and only while the flushed array still has the original length; a resized array drops that array's residue rather than risk grafting a key onto the wrong element.

A config regenerated from scratch (`skywire cli config gen`, `skywire autoconfig`) does not go through flush at all — those paths build V1 in memory and write it with their own os.WriteFile — so regenerating remains the way to clear genuinely stale keys.

Package visorconfig pkg/visor/visorconfig/read.go c3-vis-core

Reader / ReadFile / ReadRaw load a V1 config from disk or an io.Reader. All three pull encoding/json and os.ReadFile, neither of which is reachable from the install-page WASM — browsers don't have filesystems, and TinyGo's stdlib can't link encoding/json's reflect runtime helpers anyway. Build-tag-gated off the WASM path; callers under js construct V1 in memory via genvisor.Generate.

Package visorconfig pkg/visor/visorconfig/resolvers.go c3-vis-core

ResolverConfig declares an ADDITIONAL resolving proxy beyond the two singular blocks (dmsg_web, skynet_web) the visor has always hosted.

Why a list alongside the singular blocks rather than turning dmsg_web into a list: every skywire-config.json in the field carries `dmsg_web` / `skynet_web` objects, and `cli resolver up/down/bind`, the proxy-status pages and the RPC surface all address exactly those two by name. Reshaping them into an array would invalidate every on-disk config and every caller at once for no gain — the two primaries ARE the default resolver pair, and what was missing is the ability to add more. So `resolvers` is purely additive: absent (the case for every config written before this) means exactly today's behavior, and an entry in it is a third, fourth, … proxy that runs beside the primaries.

The knobs are a deliberate subset of DmsgWebConfig/SkynetWebConfig rather than an embedding of either. Embedding would flatten `secret_key` and `forward_proxy` (dmsg-only) into a skynet entry and `route_timeout` (skynet-only) into a dmsg entry, where they would be silently ignored — the kind of config that reads as supported and does nothing. Naming the fields once here, and rejecting the ones that don't apply to an entry's kind at parse time, keeps "what you wrote is what runs" true.

Package visorconfig pkg/visor/visorconfig/services.go c3-vis-core

Services + EnvServices are TYPE ALIASES to the deployment package rather than independent struct definitions. Reason: the visorconfig version was bit-identical to deployment.Services (same fields, same json tags) but lived in two places, which forced a redundant json.Unmarshal at every MakeBaseConfig call to convert the embedded deployment.ServicesJSON into a visorconfig.Services. The alias makes deployment.Prod usable in-place — same memory, same fields, zero conversion — so visorconfig no longer needs to invoke encoding/json on the WASM-clean code path.

HasDmsgEndpoints stays here as a method on the aliased type (works on alias receivers under Go's type-identity rules).

Package visorconfig pkg/visor/visorconfig/services_native.go c3-vis-core

Native (non-WASM) home of the EnvServices alias. Lives here (rather than in services.go) because deployment.EnvServices is itself only defined under !js — its json.RawMessage fields need encoding/json, which is build-tag-gated off the WASM path. See services.go's package doc for context.

This file used to also carry Fetch(), a plain-net/http helper that pulled a Services payload from the conf-service URL. It was removed: the deployment's conf service is dmsg-only (deployment.ProdConf.Conf is a dmsg:// URL), and a bare http.Client can never dial one — it fails with `unsupported protocol scheme "dmsg"`. Standing a dmsg client up here is not possible either: the CLI's dmsg fetch chain lives in cmd/skywire-cli, which imports pkg/visor, which imports this package, so the dependency can only run in that direction. Its sole caller (`skywire cli config update -a`) now uses the same dmsg-first fetch that `config gen` uses.

Package visorconfig pkg/visor/visorconfig/survey_linux.go c3-vis-core

Package visorconfig pkg/visor/visorconfig/survey_whitelist.go c3-vis-core

Package visorconfig pkg/visor/visorconfig/tcp_notsent.go c3-vis-core

Package visorconfig pkg/visor/visorconfig/types.go c3-vis-core

The Duration type's MarshalJSON / UnmarshalJSON live in types_native.go under //go:build !js because encoding/json drags the reflect runtime helpers TinyGo's stdlib lacks. Under js V1 is never (de)serialized, so the absence of those methods causes no functional change.

Package visorconfig pkg/visor/visorconfig/types_native.go c3-vis-core

Duration's JSON codec methods. Tagged off the WASM build because json.Marshal / json.Unmarshal pull the reflect runtime helpers TinyGo's stdlib doesn't provide. See types.go's package doc for the broader rationale.

Package visorconfig pkg/visor/visorconfig/v1.go c3-vis-core

Package visorconfig pkg/visor/visorconfig/values.go c3-vis-core

Package visorconfig pkg/visor/visorconfig/values_linux.go c3-vis-core

Package visorconfig pkg/visor/visorconfig/walletconfig.go c3-app-wallet

Package visorconfig pkg/visor/visorconfig/walletconfig_native.go c3-app-wallet

Index

Constants

View Source
const (
	// Stdin read config from STDIN.
	Stdin = "STDIN"
	// Stdout write config to STDOUT.
	Stdout = "STDOUT"
)
View Source
const (
	// ResolverKindDmsg resolves <pk><suffix> hostnames over dmsg, the same
	// way the dmsg_web block does. Default when Kind is empty.
	ResolverKindDmsg = "dmsg"
	// ResolverKindSkynet resolves them over skywire routing (the router),
	// the same way the skynet_web block does.
	ResolverKindSkynet = "skynet"
)

Resolver kinds. A ResolverConfig with an empty Kind is a dmsg resolver — the far more common case, and the one that can carry its own identity.

View Source
const (
	DefaultDmsgWebProxyPort   = 4445
	DefaultSkynetWebProxyPort = 4446
)

DefaultDmsgWebProxyPort / DefaultSkynetWebProxyPort are the SOCKS5 ports the two primary resolvers bind when their block leaves proxy_port at zero. They live here (not only in pkg/visor) because port-conflict detection has to know what an unset port resolves to — a config that sets an extra resolver to 4445 collides with a dmsg_web block that never mentions 4445 at all.

View Source
const (
	FileLogStore   = "file"
	MemoryLogStore = "memory"
)

LogStore types.

View Source
const (
	// DefaultTimeout is used for default config generation and if it is not set in config.
	DefaultTimeout = Duration(10 * time.Second)
	// DefaultLogRotationInterval is used as default for RotationInterval and if it is not set in Log.
	DefaultLogRotationInterval = Duration(time.Hour * 24 * 7)
)
View Source
const (
	// WalletCustodyBrowser keeps keys in the browser (localStorage on a native
	// visor or `hv serve`, an OPFS virtual directory under wasm). The host
	// never sees them. Available EVERYWHERE, because the wallet frontend is
	// always a browser SPA — hence the default.
	WalletCustodyBrowser = "browser"

	// WalletCustodyDisk keeps wallet files on a real filesystem, served by the
	// skycoin-web server (RunSkycoinWeb) at Wallet.Dir. Requires a backend Go
	// process AND a filesystem, so it is offered only on a native visor or
	// `hv serve` — never in a statically-hosted wasm PWA or a single-file
	// hypervisor. Flips the trust model: the host holds the keys.
	WalletCustodyDisk = "disk"

	// WalletCustodyRemote delegates to another visor's skycoin-web server over
	// dmsg (Wallet.RemotePK). Available anywhere a dmsg target is reachable;
	// you are trusting that remote.
	WalletCustodyRemote = "remote"
)

Wallet custody modes — WHERE the keys and wallet files live. This is the one axis the operator actually chooses; how the frontend is served and how the node API is reached vary by platform and are not the operator's concern.

See docs/design/skycoin-web-wallet-architecture.md.

View Source
const DefaultAutoconnectSiteLinks = 2

DefaultAutoconnectSiteLinks is how many transports of each site type autoconnect keeps between two sites when SiteLinks is unset.

View Source
const DefaultWispPort = 6001

DefaultWispPort is the virtual-loopback port the embedded Wisp server binds and page JS dials, matching `skywire cli wisp serve`'s default so the two are one address in two places rather than two conventions.

View Source
const KeyRingTypeDeterministic = "deterministic"

KeyRingTypeDeterministic is the only KeyRing.Type today: keys are derived from the visor secret key (the implicit seed) by a one-way HMAC chain, in the spirit of a skycoin deterministic wallet.

Variables

View Source
var (
	// DmsgHTTPPort Listening port for dmsghttp logserver. Set to
	// pkg/dmsg/dmsg.DefaultDmsgHTTPPort's value (80). Hardcoded here
	// rather than imported so values.go stays WASM-clean — the dmsg
	// client package pulls in unrelated heavy deps that the
	// in-browser config-generator doesn't need.
	DmsgHTTPPort = uint16(80)

	// PublicVisorMaxTransports is the DISTINCT-PEER count at which a public visor
	// drain-pauses its service-discovery registration (load-shedding). It counts
	// unique remote visors, not raw transports — several transport types to the
	// same peer (stcpr + sudph + squicr, for route diversity) count as one — so
	// the cap describes real peer load, not transport-type multiplication. It
	// must sit comfortably above the fleet size, because a public visor that also
	// runs public_autoconnect peers with ~every other public visor. At 1000 (≈
	// the fleet size) the busiest visors oscillated across the cap and flapped
	// out of the registry, starving browser/wasm clients of a reachable target.
	// 2048 gives ~2× headroom over the current fleet.
	PublicVisorMaxTransports = 2048
)
View Source
var (
	// ErrInvalidSK occurs when config file has an invalid secret key.
	ErrInvalidSK = errors.New("config has invalid secret key")
)
View Source
var (
	// ErrNoConfigPath is returned on attempt to read/write config when visor contains no config path.
	ErrNoConfigPath = errors.New("no config path")
)
View Source
var (
	// VisorConfigFile will contain the path to the visor's config or `stdin` to denote that the config was read from STDIN
	VisorConfigFile string
)

Functions

func HTTPAddr

func HTTPAddr() string

HTTPAddr returns the httpAddr

func HomePath

func HomePath() string

HomePath gets the current user's home folder

func IsRoot

func IsRoot() bool

IsRoot checks for root permissions.

It resolves the effective UID FIRST (0 == root), which never needs a passwd lookup. On a static (musl) build running under a UID with no /etc/passwd entry, user.Current() returns (nil, err); the previous code ignored the error and dereferenced the nil user, panicking in this package's init() — which crashed EVERY skywire command on such hosts (seen on the prod hv-serve unit, crash-looping v1.3.86). Geteuid is both safer and the canonical root test.

func NormalizeResolvers

func NormalizeResolvers(rs []ResolverConfig)

NormalizeResolvers fills in the defaults a hand-edited config may omit — today just the name, which everything downstream (log fields, app rows) needs to be non-empty and unique. Called from ValidateResolvers so both the config generator and the visor see the same names.

func PackageConfig

func PackageConfig() skyenv.PkgConfig

PackageConfig returns the package-specific config paths

func ResolverListenAddr

func ResolverListenAddr(addr string, port uint) string

ResolverListenAddr renders a resolving proxy's SOCKS5 listener the way its runtime binds it: an empty addr means loopback, and a zero port means the SOCKS5 front-end is disabled (rendered as empty, never as ":0").

func SkywireConfig

func SkywireConfig() string

SkywireConfig returns the full path to the package config

func SplitArgs

func SplitArgs(s string) ([]string, error)

SplitArgs is the exported counterpart to splitArgs — same parser, available to callers outside the package (e.g. config-gen wants to tokenize SKYCOIND_FLAGS using the same rules the on-disk config uses).

func UpdateCommand

func UpdateCommand() []string

UpdateCommand returns the commands which are run when the update button is clicked in the ui

func UseDeploymentSurveyWhitelist

func UseDeploymentSurveyWhitelist() bool

UseDeploymentSurveyWhitelist reports whether the deployment's survey whitelist — the key set the conf service hands out — should be written into this visor's config and refreshed into it thereafter.

Those keys authorize their holders to read this visor's log server, its system survey and its pprof endpoints over dmsg (see initDmsgHTTPLogServer). On a fleet node that is the point: it is how an operator inspects machines they run. The mobile pair says no — see the comment there.

func UserConfig

func UserConfig() skyenv.PkgConfig

UserConfig contains installation paths for running skywire as the user

func Version

func Version() string

Version gets the version of the installation for the config.

The runtime version-discovery path (git-describe fallback when buildinfo.Version() returns "unknown") lives in values_native.go behind a !js build tag — it shells out to `git`, which is nonsensical in a browser WASM context. Under js the returned value is just buildinfo.Version() with no fallback.

func WalletCustodyOptions

func WalletCustodyOptions() []string

WalletCustodyOptions lists the custody modes this build can realize — what a GUI should offer. browser and remote are always available; disk appears only where there is a backend process with a filesystem.

Types

type AppSettingsEntry

type AppSettingsEntry struct {
	Values map[string]int64  `json:"values,omitempty"`
	Text   map[string]string `json:"text,omitempty"`
}

AppSettingsEntry is one app's persisted live knobs: the numeric payloads and the list ones, exactly as pkg/skysocks/skysettings encodes them. The config carries them without knowing what any of them mean — a knob added to the client needs no change here.

type Autoconnect

type Autoconnect struct {
	// SiteLinks is how many transports of SiteTypes autoconnect keeps between
	// two sites. A site is the visors behind one public IP that share a LAN
	// subnet. Unset means DefaultAutoconnectSiteLinks; 0 turns the limit off.
	SiteLinks *int `json:"site_links,omitempty"`
	// SiteTypes are the transport types SiteLinks applies to; empty means sudph.
	SiteTypes []string `json:"site_types,omitempty"`
	// MaxPerType caps the transports of a type autoconnect dials. A type left
	// out keeps its built-in cap; 0 means no cap.
	MaxPerType map[string]int `json:"max_per_type,omitempty"`
	// MaxTotal caps the transports autoconnect dials of all types; 0 means no cap.
	MaxTotal int `json:"max_total,omitempty"`
}

Autoconnect limits the transports public autoconnect dials. Each limit counts only the automatic transports this visor dialed itself, and reaching one stops new dials without closing any transport.

func (Autoconnect) Cap

func (a Autoconnect) Cap(t string, builtin int) int

Cap is the dial cap for transport type t: MaxPerType's entry when set, otherwise builtin. 0 means no cap.

func (a Autoconnect) Links() int

Links is SiteLinks with its default applied.

func (Autoconnect) SiteLimited

func (a Autoconnect) SiteLimited(t string) bool

SiteLimited reports whether the site limit applies to transport type t.

type BrowseOriginConfig

type BrowseOriginConfig struct {
	// Enable must be true for the reverse-proxy origin to start.
	Enable bool `json:"enable"`
	// Mode selects how each mesh site gets its own isolated browser origin:
	//   "subdomain" (default) — ONE listener on Addr; each site is the origin
	//     "<vhost>.<base32pk><Suffix>" routed by Host header. Needs *.<Suffix> to
	//     resolve to loopback (*.mesh.localhost does in every target browser) or,
	//     for a hosted domain, real wildcard DNS.
	//   "port" — each site gets its OWN "127.0.0.1:<port>" origin from a small
	//     pool (PortBase..PortBase+PortSpan). No DNS/wildcard dependency at all;
	//     works anywhere. Weaker isolation than subdomain (cookies are domain-
	//     scoped, not port-scoped), so prefer subdomain unless *.localhost can't
	//     resolve in the target environment.
	Mode string `json:"mode,omitempty"`
	// Addr is the listen address for subdomain mode, and the "portal" listener
	// (GET /open?host=… → 302 to the per-site port) for port mode. Default
	// "127.0.0.1:8461". Loopback-only unless you front it with real TLS.
	Addr string `json:"addr,omitempty"`
	// Suffix is the subdomain-mode origin domain; must start with "." — default
	// ".mesh.localhost" (loopback, no public cert needed). Set to a wildcard
	// domain you control (e.g. ".haltingstate.net") or your own domain to serve
	// a hosted browse origin behind real TLS.
	Suffix string `json:"suffix,omitempty"`
	// TLSCert / TLSKey, when BOTH set, serve the browse origin over HTTPS with
	// this cert/key — a real cert for <Suffix>, or a locally-trusted one (e.g.
	// from mkcert) for local https parity with the hosted origin (same scheme,
	// real secure-context, real-domain cookie isolation). Empty = plain HTTP
	// (fine for *.mesh.localhost, which is a secure context without TLS).
	TLSCert string `json:"tls_cert,omitempty"`
	TLSKey  string `json:"tls_key,omitempty"`
	// PortBase / PortSpan bound the per-site loopback port pool used in "port"
	// mode (least-recently-used eviction when full). Defaults 8470 / 64.
	PortBase int `json:"port_base,omitempty"`
	PortSpan int `json:"port_span,omitempty"`
}

BrowseOriginConfig enables the native "real-origin" browse proxy: a loopback HTTP reverse-proxy origin that serves mesh (dmsg) sites so the BROWSER does native subresource loading, cookies, redirects, WASM and streaming — skywire only proxies the transport over dmsg (see pkg/visor/meshproxy.go and pkg/wasmhv/browseui/RFC-real-origin-browser.md). Content flows through THIS visor's OWN transports; it is a local listener, never a shared/central relay.

The browse iframe points at http://<vhost>.<base32pk><suffix>:<port>/, which the browser resolves to loopback (for .mesh.localhost) or to wherever the operator's DNS points (a hosted domain). This is the native counterpart of the wasm surface's Service-Worker browse origin.

type CoinNodeConfig

type CoinNodeConfig struct {
	// LocalAddr is the node's HTTP API address on this host
	// (e.g. "127.0.0.1:6420" — the skycoin daemon default). The visor
	// reverse-proxies this over dmsg and probes <LocalAddr>/api/v1/health.
	LocalAddr string `json:"local_addr"`
	// DmsgPort is the dmsg port the node is exposed on. The advertised SD
	// entry address is <this-visor-PK>:<DmsgPort>, which a wallet dials
	// through the mesh (or the wasm visor's HTTP-over-dmsg shim).
	DmsgPort uint16 `json:"dmsg_port"`
	// Whitelist, when non-empty, restricts which peer PKs may reach the
	// forwarded node. Empty (default) = any authenticated peer — the usual
	// choice for a public read-mostly coin API.
	Whitelist []cipher.PubKey `json:"whitelist,omitempty"`
}

CoinNodeConfig describes one fibercoin node this visor exposes over dmsg and advertises in service discovery (servicedisc.ServiceTypeCoin). The node (e.g. `skywire skycoin daemon`) runs INDEPENDENTLY of the visor and serves a plain HTTP API on localhost; the visor merely reverse-proxies it over the mesh and, on the SD heartbeat interval, reads its /api/v1/health to build a truthful, DETECTED CoinInfo advertisement (never operator-claimed identity).

type Common

type Common struct {
	Version string        `json:"version"`
	SK      cipher.SecKey `json:"sk,omitempty"`
	PK      cipher.PubKey `json:"pk,omitempty"`
	// contains filtered or unexported fields
}

Common represents the common fields that are shared across all config versions, alongside logging and flushing fields.

func NewCommon

func NewCommon(log *logging.MasterLogger, confPath string, sk *cipher.SecKey) (*Common, error)

NewCommon returns a new Common.

func (*Common) MasterLogger

func (c *Common) MasterLogger() *logging.MasterLogger

MasterLogger returns the underlying master logger.

func (*Common) Path

func (c *Common) Path() string

Path returns the on-disk path the config was loaded from. Empty when the config came from STDIN or was synthesized in-memory.

func (*Common) SetAfterFlush

func (c *Common) SetAfterFlush(f func())

SetAfterFlush registers f to run after each write of the config file. Set it before anything can flush. f must not lock the config.

func (*Common) SetLogger

func (c *Common) SetLogger(log *logging.MasterLogger)

SetLogger sets logger.

type CookieConfig

type CookieConfig struct {
	HashKey  Key `json:"hash_key"`  // Signs the cookie: 32 or 64 bytes.
	BlockKey Key `json:"block_key"` // Encrypts the cookie: 16 (AES-128), 24 (AES-192), 32 (AES-256) bytes. (optional)

	ExpiresDuration time.Duration `json:"expires_duration"` // Used for determining the 'expires' value for cookies.

	Path   string `json:"path"`   // optional
	Domain string `json:"domain"` // optional

	TLS bool `json:"-"`
}

CookieConfig configures cookies used for hypervisor.

func (*CookieConfig) FillDefaults

func (c *CookieConfig) FillDefaults()

FillDefaults fills config with default values.

func (*CookieConfig) HTTPOnly

func (c *CookieConfig) HTTPOnly() bool

HTTPOnly gets cookie's `HTTPOnly` value.

Unconditionally true: the session cookie is never read from JavaScript, so keeping it out of document.cookie costs nothing and blocks XSS-based session theft. It used to return !c.TLS, which inverted the protection exactly when an operator opted into TLS — the safer configuration turned HTTPOnly off.

func (*CookieConfig) SameSite

func (c *CookieConfig) SameSite() http.SameSite

SameSite gets cookie's `SameSite` value.

func (*CookieConfig) Secure

func (c *CookieConfig) Secure() bool

Secure gets cookie's `Secure` value.

type DmsgHTTPServers

type DmsgHTTPServers struct {
	Prod DmsgHTTPServersData `json:"prod"`
}

DmsgHTTPServers struct use to unmarshal dmsghttp file

type DmsgHTTPServersData

type DmsgHTTPServersData struct {
	DMSGServers        []*disc.Entry `json:"dmsg_servers"`
	DMSGDiscovery      string        `json:"dmsg_discovery"`
	TransportDiscovery string        `json:"transport_discovery"`
	AddressResolver    string        `json:"address_resolver"`
	RouteFinder        string        `json:"route_finder"`
	ServiceDiscovery   string        `json:"service_discovery"`
}

DmsgHTTPServersData is a part of DmsgHTTPServers

type DmsgWebConfig

type DmsgWebConfig struct {
	// Enable must be true for the resolver to start.
	Enable bool `json:"enable"`
	// SecretKey runs the resolver under its OWN dmsg identity instead of the
	// visor's. Unset (the default) keeps today's behavior: the resolver
	// borrows v.dmsgC and answers as the visor.
	//
	// It exists because some keys cannot move. The survey whitelist knows the
	// key the resolving proxy runs under, so rotating it means updating the
	// deployment; but running a second FULL dmsg client to hold that key
	// costs its own sessions to every server and its own discovery entry,
	// re-registered on every restart — the identity churn that got the tpviz
	// throwaway client removed in #4500/#4501.
	//
	// Set here, the resolver's client is attached IN-PROCESS to the visor's
	// own relay (dmsg.AttachInProcess): one session, over an in-memory pipe,
	// no discovery entry, and every stream carried by the visor's sessions
	// and transports. The host pays for dmsg once and the key never rotates.
	//
	// Nothing dials this key inbound except through the visor, so it suits an
	// outbound resolver and NOT a service that must be reachable on its own.
	SecretKey *cipher.SecKey `json:"secret_key,omitempty"`
	// ProxyPort is the local SOCKS5 listener. Default 4445.
	ProxyPort uint `json:"proxy_port,omitempty"`
	// ProxyAddr is the host the SOCKS5 proxy binds to. Empty = loopback
	// ("127.0.0.1"). Set to "0.0.0.0" or a LAN IP to serve OTHER devices on
	// the network — e.g. a board acting as a `.dmsg` gateway for a home
	// router's LAN. Only bind a non-loopback address on a trusted network.
	ProxyAddr string `json:"proxy_addr,omitempty"`
	// WebPort is the local HTTP bridge the SOCKS5 proxy rewrites
	// matched hosts to. Default 8080.
	WebPort uint `json:"web_port,omitempty"`
	// DomainSuffix is the TLD treated as DMSG addresses. Default ".dmsg".
	DomainSuffix string `json:"domain_suffix,omitempty"`
	// UpstreamSOCKS, if set, forwards non-matching CONNECTs to this
	// upstream SOCKS5 server (e.g. "127.0.0.1:1080" for a chained
	// skysocks-client). Empty = direct connect.
	UpstreamSOCKS string `json:"upstream_socks,omitempty"`
	// UpstreamRules send chosen domains to another upstream (a second
	// skysocks-client, say) or "direct", ahead of UpstreamSOCKS. The longest
	// matching suffix wins.
	UpstreamRules []proxyroute.Rule `json:"upstream_rules,omitempty"`
	// TLSMITM enables on-the-fly TLS termination for browser
	// connections to <pk>.dmsg on TLSPort, using a leaf cert minted
	// from a locally-installed CA at TLSCAPath/TLSCAKeyPath. Off by
	// default; when off the resolver behaves exactly as before.
	// See pkg/skynetca and docs/skynet-tls.md for the trust model.
	TLSMITM bool `json:"tls_mitm,omitempty"`
	// TLSPort is the destination port treated as TLS. Default 443.
	TLSPort uint16 `json:"tls_port,omitempty"`
	// TLSUpstreamPort is the destination port a MITM'd connection is sent
	// to once decrypted. Default 80, where visors serve HTTP.
	TLSUpstreamPort uint16 `json:"tls_upstream_port,omitempty"`
	// TLSCAPath / TLSCAKeyPath point to a CA generated by
	// `skywire cli resolver ca gen`. Both required when TLSMITM
	// is true. The visor logs and falls back to non-MITM mode if
	// either is missing or unreadable; it does not refuse to start.
	TLSCAPath    string `json:"tls_ca_path,omitempty"`
	TLSCAKeyPath string `json:"tls_ca_key_path,omitempty"`
	// SelfLoopback, when nil or true (the default), serves a request whose
	// destination is THIS visor's own PK from the local service in-process
	// (net.Pipe) instead of dialing out over dmsg back to self — which dmsg
	// does not loopback and which is 202-prone. Set false to exercise the
	// full self-dial transport path (testing self-reachability).
	SelfLoopback *bool `json:"self_loopback,omitempty"`
	// Alias is a friendly hostname label for THIS visor's own PK, so
	// "<alias>.dmsg" resolves to the local visor without hardcoding its
	// public key. Defaults to "skywire" when unset (→ "skywire.dmsg").
	Alias string `json:"alias,omitempty"`
	// SelfLoopbackAuthenticated, when nil or true (the default), makes a
	// self-loopback request (skywire.dmsg / <self-pk>.dmsg served in-process
	// via SelfDial) present THIS visor's own PK as the authenticated caller.
	// The local visor's PK is always on its own landing-page whitelist, so the
	// in-process self view renders the same authenticated endpoints (pprof,
	// /visor.log, …) a survey-whitelisted remote peer sees — local self-access
	// through one's own proxy is inherently authorized. Set false to render the
	// self-loopback landing page as an anonymous (unauthenticated) caller.
	SelfLoopbackAuthenticated *bool `json:"self_loopback_authenticated,omitempty"`
	// ForwardProxy, when true, makes the visor serve a CLEARNET HTTP forward-
	// proxy over dmsg on ForwardPort: a whitelisted peer (e.g. a browser
	// wasm-visor that cannot itself do TLS) sends an absolute-URL HTTP request
	// over dmsg and this visor fetches it from the clearnet and returns the
	// response. Default off. Access is restricted to the same whitelist that
	// gates the visor's other privileged dmsg surfaces (own PK + hypervisors +
	// survey/pty whitelist) — it is NOT an open relay. Egress prefers
	// UpstreamSOCKS when set (so the exit IP is the skysocks node, not this
	// visor); only if UpstreamSOCKS is empty does this visor connect directly
	// (its own IP becomes the exit). See docs/design/dmsg-forward-proxy.md.
	ForwardProxy bool `json:"forward_proxy,omitempty"`
	// ForwardPort is the dmsg port the forward-proxy listens on. Default 84.
	ForwardPort uint16 `json:"forward_port,omitempty"`
}

DmsgWebConfig enables the embedded `.dmsg` resolving proxy hosted by the visor. When Enable is true, the visor serves a SOCKS5 proxy on ProxyPort plus an HTTP bridge on WebPort; browsers pointed at the SOCKS5 proxy can load http://somekey.dmsg URLs directly. Reuses the visor's own dmsg client for the outbound fetches, so no extra dmsg identity or session load is incurred.

This is the in-process version of the `skywire dmsg web` CLI, so the same domain-suffix resolving behavior is available without running a separate utility alongside the visor.

type Dmsgscp

type Dmsgscp struct {
	// Disabled, when true, suppresses both the dmsg and skynet
	// listeners. Default false (== on). Inverted from the original
	// `Enabled` field so the zero value matches the default.
	Disabled bool `json:"disabled,omitempty"`
	// DmsgPort is the dmsg port to listen on; zero means use
	// dmsgscp.DefaultPort (23). The skynet mirror binds the same
	// port number on the router for parity.
	DmsgPort uint16 `json:"dmsg_port,omitempty"`
	// RootDir is the filesystem directory the host serves files
	// from (its effective chroot). Empty means
	// <local_path>/scp-root is used.
	RootDir string `json:"root_dir,omitempty"`
	// Whitelist optionally overrides the dmsgpty whitelist. When
	// nil or empty, init reuses Pty.Whitelist.
	Whitelist []cipher.PubKey `json:"whitelist,omitempty"`
}

Dmsgscp configures the dmsgscp-host (scp-over-dmsg daemon). The host is on by default — access is gated by the same whitelist that dmsgpty uses (a peer's PK must be on the list to be served). Operators who want to disable it entirely set Disabled=true. When Whitelist is empty the host reuses Pty.Whitelist so trusting a peer for pty implicitly trusts them for file transfer too.

The host listens on BOTH dmsg and the skywire router (skynet) at the same port number — same dual-listen pattern dmsg_http and others use. dmsg covers the bootstrap path (works before any router transport is up); skynet covers steady-state operation over arbitrary transports.

type Duration

type Duration time.Duration

Duration wraps around time.Duration to allow parsing from and to JSON.

The Marshal/Unmarshal methods live in types_native.go (!js) — browsers never (de)serialize V1, so dropping the methods under js doesn't change observable behavior; it just frees TinyGo from linking encoding/json's reflect path.

func (Duration) MarshalJSON

func (d Duration) MarshalJSON() ([]byte, error)

MarshalJSON implements json marshaling.

func (*Duration) UnmarshalJSON

func (d *Duration) UnmarshalJSON(b []byte) error

UnmarshalJSON implements unmarshal from json.

type EnvServices

type EnvServices = deployment.EnvServices

EnvServices is the wrapper struct for the outer JSON. Aliased to deployment.EnvServices (only defined under !js — see deployment's config.go package doc for the WASM-stripping rationale). Consumers using visorconfig.EnvServices keep working under non-WASM builds.

type HostBridgeConfig added in v1.3.101

type HostBridgeConfig struct {
	// Secret is the pairing secret the bridge gave this visor. It comes first
	// so the runtime-config view can drop its line. See redactSecrets.
	Secret string `json:"secret,omitempty"`
	// URL is the one the bridge prints, token included.
	URL      string                    `json:"url"`
	Forwards []HostBridgeForwardConfig `json:"forwards"`
}

HostBridgeConfig is the host bridge a browser visor uses for the sockets of its host, kept so the visor reconnects to it at boot. See pkg/hostbridge and `skywire cli visor hostbridge`.

type HostBridgeForwardConfig added in v1.3.101

type HostBridgeForwardConfig struct {
	Host  string `json:"host"`
	Local string `json:"local"`
}

HostBridgeForwardConfig passes connections to Host, on the bridge's host, to Local, an address the visor dials.

type HypervisorConfig

type HypervisorConfig struct {
	Enable bool `json:"enable"` // Whether the hypervisor is enabled (starts DMSG RPC + tracking + web UI on visor startup).
	// DmsgIngest is read by MOBILE builds only: it opts in to the remote-visor
	// dmsg RPC ingest — the listener other visors connect to when this PK is in
	// their `hypervisors` list (the phone app's Fleet toggle). Off by default so
	// the mobile core stays an API-only localhost surface. Desktop builds ignore
	// it: there the ingest always starts with the hypervisor, as before.
	DmsgIngest bool `json:"dmsg_ingest,omitempty"`
	// UIDisable, when true, suppresses ONLY the web UI (HTTP) server while the
	// hypervisor's DMSG-RPC listener + managed-visor tracking (and `hv ls` over
	// the visor RPC) stay active. Toggled at runtime with `hv ui enable/disable`.
	// Default false (UI served) — backward-compatible with configs that omit it.
	UIDisable bool `json:"ui_disable,omitempty"`
	// DeskAddr is where the desk — the wasm-visor hypervisor UI, hosted out of
	// the skywire command module — is served: a second listener carrying the
	// same handler as HTTPAddr, so the API, the login and every page are the
	// same on both; only the root differs. The root of HTTPAddr is the Angular
	// dashboard, the root of DeskAddr is the desk. Set from HVDESKADDR in
	// skywire.conf or `config gen --hvdeskaddr`; empty takes the platform
	// default (:8010 native, none in a browser). No listener is started when
	// the build has no command module to host a desk out of.
	DeskAddr   string        `json:"desk_addr"`
	UIAssets   fs.FS         `json:"-"`
	PK         cipher.PubKey `json:"-"`
	SK         cipher.SecKey `json:"-"`
	DBPath     string        `json:"db_path"`     // Path to store database file.
	EnableAuth bool          `json:"enable_auth"` // Whether to enable user management.
	// EnablePKEndpoint, when true, exposes an unauthenticated
	// GET /api/pk route returning the hypervisor's pubkey. Off by
	// default — only the skybian / Arch-linux-ARM image builds
	// flip it on so first-boot visors can discover the bundled
	// hypervisor's pubkey without an account. The route still
	// requires an SW-Public header naming the caller's PK as a
	// soft "looks like another visor" gate; see
	// hypervisor_handlers_misc.go. No omitempty — the field is
	// always serialized so operators see it in the generated
	// config and know it exists.
	EnablePKEndpoint bool         `json:"enable_pk_endpoint"`
	Cookies          CookieConfig `json:"cookies"`             // Configures cookies (for session management).
	DmsgDiscovery    string       `json:"-"`                   // Dmsg discovery address.
	DmsgPort         uint16       `json:"dmsg_port,omitempty"` // Dmsg port to serve the (gob) management RPC on.
	HTTPAddr         string       `json:"http_addr"`           // HTTP address to serve API/web UI on.
	// DmsgUIPort, when non-zero, also serves the hypervisor HTTP API + web UI
	// over a dmsg listener on this port — so a browser dmsg client (the WASM
	// hypervisor UI) can reach the fleet BY PUBLIC KEY with no exposed HTTP
	// port, end-to-end over dmsg. Off by default (0): exposing fleet control
	// over dmsg is opt-in. The handler's own login/CSRF auth still applies; the
	// dmsg Noise layer additionally authenticates the caller's PK.
	DmsgUIPort    uint16             `json:"dmsg_ui_port,omitempty"`
	EnableTLS     bool               `json:"enable_tls"`                // Whether to enable TLS.
	TLSCertFile   string             `json:"tls_cert_file"`             // TLS cert file location.
	TLSKeyFile    string             `json:"tls_key_file"`              // TLS key file location.
	TPViz         TPVizConfig        `json:"tp_viz"`                    // Transport visualizer config.
	LANDmsgServer *LANDmsgServerConf `json:"lan_dmsg_server,omitempty"` // LAN DMSG server config.
	// CXOSubscribeInterval is the resync floor for the on-demand
	// CXO subscription manager. Subscriptions stay open while a UI
	// tab is acquired; the manager won't tear down a feed sooner
	// than this after its last release. 0 falls back to the
	// package default (5m). Common knob to tune the bandwidth /
	// freshness tradeoff for hvui tabs that source CXO data.
	CXOSubscribeInterval Duration `json:"cxo_subscribe_interval,omitempty"`
	// WasmServe, when non-nil with a non-empty Addr, also serves the
	// standalone wasm-visor PWA / testing harness from this same visor
	// process on that address, so a rebuild-restart serves the latest (no
	// separate `hv serve`). Off by default. See pkg/visor.ServeWasm.
	WasmServe *WasmServeConf `json:"wasm_serve,omitempty"`
	// WasmModuleSource is the visor the js/wasm module served for the desk
	// is refreshed from when the desk is opened. Empty means
	// the deployment's wasm_module_source.
	WasmModuleSource string `json:"wasm_module_source,omitempty"`
}

HypervisorConfig configures the hypervisor.

func DefaultHypervisorConfig

func DefaultHypervisorConfig() HypervisorConfig

DefaultHypervisorConfig returns a HypervisorConfig with sensible defaults. Used when enabling the hypervisor at runtime on a visor that wasn't configured as a hypervisor at startup.

func GenerateWorkDirConfig

func GenerateWorkDirConfig() HypervisorConfig

GenerateWorkDirConfig generates a config with default values and uses db from current working directory.

func MakeConfig

func MakeConfig() HypervisorConfig

MakeConfig returns hypervisor config.

func (*HypervisorConfig) EffectiveDeskAddr

func (c *HypervisorConfig) EffectiveDeskAddr() string

EffectiveDeskAddr is where the desk is served: DeskAddr, or the platform default when the config predates the field or leaves it empty. Empty is the browser's default, and there it means no desk listener.

func (*HypervisorConfig) FillDefaults

func (c *HypervisorConfig) FillDefaults()

FillDefaults fills the config with default values.

func (*HypervisorConfig) Parse

func (c *HypervisorConfig) Parse(path string) error

Parse parses the file at path and decodes its JSON into c.

Tagged !js because it pulls encoding/json (and via that, the reflect runtime helpers TinyGo's stdlib doesn't provide). Browsers don't have filesystem access anyway, so the method has no use under js/wasm — callers in that context should construct HypervisorConfig in memory.

type Key

type Key []byte

Key allows a byte slice to be marshaled or unmarshaled from a hex string.

func (Key) MarshalText

func (hk Key) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler

func (Key) String

func (hk Key) String() string

String implements fmt.Stringer

func (*Key) UnmarshalText

func (hk *Key) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler

type KeyEntry

type KeyEntry struct {
	Index     uint32 `json:"index"`
	Label     string `json:"label,omitempty"`
	PublicKey string `json:"public_key"`
	SecretKey string `json:"secret_key"`
}

KeyEntry is one deterministically-derived keypair in the visor KeyRing — the derivation index, a purpose label, and the public/secret key. (No skycoin address: the keyring's only use is the standalone-hypervisor identity keypair, not a payment wallet.)

type KeyRing

type KeyRing struct {
	Type      string     `json:"type"`
	NextIndex uint32     `json:"next_index"`
	Entries   []KeyEntry `json:"entries,omitempty"`
}

KeyRing is a small wallet-like set of deterministically-derived keys recorded in the visor config, shaped loosely like a skycoin deterministic wallet (a type + tracked next-index + entries with public/secret keys).

The keys are derived ONE-WAY from the visor secret key (the implicit seed) via cipher.DeriveChildKey, so they are regenerable from the one visor key (no separate backup) and a leaked child can't expose the visor key. This is used to mint standalone-hypervisor identities (the since-retired cli hv gen) deterministically instead of random, unrelated keys — so the standalone hypervisor is provably a child of the visor and recoverable.

type LANDmsgServerConf

type LANDmsgServerConf struct {
	Enable             bool          `json:"enable"`                         // Enable the LAN/WAN DMSG server.
	Port               int           `json:"port,omitempty"`                 // TCP port (0 = auto-select; auto-selected port changes on every restart, so set explicitly for stable address).
	PublicAddress      string        `json:"public_address,omitempty"`       // Operator-set "host:port" advertised to remote visors (e.g. "203.0.113.5:8082"). Empty = LAN-only.
	MaxSessions        int           `json:"max_sessions,omitempty"`         // Max concurrent sessions (default 100).
	PK                 cipher.PubKey `json:"pk,omitempty"`                   // Server public key (auto-generated if empty).
	SK                 cipher.SecKey `json:"sk,omitempty"`                   // Server secret key (auto-generated if empty).
	PublicDiscoveryURL string        `json:"public_discovery_url,omitempty"` // Override for the dmsg-discovery URL advertised to managed visors (e.g. "http://hypervisor.example.com:8000"). Empty = auto-derived from HTTPAddr when remotely reachable.
}

LANDmsgServerConf configures an embedded DMSG server for LAN visors, optionally reachable from outside the LAN as well. When enabled, the hypervisor acts as a local DMSG relay so its managed visors don't need to route through public DMSG servers. With Port pinned and PublicAddress set, remote visors can also reach the server (operator must configure port forwarding on the router for the chosen Port). The server keypair is stored in the config so it persists across restarts.

type Launcher

type Launcher struct {
	ServiceDisc     string `json:"service_discovery"`
	ServiceDiscDmsg string `json:"service_discovery_dmsg,omitempty"` // DMSG-HTTP URL for service discovery
	// Apps uses the appsList wrapper so on-disk JSON renders each
	// AppConfig's Args as a single shell-like string (much easier
	// to hand-edit than the array form). Old configs with array-
	// form args still load. Behaviorally this is a []appserver.AppConfig
	// — readers that take the Apps field unchanged still work.
	Apps              appsList `json:"apps"`
	ServerAddr        string   `json:"server_addr"`
	BinPath           string   `json:"bin_path"`
	DisplayNodeIP     bool     `json:"display_node_ip"`
	HeartbeatInterval Duration `json:"heartbeat_interval,omitempty"`
}

Launcher configures the app

type LogServer

type LogServer struct {
	// LocalAddr enables serving on localhost (e.g., "localhost:8002" or "127.0.0.1:8002").
	// If empty, localhost serving is disabled (dmsg-only mode).
	LocalAddr string `json:"local_addr"`
}

LogServer configures the dmsghttp log server's optional localhost endpoint. When LocalAddr is set (non-empty), the log server also serves on localhost without authentication.

type LogStore

type LogStore struct {
	// Type defines the log store type. Valid values: file, memory.
	Type             string   `json:"type"`
	Location         string   `json:"location"`
	RotationInterval Duration `json:"rotation_interval"` // time value, examples: 10s, 1m, 1h etc
}

LogStore configures a LogStore.

type NetworkPolicy

type NetworkPolicy struct {
	Allow []string `json:"allow,omitempty"`
	Deny  []string `json:"deny,omitempty"`
	// Metered is the mode used while the network is metered: normal, lean,
	// dmsg-only or offline. Empty means lean.
	Metered string `json:"metered,omitempty"`
	// MeteredInterfaces are interface globs or address ranges. The network
	// counts as metered while the route to the internet leaves through one.
	MeteredInterfaces []string `json:"metered_interfaces,omitempty"`
}

NetworkPolicy is transport.network: which interfaces and addresses the visor offers to peers. Entries are interface name globs or address ranges, deny wins, and an empty allow list allows everything.

func (NetworkPolicy) IsZero

func (p NetworkPolicy) IsZero() bool

IsZero reports whether the policy is the default one.

type Pty

type Pty struct {
	DmsgPort  uint16          `json:"dmsg_port"`
	CLINet    string          `json:"cli_network"`
	CLIAddr   string          `json:"cli_address"`
	Whitelist []cipher.PubKey `json:"whitelist"`

	// SshListen, when non-empty, brings up a direct-TCP entry point
	// for the dmsgpty / dmsgscp / dmsgcat protocols, separate from the
	// dmsg-overlay path. Listening address in net.Listen format —
	// `:2022`, `0.0.0.0:2022`, `127.0.0.1:2022`, etc. Empty (default)
	// disables it: only the dmsg-overlay entry point is served.
	//
	// This is the surface that `skywire cli pty shell` (client) and
	// `skywire cli pty host` (server) expose as the OpenSSH-equivalent
	// shell over skywire identity.
	//
	// Auth flow per accepted TCP connection:
	//   1. Noise XK handshake using the visor's identity keypair —
	//      the client side pins the server's PK (same trust model as
	//      ssh's host key check) and the server learns the client's PK
	//      from the handshake.
	//   2. The learned client PK is checked against the SAME whitelist
	//      that gates the dmsg-overlay accept loop. No separate ACL.
	//   3. On accept, the stream is handed to the existing dmsgpty
	//      mux so dmsgpty / dmsgscp / dmsgcat clients work uniformly
	//      over either transport.
	//
	// Motivation: operators who have direct IP reachability to a peer
	// can `cli pty shell <pk>@<host>:<port>` — no dmsg-discovery dependency,
	// lower latency for known endpoints, same PK-based auth model.
	SshListen string `json:"ssh_listen,omitempty"`

	// PersistentSessions, when true, keeps an interactive pty's shell
	// running across a dropped stream instead of killing it: the host
	// detaches the follower and a reconnecting client (the hypervisor web
	// terminal, or `cli pty`) reattaches by session id, replaying buffered
	// output. A bounded GC reaps detached sessions after an idle TTL so an
	// abandoned shell can't leak. Off by default — opt in per visor; the
	// web terminal degrades to fresh-shell-on-reconnect against hosts that
	// have it off.
	PersistentSessions bool `json:"persistent_sessions,omitempty"`

	// AllowRPCExec gates the visor-RPC-initiated dmsgpty exec path
	// (API.DmsgPtyExec, reached by `skywire cli dmsg pty` / `cli pty
	// exec --via`). OFF by default: without it, any local process that
	// can reach the visor's RPC socket (loopback :3435 on a root
	// deployment — every local user) could borrow the visor's identity
	// to open an authenticated dmsgpty stream to itself or to any peer
	// that whitelists this visor, i.e. a local privilege-escalation /
	// lateral-movement vector distinct from the whitelist-gated INBOUND
	// dmsgpty host. Enabling it is an operator opt-in for a control /
	// jump node that legitimately drives remote `cli pty exec` sessions.
	// This gate does NOT affect the inbound dmsgpty host (the hypervisor
	// web terminal and standalone dmsgpty-cli use separate, whitelist-
	// gated paths), only the outbound RPC-initiated exec.
	AllowRPCExec bool `json:"allow_rpc_exec,omitempty"`
}

Pty configures the pseudoterminal subsystem (formerly Dmsgpty). The struct shape is unchanged across the rename; the V1.Pty field's JSON tag accepts both "pty" (canonical) and "dmsgpty" (legacy) via V1.UnmarshalJSON in config_compat.go.

type PublicVisorConfig

type PublicVisorConfig struct {
	// RegistrationTimeout is how long to wait for an external STCPR connection
	// before deregistering from service discovery. This validates the visor is
	// actually reachable from the internet (has port forwarding configured).
	// Set to 0 to skip this check (stay registered regardless).
	// Default: 10m
	RegistrationTimeout Duration `json:"registration_timeout,omitempty"`

	// MaxTransports is the DISTINCT-PEER count at which a public visor
	// drain-pauses its service-discovery registration, resuming when the
	// count drops back below it. It counts unique remote visors, not raw
	// transports — several transport types to the same peer count as one.
	// Set to 0 to never drain based on peer count.
	// Default: PublicVisorMaxTransports (2048).
	MaxTransports int `json:"max_transports,omitempty"`
}

PublicVisorConfig configures public visor behavior and service discovery registration.

type ResolverConfig

type ResolverConfig struct {
	// Name identifies this resolver in logs and as the launcher app row
	// ("resolver-<name>", so `cli visor app start resolver-lan` works). It
	// must be unique among resolvers and is restricted to [a-z0-9-] so it
	// stays a usable app name. Empty gets "r<index>" assigned at load.
	Name string `json:"name,omitempty"`
	// Kind is "dmsg" (default) or "skynet" — which transport the resolver
	// dials destinations over. See the ResolverKind* constants.
	Kind string `json:"kind,omitempty"`
	// Enable must be true for this resolver to bind its listener at boot.
	// A disabled entry is still constructed, so it can be started later
	// through the launcher, and is excluded from port-conflict checking
	// (it binds nothing).
	Enable bool `json:"enable"`
	// SecretKey runs a dmsg resolver under its OWN dmsg identity instead of
	// the visor's, attached in-process to the visor's relay — see
	// DmsgWebConfig.SecretKey for the full rationale and for why this is not
	// the second-full-client pattern that got removed in #4500/#4501.
	//
	// Meaningless for a skynet resolver, which reaches destinations through
	// the visor's router and therefore always speaks as the visor; setting it
	// on one is rejected rather than ignored.
	SecretKey *cipher.SecKey `json:"secret_key,omitempty"`
	// ProxyPort is this resolver's SOCKS5 listener port. REQUIRED and
	// non-zero: unlike the primaries there is no sensible default to fall
	// back to, and defaulting would silently land a second resolver on 4445
	// on top of dmsg_web.
	ProxyPort uint `json:"proxy_port"`
	// ProxyAddr is the host the listener binds. Empty = loopback
	// ("127.0.0.1"). "0.0.0.0" or a LAN IP serves other devices — only do
	// that on a trusted network, and prefer giving such a resolver its own
	// SecretKey so the LAN's requests are attributable to a key you can
	// revoke without rotating the visor's.
	ProxyAddr string `json:"proxy_addr,omitempty"`
	// DomainSuffix is the TLD this resolver treats as mesh addresses.
	// Empty uses the kind's default (".dmsg" / ".skynet"). A second resolver
	// may serve the SAME suffix as the primary — that is the LAN-gateway
	// case, two listeners answering for .dmsg under different identities.
	DomainSuffix string `json:"domain_suffix,omitempty"`
	// UpstreamSOCKS forwards CONNECTs that don't match DomainSuffix to this
	// SOCKS5 server. Set explicitly it always wins; left empty its value
	// depends on Chain.
	UpstreamSOCKS string `json:"upstream_socks,omitempty"`
	// UpstreamRules send chosen domains to another upstream (a second
	// skysocks-client, say) or "direct", ahead of UpstreamSOCKS. The longest
	// matching suffix wins.
	UpstreamRules []proxyroute.Rule `json:"upstream_rules,omitempty"`
	// Chain, when nil or true (the default), gives an empty UpstreamSOCKS the
	// same auto-chain the matching primary gets: a dmsg resolver chains to the
	// skynet_web listener (so one browser proxy entry covers .dmsg and .skynet),
	// and a skynet resolver chains to skysocks-client (so clearnet exits over
	// the mesh). Set false to leave the resolver unchained — non-matching
	// CONNECTs then leave this host directly, which is what a LAN .dmsg gateway
	// that must NOT become an open clearnet proxy wants.
	Chain *bool `json:"chain,omitempty"`
	// Alias is the friendly hostname label for THIS visor's own PK under this
	// resolver's suffix. Defaults to "skywire".
	Alias string `json:"alias,omitempty"`
	// SelfLoopback / SelfLoopbackAuthenticated mirror the identically-named
	// DmsgWebConfig fields; nil means on.
	SelfLoopback              *bool `json:"self_loopback,omitempty"`
	SelfLoopbackAuthenticated *bool `json:"self_loopback_authenticated,omitempty"`
	// RouteTimeout is the keepalive for routes a SKYNET resolver creates.
	// Zero uses the router default. Meaningless for a dmsg resolver.
	RouteTimeout Duration `json:"route_timeout,omitempty"`
	// TLSMITM / TLSPort / TLSUpstreamPort / TLSCAPath / TLSCAKeyPath mirror the primaries' TLS
	// interception knobs; see DmsgWebConfig for the trust model. Off by
	// default, and a CA that fails to load downgrades to plain HTTP with a
	// warning rather than failing the resolver.
	TLSMITM         bool   `json:"tls_mitm,omitempty"`
	TLSPort         uint16 `json:"tls_port,omitempty"`
	TLSUpstreamPort uint16 `json:"tls_upstream_port,omitempty"`
	TLSCAPath       string `json:"tls_ca_path,omitempty"`
	TLSCAKeyPath    string `json:"tls_ca_key_path,omitempty"`
}

ResolverConfig is ONE additional resolving proxy: its own SOCKS5 listener, its own domain suffix, and — for a dmsg resolver — optionally its own dmsg identity.

The point of more than one is that a resolver's listener is also its access policy. A loopback resolver on 4445 serves this host; a second one bound to 0.0.0.0 on another port with a DIFFERENT secret key serves the LAN under an identity the operator can whitelist (or revoke) separately, without the LAN's traffic ever appearing to come from the visor's own key. Before this existed the only way to get that was a second visor.

func ParseResolverSpec

func ParseResolverSpec(spec string) (ResolverConfig, error)

ParseResolverSpec parses ONE entry of the RESOLVERS bash array / the --resolvers CSV into a ResolverConfig.

Grammar, chosen so a whole resolver fits in one shell-array element and a comma can still separate entries:

<kind>:<port>[;key=value]...

e.g.

dmsg:4447
dmsg:4447;addr=0.0.0.0;name=lan;sk=<64-hex>;chain=false
skynet:4448;suffix=.skynet;upstream=127.0.0.1:1080

Recognized keys: name, addr, suffix, sk, upstream, chain, alias. An unrecognized key is an ERROR, not a shrug — a typo'd "adrr=0.0.0.0" that parsed silently would hand the operator a loopback-only resolver they believe is serving the LAN. Everything else (TLS interception, route timeouts, self-loopback) stays JSON-only: those are set once by hand, not from a .conf one-liner.

func ParseResolverSpecs

func ParseResolverSpecs(csv string) ([]ResolverConfig, error)

ParseResolverSpecs parses a comma-separated list of specs — the shape cmdutil.SkyenvArray hands back for RESOLVERS=('…' '…') — assigning each unnamed entry a stable default name.

func (*ResolverConfig) AppName

func (r *ResolverConfig) AppName() string

AppName is the launcher app row this resolver appears as. Prefixed so a resolver can never shadow a built-in app's registration — a resolver named "vpn-client" would otherwise replace the VPN client's entry in the global app registry, and the visor would launch a SOCKS5 proxy where the operator asked for a VPN.

func (*ResolverConfig) Chained

func (r *ResolverConfig) Chained() bool

Chained reports whether an unset UpstreamSOCKS should inherit the matching primary's auto-chain. Nil (the common case, and every entry written before the knob existed) means yes.

func (*ResolverConfig) IsSkynet

func (r *ResolverConfig) IsSkynet() bool

IsSkynet reports whether this entry resolves over skywire routing.

func (*ResolverConfig) ToDmsgWeb

func (r *ResolverConfig) ToDmsgWeb() *DmsgWebConfig

ToDmsgWeb projects this entry onto the DmsgWebConfig the EmbeddedDmsgWeb runtime consumes. The returned config is a fresh value the runtime owns: SetUpstream/SetBind mutate it, and those runtime-only changes deliberately do not write back into V1.Resolvers (which is the on-disk shape).

func (*ResolverConfig) ToSkynetWeb

func (r *ResolverConfig) ToSkynetWeb() *SkynetWebConfig

ToSkynetWeb is ToDmsgWeb's counterpart for a skynet entry. SecretKey is dropped on purpose — see the field comment.

type RewardsConfig

type RewardsConfig struct {
	Enable          bool            `json:"enable"`
	WorkDir         string          `json:"work_dir"`
	Whitelist       []cipher.PubKey `json:"whitelist,omitempty"`
	CanonicalDomain string          `json:"canonical_domain,omitempty"`
	SkycoinNode     string          `json:"skycoin_node,omitempty"`
	LoginNode       string          `json:"login_node,omitempty"`
}

RewardsConfig configures the reward system UI when hosted by the visor.

type Routing

type Routing struct {
	RouteSetupNodes     []cipher.PubKey `json:"route_setup_nodes,omitempty"`
	UserRouteSetupNodes []cipher.PubKey `json:"user_route_setup_nodes,omitempty"` // user-added keys, preserved across config refresh
	RouteSetupSK        *cipher.SecKey  `json:"route_setup_sk,omitempty"`         // Embedded route setup-node secret key
	RouteFinder         string          `json:"route_finder"`
	RouteFinderDmsg     string          `json:"route_finder_dmsg,omitempty"`
	RouteFinderTimeout  Duration        `json:"route_finder_timeout,omitempty"`
	MinHops             uint16          `json:"min_hops"`
	// NoTransit, when true, makes this visor refuse to be an INTERMEDIATE hop
	// on anyone else's route. It still originates its own routes and still
	// accepts routes that END here, and it is unaffected by anything that does
	// not install a routing rule — dmsg-over-skynet relaying rides VStreamMux
	// at route ID 0 and is deliberately not a route, so a visor hosting a dmsg
	// server can carry that traffic while refusing transit.
	//
	// This is the ENFORCING half and needs nobody's cooperation. The route
	// finder is told separately so it stops proposing routes that would be
	// refused here; a stale or missing advertisement costs a failed setup and
	// a retry, never a transited visor.
	NoTransit bool `json:"no_transit,omitempty"`
	// CalculateRoutes enables local route calculation instead of using the route finder service.
	// When enabled, routes are calculated locally using cached TPD data.
	// Can be overridden at runtime with --use-rf flag.
	CalculateRoutes bool `json:"calculate_routes,omitempty"`
	// MuxRoutes is the number of parallel routes to establish per connection.
	// 0 or 1 = single route (default), >1 = route multiplexing across transports.
	// ParallelRouteSetup is the number of candidate route groups whose setup
	// DialRoutes races concurrently per attempt (first to complete its
	// handshake wins). 0 (unset) uses the built-in default (a small N); 1
	// forces the legacy strictly-sequential setup (clean rollback switch).
	// This is the steady-connection fix: it stops a run of dead candidates
	// from burning the dial ceiling and wedging an app in "starting".
	ParallelRouteSetup int `json:"parallel_route_setup,omitempty"`

	// ExcludeSameLanHops, when nil or true (the default), makes route
	// calculation drop candidate INTERMEDIATE hops that sit on this visor's own
	// local network — a peer reached over a private/RFC1918 endpoint, or (the
	// NAT-hairpin case) a peer reached at this visor's own public IP / its /24.
	// Routing a remote-destination path through a same-LAN peer adds a hop but
	// no path diversity (same first-mile link, NAT and failure domain) and
	// starves a mux of genuinely-diverse aux legs (the same-LAN peer wins on
	// latency and is re-picked every rotation). Same-LAN peers remain usable as
	// direct dial DESTINATIONS; only their use as intermediates is suppressed.
	// Set to false only for deliberate LAN-relay topologies. Pointer so an
	// absent field (older configs) defaults ON.
	ExcludeSameLanHops *bool `json:"exclude_same_lan_hops,omitempty"`
	// TransportPreference is the transport-type priority order, most-preferred
	// first. It decides both which existing transport a route rides when
	// several reach the same peer, and which type the visor tries to CREATE
	// first when a route needs a transport that isn't there yet — so the first
	// entry is the primary transport and the rest are the fallbacks, in order.
	// Valid values are the names in tptypes.Known() ("stcpr", "squicr",
	// "sudph", "stcp", "webrtc", "swsr", "swtr", "dmsg"; the pre-rename
	// "quic"/"ws"/"wt" are accepted too). If empty, the built-in default order
	// (stcpr > squicr > sudph > stcp > webrtc > swsr > swtr > dmsg) is used.
	// Types not listed here sort last. Settable at runtime, without a restart,
	// via PUT /api/visors/{pk}/router-settings.
	TransportPreference []string `json:"transport_preference,omitempty"`

	// RouteExcludeTransportTypes hard-EXCLUDES routes that traverse any of these
	// transport types, rather than merely deprioritizing them like
	// TransportPreference does. A route (in either direction) is dropped from the
	// candidate set if ANY of its hops rides an excluded type — so an excluded type
	// never enters the mux leg pool. Intended for types whose flakiness poisons the
	// mux (e.g. "webrtc": a stalled webrtc leg wedges the no-skip reorder frontier
	// and truncates downloads). Empty by default (nothing excluded), so it stays
	// opt-in — a visor that NEEDS a type (e.g. webrtc for a wasm/browser peer) simply
	// leaves it off its own list. Values use the same names as TransportPreference.
	RouteExcludeTransportTypes []string `json:"route_exclude_transport_types,omitempty"`

	// MuxFEC opts INTO forward error correction on mux route groups: repair
	// frames (K=8, R=2) are striped alongside data so a lost frame can be
	// rebuilt without a retransmit. OFF by default: the legs ride reliable
	// transports and SACK recovery already refills a gap, so the repair
	// traffic was a flat 25%+ wire overhead on every multi-leg group (measured
	// live: 5 MB of repair for 6 MB of payload on one leg). Both ends must
	// enable it for a group to negotiate it.
	MuxFEC bool `json:"mux_fec,omitempty"`

	// RouterSettings is the visor-wide router knob catalog: one map of
	// catalog-name to formatted value, holding only the knobs an operator has
	// explicitly SET (`skywire cli route settings k=v`). A knob absent from the
	// map runs the value the binary compiled with, so the file stays small and a
	// new release's changed default is picked up rather than pinned. Restored at
	// boot by init_router, which is what makes a sweep survive a restart — the
	// 17 router atomics used to reset.
	//
	// It is one map rather than a field per knob on purpose: the catalog grows
	// every time a constant becomes live, and a field per knob would mean a
	// config migration each time.
	RouterSettings map[string]string `json:"router_settings,omitempty"`

	// RouterAppSettings holds the same shape per APP name: the overrides
	// `route settings --app <name> k=v` installs for the route groups that app
	// owns, so a subject client and its paired reference keep their different
	// values across a restart.
	RouterAppSettings map[string]map[string]string `json:"router_app_settings,omitempty"`

	// EnableCascadeRouteSetup opts INTO the source-driven cascade route-setup
	// path (RSN signs, source injects the cascade down its own transports,
	// avoiding the RSN's dmsg dependency). It is OFF by default: the cascade
	// currently has a multihop data-plane bug (control plane succeeds on every
	// hop, but the set-up route does not carry traffic), so the legacy path —
	// where the setup node dials each hop directly over dmsg to install rules —
	// is the default until that is fixed. Flip this to true (or change the
	// default) once the cascade data plane is corrected.
	EnableCascadeRouteSetup bool `json:"enable_cascade_route_setup,omitempty"`

	// PolicyPerDial is an optional operator-supplied routing
	// policy. Accepts either:
	//   "preset:<name>"          — a curated policy embedded in the
	//                              binary, no file or compile needed
	//                              (e.g. "preset:spread-bw"); presets
	//                              live in pkg/router/policy/presets
	//   "@/path/to/policy.star"  — Starlark, file-backed, hot-reloaded
	//   "@/path/to/policy.wasm"  — WASM, file-backed, hot-reloaded
	//   "<inline script source>" — small inline Starlark policies
	//   "" / unset                — no policy, built-in defaults
	//
	// The visor picks the backend by file extension when the
	// value starts with "@" (`.wasm` → WASM, anything else →
	// Starlark). Inline values are always Starlark — compiled
	// WASM doesn't fit in a JSON string. Both backends produce
	// the same RouteSpec per dial and route through the same
	// router.DialHook.
	//
	// See docs/routing_policy_rfc.md for the Starlark DSL,
	// docs/examples/routing-policies/wasm/README.md for the
	// WASM ABI + trade-offs, and `skywire cli route policy
	// test/bench` for iteration tools.
	PolicyPerDial string `json:"policy_per_dial,omitempty"`

	// PolicyOnControlPorts opts the routing policy (PolicyPerDial / per-app)
	// BACK IN for control-plane destination ports (pty 22, dmsgctrl 7, setup
	// 36, hypervisor RPC 46, transport-setup 47/48, …). Default false: those
	// small noise-XK control channels bypass the policy and take a plain base
	// route, because a bandwidth-spreading policy (e.g. the adaptive default)
	// destabilizes their handshakes — the pty 32-leg-mux footgun. Set true
	// only with a policy purpose-built for control traffic.
	PolicyOnControlPorts bool `json:"policy_on_control_ports,omitempty"`
}

Routing configures routing.

type Services

type Services = deployment.Services

Services is subdomains and IP addresses of the skywire services as deployed. Aliased to deployment.Services — both packages used to declare an identical struct, which created two json.Unmarshal hot paths (one in deployment.init, one in visorconfig.MakeBaseConfig) for the same data shape. The alias collapses the duplication.

type Skychat

type Skychat struct {
	// GroupHistoryDB, when non-empty, enables persistent group-message
	// history at the visor layer. Each inbound group message is mirrored
	// into a bolt store at this path. The Visor.GroupHistory RPC (and
	// `skywire cli skychat group history`) read from the same store.
	//
	// Path may be absolute (used verbatim) or relative — relative paths
	// resolve under <LocalPath>/skychat/. Empty (default) disables
	// persistence: groups remain in-memory-only, bounded by the inbox
	// ring buffer (~1024 messages).
	//
	// Limits track history.DefaultLimits() with a couple of group-
	// aware overrides (rate-limit disabled, larger per-group cap).
	// A future config knob may expose the limits directly.
	GroupHistoryDB string `json:"group_history_db,omitempty"`
}

Skychat carries chat-app-related visor-side configuration. Optional — nil disables all knobs (the visor still runs chat groups in-memory).

type SkymailBridgeConfig

type SkymailBridgeConfig struct {
	// Enable must be true for the bridge to start.
	Enable bool `json:"enable"`
	// Addr is the local TCP listener Postfix's transport_map relays
	// to (e.g. "127.0.0.1:1025"). Default "127.0.0.1:1025".
	Addr string `json:"addr,omitempty"`
	// Mode selects the RCPT TO rewrite rule. Both modes accept both
	// address shapes (host-prefixed and bare-PK); they differ only
	// in what mode b does with the host-prefixed shape.
	//
	//   "b" (default) — strip-when-prefixed: ".<pk><suffix>" is
	//                   stripped from host-prefixed envelopes before
	//                   forwarding (so the receiver's existing
	//                   virtual_alias chain handles delivery
	//                   natively); bare-PK envelopes are forwarded
	//                   verbatim. Strict superset of mode a.
	//   "a"           — verbatim: forward both shapes unchanged.
	//                   Receiver's Postfix must accept the full
	//                   ".<pk><suffix>" domain in every envelope.
	//
	// Either way, support for the bare-PK shape requires the
	// receiver's Postfix to know the bare-PK domain — via
	// mydestination or virtual_alias_domains.
	Mode string `json:"mode,omitempty"`
	// Suffix is the TLD the bridge treats as a skywire-routed
	// recipient. Defaults to ".skynet". Independent of the resolver's
	// DomainSuffix — operators may serve mail under a different TLD
	// than the resolver if they have reason to.
	Suffix string `json:"suffix,omitempty"`
	// HeloName is the EHLO/HELO greeting the bridge sends to the
	// peer's Postfix. Mostly cosmetic (appears in Received: trace
	// headers). Default "skymail-bridge.local".
	HeloName string `json:"helo_name,omitempty"`
	// RemotePort is the dmsg routing port to dial on the peer.
	// Default 25 (matching the SMTP convention). Receiver must
	// expose Postfix's smtpd on this port via
	// `skywire cli serve add 25 --to 127.0.0.1:25`.
	RemotePort uint16 `json:"remote_port,omitempty"`
}

SkymailBridgeConfig configures the embedded SMTP-aware bridge that relays recipient envelopes of either shape — user@<host>.<base32-pk>.skynet or user@<base32-pk>.skynet — to a peer visor's exposed SMTP listener over the visor's existing dmsg session.

Structurally parallel to DmsgWebConfig — runs inside the visor process, lifecycled via RPC (SetEmbeddedProxyEnabled("bridge", …)), no separate launcher app needed. Operators who don't run a full visor can use the standalone binary at cmd/smb instead.

type SkymailConfig

type SkymailConfig struct {
	// Enable starts the mailbox.
	Enable bool `json:"enable"`
	// Dir is the Maildir root. Default: "mail" beside the local path
	// (outside it, because the wasm visor never persists local/).
	Dir string `json:"dir,omitempty"`
	// MaxMessageSize, MaxTotalSize (bytes, Inbox and Sent together) and
	// MaxAge bound what the mailbox keeps: 0 means the default (10 MiB,
	// 64 MiB, 7 days), a negative value no bound.
	MaxMessageSize int64    `json:"max_message_size,omitempty"`
	MaxTotalSize   int64    `json:"max_total_size,omitempty"`
	MaxAge         Duration `json:"max_age,omitempty"`
}

SkymailConfig configures the visor's own mailbox (pkg/skymail): mail to <anything>@<base32-pk>.skynet or .dmsg, received over SMTP on port 25 and kept in a Maildir. The section is optional: absent, the mailbox runs with the default limits. A visor whose port 25 is forwarded to Postfix (`serve add 25`) leaves it to Postfix.

type SkynetWebConfig

type SkynetWebConfig struct {
	// Enable must be true for the resolver to start.
	Enable bool `json:"enable"`
	// ProxyPort is the local SOCKS5 listener. Default 4446.
	ProxyPort uint `json:"proxy_port,omitempty"`
	// ProxyAddr is the host the SOCKS5 proxy binds to. Empty = loopback
	// ("127.0.0.1"). Set to "0.0.0.0" or a LAN IP to serve other devices on
	// the network (a `.skynet` gateway for a home router's LAN). Only bind a
	// non-loopback address on a trusted network.
	ProxyAddr string `json:"proxy_addr,omitempty"`
	// WebPort is the local HTTP bridge port. Default 8081.
	WebPort uint `json:"web_port,omitempty"`
	// DomainSuffix is the TLD treated as skynet addresses. Default ".skynet".
	DomainSuffix string `json:"domain_suffix,omitempty"`
	// UpstreamSOCKS forwards non-matching CONNECTs to this upstream.
	UpstreamSOCKS string `json:"upstream_socks,omitempty"`
	// UpstreamRules send chosen domains to another upstream (a second
	// skysocks-client, say) or "direct", ahead of UpstreamSOCKS. The longest
	// matching suffix wins.
	UpstreamRules []proxyroute.Rule `json:"upstream_rules,omitempty"`
	// RouteTimeout is the keepalive duration for routes created by the
	// resolving proxy. Routes idle longer than this are expired by GC.
	// Zero means use DefaultRouteKeepAlive (10 min). A very large value
	// (e.g. "8760h") effectively keeps routes alive until the visor stops.
	RouteTimeout Duration `json:"route_timeout,omitempty"`
	// TLSMITM enables on-the-fly TLS termination for browser
	// connections to <pk>.skynet on TLSPort, using a leaf cert minted
	// from a locally-installed CA at TLSCAPath/TLSCAKeyPath. Off by
	// default; when off the resolver behaves exactly as before.
	// See pkg/skynetca and docs/skynet-tls.md for the trust model.
	TLSMITM bool `json:"tls_mitm,omitempty"`
	// TLSPort is the destination port treated as TLS. Default 443.
	TLSPort uint16 `json:"tls_port,omitempty"`
	// TLSUpstreamPort is the destination port a MITM'd connection is sent
	// to once decrypted. Default 80, where visors serve HTTP.
	TLSUpstreamPort uint16 `json:"tls_upstream_port,omitempty"`
	// TLSCAPath / TLSCAKeyPath point to a CA generated by
	// `skywire cli resolver ca gen`. Both required when TLSMITM
	// is true. The visor logs and falls back to non-MITM mode if
	// either is missing or unreadable; it does not refuse to start.
	TLSCAPath    string `json:"tls_ca_path,omitempty"`
	TLSCAKeyPath string `json:"tls_ca_key_path,omitempty"`
	// SelfLoopback, when nil or true (the default), serves a request whose
	// destination is THIS visor's own PK from the local service in-process
	// (net.Pipe) instead of dialing a skynet route back to self. Set false
	// to exercise the full self-route path (valid for skynet, useful for
	// testing self-transports).
	SelfLoopback *bool `json:"self_loopback,omitempty"`
	// Alias is a friendly hostname label for THIS visor's own PK, so
	// "<alias>.skynet" resolves to the local visor without hardcoding its
	// public key. Defaults to "skywire" when unset (→ "skywire.skynet").
	Alias string `json:"alias,omitempty"`
	// SelfLoopbackAuthenticated, when nil or true (the default), makes a
	// self-loopback request (skywire.skynet / <self-pk>.skynet served
	// in-process via SelfDial) present THIS visor's own PK as the
	// authenticated caller, so the in-process self view renders the same
	// authenticated landing-page endpoints a survey-whitelisted remote peer
	// sees. Set false to render the self-loopback landing page anonymously.
	SelfLoopbackAuthenticated *bool `json:"self_loopback_authenticated,omitempty"`
}

SkynetWebConfig enables the embedded `.skynet` resolving proxy — the skywire-routing counterpart to DmsgWebConfig. Browsers pointed at the SOCKS5 listener can load http://<pk>.skynet[:<port>] URLs, which the visor serves by dialing a route to the remote visor's skynet server and piping bytes through. Reuses the visor's own router, so no additional routing setup is needed.

Ports default to non-conflicting values with DmsgWebConfig so both resolvers can run simultaneously on a single visor.

type Stats

type Stats struct {
	// Path is the absolute path to the bbolt file. Empty = "<local_path>/stats.db".
	Path string `json:"path,omitempty"`
	// SampleInterval is the sampler tick (e.g. "1m"). Empty = 1m.
	SampleInterval Duration `json:"sample_interval,omitempty"`
	// RetentionDays caps bbolt history. 0 = 30 days.
	RetentionDays int `json:"retention_days,omitempty"`
	// CXOPublishWindow caps the rolling window the CXO publisher
	// exposes (in days). 0 = 7. Must be ≤ RetentionDays.
	CXOPublishWindow int `json:"cxo_publish_window,omitempty"`
	// Disabled, when true, skips the entire telemetry store and
	// associated /stats/* endpoints + CXO publisher.
	Disabled bool `json:"disabled,omitempty"`
	// DedicatedTPListFeed, when true, publishes the transport-list
	// discovery snapshot on its OWN CXO node/port
	// (skyenv.DmsgVisorTPListCXOPort) instead of on the telemetry feed.
	//
	// Default (false) is the CONSOLIDATED single-feed model: the tp-list
	// leaf rides the telemetry Root as a top-level leaf, so TPD holds ONE
	// CXO connection per visor instead of two. TPD's aggregator extracts
	// the tp-list via its targeted discovery-leaf fetch, which lands the
	// small leaf reliably now that the compact sharded telemetry keeps the
	// whole Root small (≈16 shard leaves + 1 tp-list leaf) — the condition
	// that was missing when the earlier consolidation (#4184) had to be
	// reverted (#4189) over a bulky per-transport-leaf Root. Halving the
	// visor↔TPD connections cuts TPD's per-connection goroutines and the
	// per-session dmsg handshake load on the dmsg servers.
	//
	// Set true only as a transitional escape hatch — e.g. if a busy hub's
	// tp-list is ever observed to under-fill on the combined feed.
	DedicatedTPListFeed bool `json:"dedicated_tplist_feed,omitempty"`
}

Stats configures the visor-local telemetry store. Defaults are applied lazily when the field is nil or any individual sub-field is its zero value, so dropping the entire block from a config is safe.

type Survey

type Survey struct {
	Timestamp      time.Time        `json:"timestamp"`
	PubKey         cipher.PubKey    `json:"public_key,omitempty"`
	SkycoinAddress string           `json:"skycoin_address,omitempty"`
	GOOS           string           `json:"go_os,omitempty"`
	GOARCH         string           `json:"go_arch,omitempty"`
	SYSINFO        sysinfo.SysInfo  `json:"zcalusic_sysinfo,omitempty"`
	IPAddr         string           `json:"ip_address,omitempty"`
	Disks          *ghw.BlockInfo   `json:"ghw_blockinfo,omitempty"`
	Product        *ghw.ProductInfo `json:"ghw_productinfo,omitempty"`
	Memory         *ghw.MemoryInfo  `json:"ghw_memoryinfo,omitempty"`
	UUID           uuid.UUID        `json:"uuid,omitempty"`
	SkywireVersion string           `json:"skywire_version,omitempty"`
	ServicesURLs   Services         `json:"services,omitempty"`
	DmsgServers    []string         `json:"dmsg_servers,omitempty"`
}

Survey system hardware survey struct

func SystemSurvey

func SystemSurvey() (Survey, error)

SystemSurvey returns system survey

type TPSDmsgConfig

type TPSDmsgConfig struct {
	// MinSessions is the minimum number of dmsg server sessions.
	// 0 means connect to ALL available servers (recommended for TPS).
	MinSessions int `json:"min_sessions"`
	// ServerType filters which dmsg servers to connect to: "official", "community", or "" for all.
	ServerType string `json:"server_type"`
}

TPSDmsgConfig configures the embedded Transport Setup Node's dmsg client. If nil, defaults are used: MinSessions=0 (connect to all servers), ServerType="" (all types).

type TPVizConfig

type TPVizConfig struct {
	Enable      bool   `json:"enable"`                  // Whether to enable tp viz UI at /tp-viz/.
	SurveyDir   string `json:"survey_dir,omitempty"`    // Directory containing visor surveys.
	CacheMaxAge int    `json:"cache_max_age,omitempty"` // Cache max age in minutes (default: 5).
}

TPVizConfig configures the embedded transport visualizer.

type Transport

type Transport struct {
	Discovery           string `json:"discovery"`
	DiscoveryDmsg       string `json:"discovery_dmsg,omitempty"` // DMSG-HTTP URL for transport discovery (fallback pair with discovery)
	AddressResolver     string `json:"address_resolver"`
	AddressResolverDmsg string `json:"address_resolver_dmsg,omitempty"` // DMSG-HTTP URL for address resolver
	PublicAutoconnect   bool   `json:"public_autoconnect"`
	// Autoconnect limits the transports public autoconnect dials. Unset, it
	// keeps the built-in limits and two links per pair of sites for sudph.
	Autoconnect Autoconnect `json:"autoconnect,omitzero"`
	// HypervisorAutoconnect gates the background dialing of a direct (stcpr/sudph)
	// transport to each configured hypervisor. A nil pointer means "on" (default),
	// so configs written before this field existed keep the prior always-on
	// behavior; set it to false to keep the hypervisor link on the dmsg relay only.
	HypervisorAutoconnect *bool `json:"hypervisor_autoconnect,omitempty"`
	// TCPNotSentLowat caps the unsent bytes the kernel holds for each stcpr or
	// stcp transport (TCP_NOTSENT_LOWAT). Unset is 128 KiB, 0 is the kernel's
	// default. Linux and Android only; it applies to transports opened after it.
	TCPNotSentLowat       *int            `json:"tcp_notsent_lowat,omitempty"`
	TransportSetupPKs     []cipher.PubKey `json:"transport_setup"`
	UserTransportSetupPKs []cipher.PubKey `json:"user_transport_setup,omitempty"` // user-added keys, preserved across config refresh
	TPSetupSK             *cipher.SecKey  `json:"tps_sk,omitempty"`
	TPSDmsg               *TPSDmsgConfig  `json:"tps_dmsg,omitempty"`
	LogStore              *LogStore       `json:"log_store"`
	StcprPort             int             `json:"stcpr_port"`
	SudphPort             int             `json:"sudph_port"`
	// TransportPort, when non-zero, is the single port number the transport types
	// share: QUIC + sudph on <port>/udp and stcpr + WS on <port>/tcp, each socket
	// demultiplexed by protocol (see docs/design/transport-port-unification.md),
	// instead of binding a port each. 0 (default/omitted) keeps per-type binding.
	// Per-type ports remain an override: a type whose own port (stcpr_port /
	// sudph_port / quic_port) is non-zero breaks OUT onto that dedicated port even
	// when transport_port is set; left 0 it rides the shared port. Opt-in while the
	// unified-port path proves out.
	TransportPort int `json:"transport_port,omitempty"`
	// QUICPort optionally PINS the UDP port for the QUIC transport (#2607). QUIC
	// is on by default (like stcpr/sudph); 0 (default/omitted) binds an ephemeral
	// port — exactly as sudph_port does. Set a fixed port when a stable firewall
	// rule is wanted; the bound port is registered with the address resolver
	// either way, so a random port works and survives restarts.
	QUICPort int `json:"quic_port,omitempty"`
	// WTPort optionally PINS the UDP port for the WebTransport (WT) listener,
	// mirroring quic_port. WT is HTTP/3-over-QUIC, so it needs its OWN UDP socket
	// — it cannot share the unified transport_port with the raw-QUIC (squic)
	// transport (two QUIC listeners can't bind one UDP port). 0 (default/omitted)
	// binds an ephemeral port; the bound port + cert hash are registered with the
	// address resolver either way. Set a fixed port to forward it through a NAT.
	WTPort int `json:"wt_port,omitempty"`
	// WSTable statically pins WebSocket (WS) transport peers by PK → wss:// URL —
	// the WS analog of skywire-tcp's pk_table (STCP). It lets a visor DIAL a WS
	// transport to a configured peer with NO address-resolver lookup (e.g. a peer
	// on a LAN, or any visor whose wss endpoint you know), exactly as STCP pins
	// TCP peers. Entries learned at runtime (autoconnect / a browser hook) are
	// added on top; empty/omitted = no static WS peers.
	WSTable map[cipher.PubKey]string `json:"ws_table,omitempty"`
	// WTTable statically pins WebTransport (WT) peers by PK → {url, cert_hash} —
	// the WT analog of ws_table. WT has no CA, so the dialer pins the peer's
	// self-signed cert SHA-256 (cert_hash); url is the https:// endpoint. Lets a
	// visor DIAL a WT transport to a configured peer with no address-resolver
	// lookup. Runtime entries are added on top; empty/omitted = no static WT peers.
	WTTable map[cipher.PubKey]WTPeer `json:"wt_table,omitempty"`
	// QUICTable statically pins QUIC transport peers by PK → UDP address
	// ("host:port") — the QUIC analog of skywire-tcp's pk_table (and of ws_table /
	// wt_table). Lets a visor DIAL a QUIC transport to a configured peer with no
	// address-resolver lookup. Consulted before the AR; empty/omitted = AR-only.
	QUICTable map[cipher.PubKey]string `json:"quic_table,omitempty"`
	// ARTransportLimit controls address resolver registration for privacy.
	// Only its SIGN is meaningful — it is a static switch, not a counter:
	//   >= 0 (default): register with the address resolver
	//   <  0: never register with AR at all
	// A positive value does NOT deregister after N transports. That was
	// deliberately not implemented: a running visor must never drop its AR
	// registration on a transport count, because AR discoverability is what
	// lets peers dial it. Runtime load-shedding is a separate mechanism —
	// the public-visor service-discovery drain (PublicVisorConfig.MaxTransports).
	// When never-registered, the visor cannot receive new inbound transports
	// but can still initiate outbound connections.
	ARTransportLimit int `json:"ar_transport_limit,omitempty"`
	// NoDirectTransports, when true, stops the visor from CREATING direct
	// peer-to-peer transports (stcpr/sudph/stcp/squicr/swsr/swtr/webrtc) — whether
	// via autoconnect, the on-demand route-setup hook, `tp add`, or `--direct`.
	// DMSG (a relay through a dmsg server, not a direct link) is still created, so
	// the visor keeps a baseline path and can still reach peers over existing
	// transports/routes. Inbound transports peers dial to us are unaffected.
	NoDirectTransports bool `json:"no_direct_transports,omitempty"`
	// TransportCreateDeny is an advanced/testing per-type deny list of transport
	// types the visor must not create, INCLUDING dmsg (to force a pure-direct or
	// fully-offline visor). Applied on top of no_direct_transports. Legacy type
	// aliases (quic/ws/wt) are accepted. Empty/omitted denies nothing.
	TransportCreateDeny []string `json:"transport_create_deny,omitempty"`
	// NetworkWatchInterval is how often the visor checks its interface
	// addresses and route for a move to another network. Zero means 12s and
	// a negative value turns the check off.
	NetworkWatchInterval Duration `json:"network_watch_interval,omitempty"`
	// PublicIPCheckInterval is how often the visor asks the dmsg servers
	// for its public IP to notice a change the watcher cannot see. Zero
	// means 12m and a negative value turns the check off.
	PublicIPCheckInterval Duration `json:"public_ip_check_interval,omitempty"`
	// Network limits the interfaces and addresses the visor advertises to
	// peers. Nil advertises every non-virtual interface.
	Network *NetworkPolicy `json:"network,omitempty"`
}

Transport defines a transport config.

type V1

type V1 struct {
	*Common

	Dmsg      *dmsgspec.DmsgConfig `json:"dmsg"`
	Pty       *Pty                 `json:"pty,omitempty"`
	Dmsgscp   *Dmsgscp             `json:"dmsgscp,omitempty"`
	LogServer *LogServer           `json:"log_server,omitempty"`
	DmsgWeb   *DmsgWebConfig       `json:"dmsg_web,omitempty"`
	SkynetWeb *SkynetWebConfig     `json:"skynet_web,omitempty"`
	Wisp      *WispConfig          `json:"wisp,omitempty"`
	// HostBridge is the host bridge the visor uses for host sockets.
	HostBridge *HostBridgeConfig `json:"host_bridge,omitempty"`
	// Resolvers declares ADDITIONAL resolving proxies beyond the dmsg_web /
	// skynet_web pair above — each with its own port, bind address, domain
	// suffix and (for a dmsg one) its own identity. Purely additive: absent,
	// which is every config written before it existed, means exactly the
	// two primaries. See resolvers.go.
	Resolvers     []ResolverConfig     `json:"resolvers,omitempty"`
	BrowseOrigin  *BrowseOriginConfig  `json:"browse_origin,omitempty"`
	SkymailBridge *SkymailBridgeConfig `json:"skymail_bridge,omitempty"`
	Skymail       *SkymailConfig       `json:"skymail,omitempty"`
	Rewards       *RewardsConfig       `json:"rewards,omitempty"`
	STCP          *tnspec.STCPConfig   `json:"skywire-tcp,omitempty"`
	Transport     *Transport           `json:"transport"`
	Routing       *Routing             `json:"routing"`
	Launcher      *Launcher            `json:"launcher"`
	// EmbeddedServices are deployment services (transport-discovery,
	// address-resolver, route-finder, service-discovery) this visor runs
	// in-process, each mounted under a path prefix on its dmsg HTTP port
	// and addressed as dmsg://<visor pk>:80/<prefix>/... Blocks have the
	// same shape as a services.json entry plus an optional "prefix".
	EmbeddedServices []services.Block `json:"embedded_services,omitempty"`
	// DeploymentStatusAddr serves the status page of the embedded services
	// over plain HTTP, such as 127.0.0.1:8082 for a reverse proxy. It is also
	// at /status on the dmsg HTTP port. Empty serves it there only.
	DeploymentStatusAddr string `json:"deployment_status_addr,omitempty"`

	// Stats configures the visor-local telemetry store. Nil/zero
	// values use defaults; Disabled=true skips the store entirely.
	Stats *Stats `json:"stats,omitempty"`

	// Skychat carries chat-app-related visor-side configuration that
	// doesn't fit elsewhere. Currently just opt-in group-message
	// persistence; expected to grow as the chat surface evolves.
	Skychat *Skychat `json:"skychat,omitempty"`

	// Wallet configures the skycoin-web wallet: whether to serve the
	// frontend, and — the one axis the operator actually chooses — where
	// keys live (browser | disk | remote). One block covers the native
	// visor, `hv serve` and the wasm visor; what varies is which custody
	// modes a given build can realize, which V1.WalletCustody() clamps.
	// Nil (a config predating this block) serves the wallet with browser
	// custody, i.e. existing behavior. See
	// docs/design/skycoin-web-wallet-architecture.md.
	Wallet *WalletConfig `json:"wallet,omitempty"`

	// CoinNodes lists fibercoin nodes (see servicedisc.ServiceTypeCoin)
	// this visor forwards over dmsg and advertises in service discovery,
	// so a browser/thin-client wallet can discover and reach a node for
	// the right coin over the mesh with no local full node and no launch
	// flags. Each entry is health-gated (see pkg/visor/init_coinnode.go):
	// the visor probes the node's HTTP /health on the SD heartbeat
	// interval and only advertises it while it answers — no dead entries.
	CoinNodes []CoinNodeConfig `json:"coin_nodes,omitempty"`

	SurveyWhitelist     []cipher.PubKey `json:"survey_whitelist"`
	UserSurveyWhitelist []cipher.PubKey `json:"user_survey_whitelist,omitempty"` // user-added keys, preserved across config refresh
	Hypervisors         []cipher.PubKey `json:"hypervisors"`
	CLIAddr             string          `json:"cli_addr"`

	// KeyRing is a small wallet-like set of deterministically-derived keys (from
	// the visor secret key). Currently mints standalone-hypervisor identities
	// (the since-retired cli hv gen). Optional; absent on visors that never derived one.
	KeyRing *KeyRing `json:"keyring,omitempty"`

	LogLevel             string                       `json:"log_level"`
	LogHookLevel         string                       `json:"log_hook_level,omitempty"`   // most verbose level kept for /debug/log, info when empty
	GCTraceSamples       int                          `json:"gc_trace_samples,omitempty"` // samples kept for /debug/gctrace at 5 s each, 360 when zero
	LocalPath            string                       `json:"local_path"`
	Skyenv               string                       `json:"skyenv,omitempty"` // the skywire.conf config gen read; live changes are written back there
	StunServers          []string                     `json:"stun_servers"`
	ShutdownTimeout      Duration                     `json:"shutdown_timeout,omitempty"` // time value, examples: 10s, 1m, etc
	IsPublic             bool                         `json:"is_public"`
	PublicVisorConfig    *PublicVisorConfig           `json:"public_visor,omitempty"`
	GeoIP                string                       `json:"geoip"`
	PersistentTransports []tspec.PersistentTransports `json:"persistent_transports"`
	ConfService          string                       `json:"conf_service,omitempty"`      // HTTP URL for config bootstrap service
	ConfServiceDmsg      string                       `json:"conf_service_dmsg,omitempty"` // DMSG URL for config bootstrap service
	// SurveyClientSK is the CLI-side identity used by
	// `skywire cli log <subcommand>` to authenticate against remote
	// visors' survey_whitelist gates (visor logserver port 80 over
	// dmsg). When non-empty, parses as a hex SecKey; the derived PK
	// is the identity the remote checks against its survey_whitelist.
	// Empty (default) falls through to --sk flag → DMSGCURL_SK env →
	// ephemeral random keypair. Server-side: never read; the visor
	// ignores this field entirely.
	SurveyClientSK string `json:"survey_client_sk,omitempty"`

	RewardAddress    string `json:"reward_address,omitempty"`
	RewardSystem     string `json:"reward_system,omitempty"`
	RewardSystemDmsg string `json:"reward_system_dmsg,omitempty"`
	MemoryLimit      string `json:"memory_limit,omitempty"` // Go memory limit: "none" (the default), a size like "256MiB", or "auto" for 90% of total RAM
	// FlightRecorder keeps the last seconds of execution trace in memory for
	// stall diagnosis. Off by default because it allocates heavily.
	FlightRecorder bool `json:"flight_recorder,omitempty"`

	// AppSettings holds the live tuning knobs an operator set per app
	// (`skywire cli proxy settings`), so they survive a visor restart the way
	// routing.mux_fec does. The visor restores them into the proc manager at
	// boot and the app pulls them on its first tick; a `--reset` removes the
	// entry. Absent on every config written before it existed, which means
	// exactly "no knob was ever set".
	AppSettings map[string]AppSettingsEntry `json:"app_settings,omitempty"`

	Hypervisor *HypervisorConfig `json:"hypervisor,omitempty"`
	// contains filtered or unexported fields
}

V1 is visor config

func MakeBaseConfig

func MakeBaseConfig(common *Common, dmsgHTTP bool, services *Services, dmsgHTTPServersList *DmsgHTTPServers) *V1

MakeBaseConfig returns a visor config with 'enforced' fields only. This is used as default values if no config is given, or for missing *required* fields. This function always returns the latest config version.

func Parse

func Parse(log *logging.Logger, r io.Reader, confPath string, visorBuildInfo *buildinfo.Info) (conf *V1, compat bool, err error)

Parse parses the visor config from a given reader. The config version is checked against the visor's version and if not the same we send back the error as well as compat(compatibility) as false.

func ReadFile

func ReadFile(confPath string) (*V1, error)

ReadFile reads the config file without opening or writing to it

func ReadRaw

func ReadRaw(raw []byte, confPath string) (*V1, error)

ReadRaw returns config from raw

func Reader

func Reader(r io.Reader, confPath string) (*V1, error)

Reader accepts io.Reader

func (*V1) AddAppConfig

func (v1 *V1) AddAppConfig(launch *launcher.AppLauncher, appName, binaryName string) error

AddAppConfig add new config to apps if name was not same

func (*V1) ApplyDeploymentServices added in v1.3.99

func (v1 *V1) ApplyDeploymentServices(next, prev *Services) []string

ApplyDeploymentServices updates the deployment-managed fields of v1 from next, the deployment's current services config, and returns the JSON names of the fields it changed.

A service address or the STUN list is deployment-managed when the config holds nothing, the value prev held (the services config the visor last applied), or the embedded deployment default. Anything else was set by the operator and is left alone, so pointing one service at your own instance survives a refresh. An empty field is filled only when next differs from the embedded default, since empty already resolves to it.

The deployment key sets (route_setup_nodes, transport_setup, survey_whitelist) are replaced outright, as before: operators' own keys live in the user_* fields beside them and are merged at use time.

prev may be nil, meaning nothing has been applied yet.

func (*V1) AutoconnectSettings

func (v1 *V1) AutoconnectSettings() Autoconnect

AutoconnectSettings returns a copy of the autoconnect limits.

func (*V1) DeleteAppArg

func (v1 *V1) DeleteAppArg(launch *launcher.AppLauncher, appName string) error

DeleteAppArg Delete entire of args of a custom app

func (*V1) DeleteAppConfig

func (v1 *V1) DeleteAppConfig(launch *launcher.AppLauncher, appName string) error

DeleteAppConfig removes an app entry from the launcher config and flushes the change. The caller is responsible for stopping the running proc (if any) before calling this — V1 doesn't own the procmanager. Returns an error if the named app isn't in the config so accidental no-ops surface to the operator.

func (*V1) DeriveKeyEntry

func (v1 *V1) DeriveKeyEntry(label string, index uint32) (KeyEntry, error)

DeriveKeyEntry derives the keyring entry for label+index from the visor secret key, deterministic and without mutating the config — for regenerating a known entry (same input → same key).

func (*V1) EffectiveRouteSetupNodes

func (v1 *V1) EffectiveRouteSetupNodes() []cipher.PubKey

EffectiveRouteSetupNodes returns the union of deployment route setup nodes (managed by config refresh) and user-added route setup nodes.

func (*V1) EffectiveSurveyWhitelist

func (v1 *V1) EffectiveSurveyWhitelist() []cipher.PubKey

EffectiveSurveyWhitelist returns the union of deployment survey whitelist keys (managed by config refresh) and user-added keys.

func (*V1) EffectiveTransportSetupPKs

func (v1 *V1) EffectiveTransportSetupPKs() []cipher.PubKey

EffectiveTransportSetupPKs returns the union of deployment transport setup public keys (managed by config refresh) and user-added keys.

func (*V1) Flush

func (v1 *V1) Flush() error

Flush flushes the config to file (if specified).

func (*V1) GetCalculateRoutes

func (v1 *V1) GetCalculateRoutes() bool

GetCalculateRoutes gets calculate_routes from routing config

func (*V1) GetLogRotationInterval

func (v1 *V1) GetLogRotationInterval() (Duration, error)

GetLogRotationInterval gets log_rotation_interval from config

func (*V1) GetPersistentTransports

func (v1 *V1) GetPersistentTransports() ([]tspec.PersistentTransports, error)

GetPersistentTransports gets persistent_transports from config

func (*V1) MintKey

func (v1 *V1) MintKey(label string) (KeyEntry, error)

MintKey derives the NEXT keyring entry (at KeyRing.NextIndex) for the given label, appends it, and advances NextIndex — the wallet "derive a new address" operation. The caller persists with Flush(). Returns the minted entry.

func (*V1) UnmarshalJSON

func (v *V1) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler for V1. Decodes via v1JSON (which mirrors V1's field set without the embedded sync.RWMutex), copies the result back field-by-field, then patches V1.Pty from the legacy "dmsgpty" key if the canonical "pty" key was absent.

The mirror struct is seeded with v.Common so caller-populated fields like the embedded *Common — set by MakeBaseConfig before the JSON decode runs in ReadRaw — survive the unmarshal.

func (*V1) UpdateAppArg

func (v1 *V1) UpdateAppArg(launch *launcher.AppLauncher, appName, argName string, value interface{}) error

UpdateAppArg updates the cli flag of the specified app config and also within the The updated config gets flushed to file if there are any changes. func (v1 *V1) UpdateAppArg(appName, argName string, value interface{}) error {

func (*V1) UpdateAppArgBatch

func (v1 *V1) UpdateAppArgBatch(launch *launcher.AppLauncher, appName string, args map[string]any) error

UpdateAppArgBatch updates the cli flag of the specified app config and also within the The updated config gets flushed to file if there are any changes. func (v1 *V1) UpdateAppArg(appName, argName string, value interface{}) error {

func (*V1) UpdateAppArgsFull

func (v1 *V1) UpdateAppArgsFull(launch *launcher.AppLauncher, appName string, args []string) error

UpdateAppArgsFull replaces the entire Args slice on the named app. Used by the universal app-settings panel where the operator edits the args as a shell-string and we parse + replace; per-flag UpdateAppArg is for targeted CLI surface, not whole-form save.

func (*V1) UpdateAppAutostart

func (v1 *V1) UpdateAppAutostart(launch *launcher.AppLauncher, appName string, autoStart bool) error

UpdateAppAutostart modifies a single app's autostart value within the config and also the given

The updated config gets flushed to file if there are any changes. func (v1 *V1) UpdateAppAutostart(appName string, autoStart bool) error {

func (*V1) UpdateAppEnv

func (v1 *V1) UpdateAppEnv(launch *launcher.AppLauncher, appName, key, value string) error

UpdateAppEnv sets, replaces, or deletes a KEY=value entry on the named app's Env list. An empty value deletes the entry. The updated config gets flushed to file if anything changed and the launcher is reset so the new env takes effect on the next start.

func (*V1) UpdateAppEnvBatch

func (v1 *V1) UpdateAppEnvBatch(launch *launcher.AppLauncher, appName string, env map[string]string) error

UpdateAppEnvBatch is the multi-key counterpart to UpdateAppEnv. One config flush + one launcher reset for the whole batch. An empty map value deletes that key.

func (*V1) UpdateAppEnvFull

func (v1 *V1) UpdateAppEnvFull(launch *launcher.AppLauncher, appName string, env []string) error

UpdateAppEnvFull replaces the entire Env slice on the named app. Counterpart to UpdateAppArgsFull for the env list. Per-key UpdateAppEnv is still available for targeted mutation; this method is for whole-form save where deletion of keys not in the new list is the intended semantics.

func (*V1) UpdateAppLauncherMode

func (v1 *V1) UpdateAppLauncherMode(launch *launcher.AppLauncher, appName, mode string) error

UpdateAppLauncherMode persists the launcher-mode preference for an app entry. Empty string clears the override (visor default). Validated values: "", "internal", "external".

func (*V1) UpdateAppPort

func (v1 *V1) UpdateAppPort(launch *launcher.AppLauncher, appName string, port uint16) error

UpdateAppPort update app port for communicat with visor

func (*V1) UpdateAppSettings

func (v1 *V1) UpdateAppSettings(vals map[string]map[string]int64, text map[string]map[string]string) error

UpdateAppSettings replaces the persisted per-app live knobs wholesale and flushes. An app with neither map is dropped, so a reset leaves no husk.

func (*V1) UpdateAutoconnect

func (v1 *V1) UpdateAutoconnect(set func(*Autoconnect)) error

UpdateAutoconnect applies set to the autoconnect limits and flushes.

func (*V1) UpdateCalculateRoutes

func (v1 *V1) UpdateCalculateRoutes(enabled bool) error

UpdateCalculateRoutes updates calculate_routes in routing config

func (*V1) UpdateHostBridge added in v1.3.101

func (v1 *V1) UpdateHostBridge(hb *HostBridgeConfig) error

UpdateHostBridge sets the host bridge section, nil to remove it, and saves the config.

func (*V1) UpdateHypervisors

func (v1 *V1) UpdateHypervisors(pks []cipher.PubKey) error

UpdateHypervisors persists the visor's configured hypervisor PKs so a runtime add/remove survives a restart. Same lock+flush shape as UpdateMinHops / UpdatePersistentTransports. A no-op flush on a non-file-backed config (STDIN / in-memory) returns its error to the caller, which may ignore it (the runtime connection change still took effect).

func (*V1) UpdateLogRotationInterval

func (v1 *V1) UpdateLogRotationInterval(d Duration) error

UpdateLogRotationInterval updates log_rotation_interval in config

func (*V1) UpdateMinHops

func (v1 *V1) UpdateMinHops(hops uint16) error

UpdateMinHops updates min_hops config

func (*V1) UpdateNetworkPolicy

func (v1 *V1) UpdateNetworkPolicy(p NetworkPolicy) error

UpdateNetworkPolicy sets transport.network and saves the config. The default policy removes the block.

func (*V1) UpdateNetworkWatch

func (v1 *V1) UpdateNetworkWatch(watch, ipCheck *Duration) error

UpdateNetworkWatch sets the network watcher periods and saves the config. A nil value leaves that period unchanged.

func (*V1) UpdatePersistentTransports

func (v1 *V1) UpdatePersistentTransports(pTps []tspec.PersistentTransports) error

UpdatePersistentTransports updates persistent_transports in config

func (*V1) UpdatePtyWhitelist added in v1.3.99

func (v1 *V1) UpdatePtyWhitelist(pks []cipher.PubKey) error

UpdatePtyWhitelist persists the dmsgpty whitelist. Same lock+flush shape as UpdateHypervisors.

func (*V1) UpdatePublicAutoconnect

func (v1 *V1) UpdatePublicAutoconnect(pAc bool) error

UpdatePublicAutoconnect updates public_autoconnect in config

func (*V1) UpdateTCPNotSentLowat

func (v1 *V1) UpdateTCPNotSentLowat(n *int) error

UpdateTCPNotSentLowat sets transport.tcp_notsent_lowat and flushes. nil restores the default.

func (*V1) ValidateResolvers

func (v *V1) ValidateResolvers() error

ValidateResolvers checks the extra resolvers against each other AND against the two primary blocks, and returns the FIRST problem found, naming both sides of it.

The failure this exists to prevent is a silent one. Two SOCKS5 listeners on the same port do not both work and do not both fail: whichever binds first wins, the other's Run loop returns "address already in use" into a log line nobody reads, and the operator sees a resolver that is enabled, shows as running in `app ls`, and answers nothing. Naming the conflict at config time — and refusing to construct the loser at boot — turns that into a sentence an operator can act on.

Only ENABLED entries are port-checked: a disabled resolver binds nothing, so flagging it would fail configs that are perfectly fine today. Everything else (names, kinds, required ports, kind/field mismatches) is checked regardless, since those are wrong whether or not the entry is running.

A wildcard bind ("0.0.0.0" / "::" / "*") conflicts with ANY address on the same port, because it already covers loopback — "0.0.0.0:4445" and "127.0.0.1:4445" are not two listeners, they are one bind and one EADDRINUSE.

func (*V1) WalletCustody

func (v *V1) WalletCustody() string

WalletCustody returns the effective custody mode, defaulting to browser and clamping to what this build can actually realize.

The clamp is the point of the capability gating: disk needs a backend process with a filesystem. Under wasm there is none, so a config carrying custody:"disk" — perfectly valid on the native visor that wrote it — silently degrades to browser instead of leaving the UI offering a mode that cannot work. See walletCustodyDiskCapable (per-build).

func (*V1) WalletDir

func (v *V1) WalletDir() string

WalletDir returns the disk-custody wallet directory (empty unless disk custody is both selected and realizable).

func (*V1) WalletRemotePK

func (v *V1) WalletRemotePK() string

WalletRemotePK returns the remote-custody target (empty unless remote).

func (*V1) WalletServe

func (v *V1) WalletServe() bool

WalletServe reports whether the wallet frontend should be served. Nil-safe: a config predating this block (or with the block omitted) serves the wallet, preserving existing behavior — the block is opt-OUT, not opt-in.

type WTPeer

type WTPeer struct {
	URL      string `json:"url"`
	CertHash string `json:"cert_hash"`
}

WTPeer is a statically-configured WebTransport peer (transport.wt_table): the peer's https:// WebTransport endpoint and the lowercase-hex SHA-256 of its self-signed cert, which the dialer pins (WT has no CA).

type WalletConfig

type WalletConfig struct {
	// Serve controls whether the /wallet/ frontend is served at all — the
	// feature on/off switch. Deliberately a CLI/config knob rather than a GUI
	// self-toggle: a GUI that can turn off the surface hosting it is a trap.
	Serve bool `json:"serve"`

	// Custody is browser | disk | remote (the WalletCustody* constants).
	// Empty means browser. A value the current context cannot realize is
	// clamped by WalletCustody() rather than failing the config load, so a
	// config written on a native visor still loads under wasm.
	Custody string `json:"custody,omitempty"`

	// Dir is the disk-custody wallet directory. It is a SEED-wallet store —
	// one directory holding N seed files — NOT one directory per coin. The
	// per-coin-directory layout is legacy: since bip44 carries coin_type per
	// account, one seed file already spans several chains.
	Dir string `json:"dir,omitempty"`

	// User is the account to drop to when running the wallet server, for
	// visors running as a system user (_skywire) that should not own the
	// operator's wallet files. Mirrors SKYCOINWEBUSER. Disk custody only.
	User string `json:"user,omitempty"`

	// RemotePK is the visor whose skycoin-web server holds the wallets, for
	// remote custody.
	RemotePK string `json:"remote_pk,omitempty"`
}

WalletConfig is the single config block behind the wallet feature, in all three realizations (native visor, `hv serve`, wasm/single-file). The block is uniform; what DEGRADES per context is which custody modes can actually be realized — a context with no backend filesystem offers only browser|remote. Keeping one block rather than per-context knobs is what stops the surfaces drifting apart.

The existing skycoinweb* config-gen flags (--skycoinweb, --skycoinwebwallet, --skycoinwebaddr, --skycoinwebnodes, --skycoinwebuser) become aliases into this block rather than a parallel source of truth.

type WasmServeConf

type WasmServeConf struct {
	Addr     string `json:"addr"`               // e.g. ":8443"; empty = disabled
	TLS      bool   `json:"tls,omitempty"`      // HTTPS with a self-signed localhost cert (or TLSCert/TLSKey if set)
	TLSCert  string `json:"tls_cert,omitempty"` // optional PEM cert — a locally-trusted *.mesh.localhost cert (mkcert) so real-origin browse iframes load without a per-host accept
	TLSKey   string `json:"tls_key,omitempty"`  // optional PEM key, paired with TLSCert
	Harness  bool   `json:"harness,omitempty"`  // mount the /ctl/* operator control bridge (DEV ONLY — never expose publicly)
	Password string `json:"password,omitempty"` // optional access-password gate (use with TLS)
	// ExecWasm is the path to the FULL skywire CLI built for GOOS=js, served at
	// /skywire.wasm: the desk host, the tab's visor and every command the
	// desk's terminal runs. Empty means the module kept beside the binary,
	// which is fetched and refreshed when the desk is opened. A path here is a
	// developer's own build and is served as it is. Same as
	// `skywire cli hv serve --exec-wasm`.
	ExecWasm string `json:"exec_wasm,omitempty"`
	// DeskHelpTerminal opens a second desk terminal that has already run
	// `skywire --help`. Off by default: it is a whole extra Go/wasm runtime of
	// the full binary, and that memory is never returned to the browser. See
	// pkg/visor.deskWasmBootOpts for the measurement.
	DeskHelpTerminal bool `json:"desk_help_terminal,omitempty"`
	// DeskDocsPort runs `skywire doc serve` on this virtual-loopback port of the
	// desk. 0 (the default) = off, for the same reason.
	DeskDocsPort int `json:"desk_docs_port,omitempty"`
	// BrowseSuffix is the real-origin browser's browse-origin domain suffix
	// (leading dot); empty = ".mesh.localhost".
	BrowseSuffix string `json:"browse_suffix,omitempty"`
	// BrowseOriginAddr, when set, ALSO serves the browse-origin SW bootstrap on
	// this second address (Caddy fronts it on *.<browse_suffix>). Empty = off.
	BrowseOriginAddr string `json:"browse_origin_addr,omitempty"`
	// VOrigin is the public origin of the visor app V (e.g.
	// "https://theskywirenetwork.net") that B's bootstrap postMessages to — used
	// only with BrowseOriginAddr behind a proxy. Empty = derive from Addr.
	//
	// Comma-separated for several apps sharing one browse domain, or "*" to
	// accept any parent. It is the only thing deciding who may feed content
	// into a browse origin, so "*" is a decision: an uninvited page cannot
	// reach a visitor's browsing state (storage is partitioned by top-level
	// site) but it can serve its own content from a subdomain of yours.
	VOrigin string `json:"browse_v_origin,omitempty"`
}

WasmServeConf configures the in-process standalone wasm-visor server — the same surface as `skywire cli hv serve`, hosted by the visor daemon. See pkg/visor.ServeWasm.

type WispConfig

type WispConfig struct {
	// UpstreamSOCKS is the SOCKS5 proxy that carries the streams. Empty
	// means the local skysocks-client.
	UpstreamSOCKS string `json:"upstream_socks,omitempty"`
	// Buffer is the per-stream client -> server credit, in packets. Zero
	// means the package default.
	Buffer uint32 `json:"buffer,omitempty"`
}

WispConfig tunes the embedded Wisp server: a multiplexer that carries many TCP and UDP sockets for a page over one connection, which is how a browser-side Linux guest is given a network.

It binds the page's virtual loopback rather than an HTTP port, and frames its session rather than upgrading it to a WebSocket. Both are forced: inside a tab websocket.Accept does not exist, and a service worker cannot intercept ws:// even if it did. So the page reaches this by dialing the vnet port directly.

Streams leave through UpstreamSOCKS, which defaults to the visor's own skysocks-client — putting a guest's traffic on a route to an exit instead of through whatever central relay the page shipped with.

The server runs on every browser-tab visor and never on a native one, where nothing can reach it: a browser there has no raw TCP, and `skywire cli wisp serve` is the WebSocket backend for it. This section only tunes it.

Jump to

Keyboard shortcuts

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