astral-env

Version 1.0.0.0
Language C++20
Depends On Astral 5.0.1.0+
Maintainer One Maniac™

astral-env is the declarative environment and system configuration layer that sits on top of Astral. Describe your entire system packages, services, dotfiles, hostname, timezone, file snapshots in a single .stars file. Then apply it. Reality matches the file.

Think of it as NixOS without the functional language that makes your brain hurt, or Ansible without the YAML sprawl and Python dependency.

Installation

Prerequisites

  • Astral 5.0.1.0+
  • A C++20 compiler (gcc or clang)
  • cmake >= 3.20
  • openssl >= 3.0 (for SHA-256 store hashing)
  • zstd (for snapshots astral -S zstd)
git clone https://github.com/Astaraxia-Linux/Astral-env
cd Astral-env
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
sudo cmake --install build

First-Time Setup

# Enable in /etc/astral/astral.stars
sudo astral-env system init

# Create your user config
sudo astral-env system init-user yourusername

Add to /etc/astral/astral.stars:

$AST.core: {
    astral-env        = "enabled"
    astral-env-system = "enabled"
};

Configuration Format

astral-env uses the same .stars syntax as Astral recipes. If you've written a recipe, you already know this.

System Config /etc/astral/env/env.stars

$ENV.Version = "3"

$ENV.System: {
    hostname = "izumi"
    timezone = "Asia/Kuala_Lumpur"
};

$ENV.Packages: {
    neovim
    htop
    git
    zsh
};

$ENV.Services: {
    sshd           = "enabled"
    cronie         = "enabled"
    NetworkManager = "enabled"
};

User Config /etc/astral/env/username.stars

$ENV.Version = "3"

$ENV.User: {
    name  = "izumi"
    shell = "/bin/zsh"
};

$ENV.Packages: {
    firefox
    mpv
    thunderbird
};

$ENV.Dotfiles: {
    "/home/izumi/.config/nvim" = "nvim"
    "/home/izumi/.zshrc"       = "zshrc"
    "/home/izumi/.gitconfig"   = "gitconfig"
};

$ENV.Vars: {
    EDITOR  = "nvim"
    BROWSER = "firefox"
};

Dotfiles in $ENV.Dotfiles are symlinked from /etc/astral/env/dotfiles/username/. Manage all your configs in one place.

Project Environment astral-env.stars

Per-project dev environments, like nix-shell but readable:

$ENV.Version = "3"

$ENV.Metadata: {
    Name        = "my-project"
    Description = "My cool project environment"
};

$ENV.Packages: {
    python >= 3.11
    nodejs >= 20.0
    git
};

$ENV.Vars: {
    DEBUG    = "true"
    NODE_ENV = "development"
};

$ENV.Shell: {
    echo "Welcome to my-project"
    export PATH="$PWD/bin:$PATH"
};

Auto-Snapshots $ENV.Snap

$ENV.Snap: {
    on_interval      = "true"
    default_interval = "1" "H"    # S=seconds M=minutes H=hours D=days

    path: {
        "/home/izumi/.config/hyprland"
        "/home/izumi/.zshrc": {
            interval = "autosave"         # snapshot on every file change
        };
        "/etc/astral": {
            interval = "6" "H"
        };
    };
};

System Management

CommandDescription
astral-env system diffPreview what would change safe, read-only
astral-env system applyApply changes to match the config files
astral-env system apply --dry-runShow changes without making them
astral-env system apply --yesSkip confirmation prompts
astral-env system apply --user <u>Apply only a specific user's config
astral-env system apply --global-onlyApply only env.stars, skip user configs
astral-env system rollbackRoll back to the last snapshot
astral-env system rollback --listList available rollback points
astral-env system rollback --to <id>Roll back to a specific snapshot
astral-env system checkValidate all .stars files in /etc/astral/env/
astral-env system initCreate /etc/astral/env/env.stars
astral-env system init-user <u>Create per-user config + dotfiles directory

