nativeapps

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Sep 22, 2026 License: MIT Imports: 26 Imported by: 1

README

Floe Native Apps

A private graphical runtime for applications on your Linux host.

Prepare Xpra, a headless X server, and their native dependencies through one Go API. Keep applications on the host, with their own files, identity, and permissions.

Source checks CodeQL Go Reference

Get started · Integration · Platforms · Contributing · Security

What it does

Floe Native Apps supplies the optional native graphics layer behind a host application launcher. A server can start without a desktop environment or a monitor. The SDK acquires a pinned component set into a private directory, verifies it, and proves that it can render a window and receive input before making it available to the product.

  • Private installation: no root password, system package installation, global loader configuration, or changes to the host's software sources.
  • Verified acquisition: exact archive sizes and SHA-256 digests, bounded extraction, contained links, and atomic activation after a graphical check.
  • Recoverable preparation: observable progress, owner-scoped cancellation, idempotent requests, explicit interruption after restart, and offline delivery.
  • Native execution: the user's applications run directly on Linux. No production container or virtual machine sits between the application and host.

This is an embeddable preparation SDK. The consuming product supplies the app catalog, authorization, session lifecycle, viewer, and user interface. Redeven is one consumer.

flowchart LR
    Catalog["Pinned publisher archives"] --> Verify["Download & verify"]
    Offline["Offline ZIP"] --> Verify
    Verify --> Stage["Private staging directory"]
    Stage --> Check["Launch · pixels · input"]
    Check --> Ready["Atomic activation"]

Get started

Use Go 1.27.1, aligned with Redeven. Install the released module:

go get github.com/floegence/floe-native-apps@v0.2.0

Select a supported package and create one long-lived manager for an absolute, private state directory. Construction does not download or execute components. Start preparation only after the host product admits the user's action.

pkg, err := nativeapps.NativePackage()
if err != nil {
    return err // Unsupported hosts must not offer Linux preparation.
}
manager, err := nativeapps.New(stateRoot, pkg, nil)
if err != nil {
    return err
}
defer manager.Close()

// Derive owner from the authenticated session. Retain requestID across retries.
status, err := manager.Start(owner, requestID, "download", 0)
if err != nil {
    return err
}
_ = status // Observe progress with Watch and Snapshot until ready or failure.

Import the package as nativeapps "github.com/floegence/floe-native-apps". The compiling lifecycle example demonstrates subscription, terminal states, context cancellation, and tool resolution. In a service, keep the manager alive across HTTP requests; closing it cancels active preparation.

To try the standalone qualification tool on a Linux host:

go run ./cmd/floe-native-apps -state "$HOME/.local/share/floe-native-apps"

The command reports JSON progress, performs the real graphical check, and prints the installed directory when ready. It prepares components; it does not expose a remote desktop or launch a user's application.

Integration contract

Owner Responsibilities
This SDK Fixed catalog, downloads, verification, extraction, private support environment, durable preparation, graphical self-check
Host product User admission, authentication, state-directory ownership, application metadata, private display/session, application launch, viewer and window lifetime
Operating system Process identity, filesystem permissions, network policy, executable mounts, and security policy

Observe current state. Watch provides coalesced invalidations, not an event log. Subscribe, read Snapshot(owner), and read it again after each notification. Unsubscribing or losing an observer does not cancel preparation. ready permits Directory and ResolveTools; failed, cancelled, and interrupted require an explicit new user action to retry. No partial installation is activated.

Preserve request identity. Retry an uncertain Start response with the same owner, request ID, source, and upload size. A competing operation returns ErrBusy. Only the authenticated operation owner may upload or cancel. The consumer must authorize reads and writes before calling the SDK; an owner string is an identity binding, not an authentication mechanism.

Keep support libraries out of host applications. Use the complete toolset from ResolveTools and Tools.Environment for support processes. Before a GIO launch, restore the saved FLOE_NATIVE_APPLICATION_ENV map onto the application's launch context and remove that variable and FLOE_NATIVE_ROOT. Retain the session's private display and D-Bus addresses. Never export the private loader path to user applications or substitute the support Python for their interpreter.

