Every topic is a short narrated deck. Pick any place, follow the roads at the end of each deck, and switch perspective at the top to see only what applies to you.
Click a place to enter it. Inside a topic you see only its exits, like rooms in an adventure game; m brings the whole map back. space plays, ← → step.
confdb-schema by its account, confdb-control and response-message by the device key, request-message by an operator. Experimental: experimental.confdb and experimental.remote-device-management.
Test data, abbreviated. Signed by its account-id; views map request names to storage paths with an access mode; the JSON body holds the storage schema. Acked or fetched on first use; addressed as account/schema/view. Experimental.
Test data. confdb-control is keyed by brand-id, model, serial and signed by the device key. POST /v2/confdb with delegate or undelegate re-signs it. Without one, messages are unauthorized. Experimental.
Experimental. Change device-management-exchange: exchange-mgmt-messages, dispatch-mgmt-messages, then per message validate-mgmt-message, apply-mgmt-message, queue-mgmt-response. Messages <id>-<n> run in order.
Test data. devices, valid-since and valid-until decide whether the message is for this device now. The confdb handler requires authority-id = account-id and an operator-key delegation; assumes is not checked yet. Experimental.
Experimental. rejected and unauthorized come from validation, error and success from applying. The response-message is signed by the device key and sent on the next exchange.
Roads: how assertions are checked, confdb views as configuration, confdb hooks, devices and the device key, identity and store, and the overlord.
The brand signs the model; the model's *-authority headers name other signers. The serial binds a device key that then signs device-session-request, confdb-control and response-message. repair has its own root; hardware-identity is new and unused by snapd; cluster is experimental.
Test data. authority-id must equal brand-id. serial-authority and preseed-authority list extra signers (brand always included, no *); system-user-authority may be *. A new signer needs a new model revision.
generate-device-key, then request-serial: a request-id, then a serial-request signed by the new key. 202 means poll again in 60 s. The returned serial must match brand, model and key.
device-session-request (nonce, timestamp) is signed by the device key and sent with the serial and model to get a store session. The same key signs confdb-control and response-message, both experimental.
Test data. auto-import.assert on USB is acked and creates the user automatically on an unmanaged Core device. The signer must be allowed by system-user-authority. By hand: snap ack, then snap create-user --known.
snap-repair walks repairs/canonical/N, verifies against its embedded repair-root key, filters by headers and runs the body as a script that answers done, skip or retry. Not on classic.
hardware-identity has no feature flag and is for a server-side check fed by the prepare-serial-request hook; snapd consumes none. cluster is experimental and is read only by the cluster manager behind experimental.clustering, with no way to install one yet.
Roads: how assertions are checked, the model, device hooks, confdb and messages, validation sets, identity and store, snapctl model, and boot and recovery.
account, account-key and store are signed by Canonical, and keys and stores need their account first. account-key-request is self-signed; the base-declaration is built in.
verified prints ✓ (or **), starred prints ✪ (or *). Installed snaps take it from the publisher's account assertion; --dangerous snaps show -.
public-key-sha3-384 identifies the key in the body; account-id and name say whose; since/until bound it. Optional constraints limit what it may sign. The example is test data signed by testrootorg.
Validity is checked on add, not on read. Stored assertions survive expiry; new ones signed by an expired key are refused. The assertion's own timestamp must also fall inside the key's window.
snap create-key, then snap export-key --account=<id> prints an account-key-request; the store issues the account-key. snap sign [-k key] [--chain] signs JSON whose scalar values are all strings.
store names a store, its operator-id, its url and friendly-stores. proxy.store requires a matching store assertion with a url; on-store rules accept friendly stores.
Built from interface code at startup, revision 0, signature $builtin. A store copy is refused. Print it with snap debug base-declaration; per-snap rules come from the snap-declaration.
Mechanism, snaps, devices, declaration rules, why snaps, snapctl model and the debug commands.
snap-declaration: snap-id, name, publisher and policy. snap-revision: a file's hash and size to a revision of that snap-id. validation-set: an account's list of snaps and revisions that belong together.
Keyed by series and snap-id; names snap-name and publisher-id; may carry plugs, slots, aliases and revision-authority. The panel is a unit-test fixture.
The file's SHA3-384 finds the snap-revision; snap-size, snap-id and snap-revision must match, and so must the provenance in snap.yaml. The default provenance global-upload must be signed by the store. The panel is a unit-test fixture.
revision-authority lets account-id sign revisions for the listed provenance values within min-revision/max-revision, optionally only on-store, on-brand or on-model. The provenance in snap.yaml must match. The panel is a unit-test fixture.
snap-resource-revision checks the component file's hash, size and revision; snap-resource-pair must link that component revision to the snap revision, or the component is refused.
--monitor reports, --enforce constrains and is refused unless the set is already met (or --refresh is given). =N pins a sequence. The set shown is spread-test input.
Enforcing refuses invalid snaps, wrong revisions and removal of required snaps, and steers refreshes to the required revision. --ignore-validation overrides per snap; snap validate --forget stops enforcing, but not for a set the model lists with mode enforce; prefer-enforce sets can be forgotten.
Roads: how assertions work, identity and store, interface policy, refresh, devices, snap packaging and the store.
Hooks appear in snap change as tasks such as Run install hook of "snap-hooks" snap if present. The snap supplies the program; snapd decides when it runs.
Pattern, handler, task. Exactly one handler may match a hook name. gate-auto-refresh is still accepted in a snap but has no handler, so its task completes without running the script.
Unknown names are skipped without an error, and a plug or slot name in a parametric hook is not checked against snap.yaml. Check the spelling: post-refresh, and the plug's own name after connect-plug-.
hooks: remove: plugs: [home] gives home to the remove hook only. A plug name that is not declared under plugs: becomes an implicit plug whose interface is that name.
snap run --hook=<name> <snap> reproduces the process by hand, without the per-run cookie. A successful hook's output is discarded: echo is visible only when the hook fails.
snapd serialises hooks per snap instance, not per change, so a hook task does not overlap another hook task of the same snap; fde-setup and prepare-serial-request run without a task and are outside this rule. The order between independent chains is not fixed.
A failing hook fails snap install, snap refresh, snap set or snap connect, and the change is undone. snap remove and snap disconnect complete even when the snap's hook fails.
Hooks are retried only across a snapd stop or a pending restart, never after a failure. Make every hook idempotent.
Roads: the four hook groups, snapctl, the overlord, confinement and packaging.
A top-level plugs section names the interfaces; each app, service or hook lists which plugs it uses. Fixed at build time.
A snap can offer slots to other snaps. Plugs and slots may carry attributes, which the store's rules check.
snapd installs, runs and confines snaps. Snaps come from the store, are verified against signed assertions, mounted read-only and run confined. Interfaces are the only way out.
Interfaces, the overlord, store and assertions and packaging, refresh, confinement, workloads. Hooks, services, snapshots, configuration and boot are on the map without slides yet.
Go in any order. Each deck ends with roads onward: deeper, out to the user's view, or sideways. Flags mark opened topics.
A provider slot names source directories, a consumer plug names a target, and on connection snapd bind-mounts each source onto the target or beneath it. Platform snaps share themes, libraries and runtimes this way.
The slot lists its paths under source; the plug has the same label and a target. Each source lands at target/<basename>, here $SNAP/data-dir/themes/Adwaita. A default-provider is installed when missing. Labels must match.
Top-level read/write (bind-mount mode): each source is mounted on the target itself. Under source: (spool mode): each source is mounted at target/<basename>. Read entries get bind,ro, write entries bind (read-write).
A clashing mount point gets a -2, -3 suffix. In bind-mount mode that is a sibling of the target. In spool mode it is a sibling inside the target; $SNAP_INSTANCE_NAME (only under source:, only below $SNAP_DATA or $SNAP_COMMON) gives each provider instance a distinct name.
snap-update-ns creates missing mount points: mkdir in $SNAP_DATA/$SNAP_COMMON, a writable mimic (tmpfs plus bind mounts of what was there) in $SNAP. Bind-mount mode hides the target's contents; spool mode keeps them and adds subdirectories.
Slots on app, gadget and kernel snaps. Connection when content or compatibility labels match. Auto-connection only within one publisher; other consumers need a store rule.
All six commands act on the caller's context, not through a new change. Writes are buffered and saved when the context finishes. The exception is confdb: set --view outside a confdb hook starts a set-confdb change, and get --view may start a get-confdb change, and snapctl waits for either.
In a hook, snapctl set writes into one transaction that is committed only after the hook exits 0. From an app each call is committed at once and no configure hook runs. snap set with values on a snap without a configure hook fails with snap "my-snap" has no "configure" hook.
Defaults from gadget.yaml are applied only as the patch of the configure hook (or default-configure, which then must coexist with a configure hook) on first install. A snap that has defaults but no configure hook cannot be installed with them.
snapctl set-health error ... reports; a non-zero exit from check-health fails the change. Health is per snap. The hook is scheduled only at the end of install, refresh and revert; no periodic runs were found in the code.
If a snap rejects a value, snap set fails and nothing is stored. The reason after run hook "configure": is written by the snap, not by snapd. Health other than okay shows in snap list Notes and as a health: block in snap info.
snapctl get :<plug> works in prepare-, unprepare-, connect- and disconnect- hooks; snapctl set :<plug> only in prepare-plug-/prepare-slot- hooks, own side, and not over static attributes. (The long help of set says "prepare and connect hooks"; the code allows prepare only.)
snapctl fail <reason> works only in a change-view-<plug> hook and rejects the pending confdb write; it has nothing to do with refresh gating. Requires experimental.confdb.
Roads: the snapctl mechanism, changes and tasks, the other two command groups, configuration, the gadget, hooks and interfaces.
6 commands: 3 non-root, 0 create or join a change, 3 saved when the hook or call ends, 1 hook-specific.
Seven commands, three shapes: read the device (model, system-mode), act on the host within a plug (kmod, mount, umount), answer the FDE hook (fde-setup-request, fde-setup-result). None creates a change.
Allowed: gadget or kernel snaps, snaps from the model's brand publisher, snaps with a snapd-control connection. A --dangerous snap has no snap-id and cannot be same-brand. Flags: --json, --assertion.
system-mode is run, install, recover or factory-reset. factory: true is a convenience flag, not a security signal; storage-encrypted: managed appears only when snapd manages the key.
snapd puts the request in the hook's context; the hook reads it with snapctl fde-setup-request and answers with … | snapctl fde-setup-result. Ops: features and initial-setup. The initramfs runs the fde-setup binary directly.
Only load: dynamic entries of the connected kernel-module-load plug can be inserted or removed; options need options: "*". snapd runs modprobe on the host.
Every argument must match one mount-control plug entry. snapd then writes, enables and starts a .mount unit: /run/systemd/system by default, /etc/systemd/system with --persistent. snapctl umount takes a mount point made with snapctl mount and removes that unit.
snapctl mounts are systemd .mount units described as "Mount unit for <snap>, revision <rev> via mount-control"; they show in systemctl, not in snap changes. snap remove cleans them up.
Roads: how snapctl works, the configuration and health commands, the commands that create changes, the model and the gadget and kernel snaps, boot modes and encryption, the hooks, and the snapd-control, kernel-module-load and mount-control interfaces.
7 commands: 2 non-root, 0 create or join a change, 0 saved when the hook or call ends, 2 hook-specific.
Lifecycle commands act on the calling snap only: its services, its components, its own changes and its refresh status. A snap name other than the caller is refused.
From configure the service tasks join the hook's change and run after it; from default-configure they run right after start-snap-services; from other hooks and apps a new service-control change runs and snapctl waits for it.
snapctl stop --disable in the install hook is the supported way to ship a service off by default. snapd records it and keeps it disabled across refreshes; snapctl start --enable overrides install-mode: disable.
A snap can install a service disabled on purpose. It stays disabled across refreshes; use snap start --enable to run it.
From an app, snapctl install and remove create a change the snap owns and the client polls is-ready; --no-wait prints the change ID. From a hook they are queued and run after the hook.
is-ready, change and tasks see only changes the snap started with snapctl install or remove from an app. Anything else is change "ID" not found.
snapctl refresh --hold and --proceed no longer work: the gate-auto-refresh feature is permanently disabled. --pending, --tracking and --show-lock remain, and snap refresh --hold by the user is kept.
Only the gadget's install-device hook can call snapctl reboot --halt or --poweroff. The choice is stored on restart-system-to-run-mode and applied when install mode or factory reset ends.
Roads: how snapctl works, services, hooks, the overlord, refresh, boot and recovery, and snapctl configuration and health.
11 commands: 5 non-root, 5 create or join a change, 0 saved when the hook or call ends, 1 hook-specific.
An assertion is a signed statement. snapd checks it, stores it, and decides installs, connections, refreshes and registration from what is stored. The district has one deck per group.
Headers, an optional body, then the signature. authority-id names the signer and sign-key-sha3-384 the key. Header values are strings, or lists and maps of strings; edit one byte and the signature no longer verifies.
The trusted set is the canonical account and its root account-key. Every other account-key must be signed by them, so Canonical issues brand and developer keys. Testing builds add testrootorg; production binaries cannot.
From the file hash to snap-revision, snap-declaration, the accounts and the store account-keys, ending at the built-in root. The fetcher saves prerequisites first; the database itself never fetches.
snap install hello fetches and checks. snap download then snap ack then snap install ./file.snap gives the same result offline. --dangerous skips assertions: revision x1, no snap-id, no publisher.
Store: validate-snap fetches and runs CrossCheck. Ack: a prechecked Batch that never fetches, then DeriveSideInfo from the local database. --dangerous: no lookup, no snap-id. --devmode tries the lookup and falls back.
One current assertion per primary key in asserts-v0. Higher revision wins; acking an equal or older one is a no-op. No deletion: a newer revision revokes. Built-in assertions cannot be overridden.
The type decks: identity and store, snaps, devices, confdb and messages. Neighbours: why snaps, how interfaces are implemented, the store and the overlord.
A snap declares a plug, the system or another snap offers a slot, and a connection joins them. Only a connected plug grants anything.
The developer declares plugs at build time, the store's snap-declaration sets the rules, the user connects and disconnects. The snap cannot connect itself.
For users, for developers, implementation, and three single interfaces: home, network and content.
For a user: the users' deck, then home and network. The rest is optional.
For an app developer: the developers' deck, then content, workloads and confinement.
For a snapd developer: implementation, then confinement, the overlord and workloads.
The repository tracks interfaces, plugs, slots and connections. On a change it collects security snippets from each interface and hands them to backends such as AppArmor, seccomp, mount and udev.
snap connect becomes a change with connect and setup-profiles tasks, owned by the interface manager. Tasks survive restarts and can be undone.
The built-in base-declaration and the store's per-snap snap-declaration, which replaces the whole rule per interface, feed the policy checks for installation, connection and auto-connection. One rule decides; deny wins inside that rule.
ConnectCandidate.check stops at the first rule found: plug snap-declaration, slot snap-declaration, base plug rule, base slot rule. mount-control's base slot deny-connection: true is therefore inert. check(kind) reads only that kind's sub-rules, so mir's deny-connection: true without deny-auto-connection still auto-connects. The manual check runs only when both snaps have a snap-declaration.
A store rule is a snap-declaration from the store for that snap and interface. Super-privileged means allow-installation: false in the base declaration. With --dangerous the snap has no snap-id and only the minimal check runs: slots only, base allow-installation only. Such plugs install locally and, except unity8, do not auto-connect. snap connect works, because no connection rule is evaluated when either snap lacks a snap-declaration.
Read and write access to the user's home directory, except hidden top-level directories and the snap data under ~/snap.
home auto-connects on classic and Core Desktop but not on Ubuntu Core, which is more locked down and usually has no regular users. A plug with read: all is denied by default and needs a store rule.
Connected automatically on classic desktops, not on Ubuntu Core, which is more locked down and usually has no regular users. Check with snap connections; connect or disconnect by hand, and a disconnect is remembered.
The only attribute is read, and its only value is all: it adds dac_read_search and non-owner read rules so the app can read other users' homes. Denied by default; needs a store rule. The standard policy also denies writes to ~/bin.
Five patterns: change-view-, save-view-, observe-view- on writes; load-view-, query-view- on reads. The suffix is a confdb plug name.
role: custodian on a connected confdb plug selects the custodian hooks. A write or read with no custodian connected fails with no custodian snap connected. System schemas have no custodians. A snap with several affected confdb plugs gets one observe-view task per plug.
Order: all change-view-, all save-view-, commit, observe-view- of the other affected snaps. Values set in change-view are what everyone after it sees.
A save-view failure rolls every custodian back by running its save-view- hook again with the committed data. Write save-view hooks so that they can run twice.
load-view- fills a read transaction; query-view- adjusts it. Neither is saved. A failure fails the read.
Accesses to a confdb are serialised behind the running write. Keep custodian hooks short: every reader and writer waits for them.
Roads: the mechanism, confdb itself, the snapctl commands, assertions, interfaces, interface hooks and the overlord.
Gadget: install-device, prepare-device, prepare-serial-request. Kernel: fde-setup. The dashed two have no task in snap change.
prepare-device configures registration; it does not register. The values stay in the gadget's configuration, visible with snap get <gadget>.
prepare-serial-request never appears in snap change; its failure appears as the failure of request-serial. A gadget using it also needs prepare-device for the URL.
install-device runs once per install or factory reset, with the default ten-minute limit. It is not optional and its failure is not ignored.
Two ops, features and initial-setup, each a synchronous call with a fixed 5-minute limit. A failed features query means an unencrypted install unless the model requires encryption.
Roads: the mechanism, two snapctl decks, model, boot and recovery, device assertions, lifecycle hooks and the overlord.
connect-plug-consumer runs in the plug snap, connect-slot-producer in the slot snap, for the connection basic-iface-hooks-consumer:consumer to basic-iface-hooks-producer:producer.
One hook file per plug or slot: connect-plug-special-content-basic and connect-plug-special-content-extra. A connection runs only the hooks whose suffix is its own plug and slot.
Order: prepare-plug-, prepare-slot-, connect, connect-slot-, connect-plug-. All interface hooks use the default 10-minute limit.
On first install, connect hooks run before install. On refresh, connect hooks run only for connections that did not exist before.
disconnect-slot- then disconnect-plug-, then the connection is removed. A disconnect hook reports a failure only in the change log. A disabled snap's disconnect hooks are skipped. Only a disconnect caused by hotplug removal fails on a hook error.
unprepare-plug-<plug> pairs with a failed connect, not with disconnect. A connect hook that fails must undo its own partial work before exiting.
Roads: the mechanism, attributes, interfaces and their implementation, content, lifecycle hooks and the overlord.
Hook tasks in snap change read Run install hook of "…" snap if present. Only configure run by snap set is required; only remove may fail without effect.
install → default-configure → services start → configure → check-health. default-configure requires a configure hook and is not allowed in base, snapd or OS snaps.
Which pre-refresh runs is decided by the revision being replaced, not the one arriving. Neither hook runs on snap revert.
After link-snap, a failing post-refresh, configure or check-health reverts the refresh. Configuration is restored; files are not.
remove errors are ignored and only logged. It runs before auto-disconnect and before the snapshot, and not for snap remove --revision.
Component hooks run as <snap>+<component> with SNAP_COMPONENT_* variables. Only install, pre-refresh, post-refresh and remove exist for components.
An error that names a hook comes from the snap, not from snapd. snap change <id> shows which hook failed and the end of its output.
Roads: the mechanism, the two snapctl decks these hooks lean on, interface hooks, refresh, removal, services and the overlord.
Two shapes: a system slot with a fixed device rule and a udev tag into the device cgroup, or a gadget-declared slot whose attribute names one device. Every member denies auto-connection except accel.
gpio grants one pin through the deprecated sysfs ABI; gpio-chardev grants chosen lines of a chip through a virtual aggregated chardev and cannot be connected together with gpio; gpio-control grants every pin and is super-privileged.
The slot is on the gadget snap, so snap connect names both ends; snap connections shows what the gadget offers. Nothing auto-connects without the gadget's store declaration. A connected device is allowed by the profile and added to the app's device cgroup; before that it cannot be opened.
Each gadget slot attribute becomes one AppArmor line and one udev rule: usb-vendor and usb-product give serial-port a stable /dev/serial-port-<name> symlink; an i2c or spi path becomes a device rule and a KERNEL== tag. Auto-connection comes from the gadget's store declaration.
tee has a plug-side allow-installation: false, so a store snap cannot install without a store rule. physical-memory-control is root-equivalent yet carries only deny-auto-connection: true: it installs freely and an admin's snap connect is the only gate. physical-memory-observe is the read-only pick.
The slot attributes are the policy: devices become rwk, read-devices and files.read become r, files.write becomes rw, and udev-tagging replaces the default KERNEL== rule. Connection needs matching custom-device labels; the slot is super-privileged on the slot side.
dsp accepts any flavor and emits rules only for ambarella; gpio and pwm log "cannot export not existing gpio" and emit nothing when the sysfs node is missing, so a refresh is never blocked (LP: 1866424). In both cases the connection shows as connected and grants nothing.
Roads: the interfaces overview, the implementation, the neighbouring devices-and-storage family, the model deck for gadget-provided slots, and confinement for the device cgroup.
Every interface in the family by connection class: 1 auto-connect, 1 conditionally, 33 by hand, 0 need a store rule to connect; 7 are super-privileged.
Socket interfaces grant read and write on one socket; the daemon behind it holds the privilege. Support interfaces are for the daemon: a system slot and a super-privileged plug that needs a store rule to install. Most let the daemon manage its own device cgroup, with Delegate=true on its services.
A store-installed daemon snap holds its super-privileged *-support plug, pre-connected by the store rule; that is root. The socket interface is the second half: a client snap gets access only after snap connect lxd-demo-server:lxd lxd:lxd. snap connections lxd shows both.
libvirt: manual connect allowed. docker: deny-connection: on-classic: false, so manual connect only on classic. podman: deny-connection: true everywhere. lxd: allow-installation: false on the slot, so even installing the slot snap needs a declaration.
The boolean privileged-containers adds /dev/** mrwkl,, change_profile unsafe to any profile and @unrestricted seccomp. The change_profile rule is generated with InsertAAREExclusionPatterns to exclude the two snap-confine paths.
Only lxd-support sets appArmorUnconfinedPlugs; with enable-unconfined-mode: true the backend adds the unconfined profile flag when the parser has unconfined and the kernel has policy:unconfined_restrictions. The family's other relaxations: steam-support allow all,, docker-support privileged rules, classic-support's chroot.
One AppArmor rule, userns,, plus clone and unshare in seccomp; added only when the parser reports the userns feature, because without it the kernel allows user namespaces anyway. docker-support, lxd-support, greengrass-support and browser-support add the same line themselves.
Confinement for the profiles; system control and observe for the neighbouring family; workloads for the newer model, where permissions attach to each spawned thing instead of root for the daemon. The gatehouse and the gatekeepers' hall lead back.
Every interface in the family by connection class: 0 auto-connect, 0 conditionally, 22 by hand, 5 need a store rule to connect; 19 are super-privileged.
Two shapes: a classic-only implicit slot standing for one session-bus service (absent on Ubuntu Core), and "operating as or interacting with", where the slot is the system's on classic or an app snap's on Ubuntu Core. Nearly all policy is D-Bus session-bus AppArmor rules.
On a desktop, desktop, desktop-legacy, x11 or wayland, opengl, audio-playback and home connect at install. audio-record, password-manager-service, screencast-legacy and cups-control wait for snap connect. On Ubuntu Core the slots exist only when a snap provides them, and then the store must allow the connection.
The same desktop plug gets different policy by slot owner: the system slot on classic means peer=(label=unconfined) plus a classic-only snippet; a shell snap's slot on Ubuntu Core means the slot's label and, by the plug-side rule deny-connection: slot-snap-type: [app], a store rule. wayland, x11 and audio-playback use deny-connection: on-classic: false.
pulseaudio granted playback and recording together and no longer auto-connects. audio-playback has the socket rules and auto-connects on a desktop; audio-record's policy is only a comment: the sound server asks snapd whether it is connected, so it is a deliberate snap connect and grants nothing on its own.
desktop-legacy and unity7 deny reading other snaps' desktop files at priority 0 under the key desktop-file-access. Because an AppArmor deny beats any allow, desktop-launch adds an empty snippet under the same key at priority 100, which replaces those deny rules when it is connected.
snap-themes-control adds no backend snippets; daemon/api_themes.go gates GET with interfaceOpenAccess and POST with interfaceAuthenticatedAccess plus polkit, both naming the interface. Connected is a capability token. The plug is super-privileged: allow-installation: false.
mir, online-accounts-service, ubuntu-download-manager and unity8 have a base slot rule deny-connection: true and no deny-auto-connection. Manual snap connect evaluates connection sub-rules and is refused; auto-connection evaluates auto-connection sub-rules, finds none, and absent means allow. By design, and legacy. For unity8 the slot rule is inert anyway: a plug rule exists and plug rules come first.
Interfaces is the overview; Implementation collects the snippets into profiles; Confinement applies them; home has its own deck; the files, data and snapd's API family is next.
Every interface in the family by connection class: 4 auto-connect, 8 conditionally, 19 by hand, 12 need a store rule to connect; 4 are super-privileged.
Implicit system slot plus deny-auto-connection: connect by hand or by store declaration. Access is an AppArmor path rule plus a udev TAG+= rule into the snap's device cgroup, with a RUN+=snap-device-helper rule. 26 of 35 use udev; none implements hotplug.
camera, joystick and removable-media stay disconnected until you run snap connect; snap connections shows the state. Only optical-drive connects on its own, read-only, on classic. A few interfaces need a store-vetted publisher before the snap can be published at all.
AppArmor allows /dev/input/event[0-9]* rw for every evdev node; the udev rule tags only ENV{ID_INPUT_JOYSTICK}=="1" devices into the cgroup, so keyboards and mice stay out. /dev/full is tagged so the cgroup is in effect with no joystick present.
hardware-random-observe: /dev/hwrng r and read of rng_available and rng_current. hardware-random-control adds rw on /dev/hwrng and w on rng_current. Same udev tag, both manual connect; prefer observe.
Plug-side allow-installation: false: block-devices, dm-crypt, dm-multipath, iscsi-initiator, mount-control, nvme-control, scsi-generic, uinput. A snap-declaration rule lifts it, and must restate deny-auto-connection to keep it manual. uinput is listed to vet publishers, because third-party udev rules make /dev/uinput world-writable and snapd will not adjust device permissions.
No implicit slot: the gadget or core snap declares it with path. With usb-vendor and usb-product, a permanent udev rule adds SYMLINK+=, the plug gets the glob /dev/hidraw[0-9]{,[0-9],[0-9][0-9]} rw, and the udev tag narrows it to one device. Path-only slots must name /dev/hidrawN.
Each mount entry becomes mount fstype=(...) options=(...) "what" -> "where{,/}", plus umount; only the 23 vetted kernel options reach the rule; " and @ are rejected. Validated in BeforeConnectPlug (systemd 209). The slot's deny-connection is shadowed by the plug rule: install needs a declaration, connect is manual.
Roads: gatehouse (overview), gatekeepers' hall (backends), buses and boards (the sibling family), walls (the device cgroup in the sandbox), archive (snapshots).
Every interface in the family by connection class: 0 auto-connect, 1 conditionally, 32 by hand, 2 need a store rule to connect; 8 are super-privileged.
Three groups: read access to a fixed list of host files, attribute-driven sharing between snaps, and marker interfaces that unlock parts of snapd's own API. home and content have their own decks.
Same code, different validation: $HOME/ prefix with owner for personal-files, / prefix for system-files. Both plugs have allow-installation: false, so the store reviews the exact path lists. Only personal-files creates missing parent directories for write paths.
Requests on snapd-snap.socket pass only if the calling snap has an active connection of an interface listed on the endpoint. The table shows which /v2 endpoints each marker unlocks. All five plugs are super-privileged.
One AppArmor line, /run/snapd.socket rw, and no interface check on that socket: root in the snap equals root running snap. With refresh-schedule: managed on an asserted snap, refresh.schedule may be set to managed and snapd stops scheduling auto-refreshes.
A vendor slot's library-source directories go into /etc/ld.so.conf.d/snap.system.conf through the ldconfig backend; no AppArmor. The plug is implicit on the system snap on classic only, the slot has allow-installation: false, and slots-per-plug: * lets several vendors connect.
ssh-keys and gpg-keys read your keys, private ones included; system-backup reads every file on the computer; personal-files reads a store-checked list of files in your home. None connects on its own: use snap connect, and snap connections shows what is granted.
home and content have their own decks; Interfaces is the overview; Implementation covers the mount and ldconfig backends; the overlord covers the daemon and API behind snapd-control.
Every interface in the family by connection class: 1 auto-connect, 3 conditionally, 26 by hand, 1 need a store rule to connect; 16 are super-privileged.
Shape one: an implicit system slot on classic and Ubuntu Core; only network, network-bind and network-status auto-connect, the rest need snap connect or a store rule. Shape two: a daemon interface such as network-manager with an implicit slot on classic only and an app slot on Ubuntu Core, where the connection needs a store rule.
network and network-bind auto-connect; network-observe and network-control are manual. Each rung adds system calls, but only network-bind adds accept and listen, and an app with sockets: must plug it. Pick the lowest rung that works.
network and network-bind connect on their own. The grants that reconfigure the machine are manual: check with snap connections and connect with snap connect my-snap:network-control. On Ubuntu Core a NetworkManager, bluez or ofono client needs the provider snap installed first.
On classic the plug connects to the implicit slot with peer=(label=unconfined); on Ubuntu Core to the network-manager snap with peer=(label="snap.network-manager.*"), and deny-connection: on-classic: false means a store rule is needed there. The branch is on release.OnClassic, not on the slot's snap type.
The slot (gadget or core) carries family, an integer, and family-name; the plug carries family-name. The connection generates socket AF_NETLINK - <family> in seccomp. Matching names are necessary for auto-connection but the base declaration still denies it; a store rule is needed.
The only mount-backend member: /var/lib/snapd/hostfs/var/lib/dhcp is bind-mounted at /var/lib/dhcp with x-snapd.ignore-missing. Every mount entry needs matching snap-update-ns AppArmor rules, and the interface sets affectsPlugOnRefresh.
Roads: the network interface on its own, the interfaces overview, the implementation in the gatekeepers' hall, confinement, and the system control and observe family next door.
Every interface in the family by connection class: 3 auto-connect, 0 conditionally, 21 by hand, 0 need a store rule to connect; 0 are super-privileged.
27 system-provided slots, every one with deny-auto-connection: only a store declaration auto-connects one. 17 can be connected by an admin with snap connect; 10 cannot be installed without a declaration. Most come in observe and control pairs.
system-observe: see every process and kernel state, no tracing. log-observe: the journal, /var/log and the kernel ring buffer. time-control: the system and hardware clocks. shutdown: reboot, power off, suspend, hibernate. Audit with snap connections, grant with snap connect, revoke with snap disconnect.
A slot-only rule (system-observe) installs freely and connects by hand. A plug rule with allow-installation: false (shutdown and nine others) needs a snap-declaration to install, and the same declaration can add allow-auto-connection. kernel-module-load's slot deny-connection is inert because the base plug rule is checked first.
kernel-module-observe: kmod, /proc/modules, /sys/module and modprobe.d read-only, plus capability syslog; lsmod works, modprobe fails on seccomp. kernel-module-control adds capability sys_module, the init_module, finit_module and delete_module syscalls, and /dev/mem r. Only the control side is super-privileged.
logind: login-session-observe, login-session-control, shutdown. timedated: time-control and timezone-control. hostnamed: system-observe. localed: locale-control. polkitd: polkit and polkit-agent. Each interface names the D-Bus members it allows.
The modules attribute becomes host files: load: denied a blacklist line and options an options line in /etc/modprobe.d/snap.<snap>.conf, load: on-boot an entry in /etc/modules-load.d/snap.<snap>.conf loaded at once. No sys_module for the snap; validated at connect time.
Roads: the gatehouse (interfaces), the gatekeepers' hall (rule evaluation and profiles), the network family (more observe and control pairs), the walls (confinement) and the rolls office (configuration: system.timezone, system.hostname).
Every interface in the family by connection class: 0 auto-connect, 0 conditionally, 26 by hand, 1 need a store rule to connect; 10 are super-privileged.
104 access one resource or device; 37 change a subsystem (-control); 17 read a subsystem (-observe); 21 operate as a privileged daemon (-support); 12 talk to a daemon over its socket; 7 talk to a session service (-service); 7 expose vendor libraries (-driver-libs); 6 unlock part of snapd's API; 4 read key material (-keys); 2 read or write listed paths (-files); 2 mount a filesystem type (-mount); 3 operate as a system manager daemon (-manager).
126 implicit system slot on classic and Ubuntu Core; 29 implicit system slot on classic only; 2 implicit system slot on Ubuntu Core only; 22 app-provided slot, or the system slot on classic; 17 app-provided slot only; 13 gadget-provided slot; 7 the system holds the plug, vendor snaps the slot; 6 no implicit slot.
9 auto-connects; 13 auto-connects under conditions; 179 connect by hand (snap connect); 21 store rule needed even to connect.
10 observe and control pairs; 4 socket and support pairs.
155 installs freely; 2 installs freely unless certain attributes are used; 1 plug restricted to certain snap types; 51 super-privileged plug (store rule to install); 13 super-privileged slot (store rule to install).
The classes are tags on every catalogue entry; search by class name above the map.
Client network access through AppArmor and seccomp rules. Operating as a service and configuring networking are separate interfaces.
The rule only places the slot on core; defaults allow connection and auto-connection, so network connects at install on every system. Trivia: implicit slots moved from the obsolete core snap to the snapd snap; state still says core, the CLI shows system.
Connected automatically on every system. The system's slot shows as :network. Disconnecting is possible and remembered.
Every interface supplies a plug and a slot rule string; snapd joins them under plugs: and slots:. It compiles at start or snapd panics. Only a new snapd changes it.
Six sub-rules; absent allow-* is true, absent deny-* is false. network states only allow-installation: slot-snap-type: [core], so it auto-connects.
Slot allow-installation sorts the interfaces: 157 system only, 22 app or system, 15 app only, 12 system or gadget, 13 false. core matches the core and snapd snaps.
194 slot rules say deny-auto-connection: true; 8 are conditional, such as x11's on-classic: false; 20 do not deny it. Each kind reads only its own sub-rules.
allow-installation: false on 51 plug rules and 13 slot rules: no store install without a store rule. Staying manual comes from the accompanying deny-auto-connection: true; unity8 lacks it.
docker allows app and core but denies app. Deny is checked first: the system snap installs, an app slot needs a store rule. desktop and shared-memory follow the same shape.
Base rules are string constants checked by TestValidity, TestSlotInstallation, TestPlugInstallation and TestAutoConnection. A base plug rule shadows the base slot rule for connection and auto-connection.
Evaluation and store rules next; interface classes and the developers' topic for the developer view.
Candidate, first rule found, the asked kind's deny-* then allow-*, answer. "denied" means a deny matched; "not allowed" means no allow did. One rule decides.
The install check runs per slot, then per plug, each with its own side's rule: snap-declaration, else base, else allow. A network plug installs; an app's network slot does not.
home: connection allowed unless read: all. Auto-connection denied on Ubuntu Core by alternative 1 (on-classic: false and on-core-desktop: false), allowed on classic.
content's allow-connection compares the content tag with $SLOT(content). allow-auto-connection adds plug-publisher-id: [$SLOT_PUBLISHER_ID]: other publishers connect only by hand.
Deny beats allow within the rule in force. Between rules, the first found decides: plug snap-declaration over slot snap-declaration over base. Each kind reads only its own sub-rules.
Inside an alternative every constraint must hold, in a fixed order; absent ones match. Alternatives are tried in turn. The failing constraint's reason is computed and discarded: errors never say which one failed.
The minimal check: slots only, base allow-installation only, only snap type, on-classic, on-core-desktop. No connection rule is evaluated for snap connect if either side lacks a snap-declaration.
Call sites next; the rules in the declaration decks; home and content in their own decks.
plugs and slots headers keyed by interface, in the base declaration's grammar, for one snap. Plug rules constrain the slot side, slot rules the plug side. Formats 1 to 7 mark the features used.
One store key, allow-auto-connection: true, also grants installation: the base rule is not read, and unstated sub-rules default to allow. Restate any base limit you want to keep.
The store rule pins the slot's name and attributes, restates the base allow-connection, and allows auto-connection only for one snap-id. Its features put it at format 5.
Values are anchored regexps; $MISSING requires absence; $SLOT(), $PLUG(), _COMPAT and publisher references compare with the other side and never match at installation.
Installation keys describe the snap's own plug or slot; connection keys describe the other side. on-* keys work in every sub-rule. Keys outside their list are ignored; a connection sub-rule with no known key is rejected, an installation sub-rule is not (a snapd bug). The catalogue lists all of them.
on-store, on-brand and on-model are checked against the device's model and its store's friendly-stores. Without a model a scoped allow fails and a scoped deny does not apply.
In allow-auto-connection, slots-per-plug: * means any and every other value means 1. plugs-per-slot has no effect. The plug snap's rule wins over the slot snap's.
Evaluation and timing next; the snap-declaration assertion in the assertions district; device scope's inputs in the model deck.
install? in mount-snap; auto-connect? in auto-connect and again in each injected connect; connect? in the connect task of a snap connect change. Only overlord/ifacestate asks.
Every new revision is judged in mount-snap against the current snap-declaration; --dangerous, snap try and unasserted --devmode get the reduced check. Reverts and snap enable are not judged.
The connect task evaluates connection rules after the prepare-* hooks and the sanitizers, and only when both snaps have snap-declarations. With a --dangerous snap on either side, no rule is evaluated.
Candidates must pass the auto-connection rules and the interface's own check; failures are dropped silently. With more than one candidate and any arity not *, nothing connects and the task logs the candidates.
Default arity: two candidates, no connection, a log line. A consumer plug rule that restates the base constraints and adds slots-per-plug: * connects both. A later second provider is not connected; the existing connection stays.
Gadget connections: apply only before seeding completes or in a remodel. They bypass auto-connection rules and are checked against connection rules. Snaps without a snap-id are skipped.
New snap-declarations are only stored. Restoring connections at start and in setup-profiles runs no policy check. The next refresh is judged in mount-snap; new auto-connections wait for the next auto-connect, and undesired ones stay disconnected.
Evaluation and the overview in this district; changes, users, models and assertions in theirs.
Install, connect and auto-connect are three separate questions. One rule answers each: the snap-declaration's rule for that interface if the store issued one, otherwise the base declaration's.
The base declaration is assembled from every interface at snapd start and only a new snapd changes it. The snap-declaration comes with the snap and is refreshed before each refresh. A store rule replaces the base rule; nothing is merged.
Install and connect refusals fail the change with installation not allowed by ... or connection denied by .... Auto-connect refusals are silent. No message names the constraint that failed.
The base rule predicts the three answers. network auto-connects, network-control is manual, home is manual on Ubuntu Core, content auto-connects only within one publisher, snapd-control needs a store rule to install. A store rule replaces the whole base rule.
With --dangerous: install checks slots only, snap connect evaluates no rule if either side has no snap-declaration, auto-connect follows the other side's store rule or the base declaration but publisher constraints never match, and gadget connections are skipped.
Existing connections are not re-judged when a snap-declaration or the model changes. The next refresh is checked against the new declaration; a revert is not.
The base declaration, snap-declaration rules, evaluation and when snapd asks. Neighbours: Implementation, Interfaces, Assertions, Interface classes.
Hidden only means "not in help"; a hidden subcommand still runs. (internal) is a label in the short help, not a switch. Pick the class that matches who you are.
GET /v2/debug?aspect= is open to every local user; POST {"action": ...} is root only. The verb, not the effect, decides who may call.
When snapd is wedged, state, device-cgroup, lsm and mount-namespace still answer. snap debug state defaults to ./state.json; pass /var/lib/snapd/state.json as root.
confinement and sandbox-features describe the host; device-cgroup and mount-namespace describe one snap at run time.
seeding for first boot, boot-vars for try and recovery state, timings --ensure=seed or --ensure=install-system for where the time went.
Offline graph: state. Time: timings. Live goroutines: stacktraces. Run the managers now: ensure-state-soon.
connectivity for reaching the store, refresh-app-awareness for refreshes held by a running app, can-manage-refreshes for refresh.schedule=managed.
When no subcommand exists, use snap debug api, and add --fail in scripts so HTTP errors give a non-zero exit status.
Roads: the overlord, confinement, boot and recovery, model, gadget and kernel, the store, and how interfaces are implemented.
snapctl is a thin client: each call is one POST /v2/snapctl on /run/snapd-snap.socket with the cookie and the arguments. The command itself runs inside snapd, and the reply carries its output.
Hooks get a SNAP_COOKIE that lives for one hook run; apps and services get the snap's cookie from /var/lib/snapd/cookie/snap.<instance>, which lives from first install to last removal. The cookie identifies the snap; the socket's peer credentials supply only the uid.
In a hook, snapctl set and snapctl set-health take effect only if the hook exits 0. From an app or service they take effect when the snapctl call returns.
A service the snap disabled, a health status in the Notes column of snap list, and a restart inside the snap set change all come from the snap's own snapctl calls. The rev, publisher and "blocked" status are examples; how snap set reaches the configure hook is in the configuration and health deck.
From configure, snapctl restart queues into the hook's change; from default-configure it runs right after start-snap-services; elsewhere it creates a service-control change and waits. Commands such as reboot, fde-setup-request and get --previous work only in one hook.
Socket, uid, context, then the command's own rules. Non-root callers may use get services set-health is-connected system-mode refresh model version is-ready tasks change; refresh then accepts only --tracking. The uid 1000 in the refusal is an example.
Hooks are run-hook tasks with a timeout: 10 minutes by default, 5 for configure, 30 seconds for check-health. A hook that overruns is killed and its task fails with <exceeded maximum runtime of …>. Revision 12 in the drawing is an example.
Roads: the three snapctl groups, hooks, and the overlord.
25 commands: 11 non-root, 5 create or join a change, 3 saved when the hook or call ends, 4 hook-specific.
The snap connections command lists every plug, its slot and whether it was connected manually or automatically. A dash means not connected.
snap connect and snap disconnect act on the whole snap. Auto-connected plugs that you disconnect stay disconnected across refreshes.
snap run and systemd units start apps through snap-confine, a setuid program that builds the sandbox for the security tag snap.<snap>.<app> and then execs snap-exec and the app.
In order: a per-snap mount namespace built by snap-update-ns, a device cgroup, the AppArmor profile via change_onexec, the seccomp filter, then exec.
snapd's backends write the AppArmor and seccomp files from interface snippets whenever install, refresh or a connection changes them. snap-confine only applies them.
Strict enforces the sandbox; devmode runs the same profiles in complain mode and logs violations; classic has no sandbox and needs store approval. Denials appear in the kernel log.
Three problems: one program became many packages; the author could not publish directly; the user had to trust the developer with the whole system. snapd is the shape of the three answers.
A snap is one squashfs file with its own runtime: a base snap and, for shared libraries, content snaps brought in by default-provider. The same file installs on any distribution with snapd, and refreshes follow the publisher's channel rather than a distribution repository.
account, account-key and snap-declaration assertions bind a snap name and snap-id to one publisher, who controls revisions and channels. snapd checks the assertions before installing; a file without them is refused unless --dangerous is given.
snap-confine builds the sandbox before every app runs. Interfaces are the unit of trust: the store's snap-declaration, not the developer, decides auto-connection; super-privileged interfaces such as snapd-control need a store rule to install at all; classic confinement needs store review.
One package everywhere: the store, packaging and refresh. The publisher owns it: assertions. Confined by default: interfaces and confinement. The overview is next door.
Workloads are named permission bundles a snap can create at runtime as a tree. The complete sixteen-scene deck is at Snap Workloads Explained.