The system diff output uses these markers:

MarkerMeaning
[+]Will install / create
[-]Will remove / unlink
[~]Will change
[!]Conflict needs manual resolution

astral-env automatically saves a rollback snapshot before applying anything. Rollback restores service states, symlinks, hostname, and timezone but not packages. Use astral -R to remove packages manually.

Project Environments

CommandDescription
astral-env initScaffold an astral-env.stars in current directory
astral-env lockGenerate lockfile (resolves and pins versions)
astral-env lock --update <pkg>Update a specific package in the lockfile
astral-env lock --updateUpdate all packages in the lockfile
astral-env buildBuild environment from lockfile
astral-env shellEnter interactive shell with environment active
astral-env run <cmd>Run a command inside the environment
astral-env statusShow installed vs missing packages

Packages live in a content-addressed store under /astral-env/store/. Multiple projects sharing the same version of a package pay zero extra disk cost.

File Snapshots

Content-addressed, zstd-compressed, deduplicated file snapshots. If a file hasn't changed since the last snapshot, the new snapshot is free zero bytes stored.

CommandDescription
astral-env snap <path>Snapshot a file or directory
astral-env snap listList all snapshots
astral-env snap list <path>List snapshots for a specific path
astral-env snap restore <id>Restore a snapshot
astral-env snap restore <id> --dest <path>Restore to a different location
astral-env snap prune --keep-last 5Keep only last 5 snapshots per path
astral-env snap prune --older-than 14dRemove snapshots older than 14 days

Snapshot Daemon

astral-env-snapd handles scheduled and autosave snapshots in the background. Supports systemd, OpenRC, runit, s6, dinit, SysVinit.

CommandDescription
astral-env snapd startStart daemon and enable at boot
astral-env snapd stopStop daemon
astral-env snapd restartRestart daemon
astral-env snapd statusShow daemon status

autosave uses inotify and waits 5 seconds after the last file change before snapshotting (debounce). Configurable per-path:

$ENV.Snap: {
    path: {
        "/home/izumi/.zshrc": {
            interval          = "autosave"
            autosave_debounce = "5" "S"
        };
    };
};

Store & Garbage Collection

CommandDescription
astral-env store listList all store entries
astral-env store sizeShow total store disk usage
astral-env gc --dry-runShow what would be collected
astral-env gcCollect entries unused for 30+ days
astral-env gc --max-age 7More aggressive collection (7 days)

The GC skips entries referenced by any lockfile, entries newer than --max-age days, entries without a .complete marker, and the snap/ subdirectory entirely (snapshots have their own pruning via snap prune).

Troubleshooting

ErrorFix
astral-env is not enabledAdd astral-env = "enabled" to $AST.core in /etc/astral/astral.stars
astral-env-system is not enabledAdd astral-env-system = "enabled" to the same block
zstd is required for snapshotssudo astral -S zstd
No lockfile foundRun astral-env lock before astral-env build
Snapshot blob not foundThe blob was GC'd before the index entry was pruned. Run astral-env snap prune to clean up dangling entries.
Rollback only partially workedRollback doesn't undo package installs. Use astral -R <pkg> manually, or astral-env snap restore for specific files.

FAQ

Why C++ instead of POSIX sh like Astral?
astral-env needs concurrent file hashing, inotify event loops, content-addressed storage, and a GC. Doing that in sh would be a crime against humanity. C++20 with std::filesystem hits the sweet spot.

Does astral-env replace Astral?
No. It's a layer on top. It uses astral -S to install packages it doesn't reimplement the package manager.

Can I use it without system management?
Yes. The project environment features (init, lock, build, shell, run) work completely independently without astral-env-system = "enabled".

Can I track my configs in git?
That's the whole point. Put your .stars files and /etc/astral/env/dotfiles/ in a repo. Fresh install → clone → system apply → done.

Who maintains this?
Same One Maniac™. Two projects, one maniac. The math is concerning.