Profile
Back to NewsBack
GitHub Trending 12 min
Reader Mode
Artexis10/endstate: Open-source engine behind Endstate: reinstall your apps and restore your settings on a fresh machine from one file. Windows via winget (Linux/macOS in progress). Go, Apache 2.0.

Artexis10/endstate: Open-source engine behind Endstate: reinstall your apps and restore your settings on a fresh machine from one file. Windows via winget (Linux/macOS in progress). Go, Apache 2.0.

9 hours ago

Endstate

Set up a new Windows PC in minutes. Endstate scans your current machine for installed apps and settings, saves them to one portable file you control, then reinstalls the apps and restores your settings on a fresh Windows install. Free, open source, local-first — no account, no telemetry. Windows captures Winget community and Microsoft Store apps by default, with optional Chocolatey lanes; Linux and macOS use Nix, with Homebrew available per app on macOS.

!Endstate demo: scan this PC, then set up from a real 95-app profile — preview, apply

Website: · Download:

Author: Hugo Ander Kivi Primary Language: Go Status: Stable — latest release

CI</a>


Starter setups

Ready-to-apply manifests for common machine types. Each one installs a curated set of mainstream apps via WinGet — no configuration is written, so they are safe to apply on a compatible Windows PC.

| Pack | What's inside | File | |------|--------------|------| | Full-stack dev | Git, VS Code, Cursor, Zed, Windows Terminal, WSL, Docker, Node.js LTS, GitHub CLI, Postman, DBeaver, lazygit, ripgrep, zoxide, Starship, 7-Zip, Claude | starter-fullstack-dev.jsonc | | Streamer | OBS Studio, Elgato Stream Deck, Voicemeeter, Chatterino, Discord, Spotify, CapCut, Audacity, HandBrake, VLC | starter-streamer.jsonc | | Photo & video | Adobe Creative Cloud, darktable, GIMP, Krita, Inkscape, Kdenlive, CapCut, HandBrake, Audacity, Blender, OBS Studio, VLC | starter-photo-video.jsonc | | Gaming | Steam, Epic Games, GOG GALAXY, Discord, MSI Afterburner, Playnite, RetroArch, Sunshine, VLC, 7-Zip | starter-gaming.jsonc | | Privacy-first | Brave, Tor Browser, KeePassXC, Signal, Mullvad VPN, WireGuard, VeraCrypt, Cryptomator, Gpg4win, simplewall, Thunderbird, 7-Zip | starter-privacy.jsonc | | Student | Notion, Obsidian, Anki, Zotero, Zoom, Teams, LibreOffice, Acrobat Reader, Discord, Spotify, VLC, Claude | starter-student.jsonc |

To apply a pack:

# GUI: drag the .jsonc file onto the Endstate window → Preview → Apply

CLI:

endstate apply --profile manifests/examples/starter-fullstack-dev.jsonc

Share your own setup

The loop that works today:

  1. Capture and sanitize your current machine: endstate capture --sanitize --out my-setup.jsonc
  2. Post the file anywhere (GitHub Gist, a repo, a message).
  3. The recipient drags it onto the Endstate GUI (or runs endstate apply --profile my-setup.jsonc) → previews the plan → applies.
The sanitized manifest contains only app IDs and package-manager references — no machine-specific paths, timestamps, or credentials.

Why This Exists

Rebuilding a machine after a clean install is tedious, error-prone, and mentally draining. Configuration drift accumulates silently. Manual steps get forgotten. The result is machines that cannot be reliably reconstructed.

Endstate exists to eliminate this clean install tax.

A machine should be:

  • Rebuildable — from a single manifest
  • Auditable — with clear records of what was applied
  • Deterministic — same inputs produce same outcomes
  • Safe to re-run — at any time, without side effects

Who This Is For

Endstate is designed for developers, power users, and small teams who:

  • Reinstall or migrate machines regularly
  • Care about reproducibility and auditability
  • Want automation without sacrificing safety or control

Core Principles

  • Declarative desired state — describe what should be true, not how to do it
  • Idempotence — re-running converges to the same result without duplicating work
  • Non-destructive defaults — no silent deletions, explicit opt-in for destructive operations
  • Verification-first — "it ran" is not success; success means the desired state is observable
  • Separation of concerns — install ≠ configure ≠ verify

