service

package
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 16, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidCredentials = errors.New("invalid username or password")

ErrInvalidCredentials is returned by Login and ChangeCredentials when the supplied username/password doesn't match the stored admin account.

Functions

This section is empty.

Types

type AddPeerInput

type AddPeerInput struct {
	Name        string
	InterfaceID uuid.UUID
	AllowedIPs  []string
	Endpoint    string
	// DNS sets the peer's client-side DNS (the wg-quick `[Interface] DNS`
	// line). Empty falls back to the interface's DNS when rendering the config.
	DNS []string
	// PrivateKey, when non-empty, is the peer's private key to use as-is
	// (base64 WireGuard key). Empty means generate a fresh one.
	PrivateKey string
	// PresharedKey, when non-empty and WithPresharedKey is false, is the PSK
	// to use as-is (base64 WireGuard key). Ignored when WithPresharedKey is
	// set (a fresh PSK is generated instead).
	PresharedKey      string
	WithPresharedKey  bool
	KeepaliveInterval int // seconds; 0 = use defaultKeepaliveInterval
}

type Option

type Option func(*Service)

func GeneratePresharedKey

func GeneratePresharedKey() Option

func WithVersion added in v1.1.0

func WithVersion(v string) Option

WithVersion sets the admin app's build version (set at link time via -ldflags "-X main.version=..." in each entry point). Surfaced to the UI by AppVersion so the Settings page can show it. Defaults to "dev".

type ProfileDump

type ProfileDump struct {
	Filename string `json:"filename"`
	Data     []byte `json:"data"`
}

ProfileDump is a fetched pprof dump plus a suggested download filename, returned by GetServerProfile. Data marshals as base64 over the Wails/JSON boundary, but the desktop path (App.SaveServerProfile) writes it as raw bytes and the HTTP path streams it, so that never matters in practice.

type ReconcileReport

type ReconcileReport struct {
	// AgentOnly are interfaces the agent has configured that this DB has
	// no record of for this server at all.
	AgentOnly []agentmodels.InterfaceConfig `json:"agentOnly"`
	// DBOnly are interfaces in this DB that the agent didn't report back.
	DBOnly []models.Interface `json:"dbOnly"`
}

