Skip to content

Commit 5b5ac71

Browse files
committed
feat: initial commit — Onyx package manager
Zig-based package manager with two install paths: Nix binary cache packages and third-party GitHub/domain sources. Includes CLI, resolver, NAR fetcher with parallel downloads, and symlink-based installation.
0 parents  commit 5b5ac71

21 files changed

Lines changed: 4290 additions & 0 deletions

.github/workflows/ci.yml

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
jobs:
10+
test:
11+
strategy:
12+
matrix:
13+
os: [ubuntu-latest, macos-latest]
14+
runs-on: ${{ matrix.os }}
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: goto-bus-stop/setup-zig@v2
18+
with:
19+
version: 0.15.1
20+
- name: Build
21+
run: zig build
22+
- name: Unit tests
23+
run: zig build test
24+
25+
e2e:
26+
strategy:
27+
matrix:
28+
os: [ubuntu-latest, macos-latest]
29+
runs-on: ${{ matrix.os }}
30+
steps:
31+
- uses: actions/checkout@v4
32+
- uses: goto-bus-stop/setup-zig@v2
33+
with:
34+
version: 0.15.1
35+
- name: Build
36+
run: zig build -Doptimize=ReleaseSmall
37+
38+
- name: Setup nix store (Linux)
39+
if: runner.os == 'Linux'
40+
run: sudo mkdir -p /nix/store && sudo chown $(whoami) /nix/store
41+
42+
- name: Setup nix store (macOS)
43+
if: runner.os == 'macOS'
44+
run: |
45+
# GitHub macOS runners already have /nix from the Nix installer,
46+
# but we need it writable
47+
if [ -d /nix/store ]; then
48+
sudo chown -R $(whoami) /nix/store
49+
else
50+
sudo mkdir -p /nix/store && sudo chown $(whoami) /nix/store
51+
fi
52+
53+
- name: Add to PATH
54+
run: |
55+
mkdir -p ~/.local/bin
56+
cp zig-out/bin/onyx ~/.local/bin/onyx
57+
echo "$HOME/.local/bin" >> $GITHUB_PATH
58+
59+
- name: "e2e: install"
60+
run: |
61+
onyx install hello
62+
hello --version
63+
onyx list | grep hello
64+
65+
- name: "e2e: install with version"
66+
run: |
67+
onyx install jq@1.7
68+
jq --version
69+
70+
- name: "e2e: exec"
71+
run: |
72+
onyx exec cowsay -- "moo"
73+
74+
- name: "e2e: exec does not appear in list"
75+
run: |
76+
! onyx list | grep cowsay
77+
78+
- name: "e2e: uninstall"
79+
run: |
80+
onyx uninstall hello
81+
! command -v hello
82+
! onyx list | grep hello
83+
84+
- name: "e2e: gc"
85+
run: onyx gc
86+
87+
- name: "e2e: implode"
88+
run: onyx implode --exec
89+
90+
release-please:
91+
runs-on: ubuntu-latest
92+
if: github.ref == 'refs/heads/master'
93+
needs: [test, e2e]
94+
outputs:
95+
release_created: ${{ steps.release.outputs.release_created }}
96+
tag_name: ${{ steps.release.outputs.tag_name }}
97+
permissions:
98+
contents: write
99+
pull-requests: write
100+
steps:
101+
- uses: googleapis/release-please-action@v4
102+
id: release
103+
with:
104+
token: ${{ secrets.GITHUB_TOKEN }}
105+
106+
build:
107+
needs: release-please
108+
if: needs.release-please.outputs.release_created == 'true'
109+
strategy:
110+
matrix:
111+
include:
112+
- target: x86_64-linux-musl
113+
name: onyx-x86_64-linux
114+
- target: aarch64-linux-musl
115+
name: onyx-aarch64-linux
116+
- target: aarch64-macos
117+
name: onyx-aarch64-macos
118+
runs-on: ubuntu-latest
119+
permissions:
120+
contents: write
121+
steps:
122+
- uses: actions/checkout@v4
123+
- uses: goto-bus-stop/setup-zig@v2
124+
with:
125+
version: 0.15.1
126+
- name: Build
127+
run: zig build -Dtarget=${{ matrix.target }} -Doptimize=ReleaseSmall
128+
- name: Package
129+
run: |
130+
cp zig-out/bin/onyx ${{ matrix.name }}
131+
tar czf ${{ matrix.name }}.tar.gz ${{ matrix.name }}
132+
- name: Upload to release
133+
uses: softprops/action-gh-release@v2
134+
with:
135+
tag_name: ${{ needs.release-please.outputs.tag_name }}
136+
files: ${{ matrix.name }}.tar.gz

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
zig-out/
2+
.zig-cache/
3+