Architecture

Spec → Planner → Drivers → Restorers → Verifiers → Reports/State

| Stage | Responsibility | |-------|----------------| | Spec | Declarative manifest describing desired state (apps, configs, preferences) | | Planner | Resolves spec into executable steps, detects drift, computes minimal diff | | Drivers | Install software via platform-specific package managers (winget, apt, brew) | | Restorers | Apply configuration files, registry keys, symlinks, preferences | | Verifiers | Confirm desired state is achieved (file exists, app responds, config matches) | | Reports/State | Persist run history, enable drift detection, provide human-readable logs |


Directory Structure

endstate/
├── go-engine/          # Go engine (sole CLI implementation)
│   ├── cmd/endstate/   # CLI entrypoint
│   └── internal/       # Core packages (manifest, commands, driver, restore, verifier, etc.)
├── modules/            # Config module catalog (apps.git, apps.vscodium, etc.)
├── payload/            # Staged configuration files referenced by modules
├── bundles/            # Named module groupings (JSONC)
├── manifests/          # Desired state declarations
│   ├── examples/       # Shareable example manifests
│   ├── includes/       # Reusable manifest fragments
│   └── local/          # Machine-specific captures (gitignored)
└── tests/              # Test fixtures and shared test data

Quickstart

Initial Setup

# Clone the repo
git clone https://github.com/Artexis10/endstate.git
cd endstate

Build the CLI

cd go-engine && go build -o endstate.exe ./cmd/endstate

Or run directly without building

cd go-engine && go run ./cmd/endstate bootstrap

After bootstrap completes, the endstate command is available globally from any directory.

Basic Workflow

# 1. Capture current machine state
endstate capture

2. Preview what would be applied (dry-run)

endstate apply --manifest manifests/local/my-machine.jsonc --dry-run

3. Apply the manifest

endstate apply --manifest manifests/local/my-machine.jsonc

4. Verify end state is achieved

endstate verify --manifest manifests/local/my-machine.jsonc

5. Check environment health

endstate doctor

Fresh machine (one command)

Handed a profile bundle (.zip) or a manifest? rebuild does the whole fresh-machine flow — install apps, restore configuration, then verify — in a single step:

# 1. Preview the rebuild (no changes)
endstate rebuild --from MyProfile.zip --dry-run

2. Do it (restore is on by default, so a live run needs --confirm)

endstate rebuild --from MyProfile.zip --confirm

Overwritten files are backed up first and can be undone with endstate revert. Use --no-restore to install and verify without touching configuration.

Inspect an extracted profile

To view the saved app and settings inventory without evaluating or changing the current machine, inspect an extracted manifest only — bundles and directories are not accepted:

endstate profile inspect ./extracted-profile/manifest.jsonc --json

When multiple installed app versions are valid configuration targets, apply, restore, and rebuild accept repeatable --restore-target = mappings. --restore-filter still selects modules first; Endstate never silently chooses the newest side-by-side version.

Import from UniGetUI

Already tracking your apps in UniGetUI? Export a bundle (or grab a local backup .ubundle) and turn it into an Endstate manifest:

# Convert a UniGetUI backup into a manifest (pure transform: no installs, no network)
endstate import --from unigetui --path "MY-PC installed packages.ubundle"