ReconcileReport is the result of comparing a server's agent's actual interfaces against what the local admin DB has recorded for it — see ReconcileServer. Both halves are normally empty; entries appear only when one side lost its state (the admin DB was wiped/restored from an older backup, or the agent's own storage was lost/reinstalled) — see TODO.md's "Удаление интерфейса не убирает его на ОС-уровне" section for the full background. Deliberately doesn't try to resolve either side automatically — the caller (the UI) presents a choice to a human instead.

type ServerInput

type ServerInput struct {
	Name  string
	Info  models.ServerInfo
	SSH   models.SSHConfig
	Agent models.Agent
}

ServerInput carries all fields required/optional for server creation/update.

type Service

type Service struct {
	// contains filtered or unexported fields
}

func New

func New(s storage.Storage, opts ...Option) *Service

func (*Service) AddPeer

func (s *Service) AddPeer(userID string, in AddPeerInput) (*models.User, error)

func (*Service) AppVersion added in v1.1.0

func (s *Service) AppVersion() string

AppVersion returns the admin app's build version. Wails-bound (via App embedding *Service) and served over HTTP at GET /version, so both transports can show it on the Settings page.

func (*Service) Backup

func (s *Service) Backup() ([]byte, error)

Backup returns a JSON snapshot of the entire admin database in the same bucket-dump format as `awg-migrate export`, so it can be restored with `awg-migrate import` (or by copying it to another machine). Returned as bytes rather than streamed to a writer so the method stays Wails-bindable for the desktop "Backup" button (App.SaveBackup); the database is small enough (a handful of servers/users/peers) to hold in memory.

The dump contains secrets (SSH private keys, agent mTLS keys, peer PSKs, the admin bcrypt hash) — it's a full backup, so callers must treat the result as sensitive.

func (*Service) BasicAuthEnabled

func (s *Service) BasicAuthEnabled() (bool, error)

BasicAuthEnabled reports whether the standalone server's HTTP Basic Auth gate (internal/api.BasicAuthMiddleware) is currently turned on. Off by default; toggled via SetBasicAuthEnabled from the Settings page.

func (*Service) BuildTunnel

func (s *Service) BuildTunnel(steps []models.TunnelStep, subnet string) (*models.Tunnel, error)

BuildTunnel wires the given ordered interfaces (entry first, exit last — exactly two for now) into one multi-hop tunnel so the entry's clients egress the internet via the exit. Both interfaces already exist and are reused: the entry keeps its clients + listen port and gains a "gateway peer" to the exit plus policy-routing hooks; the exit is reconfigured onto the shared subnet with the same obfuscation params, a peer back to the entry and MASQUERADE hooks (its listen port is kept so clients can also connect directly to the exit). All members get one tunnel id. The shared subnet is always the entry interface's own subnet (see below); the subnet argument is only accepted when empty or equal to it, never an arbitrary value.

func (*Service) ChangeCredentials

func (s *Service) ChangeCredentials(currentPassword, newUsername, newPassword string) error

ChangeCredentials updates the admin account's username and/or password, requiring the current password to confirm the change. Pass "" for newUsername/newPassword to leave that field as-is.

func (*Service) CreateAgentSource

func (s *Service) CreateAgentSource(name, url, path, image string, cacheLocally, userspace bool) (*models.AgentSource, error)

CreateAgentSource saves a new named deploy preset. Exactly one of url, path or image must be set: url is fetched (by the managed server itself, or by awg-admin when cacheLocally is set — see models.AgentSource), path reads the binary directly from awg-admin's own filesystem, and image is a Docker image run as a container on the server. cacheLocally is ignored (forced false) for path (nothing to cache) and image (Docker pulls it itself).

func (*Service) CreateInterface

func (s *Service) CreateInterface(serverID string, in agentmodels.InterfaceConfig) (*models.Interface, error)

func (*Service) CreateServer

func (s *Service) CreateServer(in ServerInput) (*models.Server, error)

func (*Service) CreateUser

func (s *Service) CreateUser(in UserInput) (*models.User, error)

func (*Service) CurrentUsername

func (s *Service) CurrentUsername() (string, error)

CurrentUsername returns the stored admin account's username, so the frontend can confirm a session is still valid and show who's logged in.

func (*Service) DeleteAgentInterface

func (s *Service) DeleteAgentInterface(serverID, ifaceName string) error

DeleteAgentInterface removes ifaceName directly from serverID's agent, without touching the local DB (there's nothing there to remove) — the other half of the "agent has it, DB doesn't" reconciliation choice, alongside ImportInterface.

func (*Service) DeleteAgentSource

func (s *Service) DeleteAgentSource(id string) error

DeleteAgentSource removes a saved deploy preset, and its cached binary on disk (if any) — best-effort, a cache cleanup failure doesn't block removing the preset itself.

func (*Service) DeleteInterface

func (s *Service) DeleteInterface(serverID, ifaceID string) error

func (*Service) DeletePeer

func (s *Service) DeletePeer(userID string, key string) (*models.User, error)

DeletePeer removes the peer by public key from the user and the interface.

func (*Service) DeleteServer

func (s *Service) DeleteServer(id string) error

func (*Service) DeleteUser

func (s *Service) DeleteUser(id string) error

DeleteUser removes the user and cascades peer removal from all interfaces.

func (*Service) DeployAgent

func (s *Service) DeployAgent(id string, agentSourceID string) error

