1. Project Overview & Quickstart (canonical/snap)
snap
Open-source repository canonical/snap
Repository Details
- Repository: canonical/snap
- Primary Language: Code
Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.
2. Official Technical Reference & Guides (canonical/snap-docs)
File: docs/contributing/index.md
(contribute-to-our-docs)=
Contribute to our docs
Our documentation is a community effort, published on snapcraft.io/docs.
We warmly welcome community contributions, suggestions, fixes and constructive feedback.
Contributing to documentation can be a fantastic way to get started as a contributor to open source projects, no matter your level of experience!
We hope to make it as easy as possible to contribute. If you need help at all with the contribution process, please let us know!
Publishing and hosting
The documentation for snap is
hosted in GitHub and
rendered, via Read the Docs, to https://snapcraft.io/docs/.
We use the Sphinx documentation generator to create our documentation, which is written in MyST Markdown and built from Canonical's Sphinx Starter Pack.
For further details, see the Starter Pack documentation.
The Open Documentation Academy
The SnapD project is a proud member of the
Canonical Open Documentation Academy
(CODA). If you are new to making open source contributions, or new to technical
writing and want to boost your skills -- or both! -- we will be glad to help.
Contributions we accept
We especially value contributions that improve the documentation's clarity,
accuracy, and usefulness. If you spot a problem, you can report it to us using
the "Give feedback" button at the top of every documentation page. Please give
us as much information as you can to help us address the problem.
If the issue is one of the following very small issues, and you want to fix it
yourself, you can submit a pull request (PR) with your changes directly in the
GitHub web interface:
- Typo corrections
- Minor technical corrections
- Broken link fixes
- Small clarifications
For changes larger than those, we require contributor PRs to be tied to an issue.
Examples of changes that require an issue are things like:
- Adding new sections or pages
- Restructuring existing pages
- Extensive rewording
- Significant technical corrections or updates
- Feature additions or enhancements
Contributions we don't accept
We reserve the right to reject any contribution at our own discretion where:
The outline of proposed work has not already been agreed by a maintainer.
: To protect maintainers' limited time, we do not accept unsolicited contributions. A PR must be tied to a valid and agreed issue unless it only makes a small change. Submitting an issue after a PR doesn't guarantee that we will then accept the PR.
The pull request far exceeds the scope of what was agreed.
: "Scope creep" makes it much harder to review and accept changes. If a pull request becomes too large, we may ask you to break it into multiple smaller PRs. Stick to the scope of the original issue as much as you can -- if you discover more changes need to be made, discuss them in the PR or the original issue. A new PR can be submitted with the extra fixes.
The contribution makes changes without value.
: To have value, changes must be a specific and concrete improvement over what already exists. We don't accept generic "polishing" (e.g. substituting synonyms, rewording without adding clarity). If your contribution contains some of these, we will ask you to undo them -- but will gladly accept the remaining parts of your pull request that are real improvements. If your contribution only contains low-value changes, it will be rejected.
We deem the contribution to be AI generated.
: We don't want any part of our documentation to look or feel like it was produced by AI. Therefore, if what you submitted cannot be easily distinguished from AI output, it does not meet our contribution requirements.
The issue the pull request is addressing was assigned to someone else.
: We want to make this a safe place for contributors to work on issues in their own time and without pressure. Please treat others as you'd want to be treated. If you like an issue someone else is working on, let us know and we'll create a similar one for you.
AI policy
The SnapD documentation has been created by humans, for humans. The occasional "jagged edges" you might find (typos, slightly unconventional wording, humour) represent the very human voices of the people who collaborated
to create it. We don't want to average those away by passing the documentation through LLMs.
This policy exists to protect our readers, our community of contributors, and the project maintainers.
As AI continues to evolve, we will update these guidelines accordingly.
(acceptable-use-of-ai)=
Acceptable use of AI
We don't ban our contributors from using AI. However, we do expect anyone contributing to this project to use it responsibly, as a helper, and not as something that replaces them. Some examples of acceptable usage include:
- Using AI as a spellchecker or grammar helper
- Using it to help with translation into English
- Using it to help outline and organize a draft
- Using it adversarially, to point out missing sections
- Ensuring formatting adheres to our style guide
We want to be open and transparent with our users about the way in which AI is or is not used in the our documentation. We (the maintainers) have been experimenting with the ways in which AI can be used to improve the experience for our readers, and we don't object to contributors doing the same -- with the following caveats:
Agreement: Any such work you do must be tied to an issue, and be agreed as valid and necessary with the maintainers before you start. Only maintainers are exempt from this rule, since they are most familiar with the project and its scope.
Honesty: If you use an "AI assist" when making a contribution, you must disclose that you used it, and how you used it in the pull request description.
Accessibility: Issues marked as "good first issues" and "Open Documentation Academy" are specifically created and intended for people new to Open Source to get started in a safe place. These issues are easy to complete, well-described, and self-contained for new contributors to learn from. These issues are not for AI.
Responsibility: Make sure that even if you use AI, you are still adding value based on your own personal skills and competency. You must understand what you're submitting, even if you used an LLM to help you.
Thank you!
Lastly, we would like to thank you for spending your time to help make the
SnapD documentation better. Every step in the right direction is a step
worth taking, no matter how large or small.
:maxdepth: 1
:hidden:
self
local-developmentFile: docs/contributing/local-development.md
myst: html_meta:
(local-development)=
Local development
This page outlines the different ways in which you can contribute to the Snap
documentation. Please take the time to become familiar with the contents of this
page before you begin contributing.
Prerequisites
Code of Conduct
: You will need to read and agree to the Ubuntu Code of Conduct. By participating, you implicitly agree to abide by the Code of Conduct.
GitHub account
: You need a free GitHub account to create issues, comment, reply, or submit contributions.
: You don't need to know git before you start. For many small contributions, especially where only one page is being changed, you can edit pages directly through GitHub's web interface.
Workflow overview
- {ref}
Find an issue to work on <finding-issues> - {ref}
Pick your workflow <choose-workflow> - {ref}
Test your contribution <doc-testing> - {ref}
Submit it for review <submit-work>
(finding-issues)=
Find issues to work on
We use GitHub issues to track documentation tasks. Start by
checking our issues list
to see if there are any tasks you'd like to work on.
You can see which issues are unassigned by selecting
"Assigned to nobody" from the "Assignee" drop-down menu (or use this link as
a shortcut).
Claim an issue
If you find a task you want to work on, leave a comment on the issue to let us
know. A maintainer will respond and assign that issue to you.
You can claim one open issue at a time.
Create a new issue
If you have an idea that isn't covered by an existing open issue, create one
with an outline of what you want to do. A maintainer will review your idea and
either:
- Agree the change is within the project's scope and assign it to you so you can begin working on it
- Discuss and offer suggestions to improve your outline
- Close the issue if it is out of scope of the project or does not meet our contribution guidelines
(choose-workflow)=
Choose your workflow
After you have found an issue you want to work on, and have been assigned the
issue, you have two options:
Direct edits via GitHub web interface: For simple changes like typos or
minor corrections using the GitHub web interface, see
{ref}quickstart guide <small-changes-in-gh>.Local development: For more substantial or complex changes, for example to
multiple pages, continue with {ref}local-setup.
(small-changes-in-gh)=
Small changes directly in GitHub
For small and straightforward contributions that don't need local testing, such
as typos or minor corrections, it can be easier to edit pages directly through
GitHub's web interface.
This approach is valid whether you are fixing a single typo, or making multiple
changes to a single file.
- Navigate to the page you want to edit in the documentation
- Click the "Edit on GitHub" button (pencil icon) at the top of the page (you will be prompted to create a GitHub account if you don't already have one)
- Make your changes in the GitHub web editor and save them
- Select "Create a new branch for this commit and start a pull request"
- Fill in the pull request template, including a description of why the change is needed
- Submit the pull request, and you're done!
(local-setup)=
Local setup
For more substantial changes, e.g. where you're changing multiple pages, you
may prefer to work locally so you can build and test your changes while you work.
Create a fork
Create your own fork of the Snap documentation GitHub repository.
If you're not sure what this means or need some help getting started, check out
the working with git
guide from the Open Documentation Academy which walks through the process and
explains all this terminology.
Just remember to change all instances of open-documentation-academy in the
commands to snap-docs!
Get the documentation
In your terminal, clone the Snap documentation repository by running:
git clone [email protected]:canonical/snap-docs.gitThis creates a new folder on your machine calledsnap-docs containing the contents of this repository.
Navigate to this folder:
cd snap-docsCreate a new branch
Before making any changes, ensure the main branch on your machine is
up-to-date with any recent changes made to the remote repository:
git pullThis won't have any effect if you have only just cloned the repository
(since you're already up-to-date).Now, create a branch and switch to it:
git checkout -b my-new-branchIt's a good idea to give your branch a descriptive name (something more useful
than my-new-branch). Then, even if you are working on multiple branches, you
will know at a glance what each of them is for.
Build and preview
You're now ready to start working on the docs! Run the following command to
build a live preview:
make runThis builds and serves the documentation at http://127.0.0.1:8000/.
It watches the folder so that whenever you save changes to a file, the page gets
rebuilt. In this way, you can see the effect of your changes (or be warned if
something got broken!).
If you are building locally on an Ubuntu Cloud VM or a container, you may
experience issues accessing the page from your host's browser. To resolve this,
add the export variable to your shell by running the following command:
export SPHINX_HOST=0.0.0.0The make run command should then work as expected.
If you have problems getting the documentation to run on your machine,
reach out to the team or leave a comment on your issue to get additional
support.Other build commands
You can also use these commands for different purposes:
make html-- Build the documentation only (without serving)make serve-- Serve previously built documentation without rebuildingmake clean-doc-- Clean all built files to start fresh
Run make by itself to see a list of all available commands.
(doc-testing)=
Test your changes
Once you think you are ready to submit your changes, test the documentation
to catch any spelling errors, broken links, or similar issues.
This allows reviewers to focus on the main changes you are proposing and
makes the review process more efficient.
Manual testing
If your contribution contains any code or process steps, do a final run-through
of your guide from start to finish in a clean environment. This ensures
everything works as expected.
Particularly check code snippets -- does the output in your terminal match
what you've presented in the guide?
Creating redirects
If you rename, move or delete an existing file, you must create a redirect for
that page to ensure users don't run into 404 errors when clicking links in the
published documentation.
Internal redirects
To set up a redirect from one file path to another, add a line to the end ofredirects.txt file in the docs/ directory, in the following format:
redirect/path/from/ redirect/path/to/Note that since we use dirhtml to build, the built documentation is in the
format path/to/file/index.html where file corresponds to the file name
you are redirecting. This means that you only need a trailing slash at the end
of the file name, without the file extension. See the
Sphinx Rediraffe docs
for more guidance, or reach out to us for help.
External redirects
Rediraffe doesn't currently handle redirects from a page to an external website.
To redirect outside of the Server documentation, you will need to set up a
redirect in the conf.py file in the root directory.
Under the Redirects section, add the source page and the target page as follows:
redirects = {
"example/source": "https://exampletarget.org",
"how-to/containers/lxc-containers": "https://linuxcontainers.org/lxc/documentation/",
}When you set up a redirect in this way, the path of the source file you're
redirecting from should include everything after the base URL
(https://ubuntu.com/server/docs).
(submit-work)=
Submitting your pull request
Once you've completed and tested your changes, you're ready to submit them
for review.
Commit your changes
Make sure all your proposed changes are committed:
git statuswill show your not-yet committed changes- Select the ones you want to add to each commit using
git add <filename> - Commit your selected changes using
git commit
Try to group your changes logically. For example, if you have one set of
changes that modifies spelling in 10 files, and another set of changes
that modifies formatting in 10 different files, you can group them into
two commits (one for spelling, and one for formatting). You don't need a
separate commit for every file.Push and create a pull request
Push the changes to your fork:
git push <your username>Then create a pull request
against the Snap documentation repository and fill in the pull request
template.
Click submit when you're ready!
Read the Docs preview
You will be able to see a live preview of your documentation as it will appear
on Read the Docs at the bottom of your pull request's page. Where the checks
appear, click on "Show all checks" and next to the "docs/readthedocs" line,
click on "Details".
Reviews
After you have submitted your PR, one of the Snap documentation maintainers
will review it. Depending on time zones and where we are in the development
cycle, there may be a delay before your PR is reviewed. Please be patient!
One or more of the Snap team maintainers will review the changes you
have proposed, and they will either "Approve" the changes, or leave some
feedback and suggested changes (with reasons). If you agree with the feedback,
you can make the suggested changes, and the reviewer will approve the PR.
The team has adopted the [Conventional Comments](https://conventionalcomments.org/)
approach with the intention of making feedback easier to parse.If you disagree with any parts of the review, it's OK to discuss this with the
reviewer -- feedback is made in good faith, and is intended to help strengthen
your contribution. This is a collaboration, after all! It's quite normal to
have some back-and-forth on a PR, but it should be a respectful dialogue on all
sides.
Once the discussion has concluded, and you have made any agreed changes, the PR
will be approved and then merged. Congratulations (and thank you)! You are now
an open source contributor!
File: docs/explanation/how-snaps-work/channels-and-tracks.md
(explanation-how-snaps-work-channels-and-tracks)=
Channels and tracks
Channels are an important snap concept. They define which release of a snap is installed and tracked for updates.
The complete channel name can be structured as three distinct parts separated by slashes:
/<risk>/The tracking value for an installed snap, as shown in the output from the snap list command, shows which channel is installed and is being followed for that snap.
To view the channel a snap is tracking, the following command may also be used:
snap refresh --tracking vlcTracks
All snaps have a default track. Without the snap developer specifying otherwise, the default track is called latest. Similarly, when no track is specified, a snap will implicitly install from the latest track. The track can also be specified explicitly:
snap install vlc --channel=latest/edgeDevelopers can optionally choose whether to supplement the default latest track with additional tracks. The developer is also free to designate one of these as the default track to be installed when no further preference is given.
Mozilla's Firefox snap is a good example of how tracks can be used. It contains two tracks: the default latest track for the majority of users, and an esr track for the version of Firefox with extended support.
Equally, a track could also be used to track minor updates (2.0.1, 2.0.2), major updates (2.1, 2.2), or releases held for long-term support (3.2, 4.1).
Tracks are listed in the channels section of the output from the snap info command:
$ snap info firefox
[...]
channels:
latest/stable: 149.0.2-1 2026-04-08 (8107) 287MB -
latest/candidate: 150.0-1 2026-04-16 (8172) 288MB -
latest/beta: 150.0b10-1 2026-04-15 (8166) 288MB -
latest/edge: 151.0a1 2026-04-17 (8175) 310MB -
esr/stable: 140.9.1esr-1 2026-04-07 (8106) 259MB -
esr/candidate: 140.10.0esr-1 2026-04-15 (8162) 259MB -
esr/beta: β
esr/edge: β
[...]In the above output, the Firefox snap includes the custom esr track. You can also see which tracks a snap supports by clicking Other versions from its online Store entry.
To install Firefox from its insider track, for example, use the following command:
snap install firefox --channel=esr/stableIf you already have Firefox installed, you can switch channel with the snap refresh command:
snap refresh firefox --channel=esr/stableDevelopers must currently make a request for tracks to be added to their snap via the #store-requests forum category. Releases are verified and checked to ensure that reasonable user expectations are being met. For example, only 3.2.* versions are accepted into a 3.2 track.
Risk-levels
There are four risk-levels: stable, candidate, beta and edge. These represent decreasing levels of stability for a snap, although that decision is ultimately up to the snap's publisher.
Installing from a less stable risk-level will typically mean that updates for a snap will be more frequent than for a more stable risk-level.
The risk-levels have the following meaning:
stable: for the vast majority of users running on production environments.
Releases at this risk level are as stable as they will ever get, according to the project's standards. Important software will only reach this stage once itβs ready for production use and may be used in products. There is an implied promise to avoid any changes that would disrupt those usages.
candidate: for users who need to test updates prior to stable deployment, or those verifying whether a specific issue has been resolved.
Candidate releases are considered almost ready for going into stable, but need some additional real world experimentation before they move forward. Software reaching this stage will typically have passed all available QA and review processes, since users following it expect a high stability level. Should almost never break.
beta: for users wanting to test the latest features, typically outside of a production environment.
Beta is the first level towards the stabilisation of what was before a fast moving stream of changes. Specific projects may have slightly different terminology for such releases (alpha, beta, etc) but all of these are welcome on this risk level. These releases will almost certainly have passed some sort of review and QA, but may still have unfinished parts. Breaking changes are still relatively common here.
edge: for users wanting to closely track development.
Edge releases often include a moving stream of changes without QA or review promises and are typically built automatically by a CI process from an arbitrary source code snapshot. Often the CI will only publish after some sort of automatic QA passed, and code reviews remain a good practice, but these are project specific. Assume edge releases may break often.
Snaps are installed from the stable risk-level by default. For example, the following command installs VLC from its stable channel:
sudo snap install vlcUse the --channel option to select a different risk-level. The following command will install the latest beta snap of VLC:
sudo snap install --channel=beta vlcIf the beta snap is not available, a snap will be installed from the closest
channel with a more stable risk-level.
β For brevity,
--stable,--candidate,--betaand--edgecan be used instead of--channel=<risk-level>
After installation, the risk-level being tracked can be changed with the use of the switch command option:
sudo snap switch --channel=stable vlcThis option will not automatically refresh the snap to force the installation
of a new snap. To switch channels and update the snap with a single command, add the --channel option to the refresh command:
sudo snap refresh --channel=stable vlcTo check which channel a snap is tracking, look for the tracking field in the output from the snap info command:
$ snap info vlc
[...]
snap-id: RT9mcUhVsRYrDLG8qnvGiy26NKvv6Qkd
tracking: edge
refresh-date: yesterday at 19:54 BST
[...]Risk-levels may not match a project's internal conventions. Some projects may use alpha instead of edge, for instance. However, a project's own release nomenclature should be close enough to a snap's risk-levels to allow you to judge the relative stability of the version you're installing.
(ref-channels-and-tracks_branches)=
Branches
A branch is an optional, fine-grained subdivision of a channel for a published snap that allows for the creation of short-lived sequences of snaps that can be pushed on demand by snap developers to help with fixes or temporary experimentation.
Branch names convey their purpose, such as fix-for-bug123, but the name isn't exposed in the normal way, such as with the snap info command. Instead, they can only be installed by someone who knows the branch name, and this is usually only shared by the snap developer to test a specific fix or release.
After 30 days with no further updates, a branch will be closed automatically. The replacement snap will then be chosen as it would be with closed channels (see below). For example, beta/fix-for-bug123 will fall back to beta after the fix-for-bug123 branch is closed.
See Snapcraft's Manage revisions and releases for more details on how snap developers can use branches to publish temporary snap releases.
Closing channels
A channel can be closed by a snap publisher when there is no longer a snap that fits a channel's original purpose or specification.
For example, when a specific risk-level channel is closed, the snap store will select a snap from a more stable risk-level of the same track. If the original channel is re-opened, snaps will once again be selected from the original channel.
This approach is commonly used for beta testing. If a snap is following a beta channel that is then closed, the store will offer the snap from the candidate channel. If the candidate channel is not available, the snap from the stable channel will be selected. If the beta channel re-opens, the snap will once again be selected from that channel.
In some cases, when a specific revision is given, it may appear that a snap is being installed from a less stable channel when a revision is not available on the more stable channel being tracked:
$ snap install hello-world --revision=28
hello-world (edge) 6.3 from Canonicalβ installed
Channel latest/stable for hello-world is closed; temporarily forwarding to edge.This does not mean that the snap is now being tracked from a potentially riskier channel (edge, in the above example), but that the requested revision could only fetched from a riskier channel:
$ snapcraft list-revisions hello-world
Rev. Uploaded Arches Version Channels
29 2019-04-17T16:43:58Z all 6.4 latest/beta*,latest/candidate*,latest/edge*,latest/stable*
28 2017-11-20T07:59:46Z all 6.3 latest/edge
27 2016-07-12T16:37:23Z all 6.3 latest/beta,latest/candidate,latest/edge,latest/stable
26 2016-05-31T07:02:32Z all 6.1 latest/edge,latest/stable
24 2016-04-19T15:37:06Z all 6 latest/beta,latest/candidate,latest/edge,latest/stableFile: docs/explanation/how-snaps-work/confdb-configuration-mechanism.md
(explanation-how-snaps-work-confdb-configuration-mechanism)=
Confdb configuration mechanism
Confdb is a configuration mechanism that replaces individual {ref}snap-specific configuration <how-to-guides-manage-snaps-set-system-options> with an external confdb-schema assertion, separating the configuration data from the snap installation. This allows for advanced configuration functionality, including:
- device onboarding and management
- cross-snap configuration
- precise access control
- evolving configuration settings without changing the affected snaps
See {ref}How to configure snaps with confdb <how-to-guides-manage-snaps-configure-snaps-with-confdb> for instructions on using confdb with your own snaps, and read on for specific implementation details.
Confdb is currently considered an {ref}Experimental feature <ref-experimental-features_experimental-features> and implementation details may change as development progresses.
(ref-confdb-configuration-mechanism_confdb-views)=
Confdb views
Settings are exposed via views. Views allow for both granular access control and granular configuration contracts to enable the evolution of a configuration value.
Figure: Architectural overview, showing users and snaps interacting with separate confdb databags through granular views.
The view field in the confdb-schema assertion is a set of mappings from βrequestβ paths to βstorageβ paths.
Users and snaps can access the request path, while the storage path defines the layout in storage.
The separation of the request path from the storage path provides the isolation, allowing the configuration layout to evolve without having to propagate those changes to every accessing snap.
Confdb view requests
When making a request, an administrator (or a snap accessing a configuration), must specify both a view and a request path.
An attempt is then made to match the request path against a path in the view's rules. If a ruleβs path contains a matching prefix then the corresponding storage path is used to retrieve a value.
If more than one rule matches the request, then the result is aggregated from all of the values.
Multiple views can have the same βrequestβ field, but different βstorageβ, facilitating backwards compatibility when transitioning to a new layout.
Example implementation
The following is almost the simplest confdb-schema assertion that could be defined to obtain some information about installed snaps, although we omit the other headers and the body for brevity. See {ref}Configure snaps with confdb <how-to-guides-manage-snaps-configure-snaps-with-confdb> for the full details.
Basic example
type: confdb-schema
account-id: system
name: snaps
[...]
views:
state:
rules:
-
request: snaps.firefox.status
storage: v1.snaps.firefox.status
access: read
[...]This above configuration could be accessed as follows:
$ snap get system/snaps/state snaps.firefox.status
enabledWe now want to expand the above example to allow status and revision queries for many different snaps.
Placeholders
Instead of enumerating every possible combination, we can use a placeholder to map a variable in the request path to the storage path:
[...]
rules:
-
request: snaps.{name}.status
storage: v1.snaps.{name}.status
access: read
[...]This enables the snap name to be dynamically mapped into the query:
$ snap get system/snaps/state snaps.firefox.status
enabled
$ snap get system/snaps/state snaps.spotify.status
disabledNested paths
To add more configuration paths for snaps, either add more rules or, if possible, use a βcontentβ shorthand to define nested paths:
[...]
state:
rules:
-
request: snaps.{name}
storage: v1.snaps.{name}
access: read
content:
- storage: status
- storage: revision
[...]This example only provides a storage path. When omitted, the request path is assumed to be equal to the storage path by default.
Aggregate results
As requests are matched on prefixes of the request paths in view rules, itβs possible to provide a prefix path to `snap get` and to obtain an aggregate result:
$ snap get system/snaps/state snaps.firefox.status
enabled
$ snap get system/snaps/state snaps.spotify.revision
7
$ snap get system/snaps/state snaps
{
"firefox": {
"status": "enabled"
},
"spotify": {
"status": "disabled",
"revision": 7
}
}Evolving configuration
All the rule mappings defined above have the same βrequestβ and βstorageβ paths. As snapd maps request paths to storage paths, letβs say a user or snap is interested in knowing the systemβs (in this case, snapdβs) status. To take advantage of confdbβs features for evolving configuration schemas and re-organising namespaces, we could add the following rule:
...
rules:
-
request: snaps.{name}
storage: v1.snaps.{name}
access: read
content:
- storage: status
- storage: revision
-
request: system-status
storage: v1.snaps.snapd.status
access: readThis maps an already exposed path (snaps.snapd.status) under a new request path, allowing us to query system-status directly.
In the future, if a new path was intended to provide the value for system-status, we would only need to modify the mapping.
$ snap get system/snaps/state
{
"snaps": {
"firefox": {
"status": "enabled"
},
"spotify": {
"status": "disabled",
"revision": 7
},
"snapd": {
"status": "enabled"
},
},
"system-status": "enabled"
}Requests are compared to rule prefixes until a match occurs which means an empty request matches every rule.
Namespaces
If the configuration data layout changes, only the assertion needs to be modified. The snap sees the same namespace, isolating it from schema changes. For example, the following assertion exposes the same data under the same namespace (from a user or snapβs perspective) but the storage layout it assumes is completely different:
...
rules:
-
request: snaps.{name}.status
storage: v1.statuses.{name}
access: read
-
request: snaps.{name}.revision
storage: v1.revisions.{name}
access: read
-
request: system-status
storage: v1.statuses.snapd
access: readThe configuration namespace should be considered carefully up front. While new confdb views can be added and existing ones can be extended, devices in the field may be running older snap versions that still depend on the existing namespace and views. Removing or renaming request paths, views, or schemas can therefore break those older snaps, making "outdated" namespaces difficult to retire safely.(ref-confdb-configuration-mechanism_interface-plugs)=
Interface Plugs
Snaps can use the {ref}confdb interface to express their interest in accessing a view through the plug stanza in their snapcraft.yaml (then snap.yaml). For example, a snap wishing to access the same state view would declare it as:
plugs:
snaps-state:
interface: confdb
account: system
view: snaps/stateIf the snap publisher signed the confdb assertion, the confdb interface will be auto-connected. Otherwise, the plug must be connected to the :confdb slot manually.
The snap can then refer to this interface plug when accessing confdb with the snapctl commands. It must also use the --view flag to signal this as a confdb request:
$ snapctl get --view :snaps-state snaps.firefox.status
enabledConfdb supports the same commands and options as the {ref}original configuration mechanism <how-to-guides-work-with-snaps-configure-snaps>. This includes unsetting data using snapctl unset --view ... or by using snapctl set --view with a \! prefixed to the request path.
Custodians
The confdb interface plug can have one other attribute, role, which is optional and can only take a value of custodian.
custodian declares that the snap has a special role in managing the data accessed through the referenced view. Custodians are responsible for a subset of the configuration data. As such, at least one custodian must be installed and connected for the data to be accessible.
One of the key aspects of the custodian role relates to ephemeral data which weβll cover in the next section.
Ephemeral data
In the context of confdb, ephemeral data is configuration data for which the authoritative version is stored outside of snapd. Snapd may retain data, but this should only be considered a cache, while the custodians are responsible for loading and storing the authoritative version during read and write accesses.
Configuration paths are marked as ephemeral in the storage schema which is specified in the body of the confdb-schema assertion.
During snap or snapctl access (whether to read or write), changes are either committed or read as a transaction. Snapd will get data from its storage and then schedule certain hooks from snaps with plugs referring to views affected by the data.
Weβll cover hooks in more detail in the next section so for now weβll just focus on two: save-view- and load-view-. These are invoked for custodian snaps when writing and reading, respectively, and are suffixed with the name of the interface plug for which the snap is custodian.
When snapd detects that a snap or snapctl set might impact ephemeral data, it stores the changes in a transaction object and invokes save-view- hooks for connected custodians. These hooks can then query the ongoing transaction with the usual snapctl get --view ... commands to read changes to the ephemeral data and then store them off-snapd as they wish. Snapd will also store these changes but it does so as a cache, not as an authoritative version.
When ephemeral data is read, snapd will similarly schedule load-view- hooks that give custodians an opportunity to read new data outside of snapd and then load into the transaction read with the usual snapctl set --view ... commands.
(ref-confdb-configuration-mechanism_hooks)=
Hooks
Confdb supports 5 types of hooks:
load-view-query-view-change-view-save-view-observe-view-
load-view- and query-view- are scheduled during read operations, while change-view-, save-view- and observe-view- are relevant only to writes.
The save-view/load-view hooks are mandatory if the path being accessed either maps to ephemeral data or may contain nested data (i.e., marked as ephemeral in the storage schema). The other hooks are optional.
During a read of the confdb data:
- snapd first reads the configuration data it stores internally and creates a transaction object to carry it.
- Snapd then schedules
load-view-hooks for any custodian snaps with interface plugs for the referenced view. These hooks allow the snaps to load the ephemeral data, if they wish to. - After those hooks load any off-loaded data, the
query-view-hooks run for any relevant custodian that defines them. These hooks give the custodian snaps a chance to modify the read data before itβs returned to the reader. This could be used to apply a constraint that couldnβt be enforced on write (e.g., if the data is ephemeral and could be modified off-snapd).
Both series of hooks are run in the same order, sorted by their snapβs name.
Writes to confdb data operate in a similar fashion to reads:
Snapd creates a transaction object, storing the intended changes to the confdb data.
The first hooks to be scheduled are the
change-view-hooks. These hooks provide an opportunity for custodian snaps to modify the data before itβs committed or even to reject it entirely. This is useful if the snaps must maintain complex invariants that canβt be expressed as schema types and constraints.Data modification is done with
snapctl set --viewcommand in the usual manner.
To help snaps distinguish between uncommitted and pristine data, a--pristineflag can be used withsnapctl get --view:text$ snapctl get --view :plug-name path.modified <new_data> $ snapctl get --view --pristine :plug-name path.modified <old_data>Itβs also possible to reject the change altogether while providing a reason for the rejection:
text$ snapctl fail <reason>Once all
change-view-hooks have run, thesave-view-run in the same order (i.e., ordered by snap name). If any hook should fail, the transaction would be cancelled. Anysave-view-hooks that had been invoked would be invoked again with pristine data, in order to help custodian snaps maintain consistency among the various versions of the configuration data.Finally, snapd schedules
observe-view-hooks, which is the only type of confdb hook that can be defined by a non-custodian snap. This hook simply notifies snaps plugging views affected by the write that configuration data relevant to them may have changed. The failure of these hooks does not prevent the committing of the changes, as non-custodians are not allowed to reject them. Once those hooks run (successfully or not), the changes are committed by snapd.
File: docs/explanation/how-snaps-work/debuging-startup-performance.md
(interfaces-debuging-startup-performance)=
Debug startup performance
Sometimes a snap is slow to start. This may be related to the helper scripts that are used when creating the snap. To help pinpoint these issues, from snapd 2.36 on it is possible to use snap run --trace-exec to see what helpers are launched and how long they run. This can be useful when debugging why a app start-up is slow. Here is an example:
$ snap run --trace-exec test-snapd-tools.echo hello
hello
Slowest 3 exec calls during snap run:
0.010s snap-update-ns
0.177s /snap/core/6135/usr/lib/snapd/snap-confine
0.086s /usr/lib/snapd/snap-exec
Total time: 0.270sThis example shows that there are 3 helpers executed. First snap-update-ns which takes 0.010s to run, then snap-confine and finally snap-exec. This examples shows the minimum amount of setup that each snap requires. Some snap that use desktop components may have extra scripts running (e.g. via the desktop-helpers component).
At most the slowest 10 execve() calls will be displayed.
File: docs/explanation/how-snaps-work/developing-hotplug-interfaces.md
(interfaces-developing-hotplug-interfaces)=
Developing hotplug interfaces
This page describes how developers can implement an {ref}interface <explanation-interfaces-all-about-interfaces> that supports USB hotplugging,via snapd's built-in hotplug support.
For a general user overview, including enabling this functionality and hotplug interface management, see {ref}Hotplug support .
Hotplug support is currently {ref}under active development <ref-experimental-features_experimental-features>.
Adding hotplug interfaces
The code for a snapd interface has be modified to support hotplug, and subsequently create slots on the system snap, by implementing the following new function:
HotplugDeviceDetected(deviceInfo *hotplug.HotplugDeviceInfo) (*hotplug.ProposedSlot, error)When defined, HotplugDeviceDetected is executed whenever a any hotplug device is connected to the system. The purpose of the function is to decide whether a freshly connected device is relevant for any given interface by returning one of the following:
- if the connected device is not relevant, the function should return
nil, nil - if the connected device is relevant, a definition of the proposed hotplug slot that needs to be created should be returned (see below)
- if the connected device is relevant, but itβs impossible to create a slot definition for it, an error should be returned
It is important to note that this function may be called multiple times as the device connected because, in many cases, the kernel creates various pseudo- and virtual- devices for given physical device. The function should filter out irrelevant calls and only create a single slot definition for the actual device.
The returned slot is a proposed slot because snapdβs hotplug subsystem mediates the creation of the final slot. It may simply update the slot if:
- the device is being reconnected
- the slot previously existed and had connections
Attribute access methods
The above function receives the hotplug.HotplugDeviceInfo structure which contains all the attributes provided by the udev event for the device, and offers the following methods to access them:
Subsystem() stringReturns the name of the kernel subsystem of the device, for example
ttyorsound.DeviceName() stringCorresponds to udevβs
DEVNAMEattribute and provides the actual device path, e.g. /dev/ttyUSB0. This is the path that the interface is generally interested in, as far as confinement rules are concerned.DevicePath() stringCorresponds to udevβs
DEVPATHattribute and provides device path under the sysfs filesystem, e.g. /sys/devices/pci0000:00/0000:00:14.0/usb1/1-2.DeviceType() stringCorresponds to udevβs
DEVTYPEattribute, e.g. disk.Major() stringandMinor() stringCorrespond to udevβs
MAJORandMINORattributes which provide major/minor device numbers (if applicable).Attribute(name string) (string, bool)A generic method to query any udev attribute. It returns false if the attribute is not present in udev event data.
The hotplug.ProposedSlot structure is created by HotplugDeviceDetected in response to a udev event, and may define a name and label for the slot.
However, they are both optional and, in most cases, should be empty. Snapdβs hotplug subsystem will automatically generate them by probing and sanitising some of the well-defined udev attributes, such as model and vendor names. This ensures the resulting name, whether provided by interface code or left empty for snapd to figure out, is unique.
However, the hotplug.ProposedSlot must have some attributes set, and in typical cases, should at least include an attribute that carries the path of the device.
The path attribute can then be read by the following methods of the interface (and others, as applicable), to create respective security profiles for the snap and the given device:
AppArmorConnectedSlotSecCompConnectedPlugSecCompConnectedSlot
Example implementation
The following is one potentially complete implementation of HotplugDeviceDetected:
func (iface *myInterface) HotplugDeviceDetected(di *hotplug.HotplugDeviceInfo) (*hotplug.ProposedSlot, error) {
bus, _ := di.Attribute("ID_BUSβ)
// some arbitrary criteria to filter irrelevant devices out
if di.Subsystem() != "tty" || bus != "usbβ || di.Major() != β123β ) {
return nil, nil
}
slot := hotplug.ProposedSlot{
Attrs: map[string]interface{}{
"path": di.DeviceName(),
},
}
return slot, nil
}When creating filtering logic for the above function, it is useful to inspect udev attributes of the respective device. One way of doing this with the is with the udevadm command. This can be used to report all currently existing devices (eg. udevadm info -e) or run in monitor mode (udevadm monitor -p) to continuously report all udev event, along with their attributed, as devices connected and unplugged.
As an example, the following is an example of output from udevadm for a USB serial port adapter:
P: /devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/ttyUSB0/tty/ttyUSB0
N: ttyUSB0
S: serial/by-id/usb-FTDI_FT232R_USB_UART_AH06W0EQ-if00-port0
S: serial/by-path/pci-0000:02:00.0-usb-0:2.1:1.0-port0
E: DEVLINKS=/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AH06W0EQ-if00-port0 /dev/serial/by-path/pci-0000:02:00.0-usb-0:2.1:1.0-port0
E: DEVNAME=/dev/ttyUSB0
E: DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/ttyUSB0/tty/ttyUSB0
E: ID_BUS=usb
E: ID_MM_CANDIDATE=1
E: ID_MODEL=FT232R_USB_UART
E: ID_MODEL_ENC=FT232R\x20USB\x20UART
E: ID_MODEL_FROM_DATABASE=FT232 Serial (UART) IC
E: ID_MODEL_ID=6001
E: ID_PATH=pci-0000:02:00.0-usb-0:2.1:1.0
E: ID_PATH_TAG=pci-0000_02_00_0-usb-0_2_1_1_0
E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
E: ID_PCI_INTERFACE_FROM_DATABASE=UHCI
E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller
E: ID_REVISION=0600
E: ID_SERIAL=FTDI_FT232R_USB_UART_AH06W0EQ
E: ID_SERIAL_SHORT=AH06W0EQ
E: ID_TYPE=generic
E: ID_USB_DRIVER=ftdi_sio
E: ID_USB_INTERFACES=:ffffff:
E: ID_USB_INTERFACE_NUM=00
E: ID_VENDOR=FTDI
E: ID_VENDOR_ENC=FTDI
E: ID_VENDOR_FROM_DATABASE=Future Technology Devices International, Ltd
E: ID_VENDOR_ID=0403
E: MAJOR=188
E: MINOR=0
E: SUBSYSTEM=tty
E: TAGS=:systemd:
E: USEC_INITIALIZED=415122796440Snapd hardware interfaces, whose slots may appear as part of their {ref}gadget.yaml <reference-development-yaml-schemas-the-gadget-snap> definition, for example, allowing gadget in their allow-installation section of the base declaration, must additionally implement the following function:
HandledByGadget(deviceInfo *HotplugDeviceInfo, slot *snap.SlotInfo) boolThe above function acts as a predicate that should return true if the device described by deviceInfo is the same as the one represented by the given slot. Itβs called by the hotplug subsystem whenever a device is connected, and it receives slot(s) of the given interface defined statically in gadget.yaml.
In the typical cases where gadget slots are defined by means of device paths, the implementation of this method becomes a simple comparison of device path and the path attribute of the slot, for example:
func (iface *myInterface) HandledByGadget(di *hotplug.HotplugDeviceInfo, slot *snap.SlotInfo) bool {
var path string
if err := slot.Attr("path", &path); err != nil {
return false
}
return di.DeviceName() == path
}Hotplug and interface hooks
When a supported device is connected to the system, snapd creates a hotplug slot for its respective interface. If the slot is then connected to a plug, either manually by the user, or via the auto-connect mechanism, the following interface hooks are executed if they exist:
prepare-plug-connect-plug-(for the snap on the plug side)
Similarly, when the device is disconnected and its slot had been connected to a plug, its disconnect interface hooks get executed, eg:
disconnect-plug-
This mechanism can be used by snaps to react to devices appearing/disappearing from their connected plugs. Please refer to the documentation on {ref}Interface Hooks <explanation-interfaces-interface-hooks> for more details on this functionality.
File: docs/explanation/how-snaps-work/home-outside-home.md
(interfaces-home-outside-home)=
Home outside home
The snap daemon (snapd) looks for a user's home directory ($HOME) under /home on the local filesystem. However, from snapd 2.59 onwards, the snap daemon can access an additional location using the homedirs {ref}system option <how-to-guides-manage-snaps-set-system-options>:
sudo snap set system homedirs=<destination-directory>This allows a snap's user data to be stored in a user's home location other under /home.
The following command will permit home directories to be accessible from /remote/users, for instance:
sudo snap set system homedirs=/remote/usersThe new location needs to exist and be accessible, but it can be on a different filesystem or even mounted across a network. The original /home location remains valid but it is no longer a requirement that directories be stored there.
Once set, the homedirs system option can be retrieved with the snap get command:
$ sudo snap get system homedirs
/remote/usersThe homedirs value can be cleared and restored to only /home with the snap unset command:
sudo snap unset system homedirsBind mount home directories
While the homedirs system option should work for the majority users, it's also possible to bind mount an alternative $HOME location to /home to allow other locations to be found by snapd. This process is outlined below.
β A bind mount allows a mounted filesystem to be accessible from more than one location at the filesystem level. This is unlike a hard or symbolic link, for instance, which operate as special additional files that point to a destination.
There are two steps to bind mount a home directory to a different location:
- the bind mount: create the mount point and run the mount command: text
$ sudo mkdir -p /home/$USER $ sudo mount --bind <original-home-location> /home/$USER - edit
/etc/passwd: backuppasswdand edit the home location for your user:The followingtext$ cp /etc/passwd passwd.backup # sudo edit /etc/passwd with your favourite editor $ cat /etc/passwd | grep $USER ubuntu:x:1000:1000:ubuntu,,,:/home/ubuntu:/bin/bashawkcommand can be used to edit/etc/passwd(change OLD_HOME to your old home directory):text$ awk -vold=$"OLD_HOME" -vnew=$"/home/$USER" -F: ' BEGIN {OFS = ":"} \ {sub(old,new,$6);print}' /etc/passwd > passwd.new $ sudo cp passwd.new /etc/passwd
Log out and back in again, and snap will work from the freshly mounted home location. If you run into difficulties, copy the backup passwd file to /etc/passwd.
File: docs/explanation/how-snaps-work/hotplug-support.md
(interfaces-hotplug-support)=
Hotplug support
Hotplug support gives snaps the ability to integrate with Linux's USB hotplugging and Udev device management subsystem. This enables snaps to recognise specific classes of device and create corresponding system slots when a device is connected.
Developers wishing to add hotplug functionality to their own interfaces should see {ref}Developing hotplug interfaces .
Initially, only USB serial port adapters using the {ref}serial-port interface are supported. Other types of device, such as USB cameras, will be added in the future.
In addition to creating a slot, snapd's hotplug support also remembers connections. If the device is unplugged, its slot disappears from the system. When the device is reconnected, its slot and connections are restored.
snapd tracks devices by matching specific udev attributes. This means that even when a device is reconnected with a different udev path, such when it uses a different USB port or gets a non-deterministic enumeration by the kernel, it is still recognised as the old device by snapd and its old connections are restored with updated attributes.
Enabling hotplug
Hotplug is currently an experimental feature that needs to be enabled. First ensure you have a recent snapd installed, either via the core snap or the snapd snap, at least 2.39.
Then enable hotplug support with the following command:
sudo snap set system experimental.hotplug=trueYou will need to either restart snapd (with sudo systemctl restart snapd, for example), or reconnect USB devices to initiate hotplug processing for connected devices.
Hotplug slots
The name of a hotplug slot is typically derived from a deviceβs udev attributes, such as NAME, ID_MODEL_FROM_DATABASE and ID_MODEL. If multiple devices of the same type are connected, a numeric suffix could be added to make their names unique.
For example, a USB serial adapter from Future Technology Devices International (FTDI) with a model name of FT232R_USB_UART, will create the following slot for the system snap when connected:
$ snap interface serial-port
name: serial-port
summary: allows accessing a specific serial port
slots:
- core:ft232rusbuart
$ snap connections system
Interface Plug Slot Notes
[...]
serial-port - :ft232rusbuart -Current limitations
Along with only supporting USB serial port adapters, hotplug is restricted to devices that provide a specific set of udev attributes; name, vendor identifier and/or serial number.
These attributes are needed to identify and track devices. This isnβt normally a problem for branded devices, but it may cause issues with unbranded devices. Without the required attributes, a device cannot be supported and will be ignored when connected.
There is also no easy way to list connected devices and their corresponding slots. This limitation will be addressed in an upcoming snapd release.
File: docs/explanation/how-snaps-work/index.md
myst: html_meta:
(explanation-how-snaps-work-index)=
How snaps work
Understand how snaps update automatically, and use revisions and transactional updates to mange risk and mitigate potential issues.
- {ref}
Refresh awareness <explanation-how-snaps-work-refresh-awareness>: How updates are handled when apps are running. - {ref}
Using channels <explanation-how-snaps-work-channels-and-tracks>: Understanding channels, tracks, risk-levels and branches. - {ref}
Revisions <explanation-how-snaps-work-revisions>: The unique identity for each and every snap version. - {ref}
Transactional updates <explanation-how-snaps-work-transactional-updates>: Update a group of snaps together, or none at all. - {ref}
Snap performance <explanation-how-snaps-work-snap-performance>: How snaps limit any performance overhead.
:hidden:
:titlesonly:
:maxdepth: 2
:glob:
Channels and tracks <channels-and-tracks>
Home locations <home-outside-home>
Snap deltas <snap-deltas>
Snap revisions <revisions>
Snap components <snap-components>
Parallel installs
Refresh awareness <refresh-awareness>
Confdb <confdb-configuration-mechanism>
Validation sets <validation-sets>
Transactional updates
Hotplug support <hotplug-support>
Hotplug development <developing-hotplug-interfaces>
Snap performance <snap-performance>
Startup performance <debuging-startup-performance>
System snap <system-snap-directory>