Files
mesh-catalog/modules/snapd/cmd/snapd-tools/main.go
T
jochen db297e8bdd snapd: tools for the snaps, the package blocked on the mesh's AUR repository (hq to-be 42 phase 2.9)
snapd is not in the official repositories, and ADR 0205's archive does not
fit a daemon with setuid helpers, so the module declares nothing until
research 027 question 1 (P2) builds it into the mesh's own repository. Not
even its units: on the laptop they do not exist, and the module would fail
there.

Ten Go tools that work wherever snapd is installed and say so where it is
not: status (with AppArmor's absence from the kernel named), list, info,
updates, disk usage with the disabled revisions' share, services, changes,
and install, remove and refresh through sudo -n with --no-wait, answering
snapd's change id.
2026-10-04 13:02:23 +02:00

154 lines
5.4 KiB
Go

// The snapd module's tools (novox/hq research 027/02, 026/05): the snaps on this machine, their
// revisions and the space they take, the store's pending updates, snapd's changes, and installing,
// removing and refreshing a snap. A Go bundle the node's runtime launches over stdio (ADR 0188,
// ADR 0193); it runs as the operator account, and an act goes through `sudo -n`.
//
// The module installs nothing: snapd is not in the distribution's official repositories (README).
package main
import (
"fmt"
"os"
stdio "git.novox.be/novox/mesh-sdk/go"
)
var providedBy = map[string]string{
"snap": "snapd is not installed; it is not in the official repositories, and this module does not install it (see its README)",
"systemctl": "the systemd package",
}
func main() {
if err := stdio.Serve("", tools()); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
var nameArg = map[string]any{"type": "string", "description": "the snap's name"}
func tools() []stdio.Tool {
return []stdio.Tool{
{
Name: "snapd_status",
Description: "Whether snapd is here and working: its version, its units (socket, service, AppArmor loader), " +
"whether the kernel runs AppArmor (without it strict snaps are not confined), and whether /snap exists " +
"for classic snaps. (r)",
Input: map[string]any{},
Run: func(map[string]any) (any, error) { return Status() },
},
{
Name: "snapd_list",
Description: "Every installed snap and revision: version, revision, channel tracked, publisher, notes, and " +
"whether the revision is disabled (kept for rollback, taking space). (r)",
Input: map[string]any{},
Run: func(map[string]any) (any, error) { return List() },
},
{
Name: "snapd_info",
Description: "One snap as the store and snapd describe it: summary, publisher, licence, commands, tracking, and the channels with their versions. (r)",
Input: map[string]any{"name": nameArg},
Run: func(args map[string]any) (any, error) {
name, err := snapName(args)
if err != nil {
return nil, err
}
return Info(name)
},
},
{
Name: "snapd_updates",
Description: "What a refresh would change: each snap with an update, its new version, revision and size. (r)",
Input: map[string]any{},
Run: func(map[string]any) (any, error) { return Updates() },
},
{
Name: "snapd_disk_usage",
Description: "The space each snap's revisions take in /var/lib/snapd/snaps, which of it is disabled revisions " +
"kept for rollback, and the total. (r)",
Input: map[string]any{},
Run: func(map[string]any) (any, error) { return DiskUsage() },
},
{
Name: "snapd_services",
Description: "The services installed snaps provide, with whether each starts at boot and runs now. (r)",
Input: map[string]any{},
Run: func(map[string]any) (any, error) { return Services() },
},
{
Name: "snapd_changes",
Description: "snapd's recent changes (installs, refreshes, removals): id, status, when, summary; or, with id, " +
"one change's tasks. An act answers the change id to follow here. (r)",
Input: map[string]any{"id": map[string]any{"type": "string", "description": "one change's id, for its tasks"}},
Run: func(args map[string]any) (any, error) {
id, err := optText(args, "id", "")
if err != nil {
return nil, err
}
return Changes(id)
},
},
{
Name: "snapd_install",
Description: "Install a snap from the store, optionally from a channel and in classic confinement. snapd " +
"carries it out in the background; the answer is the change id to follow with snapd_changes. (a)",
Input: map[string]any{
"name": nameArg,
"channel": map[string]any{"type": "string", "description": "a channel such as latest/stable or 3.x/edge"},
"classic": map[string]any{"type": "boolean", "description": "classic confinement, for a snap that asks for it"},
},
Run: func(args map[string]any) (any, error) {
name, err := snapName(args)
if err != nil {
return nil, err
}
channel, err := optText(args, "channel", "")
if err != nil {
return nil, err
}
classic, err := optFlag(args, "classic", false)
if err != nil {
return nil, err
}
return Install(name, channel, classic)
},
},
{
Name: "snapd_remove",
Description: "Remove a snap, keeping a snapshot of its data unless purge is set. Answers the change id. (a)",
Input: map[string]any{
"name": nameArg,
"purge": map[string]any{"type": "boolean", "description": "remove its data too, without a snapshot"},
},
Run: func(args map[string]any) (any, error) {
name, err := snapName(args)
if err != nil {
return nil, err
}
purge, err := optFlag(args, "purge", false)
if err != nil {
return nil, err
}
return Remove(name, purge)
},
},
{
Name: "snapd_refresh",
Description: "Refresh one snap, or every snap when no name is given. Answers the change id, or that nothing needed it. (a)",
Input: map[string]any{"name": map[string]any{"type": "string", "description": "the snap to refresh (default all)"}},
Run: func(args map[string]any) (any, error) {
name, err := optText(args, "name", "")
if err != nil {
return nil, err
}
if name != "" {
if err := checkSnapName(name); err != nil {
return nil, err
}
}
return Refresh(name)
},
},
}
}