DeployAgent starts installing and starting the awg-agent service on the server's host over SSH, using its currently stored SSH credentials, agent address and (if generated) mTLS certificates, returning as soon as the deploy has started rather than waiting for it to finish — it can take a while (e.g. downloading a large binary), so the actual work runs in the background; poll GetDeployStatus(id) for progress and the outcome. agentSourceID picks which saved models.AgentSource preset to fetch the binary from (see Service.CreateAgentSource). Only the up-front validation (server/source must exist) is synchronous — any error returned here means the deploy never started at all.

func (*Service) GenerateAgentTLS

func (s *Service) GenerateAgentTLS(id string) (*models.Server, error)

GenerateAgentTLS (re)issues a CA, server and client certificate for the server's agent and stores them on the server record. The server cert's SAN is derived from Agent.Address so it matches whatever "white" IP or hostname the agent will be reached on directly (without an SSH tunnel).

func (*Service) GenerateInterfaceDefaults

func (s *Service) GenerateInterfaceDefaults() agentmodels.InterfaceConfig

GenerateInterfaceDefaults returns a fresh InterfaceConfig populated with a generated private key and a full set of AmneziaWG obfuscation parameters. The add-interface form calls it to pre-fill the "Amnezia" tab; it touches no storage and reaches no agent, so it's safe to call on every modal open.

func (*Service) GetDeployStatus

func (s *Service) GetDeployStatus(id string) (*models.DeployStatus, error)

GetDeployStatus returns the most recent DeployAgent run's progress for a server, polled by the frontend while its "Deploy agent" modal is open. Returns storage.ErrNotFound if no deploy has been started for this server since the process started (status is in-memory only, see deployStatusStore).

func (*Service) GetInterface

func (s *Service) GetInterface(serverID, ifaceID string) (*models.Interface, error)

func (*Service) GetPeer

func (s *Service) GetPeer(userID string, publicKey string) (*models.Peer, error)

func (*Service) GetPeerConfig

func (s *Service) GetPeerConfig(userID string, key string) (string, error)

GetPeerConfig renders a wg-quick style client config for the peer identified by key, for download or QR-code provisioning on the frontend. The agent only knows InterfaceConfig/InterfacePeer (server-side shapes); this assembles the client-side equivalent from the peer's own private key plus its owning interface's server public key, listen port and AmneziaWG obfuscation params (if any).

func (*Service) GetPeerQRCode

func (s *Service) GetPeerQRCode(userID string, key string) (string, error)

GetPeerQRCode renders key's client config (see GetPeerConfig) as a PNG QR code, base64-encoded so it can travel as plain JSON/Wails-bound string data and be dropped straight into an <img src="data:image/png;base64,..."> on the frontend. Generating the image here — rather than handing the raw config text to the frontend and rendering the QR code client-side with a JS library — means the wg-quick text (which embeds the peer's private key) only ever needs to leave the process as pixels, not as a string the browser has to hold onto and could leak via devtools/extensions/clipboard history.

func (*Service) GetServer

func (s *Service) GetServer(id string) (*models.Server, error)

func (*Service) GetServerMetrics

func (s *Service) GetServerMetrics(serverID string) (*agentmodels.MetricsSnapshot, error)

GetServerMetrics fetches the latest CPU/RAM/load/network/peer snapshot from serverID's agent, for display on the frontend Dashboard.

func (*Service) GetServerMetricsHistory

func (s *Service) GetServerMetricsHistory(serverID string) (*agentmodels.SystemHistory, error)

GetServerMetricsHistory fetches every host-level sample plus every per-peer sample still retained in serverID's agent's in-memory ring buffers (up to 48h), for the Dashboard's per-server metrics chart modal. Per-peer series come back in SystemHistory.Interfaces (the agent serves all history through its single /metrics/history endpoint).

func (*Service) GetServerProfile

func (s *Service) GetServerProfile(serverID, name string, seconds int) (*ProfileDump, error)