Keep the trust boundary closed. Use the checked-in catalog selected through NativePackage or ForPlatform. Do not accept executable paths, artifact URLs, hashes, custom catalogs, or package identities from renderer/client input. Pass nil as the validator to retain the built-in graphical check. A custom validator is a trusted host integration hook, not a client option.

Disable unused audio completely. Append XpraNoAudioArgs() when starting a silent Xpra 6.x graphics session. It disables the audio subsystem, PulseAudio, speaker, and microphone support. --speaker=off and --microphone=off only start muted; they still initialize audio codecs and can make every window inventory query wait five seconds when an audio backend is unavailable. The graphical self-check uses the same options and requires two inventory queries to complete within three seconds each, in addition to proving decoded pixels and input. Consumers retain their own authentication, private display, process lifetime, transport, and actual-window readiness checks.

Offline delivery

Hosts that supply another reviewed archive catalog can use the public github.com/floegence/floe-native-apps/artifactcache package. This is the same acquisition implementation used by native component preparation and offline bundles. It downloads original publisher bytes without extracting or executing them, independently of the downloading machine's OS and architecture.

result, err := artifactcache.Acquire(ctx, privateCacheDirectory, artifactcache.Spec{
    URL: pinnedPublisherURL,
    SHA256: pinnedSHA256,
    SizeBytes: pinnedSize,
}, artifactcache.Options{
    OnProgress: func(p artifactcache.Progress) error {
        // Absolute byte counts; checking, downloading, or verifying.
        return report(p)
    },
})
// Use result.Path only after success. result.FromCache identifies a verified hit.

The host chooses the catalog, absolute private cache directory and user consent before calling Acquire; renderer-supplied URLs, digests and paths are not a trusted catalog. Cache filenames are exact SHA-256 digests. Every reuse checks length and digest without network access. Verify validates an existing regular file without modifying it; integrity errors match artifactcache.ErrIntegrity, while cancellation and filesystem errors retain their normal classifications. HTTP redirects cannot downgrade HTTPS. Invalid or incomplete downloads never replace a cache entry. Concurrent acquisitions use independent temporary files and may repeat a download; cancellation removes only the current call's staging file. The host owns cache retention and must not remove entries while readers use them. This API grants neither permission to install nor to execute an archive.

Acquire original publisher archives on Linux, macOS, or Windows for a Linux target. Acquisition does not execute those components on the downloading machine.

go run ./cmd/floe-native-apps \
  -arch arm64 \
  -state /absolute/private/cache \
  -bundle /absolute/native-arm64.zip

On the receiving host:

  1. Call Start(owner, requestID, "upload", actualZIPSize).
  2. Send ordered chunks of at most 256 KiB through WriteChunk using the returned operation ID. An identical replay is accepted; gaps and conflicting replays are rejected. Resume an interrupted connection from ReceivedBytes.
  3. Call CompleteUpload. The host verifies every original archive and performs the same installation and graphical qualification as a direct download.

A process restart reports an interrupted operation and discards incomplete staging; begin a new admitted operation. An already activated package remains available. Redistributing the ZIP carries the components' license obligations.

Platforms and requirements

Capability Platforms
Install and execute native graphics Linux amd64, Linux arm64; glibc and musl hosts
Acquire an offline ZIP Linux, macOS, Windows; portable CLI builds checked for amd64 and arm64
macOS application capture and control Owned by the consuming product's macOS integration

The pinned stack uses Alpine 3.23 packages, Xpra 6.2.2, and Xpra HTML5 v20. Each architecture includes its own musl loader and library closure. It does not replace the host's libc. Applications still use their own installed dependencies.

The host needs an executable writable state filesystem, a compatible Linux kernel, /bin/sh and basic shell utilities, ordinary process/Unix socket permissions, and access to the catalog's HTTPS publishers (or an offline ZIP). A physical monitor, desktop session, GPU, and system Xpra installation are not required. X11 applications are the target; native Wayland-only applications, GPU/DRM capture, and arbitrary third-party application compatibility are outside this SDK's guarantee. SELinux, AppArmor, and other host policies remain in force; a denied operation fails without changing policy or escalating privileges.

Qualification evidence