.release-please-manifest.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
".": "0.1.0"
3+
}

AGENTS.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to agents when working with code in this repository.
4+
5+
## Build and Test
6+
7+
```bash
8+
zig build # debug build
9+
zig build -Doptimize=ReleaseSmall # optimized, <1MB
10+
zig build test # run all tests
11+
```
12+
13+
Zig 0.15+ required. This is Zig 0.15 which uses newer APIs (`std.Io.Writer.Allocating`, `std.Io.Reader.fixed`, `std.compress.zstd.Decompress`). Don't use `std.io.getStdOut()` — it doesn't exist in 0.15. For stdout output, use `std.posix.write(std.posix.STDOUT_FILENO, ...)`.
14+
15+
## Architecture
16+
17+
Onyx is a package manager with two install paths: **registry packages** (resolved via Onyx registry, fetched from the Nix binary cache) and **third-party packages** (GitHub/domain sources with `onyx.toml` manifests, installed directly).
18+
19+
### Source files
20+
21+
- **main.zig** — CLI entry point, all command implementations (`cmdInstall`, `cmdExec`, `cmdUninstall`, `cmdUse`, `cmdGc`, `cmdUpgrade`, `cmdInit`, `cmdImplode`)
22+
- **cli.zig** — argument parsing, `PackageRef` (name@version), `Command` union
23+
- **resolver.zig** — registry alias resolution, Nixhub API calls, index caching with TTL
24+
- **fetcher.zig** — NAR closure fetching, parallel download (thread pool), hash verification
25+
- **source.zig** — third-party package resolution (GitHub, domain), TOML manifest parsing, meta tag discovery
26+
- **store.zig**`Database` struct: state.json load/save, version management, symlink install/remove
27+
- **nar.zig** — NAR archive format unpacker (regular files, directories, symlinks)
28+
- **ui.zig** — output functions. Data output (`print`, `ok`, `pkg`, `listPackage`) goes to stdout. Errors/warnings (`err`, `warn`, `status`, `dim`) go to stderr.
29+
- **xdg.zig** — XDG paths (`~/.local/share/onyx/`, `~/.cache/onyx/`, `~/.local/bin/`)
30+
31+
### Two install paths
32+
33+
**Nix path**: `resolveAlias` (registry index) → `resolve` (Nixhub API) → `fetchClosure` (cache.nixos.org, parallel NARs) → unpack to `/nix/store/` → symlink to `~/.local/bin/`
34+
35+
**Third-party path**: `resolveGithub`/`resolveDomain` → download binary/tarball → install to `~/.local/share/onyx/packages/{name}/{version}/` → symlink to `~/.local/bin/`
36+
37+
Both paths store state in `~/.local/share/onyx/state.json` and create symlinks in `~/.local/bin/`.
38+
39+
### Key patterns
40+
41+
- **Locking**: `acquireLock`/`releaseLock` via file lock on `~/.local/share/onyx/lock`. Commands that mutate state (`install`, `uninstall`, `use`, `gc`, `upgrade`) must hold the lock. `cmdInstall` wraps `cmdInstallInner` to avoid recursive lock acquisition when installing dependencies.
42+
- **Alias resolution**: `resolveAlias` checks `~/.cache/onyx/index.json`. Fast commands (`exec`, `use`, `list`) use cached index forever. Slow commands (`install`, `upgrade`) refresh if >1 day old via `resolveAliasFresh`.
43+
- **Ephemeral packages**: `exec` auto-installs packages marked `ephemeral: true` with `last_used` timestamp. These are hidden from `list` and cleaned by `gc` after 30 days.
44+
- **Symlink ownership**: `removeSymlinks` uses `readLink` to verify the symlink points to `/nix/store/` or `/onyx/packages/` before deleting — never removes files owned by other tools.
45+
- **exec fast path**: `cmdExec` checks state.json first. If the package is already installed, it skips all network calls and directly `execv`s into the binary.
46+
47+
### Registry
48+
49+
The registry lives on the `registry/v0` branch. `index.json` maps aliases (e.g., `nodejs` → nixpkgs attribute). Per-package `.toml` files contain cleanup metadata (paths to delete on uninstall, extracted from Homebrew).

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Dan Lilienblum
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

0 commit comments

Comments
 (0)