GetServerProfile fetches a Go runtime profiling dump (pprof) from serverID's agent. name must be one of validProfileKinds; seconds (clamped to 1..profileMaxSeconds) sets the sampling window for the CPU "profile"/"trace" kinds and is ignored for the instantaneous ones. The agent answers 403 unless profiling is enabled there (SetServerProfiling), surfaced as an error here.

func (*Service) GetUser

func (s *Service) GetUser(id string) (*models.User, error)

func (*Service) ImportInterface

func (s *Service) ImportInterface(serverID, ifaceName string) (*models.Interface, error)

ImportInterface creates a new models.Interface in the local DB from ifaceName's current config on serverID's agent — the "agent has it, DB doesn't" half of ReconcileServer.

Only the interface shell is recovered (address, keys, AmneziaWG params, and whatever peer public-key/AllowedIPs entries the agent reports for its InterfaceConfig.Peers). The admin-side association between a peer and the models.User it belongs to is NOT recoverable from the agent — the agent only ever stores the server-side wire shape (agentmodels.InterfacePeer: public key, AllowedIPs, PSK, keepalive), it has no idea which awg-admin user a key belongs to. If those user/peer records are also gone from the DB, they need to be re-created by hand (or the existing peers re-added via AddPeer, which will then need new keys — the old client configs/QR codes for them are gone for good, since the private keys never lived anywhere but the lost DB).

func (*Service) ListAgentReleases added in v1.2.0

func (s *Service) ListAgentReleases() ([]models.AgentReleaseAsset, error)

ListAgentReleases fetches the newest agent releases from GitHub (tagged agent/v*) and flattens their downloadable awg-agent binaries into AgentReleaseAssets — newest release first — ready to become URL AgentSources via the "GitHub releases" picker. It reaches the public GitHub API unauthenticated (no credentials involved); a failure (offline, rate limit, non-200) is returned as an error the UI reports.

func (*Service) ListAgentSources

func (s *Service) ListAgentSources() ([]models.AgentSource, error)

ListAgentSources returns every saved agent-binary deploy preset (see models.AgentSource), shown as options in the "Deploy agent" UI.

func (*Service) ListInterfaces

func (s *Service) ListInterfaces(serverID string) ([]models.Interface, error)

func (*Service) ListPeers

func (s *Service) ListPeers(userID string) ([]models.Peer, error)

func (*Service) ListServers

func (s *Service) ListServers() ([]models.Server, error)

func (*Service) ListTunnels

func (s *Service) ListTunnels() ([]models.Tunnel, error)

ListTunnels groups every interface that carries a tunnel id into its Tunnel, entry member first. Reconstructed from the interfaces (no separate storage).

func (*Service) ListUsers

func (s *Service) ListUsers() ([]models.User, error)

func (*Service) Login

func (s *Service) Login(username, password string) error

Login validates username/password against the single stored admin account. Only used by the standalone web-server's session-cookie login flow (internal/api) — the Wails desktop app never calls this, since it already runs as a local single-user process with no network exposure.

func (*Service) MigratePeer

func (s *Service) MigratePeer(userID, publicKey, targetIfaceID string) (*models.User, error)