The component set shipped since v0.1.2 passed GIO application launch, a live private D-Bus connection, window discovery, decoded pixels, and input delivery:

Environment Architecture Evidence scope
Ubuntu 22.04; Debian 11 arm64; amd64 respectively Native hosts
Debian 13; Alpine 3.23 amd64 and arm64 Clean unprivileged userspace fixtures
Arch Linux; Rocky Linux 9; AlmaLinux 9; RHEL UBI 9 amd64 Clean unprivileged userspace fixtures

RHEL UBI qualifies the userspace image, not a subscribed RHEL host, its kernel, or every SELinux policy. The release qualification workflow repeats fresh installation on native GitHub Linux runners for both architectures and runs the clean distribution fixtures. Its logs record the actual images and results. Containers are test fixtures only.

Development and maintenance

./scripts/check.sh
# Network-backed security checks, also scheduled and required for releases:
go tool govulncheck ./...
go tool govulncheck golang.org/x/vuln/cmd/govulncheck github.com/rhysd/actionlint/cmd/actionlint

The source gate checks formatting, module tidiness and checksums, vet, race tests, workflow syntax, Python/shell syntax, and six portable build targets. Tests cover malicious archives, integrity, ownership, idempotency, cancellation, restart, persistence failure, and activation. The runtime uses the Go standard library; go.mod and go.sum additionally pin maintained development tools and their transitive dependencies. Native archive integrity is separately pinned in catalog.json.

CodeQL scans Go, Python, and GitHub Actions daily and on demand with extended security queries. Dependency vulnerability checks run daily and before release. Dependabot proposes weekly dependency and action updates; updates require review and passing checks. See CONTRIBUTING.md for release criteria, platform qualification, catalog changes, and commit attribution.

License and security

The Go SDK, installer, and repository code are MIT-licensed. Downloaded components retain their original licenses; each catalog entry records its publisher URL, SHA-256, license, and source location. This repository releases source and a catalog, not a combined third-party binary distribution. If you redistribute archives or an installed stack, satisfy the relevant licenses, including applicable source availability obligations. Retained original license files and catalog metadata are evidence, not a substitute for those obligations.

Report vulnerabilities through GitHub's private reporting form. See SECURITY.md for the security boundary and supported versions.

Documentation

Overview

Package nativeapps prepares a private native graphical stack without changing system packages. Callers own user consent, authorization, and application launch.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrBusy = errors.New("native preparation is already running")
View Source
var ErrForbidden = errors.New("native preparation belongs to another caller")
View Source
var ErrInvalid = errors.New("invalid native preparation request")
View Source
var ErrUnsupported = errors.New("native graphical components are unavailable for this platform")

Functions

func SelfTest

func SelfTest(parent context.Context, root string) (result error)

SelfTest creates only its own virtual display and transient GTK fixture. A successful check proves actual window discovery, rendered pixels, and input; importing a module or starting a process alone is not readiness evidence.

func WriteBundle

func WriteBundle(ctx context.Context, pkg Package, cacheRoot string, output io.Writer, progress func(int64)) error

WriteBundle acquires the exact catalog archives for an offline host. It neither extracts nor executes them; the destination host verifies every original byte. Callers must publish the output only after this function returns successfully.

func XpraNoAudioArgs added in v0.2.1

func XpraNoAudioArgs() []string

XpraNoAudioArgs disables audio support for a silent Xpra 6.x graphics session. Muting the speaker and microphone with "off" still initializes audio codecs; a missing audio backend can then delay every server inventory request. Session ownership, transport, and all other feature choices remain with the host.

Types

type Artifact

type Artifact struct {
	Name    string `json:"name"`
	URL     string `json:"url"`
	SHA256  string `json:"sha256"`
	Size    int64  `json:"size_bytes"`
	Format  string `json:"format"`
	License string `json:"license"`
	Source  string `json:"source"`
}

type Manager

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

func New

func New(root string, pkg Package, validate func(context.Context, string) error) (*Manager, error)

New requires a private state root. Creating a manager never downloads or starts native processes. The optional validator is useful for host-owned qualification; production callers use the built-in graphical self-check by passing nil.

Example