Record versions too (a package's pinned version wins over the observed one)

endstate import --from unigetui --path backup.ubundle --pin

The default output is manifests/local/imported-unigetui.jsonc (gitignored); outside a repo checkout it lands next to the input bundle instead. Use --out to choose another path. From there, endstate plan/apply installs the apps and Endstate's module catalog lights up config restore for the apps it recognises.

Scope — the honest version: import moves the package list only. UniGetUI's own settings are not imported (UniGetUI's backup doesn't include them either). Winget-source packages become manifest apps; everything else is reported, never silently dropped — non-winget managers (chocolatey, scoop, pip, ...) and UniGetUI's own incompatible entries are listed with a reason. Config restore comes from Endstate's module catalog after import, not from the bundle.

CLI Commands

| Command | Description | |---------|-------------| | bootstrap | Install endstate command to user PATH for global access | | capture | Capture current machine state into a manifest | | catalog-plan | Resolve one tracked catalog bundle into read-only module actions (no package installation or manifest composition) | | plan | Generate execution plan from manifest without applying | | apply | Execute the plan (with optional -DryRun) | | rebuild | Rebuild a machine from a bundle/manifest in one step (install + restore + verify; live run needs --confirm) | | import | Import an external package list into a manifest (--from unigetui; winget packages in, non-winget reported) | | restore | Restore configuration files from manifest (requires -EnableRestore) | | export-config | Export config files from system to export folder (inverse of restore) | | validate-export | Validate export integrity before restore | | revert | Revert last restore operation by restoring backups | | verify | Check current state against manifest without modifying | | doctor | Diagnose environment issues (missing drivers, permissions, etc.) | | report | Show history of previous runs and their outcomes | | profile inspect --json | Read an extracted profile inventory without machine evaluation | | state | Manage endstate state (subcommands: reset, export, import) |


Manifest Format

Humans author manifests in JSONC (JSON with comments). Plans, state, and reports are emitted as plain JSON.

Supported formats: .jsonc (preferred), .json, .yaml, .yml

Basic Example

// my-machine.jsonc
{
  "version": 1,
  "name": "dev-workstation",

// Applications to install "apps": [ { "id": "vscode", "refs": { "windows": "Microsoft.VisualStudioCode", "linux": "code", "macos": "visual-studio-code" } }, { "id": "git", "refs": { "windows": "Git.Git", "linux": "git", "macos": "git" } } ],

// Configuration restore (opt-in) "restore": [ { "type": "copy", "source": "./configs/.gitconfig", "target": "~/.gitconfig", "backup": true } ],

// Verification steps "verify": [ { "type": "file-exists", "path": "~/.gitconfig" } ] }

Modular Manifests with Includes

Large manifests can be split into reusable modules:

// main.jsonc
{
  "version": 1,
  "name": "dev-workstation",

// Include other manifest files (resolved relative to this file) "includes": [ "./includes/dev-tools.jsonc", "./includes/media.jsonc", "./includes/dotfiles.jsonc" ],

// Local apps are merged with included apps "apps": [ { "id": "custom-tool", "refs": { "windows": "Custom.Tool" } } ] }

Include rules:

  • Paths are resolved relative to the including manifest
  • Arrays (apps, restore, verify) are concatenated
  • Scalar fields in the root manifest take precedence
  • Circular includes are detected and rejected with a clear error

Safety Defaults

Endstate prioritizes safety over speed:

| Default | Behavior | |---------|----------| | Backup before overwrite | Existing files are backed up before restoration | | Non-destructive | No deletions unless explicitly configured | | Dry-run support | All commands support -DryRun to preview changes | | Explicit destructive ops | Destructive operations require explicit flags | | Atomic operations | Failed operations roll back where possible | | Checksum verification | Restored files are verified against expected hashes |

Backup location: state/backups//


Endstate Cloud (optional, paid tier)

Endstate Cloud is the managed service Endstate operates: end-to-end encrypted profile backups you can push from one machine and pull on another. It is entirely optional — backing up to a location you control is free, and always will be. The same protocol is open, so you can run your own backend instead (see docs/contracts/hosted-backup-contract.md).

The engine derives keys client-side via Argon2id, encrypts each chunk with AES-256-GCM, ships the chunks to Cloudflare R2 via short-lived presigned URLs, and persists the refresh token + the unwrapped DEK in the OS keychain so subsequent push / pull operations don't re-prompt for the passphrase.

Endstate cannot decrypt user data uploaded to Endstate Cloud. This is a structural property: only the user's passphrase and recovery key can unlock the data. See contract §1 for the trust model.

Configuration

| Variable | Default | Purpose | |---|---|---| | ENDSTATE_OIDC_ISSUER_URL | https://substratesystems.io | OIDC issuer / backend URL | | ENDSTATE_OIDC_AUDIENCE | endstate-backup | JWT audience claim | | ENDSTATE_BACKUP_CONCURRENCY | 4 | Upload/download worker pool size (clamped 1–16) |

End-to-end workflow

# 1. Create an account. Passphrase comes from stdin (line 1).

The 24-word BIP39 recovery mnemonic is generated client-side and

written to --save-recovery-to before the network call. Save this

file somewhere offline — it is the only second-factor recovery

path if you forget your passphrase. If you lose both the

passphrase and the recovery file, your data is unrecoverable.

echo "<your-passphrase>" | endstate backup signup \ --email [email protected] \ --save-recovery-to ~/endstate-recovery.txt

2. Push a profile. The first push to a fresh account auto-creates a

backup named "default" if --backup-id is omitted.

endstate backup push --profile <path-to-profile> --name "primary"

3. Pull on a different machine. After backup login, the engine

caches the unwrapped DEK in the OS keychain so push/pull don't

re-prompt.

echo "<your-passphrase>" | endstate backup login --email [email protected] endstate backup list --json endstate backup versions --backup-id <id> --json endstate backup pull --backup-id <id> --to ~/restored-profile

4. Forgotten passphrase: stdin line 1 = recovery phrase, line 2 = new passphrase.

endstate backup recover --email [email protected]

5. Sign out — clears refresh token AND DEK from the keychain.

endstate backup logout

6. Destructive operations require --confirm.

endstate backup delete --backup-id <id> --confirm endstate backup delete-version --backup-id <id> --version-id <id> --confirm

7. GDPR account deletion (hard-delete: account, subscription, all data).

endstate account delete --confirm

Commands

| Command | Purpose | Stdin | |---|---|---| | backup signup --email --save-recovery-to | Create account, write recovery file | passphrase (line 1); optional mnemonic (line 2) | | backup login --email | Sign in; cache DEK in keychain | passphrase | | backup logout | Clear refresh token + DEK from keychain | — | | backup status [--json] | Report session state | — | | backup push --profile [--backup-id ] [--name | Encrypt and upload | — | | backup pull --backup-id --to [--version-id ] [--overwrite] | Download and restore | — | | backup list [--json] | List backups | — | | backup versions --backup-id [--json] | List versions of one backup | — | | backup delete --backup-id --confirm | Permanently delete a backup | — | | backup delete-version --backup-id --version-id --confirm | Soft-delete a version (purged after 7 days) | — | | backup recover --email | Reset passphrase using recovery phrase | line 1: phrase; line 2: new passphrase | | account delete --confirm | Hard-delete account + subscription + all data | — |

--events jsonl enables NDJSON event streaming on stderr for push and pull so the GUI can render per-chunk progress.

The capabilities response (endstate capabilities --json) advertises the configured issuer and audience under data.features.hostedBackup, so the GUI can gate its Endstate Cloud UI on a single handshake. The hostedBackup key name is a locked wire identifier and does not change with the public naming.


Prerequisites

| Requirement | Version | Purpose | |-------------|---------|---------| | Go | 1.22+ | Build and development | | Winget | Latest | Default app installation (Windows) | | Chocolatey | Current, optional | Additional Windows app catalog; Endstate can set it up with explicit consent |


Testing

Endstate uses Go's standard testing package for deterministic, offline-capable testing.

# Run all tests
cd go-engine && go test ./...

Run a specific package's tests

cd go-engine && go test ./internal/manifest/...

Run tests with verbose output

cd go-engine && go test -v ./...

Status

Endstate is stable and actively released — see Releases for the latest version. The CLI contract (JSON schema 1.0) is locked. Capture, apply, verify, restore, and drift detection are production-ready. Endstate Cloud generations are durable only after their explicit commit succeeds; the managed recovery claim remains gated on a recorded clean-Windows recovery-drill receipt. Winget is the primary Windows driver, with macOS/Linux (Nix) in progress.

A desktop GUI is available as a separate free, open-source app built on top of Endstate's core, with an optional paid Endstate Cloud tier — substratesystems.io/endstate

License

Endstate is licensed under the Apache License, Version 2.0.

See the LICENSE file for details.

Copyright © 2025–2026 Substrate Systems OÜ

Created by Hugo Ander Kivi at Substrate Systems OÜ.

Chat with me