MigratePeer moves a peer to a different interface, preserving its keys, name, DNS and preshared key. Its address is kept when it's still inside the target interface's subnet and free there (e.g. moving between a tunnel's members, which share an address space); otherwise a free host address on the target is auto-assigned. Both the source and target interfaces are re-pushed. Returns the updated (sanitized) user.

func (*Service) ReconcileServer

func (s *Service) ReconcileServer(serverID string) (*ReconcileReport, error)

ReconcileServer fetches serverID's agent's actual interface list and diffs it against the local DB's records for that server, by interface name. Read-only — it only reports mismatches, see ImportInterface/ DeleteAgentInterface (and the existing SyncServer/DeleteInterface) for the actions a human can take on what it finds.

func (*Service) RefreshAgentSourceCache

func (s *Service) RefreshAgentSourceCache(id string) error

RefreshAgentSourceCache re-downloads a CacheLocally preset's binary, replacing whatever was previously cached — for "rolling" URLs whose content changes over time, where a deploy would otherwise keep using whatever was cached the first time forever.

func (*Service) RemoveTunnel

func (s *Service) RemoveTunnel(tunnelID string) error

RemoveTunnel tears a tunnel down, leaving its interfaces "empty" — it resets every member to a plain interface (clears peers, hooks, routing table and the tunnel id, keeping only name/key/address/listen port/obfuscation) and pushes it. Exit members are pushed before the entry so the relay's route/rule (which the reconcile-on-update path removes via the old PostDown) go last.

func (*Service) ServerAgentStatus

func (s *Service) ServerAgentStatus(id string) (models.AgentStatus, error)

ServerAgentStatus reports a tri-state health for id's agent, for the dashboard (see models.AgentStatus). It combines transport liveness (the SSH tunnel, or the direct mTLS path) with an actual probe of the agent's HTTP API (a lightweight List /interfaces/ via callAgent, which self-heals a dead SSH tunnel by reopening it):

  • AgentStatusOK (green): the agent is reachable and answering — which for an SSH server also means its tunnel is up.
  • AgentStatusDown (red): the SSH tunnel could not be brought up, or an mTLS agent is unreachable — the connection is simply down.
  • AgentStatusDegraded (amber): the SSH tunnel is up but the agent behind it isn't answering, or the state is indeterminate.

Any non-OK state is logged at error level (with server_id) so problems are visible in the logs. This is a passive check: reconnect/passphrase failures collapse into the status rather than surfacing as an error (or a passphrase prompt) to the caller.

func (*Service) ServerHostInfo

func (s *Service) ServerHostInfo(serverID string) (*agentmodels.HostInfo, error)

ServerHostInfo fetches the host facts serverID's agent discovered at startup — its backend, creatable interface kinds, Docker availability, whether it runs in a container, kernel-module presence (see agentmodels.HostInfo) — for the dashboard. Goes through callAgent so a stale SSH tunnel is reopened-and-retried first. Best-effort from the frontend's side: an unreachable agent surfaces as an error the caller treats as "no data".

func (*Service) SetBasicAuthEnabled

func (s *Service) SetBasicAuthEnabled(enabled bool) error

SetBasicAuthEnabled persists whether the standalone server requires HTTP Basic Auth (checked against the same admin account as session login) in front of every request, including the login page and static assets.

func (*Service) SetPeerDisabled

func (s *Service) SetPeerDisabled(userID, publicKey string, disabled bool) (*models.User, error)

SetPeerDisabled activates or deactivates a peer, mirroring the interface-level Disabled toggle. A deactivated peer keeps its stored config (keys, address, PSK, keepalive) but is dropped from the InterfaceConfig pushed to the agent (ToAmneziaConfig omits it), so it's removed from the live WireGuard device and can't connect until reactivated. Both the user's models.Peer and the owning interface's InterfacePeer carry the flag; both are updated in one storage write, then the interface is re-pushed. Returns the updated (sanitized) user.

func (*Service) SetServerMonitoring

func (s *Service) SetServerMonitoring(serverID string, enabled bool) (*models.Server, error)

SetServerMonitoring enables/disables the agent's metrics collection for serverID and persists the desired state on the server record so it's re-applied by SyncServer (e.g. after a redeploy, which starts the agent's collector back up enabled by default).

func (*Service) SetServerProfiling

func (s *Service) SetServerProfiling(serverID string, enabled bool) (*models.Server, error)

SetServerProfiling turns the agent's Go runtime profiling (the /debug/pprof endpoints) on/off for serverID and persists the desired state on the server record so SyncServer re-applies it (a freshly (re)deployed agent starts with profiling off). Mirrors SetServerMonitoring.

func (*Service) StartTunnels

func (s *Service) StartTunnels()

StartTunnels opens an SSH tunnel to the agent of every stored server that doesn't have mTLS configured, and keeps each connection open for the lifetime of the process (see sshclient.Manager). Servers with mTLS are reached directly instead. Per-server dial failures are logged but don't prevent the rest of the servers from getting a tunnel, since a server being temporarily unreachable at startup shouldn't block the whole app.

func (*Service) StopTunnels

func (s *Service) StopTunnels()

StopTunnels closes every tunnel opened by StartTunnels.

func (*Service) SyncServer

func (s *Service) SyncServer(serverID string) error

SyncServer resends every stored interface of serverID to that server's agent. Useful after the agent was redeployed/reinstalled (its local state is gone), after a prolonged outage (it may have missed pushes that failed while it was down), or just as a manual "force re-apply" from the UI.

func (*Service) UnlockServerSSH

func (s *Service) UnlockServerSSH(id string, passphrase string, applyToAll bool) error

UnlockServerSSH caches passphrase as the passphrase for the server's SSH key for the remainder of the process's lifetime (never written to storage — see sshclient.Manager.SetPassphrase) and immediately retries opening its tunnel. When applyToAll is true, the passphrase also becomes the fallback tried for any other server whose key turns out to need one, so a single entry covers every server sharing the same key/passphrase for the session.

func (*Service) UnlockServerSudo added in v1.3.0

func (s *Service) UnlockServerSudo(id string, password string, applyToAll bool) error

UnlockServerSudo caches password as the sudo password for the server's SSH user for the remainder of the process's lifetime (never persisted — see sshclient.Manager.SetSudoPassword), so the next DeployAgent for this server can escalate a non-root user's privileged commands. When applyToAll is true the password also becomes the fallback for any other server whose host turns out to need a sudo password. Unlike UnlockServerSSH it doesn't reconnect anything — the caller simply retries the deploy, which reads the cached password (see Service.DeployAgent / the AgentModal retry flow).

func (*Service) UpdateAgentSource added in v1.1.0

func (s *Service) UpdateAgentSource(id, name, url, path, image string, cacheLocally, userspace bool) (*models.AgentSource, error)

UpdateAgentSource edits an existing preset in place (same ID), with the same validation as CreateAgentSource. If it stops being a cached URL (caching turned off, switched to a path/image kind, or the URL changed), its previously cached binary is dropped best-effort — the cache is keyed by ID, so otherwise a changed URL would keep serving the stale cached binary until a manual refresh.

func (*Service) UpdateInterfaceConfig

func (s *Service) UpdateInterfaceConfig(serverID, ifaceID string, cfg agentmodels.InterfaceConfig) (*models.Interface, error)

UpdateInterfaceConfig replaces all config fields but always preserves peers.

func (*Service) UpdateServer

func (s *Service) UpdateServer(id string, in ServerInput) (*models.Server, error)

func (*Service) UpdateUser

func (s *Service) UpdateUser(id string, in UserInput) (*models.User, error)

func (*Service) VerifyBasicAuth

func (s *Service) VerifyBasicAuth(username, password string) (bool, error)

VerifyBasicAuth reports whether a request carrying username/password (as decoded from an HTTP Basic Auth header by http.Request.BasicAuth) should be let through. When BasicAuthEnabled is off, every request passes (true) regardless of the supplied credentials — there's nothing to check. When on, it's a straight comparison against the same stored admin account session login uses.

type UserInput

type UserInput struct {
	Name        string
	Description string
	Disabled    bool
}

type ValidationError

type ValidationError struct{ Msg string }

ValidationError marks an error as caused by bad user input (a malformed field or a uniqueness conflict) rather than an internal fault, so the HTTP layer can map it to a 4xx instead of a 500 (see internal/api's handleErr). Its message is the user-facing explanation.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Jump to

Keyboard shortcuts

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