This example compiles in source checks but performs no network operations in tests. A service should retain its manager for the service's entire lifetime.

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"time"

	nativeapps "github.com/floegence/floe-native-apps"
)

func main() {
	prepare := func(ctx context.Context, stateRoot, owner, requestID string) (nativeapps.Tools, error) {
		pkg, err := nativeapps.NativePackage()
		if err != nil {
			return nativeapps.Tools{}, err
		}
		manager, err := nativeapps.New(stateRoot, pkg, nil)
		if err != nil {
			return nativeapps.Tools{}, err
		}
		defer manager.Close()
		changes, stop := manager.Watch()
		defer stop()
		if _, err := manager.Start(owner, requestID, "download", 0); err != nil {
			return nativeapps.Tools{}, err
		}
		for {
			status := manager.Snapshot(owner)
			if status.State == "ready" {
				root, err := manager.Directory()
				if err != nil {
					return nativeapps.Tools{}, err
				}
				return nativeapps.ResolveTools(root)
			}
			if !status.Active() {
				return nativeapps.Tools{}, fmt.Errorf("preparation %s: %s", status.State, status.ErrorCode)
			}
			select {
			case <-ctx.Done():
				return nativeapps.Tools{}, ctx.Err()
			case <-changes:
			}
		}
	}
	cache, err := os.UserCacheDir()
	if err != nil {
		log.Fatal(err)
	}
	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute)
	defer cancel()
	// A real product supplies an authenticated owner and a stable request ID.
	tools, err := prepare(ctx, filepath.Join(cache, "floe-native-apps"), "local-user", "prepare-1")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(tools.Xpra)
}

func (*Manager) Cancel

func (m *Manager) Cancel(owner, id string) (Status, error)

func (*Manager) Close

func (m *Manager) Close()

func (*Manager) CompleteUpload

func (m *Manager) CompleteUpload(owner, id string) (Status, error)

func (*Manager) Directory

func (m *Manager) Directory() (string, error)

func (*Manager) Package

func (m *Manager) Package() Package

func (*Manager) Snapshot

func (m *Manager) Snapshot(owner string) Status

func (*Manager) Start

func (m *Manager) Start(owner, requestID, source string, uploadSize int64) (Status, error)

func (*Manager) Watch

func (m *Manager) Watch() (<-chan struct{}, func())

func (*Manager) WriteChunk

func (m *Manager) WriteChunk(owner, id string, offset int64, data []byte) (Status, error)

type Package

type Package struct {
	ID             string     `json:"id"`
	Architecture   string     `json:"architecture"`
	SizeBytes      int64      `json:"size_bytes"`
	InstalledBytes int64      `json:"installed_bytes"`
	Artifacts      []Artifact `json:"artifacts"`
}

func ForPlatform

func ForPlatform(platform, architecture string) (Package, error)

func NativePackage

func NativePackage() (Package, error)

func (Package) Digest

func (p Package) Digest() string

func (Package) Validate

func (p Package) Validate() error

type Status

type Status struct {
	State         string `json:"state"`
	OperationID   string `json:"operation_id,omitempty"`
	ReceivedBytes int64  `json:"received_bytes"`
	ExpectedBytes int64  `json:"expected_bytes"`
	ErrorCode     string `json:"error_code,omitempty"`
	CanCancel     bool   `json:"can_cancel"`
}

func (Status) Active

func (s Status) Active() bool

type Tools

type Tools struct{ Root, Xpra, Python, Xvfb, Xauth, DBus, HTML string }

func ResolveTools

func ResolveTools(root string) (Tools, error)

func (Tools) Environment

func (t Tools) Environment(base []string) []string

Environment scopes support-tool overrides. The GIO launcher must restore this small saved map on its AppLaunchContext before starting the user's program. Display and D-Bus addresses are deliberately not restored: those belong to the caller's private graphical session.

Directories

Path Synopsis
Package artifactcache acquires pinned publisher archives into a private, content-addressed cache.
Package artifactcache acquires pinned publisher archives into a private, content-addressed cache.
cmd
floe-native-apps command
floe-native-apps is an explicit qualification and offline acquisition tool.
floe-native-apps is an explicit qualification and offline acquisition tool.

Jump to

Keyboard shortcuts

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