# Running Snaps Without Systemd: A Manual Approach ## Overview This guide documents how to manually run a simple snap (command-line tool, no services) when systemd is not available. This is an educational exercise to understand snap internals—**this approach is NOT recommended for production use**. ## Prerequisites - A `.snap` file (the snap package you want to run) - `unsquashfs` or `squashfs-tools` installed - Basic understanding of Linux filesystems and environment variables - Root or appropriate permissions for mounting ## Understanding Snap Structure A `.snap` file is a **SquashFS filesystem** containing: - `/meta/snap.yaml` - Snap metadata - Application binaries and data - Libraries and dependencies - Desktop files, icons, etc. The snap expects to be mounted at a specific location (default: `/snap//`). ## Manual Steps to Run a Snap ### Step 1: Extract or Mount the Snap #### Option A: Extract the Snap (Simpler, No Root Required) ```bash # Extract the snap to a directory mkdir -p ~/snaps/extracted unsquashfs -d ~/snaps/extracted/my-snap /path/to/my-snap_1.0_amd64.snap # Note the extraction path SNAP_MOUNT=/home/yourusername/snaps/extracted/my-snap ``` #### Option B: Mount the Snap (More Authentic) ```bash # Create a mount point sudo mkdir -p /snap/my-snap/x1 # Mount the squashfs file sudo mount -t squashfs -o ro,nodev /path/to/my-snap_1.0_amd64.snap /snap/my-snap/x1 # Set mount point SNAP_MOUNT=/snap/my-snap/x1 ``` ### Step 2: Parse the Snap Metadata ```bash # Read the snap.yaml to find the app command cat $SNAP_MOUNT/meta/snap.yaml # Example output: # name: hello # version: 2.10 # apps: # hello: # command: bin/hello ``` ### Step 3: Set Up Required Environment Variables Snaps expect specific environment variables to be set. Create a script or export these: ```bash #!/bin/bash # Basic snap environment variables export SNAP="$SNAP_MOUNT" export SNAP_NAME="my-snap" export SNAP_VERSION="1.0" export SNAP_REVISION="x1" export SNAP_ARCH="amd64" # or your architecture # Data directories (writable areas for the snap) export SNAP_DATA="/var/snap/$SNAP_NAME/$SNAP_REVISION" export SNAP_COMMON="/var/snap/$SNAP_NAME/common" # User-specific data directories export SNAP_USER_DATA="$HOME/snap/$SNAP_NAME/$SNAP_REVISION" export SNAP_USER_COMMON="$HOME/snap/$SNAP_NAME/common" # Instance-related (for regular snaps, same as SNAP_NAME) export SNAP_INSTANCE_NAME="$SNAP_NAME" export SNAP_INSTANCE_KEY="" # Library path for graphics drivers, etc. export SNAP_LIBRARY_PATH="/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl32" # User ID information export SNAP_UID=$(id -u) export SNAP_EUID=$(id -u) # Exposed home directory (uppercase version) export SNAP_REAL_HOME="$HOME" # For classic confinement, preserve TMPDIR # For strict confinement, you might want a snap-specific tmp # export TMPDIR="/tmp/snap.$SNAP_NAME" ``` ### Step 4: Create Data Directories ```bash # Create system-wide data directories (may require sudo) sudo mkdir -p /var/snap/$SNAP_NAME/$SNAP_REVISION sudo mkdir -p /var/snap/$SNAP_NAME/common # Create user data directories mkdir -p $HOME/snap/$SNAP_NAME/$SNAP_REVISION mkdir -p $HOME/snap/$SNAP_NAME/common ``` ### Step 5: Run the Application ```bash # Source the environment (or use the script above) source /path/to/snap-env.sh # Execute the command from snap.yaml # If the command is "bin/hello", then: $SNAP/bin/hello [arguments] # Or more generally: cd $SNAP ./path/to/command ``` ### Complete Example Script Here's a complete script to run a simple snap: ```bash #!/bin/bash set -e # Configuration SNAP_FILE="/path/to/my-snap_1.0_amd64.snap" SNAP_NAME="my-snap" SNAP_REVISION="x1" EXTRACT_DIR="$HOME/snaps/extracted" MOUNT_DIR="$EXTRACT_DIR/$SNAP_NAME" # Step 1: Extract the snap echo "Extracting snap..." mkdir -p "$EXTRACT_DIR" unsquashfs -f -d "$MOUNT_DIR" "$SNAP_FILE" # Step 2: Parse snap.yaml to find the command echo "Reading snap metadata..." COMMAND=$(grep -A 2 "^ $SNAP_NAME:" "$MOUNT_DIR/meta/snap.yaml" | grep "command:" | awk '{print $2}') VERSION=$(grep "^version:" "$MOUNT_DIR/meta/snap.yaml" | awk '{print $2}') echo "Found command: $COMMAND" echo "Version: $VERSION" # Step 3: Set up environment export SNAP="$MOUNT_DIR" export SNAP_NAME="$SNAP_NAME" export SNAP_VERSION="$VERSION" export SNAP_REVISION="$SNAP_REVISION" export SNAP_ARCH=$(uname -m) export SNAP_DATA="/var/snap/$SNAP_NAME/$SNAP_REVISION" export SNAP_COMMON="/var/snap/$SNAP_NAME/common" export SNAP_USER_DATA="$HOME/snap/$SNAP_NAME/$SNAP_REVISION" export SNAP_USER_COMMON="$HOME/snap/$SNAP_NAME/common" export SNAP_INSTANCE_NAME="$SNAP_NAME" export SNAP_INSTANCE_KEY="" export SNAP_LIBRARY_PATH="/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl32" export SNAP_UID=$(id -u) export SNAP_EUID=$(id -u) export SNAP_REAL_HOME="$HOME" # Step 4: Create data directories echo "Creating data directories..." mkdir -p "$SNAP_USER_DATA" mkdir -p "$SNAP_USER_COMMON" # For system directories, you might need sudo: # sudo mkdir -p "$SNAP_DATA" # sudo mkdir -p "$SNAP_COMMON" # Step 5: Run the application echo "Running snap application..." cd "$SNAP" exec ./$COMMAND "$@" ``` ## What Will Be Missing Running snaps manually without the snapd ecosystem means you lose **significant functionality**: ### 1. **No Confinement/Security** - **AppArmor profiles**: Not loaded, snap runs without MAC (Mandatory Access Control) - **Seccomp filters**: Not applied, snap has access to all syscalls - **Device cgroup**: Not configured, snap can access all devices - **Mount namespaces**: Not created, snap sees the entire host filesystem - **User namespaces**: Not used for privilege separation **Impact**: The snap runs with **no security boundaries**. A malicious snap could compromise your entire system. ### 2. **No Automatic Updates** - No connection to Snap Store - No background update checks - No automatic snap refreshes - Must manually download and replace snap files **Impact**: You must manually track and install updates. Security patches won't be applied automatically. ### 3. **No Interface Connections** - No access to plugs and slots system - Cannot connect to system resources like: - Network Manager - Audio (PulseAudio/PipeWire) - Bluetooth - Hardware devices (cameras, printers, etc.) - No automatic permission management **Impact**: The snap may fail if it expects interface connections. Many features won't work. ### 4. **No Services** - Cannot run systemd services defined in the snap - No daemon management (start/stop/restart) - No socket activation - No timer-based execution - No automatic service restarts on failure **Impact**: Only command-line applications can be run. Background services, daemons, and scheduled tasks won't work. ### 5. **No Mount Management** - No dynamic mount namespace setup - Layouts defined in snap.yaml won't be applied - No bind mounts for content sharing - No per-snap `/tmp` or other private mounts **Impact**: Applications expecting specific filesystem layouts will fail. Content interfaces won't work. ### 6. **No State Management** - No tracking of installed snaps - No revision management - No rollback capability - No state.json or other snapd state files **Impact**: Cannot track what's installed, cannot rollback to previous versions. ### 7. **No snapctl Integration** #### What is snapctl? `snapctl` is a command-line tool that snaps use to communicate with the snapd daemon. It's the bridge between a snap application and the snap system's runtime services. When a snap executes, snapd sets up environment variables (like `$SNAP_COOKIE`) that allow `snapctl` to authenticate and communicate with the snapd daemon over a Unix socket. #### Why is it needed? Snaps use `snapctl` for several critical functions: 1. **Configuration Management** - `snapctl get ` - Read configuration values - `snapctl set =` - Write configuration values - Configuration is stored by snapd and persists across snap updates 2. **Service Management** - `snapctl start/stop/restart ` - Control snap services - Query service status - Coordinate between multiple services in a snap 3. **Hook Execution Context** - Hooks (install, configure, pre-refresh, post-refresh, etc.) run with snapctl access - Hooks use snapctl to validate configuration, set defaults, perform migrations - Essential for snap lifecycle management 4. **Health Checks** - `snapctl set-health` - Report snap health status to snapd - Used by monitoring and management tools 5. **Connection Information** - Query which interfaces are connected - Get connection attributes and parameters 6. **System Information** - Query snap revision, version, architecture - Get information about other installed snaps #### The Problem Without snapd running, `snapctl` has nowhere to connect. The command will fail with: ``` error: snapctl: cannot invoke snapctl operation commands from outside of a snap ``` Or if it tries to connect: ``` error: cannot communicate with server: connection refused ``` Many "enterprise" or complex snaps (like charmcraft, lxd, multipass, etc.) heavily rely on snapctl for configuration management. They expect to be able to read/write configuration through snapctl, which breaks completely without snapd. #### Detection Mechanism Snaps detect they're running under snapd via: - `$SNAP_COOKIE` environment variable (authentication token) - `$SNAP_CONTEXT` environment variable (legacy) - Unix socket at `/run/snapd.socket` or `/run/snapd-snap.socket` Libraries like `snaphelpers` (Python) automatically try to use snapctl and fail when it's unavailable. #### Workarounds **1. Mock Configuration Files** Create a fake configuration that the snap might read: ```bash # Some snaps fall back to reading config files mkdir -p "$SNAP_DATA" cat > "$SNAP_DATA/config.json" << EOF { "key": "value" } EOF ``` **Limitation**: Only works if the snap has fallback code paths. Most don't. **2. Stub snapctl Command** Create a fake `snapctl` that returns empty/default values: ```bash #!/bin/bash # Fake snapctl that returns empty config case "$1" in get) echo "{}" # Return empty JSON config ;; set) exit 0 # Pretend set succeeded ;; *) exit 1 ;; esac ``` Add it to PATH before running the snap: ```bash export PATH="/path/to/fake-snapctl:$PATH" ``` **Limitation**: - Very brittle, depends on what the snap expects - Doesn't work for snaps that validate responses - Can cause unexpected behavior if snap makes decisions based on config **3. Patch the Snap** For Python-based snaps using `snaphelpers`: ```bash # Extract the snap unsquashfs -d my-snap my-snap.snap # Modify the code to skip snapctl # Example: patch snaphelpers to not use snapctl vim my-snap/lib/python3.12/site-packages/snaphelpers/_conf.py # Repack (requires snap-pack tool, part of snapd) snap pack my-snap/ ``` **Limitation**: - Requires understanding the snap's internals - Must repack the snap (needs snapd tools!) - Breaks snap signatures - Must repeat for every snap update **4. Run with Environment Variables** Some snaps check for specific environment variables and disable features: ```bash # Tell snap not to use snapctl (if supported) export SNAP_TESTING=1 export IGNORE_SNAP_CONFIG=1 # Run the snap ./my-snap ``` **Limitation**: Only works if the snap explicitly supports these flags (rare). **5. Use Classic Confinement Snaps** Classic confinement snaps sometimes have less dependency on snapctl: ```bash # Classic snaps might work better without snapd # But classic snaps typically need snapd to install anyway ``` **Limitation**: Classic snaps still often use snapctl. #### Real-World Example: charmcraft Charmcraft (as seen in the error above) uses the `craft-application` framework which uses `snaphelpers` for configuration. On startup, it tries: ```python debug_mode = self.services.get("config").get("debug") ``` This internally calls: ```python snapctl.run("get", "-d", "debug") ``` Which fails immediately without snapd. **Workaround**: Patch craft-application or set environment to skip config: ```bash # This doesn't actually work for charmcraft, just illustrative export CRAFT_DEBUG=0 # Try to bypass config system ``` Unfortunately, charmcraft doesn't have a clean way to disable snapctl usage. #### Bottom Line **There is no general workaround for snapctl.** Each snap that uses it must be handled individually: - Simple snaps that only use snapctl for optional features might work - Complex snaps that depend on snapctl for core functionality will fail - The only real solution is to run snapd If you cannot run snapd, you must: 1. Choose snaps that don't use snapctl (rare, mostly simple CLI tools) 2. Patch the snap to remove snapctl dependencies (difficult) 3. Use alternative packaging formats (AppImage, static binaries, containers) **Snaps that commonly use snapctl** (avoid these without snapd): - charmcraft, lxd, multipass, microk8s - Most "platform" or "enterprise" snaps - Snaps with complex configuration - Snaps with services and hooks **Snaps that might work** (less snapctl usage): - Simple CLI tools: shellcheck, yq, jq-alike tools - Static binaries wrapped as snaps - Snaps with no configuration or hooks ### 8. **No Command Chain Support** - Command chains defined in snap.yaml won't execute - No wrapper script execution - No environment setup by command chains **Impact**: Some snaps rely on command chains for initialization. These will fail silently. ### 9. **No User Data Migration** - No automatic migration of user data between revisions - No XDG directory symlink management - No exposed home directory setup **Impact**: User data management is manual and error-prone. ### 10. **No Desktop Integration** - Desktop files not registered with system - Application won't appear in application menus - Icons not installed to system locations - MIME type associations not created - URL handlers not registered **Impact**: The application won't integrate with your desktop environment. ### 11. **No Parallel Installs** - Cannot install multiple versions or instances - No instance key support - Name conflicts if multiple versions exist **Impact**: Limited to one "installation" per snap name. ### 12. **No Graphics Acceleration** - No automatic graphics driver mounting - NVIDIA/AMD drivers not available inside snap - No access to `/var/lib/snapd/lib/gl` **Impact**: Graphics applications may fail or run with software rendering only. ### 13. **No Snap-Confine Isolation** - No PID 1 mount namespace tricks - No `/snap` directory bind mounts - No namespace file creation in `/run/snapd/ns/` - No cgroup tracking for process management **Impact**: Multiple issues with process isolation and management. ### 14. **No Seed/Preseeding Support** - Cannot participate in system seeding - Not part of system image creation - Cannot be part of Ubuntu Core images **Impact**: Cannot be used in embedded or appliance scenarios. ### 15. **No Assertions/Signing Verification** - Snap signatures not verified - Snap declarations not checked - No trust chain validation - No revision authority verification **Impact**: Cannot verify snap authenticity or integrity. Could run tampered snaps. ## Additional Caveats ### Environment Variables The snap may expect additional environment variables: - `$SNAP_COOKIE` - For snapctl communication (won't work) - `$SNAP_CONTEXT` - Legacy snapctl communication (won't work) - `$XDG_RUNTIME_DIR` - Should point to snap-specific runtime dir - `$HOME` - Typically overridden to `$SNAP_USER_DATA` for strict confinement ### Library Dependencies - The snap may have dependencies on system libraries - Without proper `SNAP_LIBRARY_PATH` setup, libraries won't be found - Classic confinement snaps expect host libraries ### Path Issues - The snap may expect to run from specific mount points (`/snap/...`) - Some snaps hardcode paths assuming standard snap mount locations - Environment variable expansion (`$SNAP`, etc.) must work correctly ### Permissions - File permissions inside the snap may not match your user - SELinux/AppArmor contexts won't be correct - Extended attributes may cause issues ## Use Cases for Manual Snap Running Despite all the limitations, manual snap running might be useful for: 1. **Educational purposes**: Understanding how snaps work internally 2. **Debugging**: Examining snap contents without installing 3. **Offline/air-gapped systems**: Where snapd cannot be installed 4. **Emergency recovery**: Running tools from snaps when system is broken 5. **Embedded systems**: Very constrained environments without systemd 6. **Research**: Analyzing snap behavior and structure ## Alternative: Snap-Confine Without Snapd If you need **some** confinement without full snapd, you could theoretically: 1. Compile `snap-confine` standalone 2. Write AppArmor profiles manually 3. Create seccomp filters manually 4. Set up mount namespaces manually 5. Configure device cgroups manually This is **extremely complex** and not recommended. You're essentially reimplementing snapd. ## Recommended Alternatives If you're in a restricted environment, consider these alternatives: ### 1. Static Binaries - Use statically compiled binaries instead - Tools like Go produce static binaries - No runtime dependencies ### 2. AppImage - Self-contained application bundles - Don't require systemd - Mount via FUSE (which has its own requirements) ### 3. Flatpak - Also uses namespaces but has different requirements - Can run without systemd (with limitations) ### 4. Docker/Podman Containers - Better isolation without snapd - More mature tooling for constrained environments ### 5. Traditional Packages - Distribution packages (deb, rpm) - Follows system conventions - Better integration without systemd ## Conclusion Running snaps without snapd and systemd is possible for **very simple, statically-linked command-line tools**, but you lose: - All security features (AppArmor, seccomp, namespaces) - All management features (updates, rollback, configuration) - All integration features (interfaces, desktop integration, services) - All snapd-specific functionality The effort required grows exponentially with snap complexity. For anything beyond basic CLI tools, the missing functionality makes this approach impractical. **Bottom line**: If you can't install systemd, snaps are not the right packaging format for your use case. Consider the alternatives listed above. ## References - [Snapd Source Code](https://github.com/canonical/snapd) - [SquashFS Documentation](https://www.kernel.org/doc/Documentation/filesystems/squashfs.txt) - Key snapd components: - `snap-confine`: Confinement helper (requires compiled binary) - `snap-exec`: Execution helper inside confinement - `snap-update-ns`: Mount namespace manager - `snapd` daemon: State management and API server ## Warning ⚠️ **This guide is for educational purposes only.** Running snaps without proper confinement is a security risk. The snap has full access to your system, just like any other executable. Only run snaps from trusted sources, and never use this approach in production environments.