Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 24a75ee8d5 | |||
| e534dfcff6 | |||
| f77f3b7f11 | |||
| cdbeebf5e7 | |||
| e311bb1066 | |||
| 2aadba34e2 | |||
| 7f2979d7ff | |||
| f8053537a9 | |||
| 550c1d9f44 | |||
| 55594fd632 | |||
| b797aefb28 | |||
| e8050f9dfd | |||
| 0fd4cfe83d | |||
| 9d80f47625 | |||
| ef13a18b4c | |||
| 26bdad6d1b | |||
| b9d13a4814 | |||
| ac58ade9da | |||
| cb369d3be3 | |||
| b38558cdcf | |||
| ca88d13535 | |||
| 577274b382 | |||
| 25bf426ae5 | |||
| 55d9a62c33 | |||
| bf0095dcd2 | |||
| c4aa88d677 | |||
| 16b6f1ed21 | |||
| a9d70c58b1 | |||
| 0e20e87361 | |||
| 2af7732077 | |||
| 7d58ed23a4 | |||
| cf54f1c94f | |||
| ef014a9116 | |||
| bbf81d6ac5 | |||
| d5101ea3fe | |||
| 3dda8347e4 | |||
| 949f0d2153 | |||
| 45c8bc03e8 | |||
| c462cee781 | |||
| 80c05b582e | |||
| d95501ed01 | |||
| 65101ed034 | |||
| d971b72ba4 | |||
| 44972326e9 | |||
| df96ba5c89 | |||
| e3e8c6f5e3 | |||
| 4e2bb71ed5 | |||
| 7599dbd981 | |||
| 21684819ec | |||
| 3c4fcfc3ed | |||
| 0af380cd9a | |||
| 9a4b7eaa11 | |||
| 0d46839c04 | |||
| 4b5e5eec2d | |||
| cc57704fe0 | |||
| cfa685cf91 | |||
| 8e628dad9c | |||
| ce394ed7b8 | |||
| cb45365285 | |||
| 7da15f25ff | |||
| 80fb557b73 | |||
| bf0191b68e | |||
| b319fe443b | |||
| 547d804a81 | |||
| f81e3807a3 | |||
| 5fe57dc74c | |||
| 4a50fe3f31 | |||
| 2828abdfd4 | |||
| 1bc3afe912 | |||
| 486dee4b30 | |||
| 67098eb3b7 | |||
| 42380de028 | |||
| 20740d8735 | |||
| 49fd58b363 |
@@ -0,0 +1,2 @@
|
||||
result
|
||||
result-*
|
||||
@@ -1,18 +0,0 @@
|
||||
when:
|
||||
- event: tag
|
||||
ref: "refs/tags/v*"
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: git.oleks.space/oleks/nix-ci:latest
|
||||
environment:
|
||||
GITEA_CLONE_TOKEN:
|
||||
from_secret: gitea_clone_token
|
||||
commands:
|
||||
- nixos-ci-entrypoint true
|
||||
- nix build .#xonsh --no-link --print-out-paths
|
||||
- nix build .#hello-world --no-link --print-out-paths
|
||||
backend_options:
|
||||
kubernetes:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: howard2404
|
||||
@@ -1,64 +0,0 @@
|
||||
# Build flake-hub packages for all architectures and push to attic
|
||||
|
||||
clone:
|
||||
- name: clone
|
||||
image: woodpeckerci/plugin-git
|
||||
environment:
|
||||
CI_NETRC_MACHINE: git.oleks.space
|
||||
CI_NETRC_USERNAME: oleks
|
||||
CI_NETRC_PASSWORD:
|
||||
from_secret: gitea_clone_token
|
||||
|
||||
when:
|
||||
- event: push
|
||||
branch: main
|
||||
|
||||
# Build packages for each architecture in parallel
|
||||
steps:
|
||||
- name: build-x86_64-linux
|
||||
image: git.oleks.space/oleks/nix-ci:latest
|
||||
depends_on: []
|
||||
environment:
|
||||
ATTIC_TOKEN:
|
||||
from_secret: attic_token
|
||||
GITEA_CLONE_TOKEN:
|
||||
from_secret: gitea_clone_token
|
||||
backend_options:
|
||||
kubernetes:
|
||||
nodeSelector:
|
||||
kubernetes.io/arch: amd64
|
||||
commands:
|
||||
- sh ci/setup.sh
|
||||
- sh ci/build.sh x86_64-linux
|
||||
|
||||
- name: build-aarch64-linux
|
||||
image: git.oleks.space/oleks/nix-ci:latest
|
||||
depends_on: []
|
||||
environment:
|
||||
ATTIC_TOKEN:
|
||||
from_secret: attic_token
|
||||
GITEA_CLONE_TOKEN:
|
||||
from_secret: gitea_clone_token
|
||||
backend_options:
|
||||
kubernetes:
|
||||
nodeSelector:
|
||||
kubernetes.io/arch: arm64
|
||||
commands:
|
||||
- sh ci/setup.sh
|
||||
- sh ci/build.sh aarch64-linux
|
||||
|
||||
- name: build-s390x-linux
|
||||
image: git.oleks.space/oleks/nix-ci:latest
|
||||
depends_on: []
|
||||
environment:
|
||||
ATTIC_TOKEN:
|
||||
from_secret: attic_token
|
||||
GITEA_CLONE_TOKEN:
|
||||
from_secret: gitea_clone_token
|
||||
backend_options:
|
||||
kubernetes:
|
||||
nodeSelector:
|
||||
kubernetes.io/arch: amd64
|
||||
commands:
|
||||
- sh ci/setup.sh
|
||||
- sh ci/build.sh s390x-linux
|
||||
@@ -0,0 +1,44 @@
|
||||
# Build flake-hub packages for x86_64-linux and push to attic.
|
||||
# Separate workflow per arch — sharing one PVC across arches makes
|
||||
# the second pod permanently Unschedulable (PV node affinity binds
|
||||
# to the first arch's node).
|
||||
|
||||
when:
|
||||
- event: push
|
||||
branch: main
|
||||
|
||||
clone:
|
||||
- name: clone
|
||||
image: woodpeckerci/plugin-git
|
||||
environment:
|
||||
CI_NETRC_MACHINE: git.oleks.space
|
||||
CI_NETRC_USERNAME: oleks
|
||||
CI_NETRC_PASSWORD:
|
||||
from_secret: gitea_clone_token
|
||||
PLUGIN_TAGS: "false"
|
||||
PLUGIN_DEPTH: "1"
|
||||
|
||||
steps:
|
||||
- name: build-amd64
|
||||
image: git.oleks.space/oleks/nix-ci:latest
|
||||
environment:
|
||||
ATTIC_TOKEN:
|
||||
from_secret: attic_token
|
||||
GITEA_CLONE_TOKEN:
|
||||
from_secret: gitea_clone_token
|
||||
backend_options:
|
||||
kubernetes:
|
||||
nodeSelector:
|
||||
kubernetes.io/arch: amd64
|
||||
labels:
|
||||
commit-tag: "${CI_COMMIT_TAG}"
|
||||
commit-branch: "${CI_COMMIT_BRANCH}"
|
||||
pipeline-number: "${CI_PIPELINE_NUMBER}"
|
||||
commands:
|
||||
- echo "▸ arch=$(uname -m)"
|
||||
- sh ci/setup.sh
|
||||
# Same front door as a local `nix run .#publish -- --publish`. The app
|
||||
# is parity-lib's mkAtticClosurePublish (attic-closure archetype); CI and
|
||||
# local share one audited impl. PUBLISH=1 makes it actually push (local
|
||||
# runs dry-run).
|
||||
- PUBLISH=1 nix run .#publish
|
||||
@@ -0,0 +1,42 @@
|
||||
# Build flake-hub packages for aarch64-linux and push to attic.
|
||||
# Separate workflow per arch — see amd64.yaml for rationale.
|
||||
|
||||
when:
|
||||
- event: push
|
||||
branch: main
|
||||
|
||||
clone:
|
||||
- name: clone
|
||||
image: woodpeckerci/plugin-git
|
||||
environment:
|
||||
CI_NETRC_MACHINE: git.oleks.space
|
||||
CI_NETRC_USERNAME: oleks
|
||||
CI_NETRC_PASSWORD:
|
||||
from_secret: gitea_clone_token
|
||||
PLUGIN_TAGS: "false"
|
||||
PLUGIN_DEPTH: "1"
|
||||
|
||||
steps:
|
||||
- name: build-arm64
|
||||
image: git.oleks.space/oleks/nix-ci:latest
|
||||
environment:
|
||||
ATTIC_TOKEN:
|
||||
from_secret: attic_token
|
||||
GITEA_CLONE_TOKEN:
|
||||
from_secret: gitea_clone_token
|
||||
backend_options:
|
||||
kubernetes:
|
||||
nodeSelector:
|
||||
kubernetes.io/arch: arm64
|
||||
labels:
|
||||
commit-tag: "${CI_COMMIT_TAG}"
|
||||
commit-branch: "${CI_COMMIT_BRANCH}"
|
||||
pipeline-number: "${CI_PIPELINE_NUMBER}"
|
||||
commands:
|
||||
- echo "▸ arch=$(uname -m)"
|
||||
- sh ci/setup.sh
|
||||
# NODE-BOUND LEG (emmett#44, cluster#192): aarch64-linux can't be built on
|
||||
# emmett (linux/amd64) and these native packages have no cross path, so this
|
||||
# leg must run on an aarch64 node (the arch nodeSelector above). Same
|
||||
# parity-lib attic-closure front door as amd64, only the arch app differs.
|
||||
- PUBLISH=1 nix run .#publish-aarch64-linux
|
||||
@@ -0,0 +1,39 @@
|
||||
# flake-hub
|
||||
|
||||
Personal Nix flake hub — custom packages and overlays
|
||||
|
||||
## Overview
|
||||
|
||||
Personal Nix flake hub for publishing custom packages and overlays.
|
||||
Provides packages for x86_64-linux and aarch64-linux.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
nix build .#<package-name>
|
||||
```
|
||||
|
||||
## Antigravity
|
||||
|
||||
`google-antigravity` (FHS-wrapped) and `google-antigravity-no-fhs` are
|
||||
re-exposed from
|
||||
[jacopone/antigravity-nix](https://github.com/jacopone/antigravity-nix)
|
||||
(MIT). Upstream auto-updates daily via its own GitHub Actions; we just bump
|
||||
the flake input and CI caches the build to attic.
|
||||
|
||||
When the in-app "new version available" banner appears (or you just want to
|
||||
refresh the pin):
|
||||
|
||||
```bash
|
||||
just antigravity-update # nix flake update antigravity-nix
|
||||
git commit -am "antigravity: bump"
|
||||
git push # CI rebuilds and pushes to attic
|
||||
```
|
||||
|
||||
Then, on the consumer:
|
||||
|
||||
```bash
|
||||
cd ~/projects/servers/emmett
|
||||
nix flake update oleks-nixpkgs
|
||||
nix run .#deploy
|
||||
```
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Build all flake-hub packages and push to attic
|
||||
set -e
|
||||
|
||||
ARCH="$1"
|
||||
ATTIC_CACHE="attic-infra-cache-k3s-1"
|
||||
ATTIC_SERVER="https://nix-cache-upload.oleks.space"
|
||||
|
||||
echo "=== Building flake-hub packages for ${ARCH} ==="
|
||||
|
||||
# Setup attic
|
||||
attic=$(nix build --inputs-from . nixpkgs#attic-client --print-out-paths --no-link)/bin/attic
|
||||
"${attic}" login ci "${ATTIC_SERVER}" "${ATTIC_TOKEN}"
|
||||
|
||||
echo "Building packages..."
|
||||
out=$(nix build ".#packages.${ARCH}.hello-world" --print-build-logs --print-out-paths --no-link)
|
||||
"${attic}" push "${ATTIC_CACHE}" "${out}"
|
||||
|
||||
out=$(nix build ".#packages.${ARCH}.xonsh" --print-build-logs --print-out-paths --no-link)
|
||||
"${attic}" push "${ATTIC_CACHE}" "${out}"
|
||||
|
||||
echo "✅ Build completed for ${ARCH}"
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
# Bootstrap nix environment for CI (runs inside nixos/nix:latest)
|
||||
set -e
|
||||
set -ex
|
||||
|
||||
# Direct to armer public IP — bypass Cloudflare upload size limits
|
||||
# Hairpin NAT on armer handles redirect for pods running on armer itself
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
# Publishing `gitea-local-fork`
|
||||
|
||||
End-to-end flow for taking a change in Oleks's local gitea fork
|
||||
(`/home/oleks/projects/gitea`, branch `oleks/main`) all the way to a
|
||||
binary in the `attic-infra-cache-k3s-1` cache, ready for any consumer
|
||||
to fetch instead of recompiling Go 1.26.3 locally.
|
||||
|
||||
## Step-by-step
|
||||
|
||||
### 1. Develop in the gitea fork
|
||||
|
||||
```bash
|
||||
cd ~/projects/gitea
|
||||
# work on oleks/main (the fork integration tip)
|
||||
git commit -m "..."
|
||||
git push oleks oleks/main:main
|
||||
```
|
||||
|
||||
Lands on `git.oleks.space/oleks/gitea` at `refs/heads/main`.
|
||||
|
||||
This step alone does **not** trigger any CI on flake-hub — the binding
|
||||
contract is the flake-hub pin commit in step 2.
|
||||
|
||||
### 2. Bump the pin in flake-hub
|
||||
|
||||
```bash
|
||||
cd ~/projects/nix-customs/flake-hub
|
||||
just gitea-update
|
||||
```
|
||||
|
||||
Under the hood, `scripts/update-gitea-local-fork.sh`:
|
||||
|
||||
1. **Wipes stale unstable tags** — `git push oleks :refs/tags/v*-unstable-*`
|
||||
so `nix-update` sees only clean semver tags (`v1.26.0` etc.) when
|
||||
constructing the new version.
|
||||
2. **Bumps the pin** —
|
||||
|
||||
```bash
|
||||
nix run nixpkgs#nix-update -- \
|
||||
--flake gitea-local-fork \
|
||||
--version=branch=main \
|
||||
--build
|
||||
```
|
||||
|
||||
Updates `rev`, `vendorHash`, `pnpmDeps` hash, and `version` (formatted
|
||||
as `<latest-semver-tag>-unstable-<commit-date>`).
|
||||
3. **Tags the gitea fork** at the new rev with `v<version>` and pushes
|
||||
the tag. **This is the load-bearing step:** the tag makes the rev
|
||||
reachable forever, surviving any future rebase / force-push of
|
||||
`oleks/main`.
|
||||
4. **Commits + pushes flake-hub** — the bumped `packages/gitea-local-fork.nix`
|
||||
lands on `git.oleks.space/oleks/flake-hub` `main`.
|
||||
|
||||
### 3. Woodpecker picks up the flake-hub push
|
||||
|
||||
Pipeline auto-triggers from `.woodpecker/amd64.yaml` and `.woodpecker/arm64.yaml`
|
||||
running in parallel. Each workflow:
|
||||
|
||||
1. Clones `oleks/flake-hub` using the `gitea_clone_token` secret.
|
||||
2. Runs `ci/setup.sh` — configures `/etc/hosts` to pin `armer`
|
||||
directly (bypassing Cloudflare upload-size limits via hairpin NAT)
|
||||
and writes the trusted substituters / public keys to `/etc/nix/nix.conf`.
|
||||
3. Runs `ci/build.py <arch>` — for each package (including
|
||||
`gitea-local-fork` on `x86_64-linux` and `aarch64-linux`):
|
||||
|
||||
```bash
|
||||
nix build .#packages.<arch>.gitea-local-fork --print-build-logs ...
|
||||
attic push attic-infra-cache-k3s-1 <closure>
|
||||
```
|
||||
|
||||
Authenticates the push with `ATTIC_TOKEN` against
|
||||
`https://nix-cache-upload.oleks.space`.
|
||||
|
||||
### 4. Closure lives in attic
|
||||
|
||||
After both arch workflows succeed, the closure is queryable from:
|
||||
|
||||
- **Cache URL:** `https://nix-cache-custom.oleks.space/attic-infra-cache-k3s-1`
|
||||
- **Public key:** `attic-infra-cache-k3s-1:qYSNK3DmttQXCFqn1t50qoWGtQNPRFWq9mgQjD05DeU=`
|
||||
|
||||
### 5. Consume from cache
|
||||
|
||||
Anyone (or any host) can pull the binary instead of building:
|
||||
|
||||
<!-- markdownlint-disable MD013 -->
|
||||
```bash
|
||||
just gitea-run
|
||||
# or, equivalently:
|
||||
nix run \
|
||||
--extra-substituters "https://nix-cache-custom.oleks.space/attic-infra-cache-k3s-1" \
|
||||
--extra-trusted-public-keys "attic-infra-cache-k3s-1:qYSNK3DmttQXCFqn1t50qoWGtQNPRFWq9mgQjD05DeU=" \
|
||||
git+https://git.oleks.space/oleks/flake-hub#gitea-local-fork
|
||||
```
|
||||
<!-- markdownlint-enable MD013 -->
|
||||
|
||||
## Diagram
|
||||
|
||||
```text
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ 1. ~/projects/gitea (branch oleks/main) │
|
||||
│ ─ git push oleks oleks/main:main ─► oleks/gitea refs/heads/main │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ 2. ~/projects/nix-customs/flake-hub │
|
||||
│ ─ just gitea-update │
|
||||
│ a. wipe v*-unstable-* tags on fork │
|
||||
│ b. nix-update → bump rev/hashes/version │
|
||||
│ c. tag fork at new rev: v<version> ─► push │
|
||||
│ d. commit + push flake-hub origin/main │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ 3. ci.oleks.space pipeline auto-fires │
|
||||
│ amd64 + arm64 (parallel): │
|
||||
│ clone → ci/setup.sh → ci/build.py <arch> │
|
||||
│ ─ nix build .#packages.<arch>.gitea-local-fork │
|
||||
│ ─ attic push attic-infra-cache-k3s-1 <closure> │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ 4. attic-infra-cache-k3s-1 │
|
||||
│ URL: https://nix-cache-custom.oleks.space/attic-infra-cache-k3s-1 │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ 5. Consumer: just gitea-run / nix run …#gitea-local-fork │
|
||||
│ ─ closure pulled from cache, no recompile │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Secrets
|
||||
|
||||
Both are Woodpecker repo secrets on `oleks/flake-hub` (never in code):
|
||||
|
||||
- `gitea_clone_token` — clone flake-hub source; netrc for `fetchgit` of
|
||||
the fork.
|
||||
- `attic_token` — authenticate `attic push` against
|
||||
`nix-cache-upload.oleks.space`.
|
||||
|
||||
## Why the dance with tags in step 2c
|
||||
|
||||
The fork's `oleks/main` branch is rebaseable. `fetchgit` in the
|
||||
derivation resolves by `rev`, so the rev must remain reachable from
|
||||
*some* ref on the remote. Tags are immutable. The
|
||||
`v<version>` tag created at update time becomes the load-bearing
|
||||
reference: even if `oleks/main` later gets force-pushed and the rev
|
||||
falls off the branch, the tag keeps it alive (and Gitea won't GC it).
|
||||
|
||||
## Failure points to watch
|
||||
|
||||
- **Step 2c — tag push rejected.** Check `oleks` remote auth in your
|
||||
shell; the pre-push hook also runs lint.
|
||||
- **Step 3 build — first cold build ≈10–15 min/arch, later ≈1–2 min.**
|
||||
Normal; Go 1.26.3 compiles from source.
|
||||
- **Step 3 attic — push hangs or 413.** `ci/setup.sh` pins armer in
|
||||
`/etc/hosts` to bypass the Cloudflare 100 MB upload cap; confirm the
|
||||
pin survived.
|
||||
- **Step 5 consume — cache miss, recompile locally.** Check the closure
|
||||
landed (`attic info` or browse the cache URL) and that the public key
|
||||
matches.
|
||||
|
||||
## Reference
|
||||
|
||||
- Derivation: `packages/gitea-local-fork.nix`
|
||||
- Flake attr: `flake.nix` → `packages.<system>.gitea-local-fork`
|
||||
- Update script: `scripts/update-gitea-local-fork.sh` *(untracked, local-only)*
|
||||
- Pipelines: `.woodpecker/amd64.yaml`, `.woodpecker/arm64.yaml`
|
||||
- Build driver: `ci/build.py`
|
||||
- Justfile recipes: `just gitea-build`, `just gitea-update`, `just gitea-run`
|
||||
Generated
+517
-9
@@ -1,5 +1,26 @@
|
||||
{
|
||||
"nodes": {
|
||||
"antigravity-nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1780561919,
|
||||
"narHash": "sha256-joj1ZN1q3dBtaPuYXvvFuXJzKt1r8g5FwsO9wgIOhkI=",
|
||||
"owner": "jacopone",
|
||||
"repo": "antigravity-nix",
|
||||
"rev": "dafe38f5bf01ff5e4e105b18d2be9e3d754b6345",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "jacopone",
|
||||
"repo": "antigravity-nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
@@ -18,6 +39,159 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_3": {
|
||||
"inputs": {
|
||||
"systems": "systems_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_4": {
|
||||
"inputs": {
|
||||
"systems": "systems_4"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_5": {
|
||||
"inputs": {
|
||||
"systems": "systems_5"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_6": {
|
||||
"inputs": {
|
||||
"systems": "systems_6"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fleet": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixpkgs-armer": [
|
||||
"parity",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-bim": [
|
||||
"parity",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-ci": [
|
||||
"parity",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-emmett": [
|
||||
"parity",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-howard": [
|
||||
"parity",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-mermaid": [
|
||||
"parity",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-mermaid-gpu": [
|
||||
"parity",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-micron": [
|
||||
"parity",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-projects": [
|
||||
"parity",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1780368078,
|
||||
"narHash": "sha256-tLzA5XveUF4PfuKNz3KuhmVhuME3PX5zvtFa17hhQPU=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "1626405d46ff3595b91c9e2d3ed9399f67c18b83",
|
||||
"revCount": 15,
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/fleet-pins"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/fleet-pins"
|
||||
}
|
||||
},
|
||||
"fleet-pins": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
@@ -45,6 +219,10 @@
|
||||
"fleet-pins",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-mermaid-gpu": [
|
||||
"fleet-pins",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-micron": [
|
||||
"fleet-pins",
|
||||
"nixpkgs"
|
||||
@@ -55,11 +233,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1772635961,
|
||||
"narHash": "sha256-4uQsEqeDCbZp4g5Qk4lb7PDBYcFykHr57FynIc4m/a0=",
|
||||
"lastModified": 1780368078,
|
||||
"narHash": "sha256-tLzA5XveUF4PfuKNz3KuhmVhuME3PX5zvtFa17hhQPU=",
|
||||
"ref": "main",
|
||||
"rev": "513059f6a1b4cea423e600a142a1e6fe8b8e3e4f",
|
||||
"revCount": 7,
|
||||
"rev": "1626405d46ff3595b91c9e2d3ed9399f67c18b83",
|
||||
"revCount": 15,
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/fleet-pins"
|
||||
},
|
||||
@@ -69,30 +247,262 @@
|
||||
"url": "https://git.oleks.space/oleks/fleet-pins"
|
||||
}
|
||||
},
|
||||
"fleet_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
"nixpkgs-armer": [
|
||||
"woodpecker-peek",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-bim": [
|
||||
"woodpecker-peek",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-ci": [
|
||||
"woodpecker-peek",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-emmett": [
|
||||
"woodpecker-peek",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-howard": [
|
||||
"woodpecker-peek",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-mermaid": [
|
||||
"woodpecker-peek",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-mermaid-gpu": [
|
||||
"woodpecker-peek",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-micron": [
|
||||
"woodpecker-peek",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-projects": [
|
||||
"woodpecker-peek",
|
||||
"fleet",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1780368078,
|
||||
"narHash": "sha256-tLzA5XveUF4PfuKNz3KuhmVhuME3PX5zvtFa17hhQPU=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "1626405d46ff3595b91c9e2d3ed9399f67c18b83",
|
||||
"revCount": 15,
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/fleet-pins"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/fleet-pins"
|
||||
}
|
||||
},
|
||||
"hyprspace": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1764820995,
|
||||
"narHash": "sha256-IMa4mvkF0w7OAy+yEzPFYs2an332K30lf5qfUOAS9Cw=",
|
||||
"owner": "KZDKM",
|
||||
"repo": "Hyprspace",
|
||||
"rev": "0467be86b18cfc324fab04afbd40fe9ef80f7fa9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "KZDKM",
|
||||
"repo": "Hyprspace",
|
||||
"rev": "0467be86b18cfc324fab04afbd40fe9ef80f7fa9",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"mcp-chrome": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_3",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1779817034,
|
||||
"narHash": "sha256-uRiqIPmTGeTCeoQtCz+CuC2NXNEv8xoP696HgYrA8fU=",
|
||||
"ref": "main",
|
||||
"rev": "8b5ec6cedfed0d46eff8ee4f6bf3912fd33acc22",
|
||||
"revCount": 210,
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/mcp-chrome"
|
||||
},
|
||||
"original": {
|
||||
"ref": "main",
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/mcp-chrome"
|
||||
}
|
||||
},
|
||||
"nix-deps": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_4",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778863463,
|
||||
"narHash": "sha256-TynOVZBZHiBZAjZbW2sQfaVEh5UXi4UgZ3X0pcu4nYE=",
|
||||
"owner": "manelinux",
|
||||
"repo": "nix-deps",
|
||||
"rev": "5fdd0979c6254969eeb34ff49da2fa73c02972f3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "manelinux",
|
||||
"repo": "nix-deps",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1771848320,
|
||||
"narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=",
|
||||
"lastModified": 1777268161,
|
||||
"narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2fc6539b481e1d2569f25f8799236694180c0993",
|
||||
"rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2fc6539b481e1d2569f25f8799236694180c0993",
|
||||
"rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1777268161,
|
||||
"narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1777268161,
|
||||
"narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"parity": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_5",
|
||||
"fleet": "fleet",
|
||||
"nixpkgs": [
|
||||
"parity",
|
||||
"fleet",
|
||||
"nixpkgs-ci"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1780602315,
|
||||
"narHash": "sha256-msVp9pBMP+I/n0+GGADG1XQembM3F19fUIh79rxqkgs=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "089bd03264d848f8391d9d53d79da0fd97ed1857",
|
||||
"revCount": 13,
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/parity-lib"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/parity-lib"
|
||||
}
|
||||
},
|
||||
"parity_2": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_6",
|
||||
"fleet": [
|
||||
"woodpecker-peek",
|
||||
"fleet"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"woodpecker-peek",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1780472546,
|
||||
"narHash": "sha256-cx+821qtyNZNe9t3ab8qImmeg/rxVzPpZIS45SflrI0=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "d265a79ddb84b297364e6cc3638c9f6b5dc583d7",
|
||||
"revCount": 10,
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/parity-lib"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/parity-lib"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"antigravity-nix": "antigravity-nix",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"fleet-pins": "fleet-pins",
|
||||
"hyprspace": "hyprspace",
|
||||
"mcp-chrome": "mcp-chrome",
|
||||
"nix-deps": "nix-deps",
|
||||
"nixpkgs": [
|
||||
"fleet-pins",
|
||||
"nixpkgs-projects"
|
||||
],
|
||||
"parity": "parity",
|
||||
"stalewood": "stalewood",
|
||||
"woodpecker-peek": "woodpecker-peek"
|
||||
}
|
||||
},
|
||||
"stalewood": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1779473107,
|
||||
"narHash": "sha256-PjX2So2b4xPuXZCkgTb0eXnV9wIELQ7D0EPlDWD0NBw=",
|
||||
"owner": "retif",
|
||||
"repo": "stalewood",
|
||||
"rev": "415f3de6c23f662dabceaf0cba540a289c609174",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "retif",
|
||||
"repo": "stalewood",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
@@ -109,6 +519,104 @@
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_3": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_4": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_5": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_6": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"woodpecker-peek": {
|
||||
"inputs": {
|
||||
"fleet": "fleet_2",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"parity": "parity_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1780743249,
|
||||
"narHash": "sha256-qVrrS69mzq3Fo8+pFtFukDogAl+2qQl5ib/7DJi1oRw=",
|
||||
"ref": "main",
|
||||
"rev": "8db46bec6c1dfed1c411df6b139d60c87d739696",
|
||||
"revCount": 31,
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/woodpecker-peek"
|
||||
},
|
||||
"original": {
|
||||
"ref": "main",
|
||||
"type": "git",
|
||||
"url": "https://git.oleks.space/oleks/woodpecker-peek"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
||||
@@ -5,6 +5,57 @@
|
||||
fleet-pins.url = "git+https://git.oleks.space/oleks/fleet-pins?ref=main";
|
||||
nixpkgs.follows = "fleet-pins/nixpkgs-projects";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
# Shared per-archetype parity publish-app builders (cluster#104, emmett#44).
|
||||
# flake-hub is an ATTIC-CLOSURE repo: it builds package closures and warms
|
||||
# the Attic cache with them — there is NO registry artifact.
|
||||
# mkAtticClosurePublish makes that archetype explicit to pipeline-doctor.
|
||||
parity.url = "git+https://git.oleks.space/oleks/parity-lib";
|
||||
|
||||
# Hyprspace source; no flake.nix in the repo so we consume it as raw src.
|
||||
# Pin tracks the last v0.52-compatible commit of Hyprspace.
|
||||
hyprspace = {
|
||||
url = "github:KZDKM/Hyprspace/0467be86b18cfc324fab04afbd40fe9ef80f7fa9";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
# Google Antigravity packaging. Upstream auto-updates daily; we re-expose
|
||||
# the overlay and build into our attic cache so emmett pulls from there.
|
||||
antigravity-nix = {
|
||||
url = "github:jacopone/antigravity-nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
# nix-deps: "see the real cost of installing packages on NixOS".
|
||||
# Re-exposed through our overlay so CI builds it into attic.
|
||||
nix-deps = {
|
||||
url = "github:manelinux/nix-deps";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
# stalewood — find/reap merged git worktrees. Ships its own flake;
|
||||
# re-expose its package (mirrors the nix-deps pattern).
|
||||
stalewood = {
|
||||
url = "github:retif/stalewood";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
# woodpecker-peek — tray app for Woodpecker CI (on git.oleks.space).
|
||||
# Re-exposed so flake-hub CI warms attic and emmett pulls cached.
|
||||
woodpecker-peek = {
|
||||
url = "git+https://git.oleks.space/oleks/woodpecker-peek?ref=main";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
# mcp-chrome — Chrome MCP server + extension (English-localized fork of
|
||||
# hangwin/mcp-chrome). Re-exposes the from-source wasm-simd worker (proven
|
||||
# green, ~22 s) and the full chrome-mcp-extension build. The extension
|
||||
# target is KNOWN-BROKEN under nix-daemon at this pin (see oleks/mcp-chrome
|
||||
# issue #1 close comment); expect attic to miss it until that's resolved.
|
||||
mcp-chrome = {
|
||||
url = "git+https://git.oleks.space/oleks/mcp-chrome?ref=main";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
@@ -13,6 +64,13 @@
|
||||
nixpkgs,
|
||||
fleet-pins,
|
||||
flake-utils,
|
||||
parity,
|
||||
hyprspace,
|
||||
antigravity-nix,
|
||||
nix-deps,
|
||||
stalewood,
|
||||
woodpecker-peek,
|
||||
mcp-chrome,
|
||||
...
|
||||
}:
|
||||
let
|
||||
@@ -27,15 +85,171 @@
|
||||
"s390x-linux" = "s390x-linux";
|
||||
};
|
||||
|
||||
mkPackages = pkgs: {
|
||||
hello-world = pkgs.callPackage ./packages/hello-world.nix { };
|
||||
xonsh = pkgs.callPackage ./packages/xonsh.nix {
|
||||
xonsh-unwrapped = import ./packages/xonsh-unwrapped.nix {
|
||||
inherit (pkgs) lib python3Packages fetchFromGitHub;
|
||||
mkPackages =
|
||||
pkgs:
|
||||
let
|
||||
sys = pkgs.stdenv.hostPlatform.system;
|
||||
# Antigravity ships a Google-provided x86_64/aarch64 Linux binary.
|
||||
# Skip it for cross targets (e.g. s390x) where it can't run anyway.
|
||||
supportsAntigravity = sys == "x86_64-linux" || sys == "aarch64-linux";
|
||||
xontribs = import ./packages/xontribs.nix {
|
||||
inherit (pkgs) python3Packages fetchurl;
|
||||
};
|
||||
in
|
||||
{
|
||||
hello-world = pkgs.callPackage ./packages/hello-world.nix { };
|
||||
geesefs = pkgs.callPackage ./packages/geesefs.nix { };
|
||||
metamcp = pkgs.callPackage ./packages/metamcp.nix { };
|
||||
xonsh = pkgs.callPackage ./packages/xonsh.nix {
|
||||
xonsh-unwrapped = import ./packages/xonsh-unwrapped.nix {
|
||||
inherit (pkgs) lib python3Packages fetchFromGitHub;
|
||||
};
|
||||
};
|
||||
}
|
||||
# Native-only packages — skip on s390x cross (gitea's pnpm step and
|
||||
# cgo+sqlite link don't cross-compile cleanly).
|
||||
// nixpkgs.lib.optionalAttrs (sys == "x86_64-linux" || sys == "aarch64-linux") {
|
||||
gitea-local-fork =
|
||||
let
|
||||
# Our fork's go.mod requires Go 1.26.3; nixpkgs at this pin has
|
||||
# only 1.26.0 (and unstable has 1.26.2). Bump the package's src.
|
||||
go = pkgs.go_1_26.overrideAttrs (_: rec {
|
||||
version = "1.26.3";
|
||||
src = pkgs.fetchurl {
|
||||
url = "https://go.dev/dl/go${version}.src.tar.gz";
|
||||
hash = "sha256-HGRoddCqh5kTMYTtV895/yS97+jIggRwYCqdPW2Rkrg=";
|
||||
};
|
||||
});
|
||||
in
|
||||
pkgs.callPackage ./packages/gitea-local-fork.nix {
|
||||
buildGoModule = pkgs.buildGoModule.override { inherit go; };
|
||||
};
|
||||
}
|
||||
# Xontribs: pass into `programs.xonsh.extraPackages` or
|
||||
# `pkgs.xonsh.override { extraPackages = ps: [...]; }`.
|
||||
// xontribs
|
||||
# Antigravity: re-expose jacopone/antigravity-nix's outputs so emmett
|
||||
# consumes a single flake-hub input and our CI builds into attic.
|
||||
// nixpkgs.lib.optionalAttrs supportsAntigravity {
|
||||
inherit (antigravity-nix.packages.${sys})
|
||||
google-antigravity
|
||||
google-antigravity-no-fhs
|
||||
google-antigravity-ide
|
||||
google-antigravity-ide-no-fhs
|
||||
google-antigravity-cli
|
||||
;
|
||||
# nix-deps' flake only outputs eachDefaultSystem (no s390x), so
|
||||
# gate it on the same native x86_64/aarch64 condition.
|
||||
nix-deps = nix-deps.packages.${sys}.default;
|
||||
# stalewood — re-exposed from its own flake. No s390x output,
|
||||
# so it rides the same native-only gate.
|
||||
stalewood = stalewood.packages.${sys}.default;
|
||||
# woodpecker-peek — same pattern; consumers (emmett) read the
|
||||
# attic-cached binary via flake-hub's overlay, then set
|
||||
# services.woodpecker-peek.package = pkgs.woodpecker-peek;.
|
||||
woodpecker-peek = woodpecker-peek.packages.${sys}.default;
|
||||
# mcp-chrome — Rust→wasm worker (proven green) plus the full
|
||||
# chrome-mcp-extension build. The latter is KNOWN-BROKEN under
|
||||
# nix-daemon at this pin; flake-hub CI will miss the cache on it
|
||||
# until upstream resolves that. wasm-simd alone is what consumers
|
||||
# actually pull cached today (commits 9534234, b276465 in
|
||||
# oleks/mcp-chrome sync the built wasm back into the tree).
|
||||
mcp-chrome-wasm-simd = mcp-chrome.packages.${sys}.wasm-simd;
|
||||
mcp-chrome-extension = mcp-chrome.packages.${sys}.chrome-mcp-extension;
|
||||
};
|
||||
|
||||
# Overlay providing Hyprspace. Requires `pkgs.hyprland` to be present
|
||||
# (consumer applies the Hyprland flake's overlay first). Kept out of
|
||||
# `mkPackages` because standalone `nix build .#hyprspace` has no
|
||||
# Hyprland in scope.
|
||||
hyprspaceOverlay = final: _prev: {
|
||||
hyprspace = final.callPackage ./packages/hyprspace.nix {
|
||||
src = hyprspace;
|
||||
};
|
||||
};
|
||||
|
||||
# Rustc bootstrap: symlink_file panics with "File exists" during
|
||||
# s390x cross-compilation. Multiple call sites use t!(symlink_file(...)).
|
||||
# Patch the symlink_file method body to remove an existing dest first.
|
||||
# Exported as overlays.s390xRustcSymlink so nixos-ci consumes this single
|
||||
# definition (and the patch travels with it) instead of duplicating it.
|
||||
s390xRustcSymlinkOverlay =
|
||||
final: prev:
|
||||
let
|
||||
patchedRustcUnwrapped = prev.rustc-unwrapped.overrideAttrs (old: {
|
||||
patches = (old.patches or [ ]) ++ [
|
||||
./patches/rustc-symlink-file-eexist.patch
|
||||
];
|
||||
});
|
||||
in
|
||||
{
|
||||
rustc-unwrapped = patchedRustcUnwrapped;
|
||||
rustc = prev.rustc.override {
|
||||
rustc-unwrapped = patchedRustcUnwrapped;
|
||||
};
|
||||
};
|
||||
|
||||
# Overlays needed for s390x cross-compilation of attic-client
|
||||
s390xOverlays = [
|
||||
# OpenSSL s390x assembly uses z10 instructions (cijne) that the
|
||||
# nix-bootstrapped assembler doesn't recognize
|
||||
(final: prev: {
|
||||
openssl = prev.openssl.overrideAttrs (old: {
|
||||
configureFlags = (old.configureFlags or [ ]) ++ [ "no-asm" ];
|
||||
});
|
||||
})
|
||||
# musl doesn't support s390x long double (IBM double-double format:
|
||||
# LDBL_MANT_DIG=106, sizeof=16). Make musl appear unavailable so
|
||||
# busybox-sandbox-shell uses glibc static instead.
|
||||
(final: prev: {
|
||||
busybox-sandbox-shell = prev.busybox-sandbox-shell.override {
|
||||
musl = prev.musl // {
|
||||
meta = prev.musl.meta // {
|
||||
platforms = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
# libarchive tests fail in k8s pod sandboxes
|
||||
(final: prev: {
|
||||
libarchive = prev.libarchive.overrideAttrs (_: {
|
||||
doCheck = false;
|
||||
});
|
||||
})
|
||||
# nix 2.28 is a direct dependency of attic-client — disable tests
|
||||
# and fix missing RPATH for boost/zstd/libarchive
|
||||
(final: prev: {
|
||||
nixVersions = prev.nixVersions // {
|
||||
nix_2_28 = prev.nixVersions.nix_2_28.overrideAttrs (old: {
|
||||
doCheck = false;
|
||||
doInstallCheck = false;
|
||||
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ final.patchelf ];
|
||||
postFixup = (old.postFixup or "") + ''
|
||||
for rpath in ${final.boost}/lib ${final.zstd.out}/lib ${final.libarchive.out}/lib; do
|
||||
patchelf --add-rpath "$rpath" $out/bin/nix
|
||||
for lib in $out/lib/lib*.so; do
|
||||
[ -f "$lib" ] && patchelf --add-rpath "$rpath" "$lib"
|
||||
done
|
||||
done
|
||||
'';
|
||||
});
|
||||
};
|
||||
})
|
||||
# LLVM test failures in CI pod environment — override libllvm
|
||||
# (not llvm) so it propagates through rustc bootstrap chain
|
||||
(final: prev: {
|
||||
llvmPackages = prev.llvmPackages // {
|
||||
libllvm = prev.llvmPackages.libllvm.overrideAttrs (old: {
|
||||
doCheck = false;
|
||||
doInstallCheck = false;
|
||||
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ prev.gitMinimal ];
|
||||
});
|
||||
};
|
||||
})
|
||||
# Rustc symlink_file "File exists" fix (defined + exported above).
|
||||
s390xRustcSymlinkOverlay
|
||||
];
|
||||
|
||||
# Native builds
|
||||
native = flake-utils.lib.eachSystem buildSystems (
|
||||
system:
|
||||
@@ -48,7 +262,7 @@
|
||||
default = packages.hello-world;
|
||||
};
|
||||
|
||||
overlays.default = final: prev: mkPackages final;
|
||||
formatter = pkgs.nixfmt-rfc-style;
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
name = "oleks-hub-shell";
|
||||
@@ -69,6 +283,11 @@
|
||||
builtins.map (
|
||||
target:
|
||||
let
|
||||
targetOverlays =
|
||||
{
|
||||
"s390x-linux" = s390xOverlays;
|
||||
}
|
||||
.${target} or [ ];
|
||||
pkgs = import nixpkgs {
|
||||
system = "x86_64-linux";
|
||||
crossSystem.config =
|
||||
@@ -78,14 +297,33 @@
|
||||
}
|
||||
.${target}
|
||||
}.config;
|
||||
overlays = targetOverlays;
|
||||
};
|
||||
packages = mkPackages pkgs;
|
||||
crossOnlyPackages =
|
||||
{
|
||||
"s390x-linux" = {
|
||||
inherit (pkgs)
|
||||
attic-client
|
||||
rustc
|
||||
cargo
|
||||
rustfmt
|
||||
sccache
|
||||
mold
|
||||
;
|
||||
nix = pkgs.nixVersions.nix_2_28;
|
||||
};
|
||||
}
|
||||
.${target} or { };
|
||||
in
|
||||
{
|
||||
name = target;
|
||||
value = packages // {
|
||||
default = packages.hello-world;
|
||||
};
|
||||
value =
|
||||
packages
|
||||
// crossOnlyPackages
|
||||
// {
|
||||
default = packages.hello-world;
|
||||
};
|
||||
}
|
||||
) (builtins.attrNames crossTargets)
|
||||
);
|
||||
@@ -93,5 +331,99 @@
|
||||
native
|
||||
// {
|
||||
packages = (native.packages or { }) // cross;
|
||||
overlays = {
|
||||
default = final: _prev: mkPackages final;
|
||||
gcc15-fixes = import ./overlays/gcc15-fixes.nix;
|
||||
hyprspace = hyprspaceOverlay;
|
||||
# Single home for the s390x rustc symlink_file patch overlay so
|
||||
# consumers (nixos-ci) don't re-declare it + the patch file.
|
||||
s390xRustcSymlink = s390xRustcSymlinkOverlay;
|
||||
};
|
||||
|
||||
# `nix run .#<app>` — local-parity entrypoints (emmett#44, cluster#192,
|
||||
# ATTIC-CLOSURE archetype, cluster#104). The stage/publish/push-staged
|
||||
# apps come straight from parity-lib's mkAtticClosurePublish, so CI and a
|
||||
# local run share one audited implementation and cannot drift. There is NO
|
||||
# registry artifact — these apps build the package closures and warm the
|
||||
# Attic cache with them.
|
||||
#
|
||||
# TWO HALVES (emmett#44): STAGE `nix build`s every package in the arch's
|
||||
# list into the local /nix store (cluster-independent, runs on emmett);
|
||||
# PUBLISH additionally `attic push`es each closure to the cache that lives
|
||||
# next to the cluster. Local runs DRY-RUN (stage + show the pushes) unless
|
||||
# `--publish`/PUBLISH=1.
|
||||
#
|
||||
# nix run .#stage-x86_64-linux stage amd64 closures, no publish
|
||||
# nix run .#publish stage amd64 then push if PUBLISH=1
|
||||
# nix run .#publish -- --publish actually push to attic
|
||||
# nix run .#push-staged replay already-staged amd64 paths
|
||||
#
|
||||
# arm64 leg: aarch64-linux cannot be built on emmett (linux/amd64) and the
|
||||
# native packages have no cross path, so it MUST run on an aarch64 node
|
||||
# (.woodpecker/arm64.yaml runs `PUBLISH=1 nix run .#publish-aarch64-linux`).
|
||||
#
|
||||
# The package set per arch mirrors flake-hub's CI warm list (was
|
||||
# ci/publish.py packages_for): the always-on core plus arch-conditional
|
||||
# extras. Cross-only/known-broken targets stay out (see comments).
|
||||
apps =
|
||||
let
|
||||
# The Attic cache token lives at `pass infra/attic/ci_token` and the
|
||||
# cache is reached via the armer hairpin endpoint — preserve both so
|
||||
# the push is byte-for-byte the pre-parity behaviour.
|
||||
atticEndpoint = "https://nix-cache-upload.oleks.space";
|
||||
atticPass = "infra/attic/ci_token";
|
||||
|
||||
# Package names to warm into Attic for a given native arch.
|
||||
# Native arches only (amd64/arm64); mcp-chrome-extension stays OUT
|
||||
# (known-broken under nix-daemon at this pin, see oleks/mcp-chrome #1).
|
||||
packageNamesFor =
|
||||
arch:
|
||||
[
|
||||
"hello-world"
|
||||
"geesefs"
|
||||
"xonsh"
|
||||
]
|
||||
++ nixpkgs.lib.optionals (arch == "x86_64-linux" || arch == "aarch64-linux") [
|
||||
"woodpecker-peek"
|
||||
"mcp-chrome-wasm-simd"
|
||||
"gitea-local-fork"
|
||||
"google-antigravity"
|
||||
"google-antigravity-no-fhs"
|
||||
"google-antigravity-ide"
|
||||
"google-antigravity-ide-no-fhs"
|
||||
"google-antigravity-cli"
|
||||
];
|
||||
|
||||
drvsFor = arch: map (n: self.packages.${arch}.${n}) (packageNamesFor arch);
|
||||
|
||||
buildersFor = arch: parity.lib.mkParityBuilders (import nixpkgs { system = arch; });
|
||||
|
||||
atticAppsFor =
|
||||
arch:
|
||||
(buildersFor arch).mkAtticClosurePublish {
|
||||
drvs = drvsFor arch;
|
||||
inherit arch;
|
||||
endpoint = atticEndpoint;
|
||||
passEntry = atticPass;
|
||||
};
|
||||
|
||||
amd64Apps = atticAppsFor "x86_64-linux";
|
||||
arm64Apps = atticAppsFor "aarch64-linux";
|
||||
in
|
||||
nixpkgs.lib.genAttrs buildSystems (system:
|
||||
# amd64 is the emmett-buildable arch: expose its stage/publish/
|
||||
# push-staged plus the top-level `publish`. Also surface the arm64
|
||||
# stage/publish under their arch-suffixed names for the node-bound
|
||||
# CI leg (.woodpecker/arm64.yaml).
|
||||
{
|
||||
inherit (amd64Apps)
|
||||
"stage-x86_64-linux"
|
||||
"publish-x86_64-linux"
|
||||
"publish"
|
||||
"push-staged"
|
||||
;
|
||||
"stage-aarch64-linux" = arm64Apps."stage-aarch64-linux";
|
||||
"publish-aarch64-linux" = arm64Apps."publish-aarch64-linux";
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# ── Antigravity ────────────────────────────────────────────
|
||||
|
||||
# Run antigravity (FHS-wrapped) from the attic cache
|
||||
antigravity-run:
|
||||
nix run \
|
||||
--extra-substituters "https://nix-cache-custom.oleks.space/attic-infra-cache-k3s-1" \
|
||||
--extra-trusted-public-keys "attic-infra-cache-k3s-1:qYSNK3DmttQXCFqn1t50qoWGtQNPRFWq9mgQjD05DeU=" \
|
||||
git+https://git.oleks.space/oleks/flake-hub#google-antigravity
|
||||
|
||||
# Run antigravity (non-FHS variant) from the attic cache
|
||||
antigravity-run-no-fhs:
|
||||
nix run \
|
||||
--extra-substituters "https://nix-cache-custom.oleks.space/attic-infra-cache-k3s-1" \
|
||||
--extra-trusted-public-keys "attic-infra-cache-k3s-1:qYSNK3DmttQXCFqn1t50qoWGtQNPRFWq9mgQjD05DeU=" \
|
||||
git+https://git.oleks.space/oleks/flake-hub#google-antigravity-no-fhs
|
||||
|
||||
# Build antigravity (FHS-wrapped)
|
||||
antigravity-build:
|
||||
nix build .#google-antigravity --print-build-logs
|
||||
|
||||
# Build antigravity (non-FHS variant)
|
||||
antigravity-build-no-fhs:
|
||||
nix build .#google-antigravity-no-fhs --print-build-logs
|
||||
|
||||
# Pull the latest jacopone/antigravity-nix pin
|
||||
antigravity-update:
|
||||
nix flake update antigravity-nix
|
||||
|
||||
# ── gitea-local-fork ───────────────────────────────────────
|
||||
# Builds Oleks's local gitea fork (/home/oleks/projects/gitea, branch oleks/main).
|
||||
|
||||
# Build the gitea fork (compiles Go 1.26.3 first time; ~5-8 min cold)
|
||||
gitea-build:
|
||||
nix build .#gitea-local-fork --print-build-logs
|
||||
|
||||
# Refresh gitea-local-fork: bump rev/hashes/version, retag the gitea fork, push both repos
|
||||
gitea-update:
|
||||
nix run nixpkgs#nix-update -- \
|
||||
--flake gitea-local-fork \
|
||||
--use-update-script
|
||||
|
||||
# Run gitea from the attic-cached build (no local recompile)
|
||||
gitea-run:
|
||||
nix run \
|
||||
--extra-substituters "https://nix-cache-custom.oleks.space/attic-infra-cache-k3s-1" \
|
||||
--extra-trusted-public-keys "attic-infra-cache-k3s-1:qYSNK3DmttQXCFqn1t50qoWGtQNPRFWq9mgQjD05DeU=" \
|
||||
git+https://git.oleks.space/oleks/flake-hub#gitea-local-fork -- --help
|
||||
@@ -0,0 +1,43 @@
|
||||
# Narrow fixes for packages that fail under the fleet's pinned nixpkgs
|
||||
# with GCC 15 / current upstream breakage. Remove entries as upstream fixes
|
||||
# land so consumers can drop them on the next pin bump.
|
||||
final: prev: {
|
||||
# hotdoc's bundled cmark declares `project(cmark VERSION 0.28.3)` without
|
||||
# LANGUAGES, which makes CMake probe CXX; GCC 15 fails the probe. Adding
|
||||
# LANGUAGES C skips the CXX check.
|
||||
hotdoc = prev.hotdoc.overrideAttrs (old: {
|
||||
postPatch = (old.postPatch or "") + ''
|
||||
substituteInPlace cmark/CMakeLists.txt \
|
||||
--replace-fail "project(cmark VERSION 0.28.3)" "project(cmark VERSION 0.28.3 LANGUAGES C)"
|
||||
'';
|
||||
});
|
||||
|
||||
# kitty's bundled base64 lib has incompatible pointer types that GCC 15
|
||||
# promotes from warning to error.
|
||||
kitty = prev.kitty.overrideAttrs (old: {
|
||||
env = (old.env or { }) // {
|
||||
NIX_CFLAGS_COMPILE = (old.env.NIX_CFLAGS_COMPILE or "") + " -Wno-error=incompatible-pointer-types";
|
||||
};
|
||||
});
|
||||
|
||||
# Skip D-Bus-dependent checks that fail under remote-builder sandboxes.
|
||||
libsecret = prev.libsecret.overrideAttrs (_: {
|
||||
doCheck = false;
|
||||
});
|
||||
xdg-desktop-portal = prev.xdg-desktop-portal.overrideAttrs (_: {
|
||||
doCheck = false;
|
||||
});
|
||||
|
||||
# afdko has two pre-existing test failures that block
|
||||
# nototools → noto-fonts-color-emoji.
|
||||
pythonPackagesExtensions = (prev.pythonPackagesExtensions or [ ]) ++ [
|
||||
(pyFinal: pyPrev: {
|
||||
afdko = pyPrev.afdko.overrideAttrs (old: {
|
||||
disabledTests = (old.disabledTests or [ ]) ++ [
|
||||
"test_non_varying_glyphs_bug356"
|
||||
"test_ufo_contentsplist_parsing"
|
||||
];
|
||||
});
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
lib,
|
||||
buildGoModule,
|
||||
fetchFromGitHub,
|
||||
}:
|
||||
|
||||
buildGoModule rec {
|
||||
pname = "geesefs";
|
||||
version = "0.43.5";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "yandex-cloud";
|
||||
repo = "geesefs";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-cfeL7fnxS+UFUlRVLiO09GHuEOvkiH5PkKcoH+jNRhY=";
|
||||
};
|
||||
|
||||
proxyVendor = true;
|
||||
vendorHash = "sha256-p+shpYrPxYLXpW6A4a/5qM90KH+pcMCqZOPoYTE77f0=";
|
||||
|
||||
subPackages = [ "." ];
|
||||
|
||||
meta = {
|
||||
description = "FUSE FS implementation over S3";
|
||||
homepage = "https://github.com/yandex-cloud/geesefs";
|
||||
license = [ lib.licenses.mit ];
|
||||
platforms = lib.platforms.unix;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
{
|
||||
lib,
|
||||
buildGoModule,
|
||||
fetchgit,
|
||||
makeWrapper,
|
||||
git,
|
||||
bash,
|
||||
coreutils,
|
||||
gzip,
|
||||
nodejs,
|
||||
openssh,
|
||||
fetchPnpmDeps,
|
||||
pnpmConfigHook,
|
||||
pnpm,
|
||||
stdenv,
|
||||
sqliteSupport ? true,
|
||||
}:
|
||||
|
||||
# Custom Gitea built from Oleks's local fork.
|
||||
#
|
||||
# Fork repo (local working copy): /home/oleks/projects/gitea
|
||||
# Fork remote: https://git.oleks.space/oleks/gitea.git
|
||||
# Tracked branch: oleks/main (the fork's integration tip;
|
||||
# formerly `feat/projects-api`, renamed
|
||||
# 2026-05-13. Carries upstream/main +
|
||||
# PR #37518 Projects REST API + a few
|
||||
# fork-local commits.)
|
||||
# Pin discipline: nix-update bumps `rev` and tags the fork
|
||||
# at `v<version>` (see passthru.updateScript
|
||||
# below) so the rev stays reachable even
|
||||
# after future force-pushes / rebases.
|
||||
#
|
||||
# Mirrors the structure of nixpkgs' gitea derivation (frontend pnpm sub-drv
|
||||
# + buildGoModule with go:embed of public/) but with our fork as src.
|
||||
|
||||
let
|
||||
pname = "gitea-local-fork";
|
||||
version = "1.26.0-unstable-2026-05-17.1";
|
||||
|
||||
src = fetchgit {
|
||||
url = "https://git.oleks.space/oleks/gitea.git";
|
||||
rev = "c45ea82fd1408bdb7170308080e0eb0ffdea85b2";
|
||||
hash = "sha256-AZKXcweNi6JfEgpfuj+agBNLWrQ50fTpxUalNv3XC9A=";
|
||||
};
|
||||
|
||||
# Gitea's package.json requires pnpm >= 11; nixpkgs ships pnpm 10. The
|
||||
# lockfile is v9 (forward-compatible), so strip the engine constraint and
|
||||
# the packageManager preference before any pnpm tool sees the source.
|
||||
# We only patch the source the frontend sees; the Go build doesn't care.
|
||||
frontendSrc = stdenv.mkDerivation {
|
||||
pname = "${pname}-frontend-src";
|
||||
inherit version src;
|
||||
dontConfigure = true;
|
||||
dontBuild = true;
|
||||
installPhase = ''
|
||||
cp -R . $out
|
||||
chmod -R +w $out
|
||||
${nodejs}/bin/node -e '
|
||||
const fs = require("fs");
|
||||
const path = process.argv[1];
|
||||
const pkg = JSON.parse(fs.readFileSync(path, "utf8"));
|
||||
if (pkg.engines) delete pkg.engines.pnpm;
|
||||
delete pkg.packageManager;
|
||||
fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n");
|
||||
' "$out/package.json"
|
||||
'';
|
||||
};
|
||||
|
||||
frontend = stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "${pname}-frontend";
|
||||
inherit version;
|
||||
src = frontendSrc;
|
||||
|
||||
pnpmDeps = fetchPnpmDeps {
|
||||
inherit (finalAttrs) pname version src;
|
||||
fetcherVersion = 3;
|
||||
hash = "sha256-TisyRChMRFm5GgdW2wXPHS7NIPu04SSd97oVEgKJ2dI=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
nodejs
|
||||
pnpmConfigHook
|
||||
pnpm
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
make frontend
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -R public $out/
|
||||
'';
|
||||
});
|
||||
in
|
||||
buildGoModule {
|
||||
inherit pname version src;
|
||||
|
||||
proxyVendor = true;
|
||||
vendorHash = "sha256-TxLmmeMinazGmIx94iQvpsncWKXb+42ddZcxbwayVgk=";
|
||||
|
||||
outputs = [
|
||||
"out"
|
||||
"data"
|
||||
];
|
||||
|
||||
subPackages = [ "." ];
|
||||
|
||||
nativeBuildInputs = [ makeWrapper ];
|
||||
|
||||
tags = lib.optionals sqliteSupport [
|
||||
"sqlite"
|
||||
"sqlite_unlock_notify"
|
||||
];
|
||||
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X main.Version=${version}"
|
||||
"-X 'main.Tags=${
|
||||
lib.concatStringsSep " " (
|
||||
lib.optionals sqliteSupport [
|
||||
"sqlite"
|
||||
"sqlite_unlock_notify"
|
||||
]
|
||||
)
|
||||
}'"
|
||||
];
|
||||
|
||||
postInstall = ''
|
||||
mkdir $data
|
||||
ln -s ${frontend}/public $data/public
|
||||
cp -R ./{templates,options} $data
|
||||
mkdir -p $out
|
||||
cp -R ./options/locale $out/locale
|
||||
|
||||
wrapProgram $out/bin/gitea \
|
||||
--prefix PATH : ${
|
||||
lib.makeBinPath [
|
||||
bash
|
||||
coreutils
|
||||
git
|
||||
gzip
|
||||
openssh
|
||||
]
|
||||
}
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
# Expose hashes nix-update needs to find when iterating.
|
||||
inherit (frontend) pnpmDeps;
|
||||
|
||||
# Custom update flow: refresh hashes + retag the gitea fork + push.
|
||||
# Invoke with `nix-update --use-update-script gitea-local-fork` or
|
||||
# `just gitea-update` (which passes `--use-update-script`).
|
||||
updateScript = ../scripts/update-gitea-local-fork.sh;
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "Gitea fork with Projects REST API (upstream PR #37518)";
|
||||
homepage = "https://git.oleks.space/oleks/gitea";
|
||||
license = lib.licenses.mit;
|
||||
mainProgram = "gitea";
|
||||
platforms = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# Rebuilds the Hyprspace plugin against the consumer's Hyprland. Requires
|
||||
# `pkgs.hyprland` to already be in scope (consumers apply the upstream
|
||||
# Hyprland flake overlay first). `src` comes from the hyprspace flake
|
||||
# input pinned in ../flake.nix.
|
||||
{
|
||||
gcc14Stdenv,
|
||||
hyprland,
|
||||
src,
|
||||
}:
|
||||
|
||||
gcc14Stdenv.mkDerivation {
|
||||
pname = "Hyprspace";
|
||||
version = "unstable-${src.shortRev or "dirty"}";
|
||||
inherit src;
|
||||
inherit (hyprland) nativeBuildInputs;
|
||||
buildInputs = [ hyprland ] ++ hyprland.buildInputs;
|
||||
dontUseCmakeConfigure = true;
|
||||
installFlags = [ "PREFIX=$(out)" ];
|
||||
postInstall = ''
|
||||
mv $out/lib/Hyprspace.so $out/lib/libHyprspace.so
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
diff -urN metamcp-2.4.22.orig/apps/backend/src/index.ts metamcp-2.4.22/apps/backend/src/index.ts
|
||||
--- a/apps/backend/src/index.ts 2026-05-23 23:45:36.313040968 +0300
|
||||
+++ b/apps/backend/src/index.ts 2026-05-23 23:50:43.286804066 +0300
|
||||
@@ -1,6 +1,14 @@
|
||||
import express from "express";
|
||||
|
||||
import { auth } from "./auth";
|
||||
+import {
|
||||
+ mcpCancellationTotal,
|
||||
+ mcpHopDurationSeconds,
|
||||
+ mcpTraceparentSynthesizedTotal,
|
||||
+ mcpUncaughtThrowTotal,
|
||||
+ renderMetricsExposition,
|
||||
+} from "./lib/observability/metrics";
|
||||
+import { bindFromHeader, HOP_NAME, traceLog, traceStore } from "./lib/observability/trace";
|
||||
import { initializeIdleServers } from "./lib/startup";
|
||||
import mcpProxyRouter from "./routers/mcp-proxy";
|
||||
import oauthRouter from "./routers/oauth";
|
||||
@@ -9,6 +17,42 @@
|
||||
|
||||
const app = express();
|
||||
|
||||
+// cluster#49 — bind W3C trace context for every request and stamp every
|
||||
+// downstream log via traceStore.run(). cluster#50 — observe wall time
|
||||
+// per hop and count cancellations.
|
||||
+app.use((req, res, next) => {
|
||||
+ const raw = req.headers.traceparent as string | undefined;
|
||||
+ const { ctx, event } = bindFromHeader(raw);
|
||||
+ if (event === "traceparent_synthesized") {
|
||||
+ mcpTraceparentSynthesizedTotal.inc({ hop: HOP_NAME });
|
||||
+ }
|
||||
+ const t0 = process.hrtime.bigint();
|
||||
+ let status: "success" | "error" | "cancelled" = "success";
|
||||
+ res.on("close", () => {
|
||||
+ // res.writableEnded is true when we finished writing; false here
|
||||
+ // means the downstream client hung up mid-response.
|
||||
+ if (!res.writableEnded) {
|
||||
+ status = "cancelled";
|
||||
+ mcpCancellationTotal.inc({ hop: HOP_NAME, source: "downstream" });
|
||||
+ }
|
||||
+ const dt = Number(process.hrtime.bigint() - t0) / 1e9;
|
||||
+ mcpHopDurationSeconds.observe(
|
||||
+ { hop: HOP_NAME, tool_name: "", status },
|
||||
+ dt,
|
||||
+ );
|
||||
+ });
|
||||
+ traceStore.run(ctx, () => {
|
||||
+ if (event) traceLog("info", `event=${event}`);
|
||||
+ next();
|
||||
+ });
|
||||
+});
|
||||
+
|
||||
+// cluster#50 — Prometheus exposition. Schema in Specs/mcp-metric-schema.
|
||||
+app.get("/metrics", (_req, res) => {
|
||||
+ res.set("Content-Type", "text/plain; version=0.0.4");
|
||||
+ res.send(renderMetricsExposition());
|
||||
+});
|
||||
+
|
||||
// Global JSON middleware for non-proxy routes
|
||||
app.use((req, res, next) => {
|
||||
if (req.path.startsWith("/mcp-proxy/") || req.path.startsWith("/metamcp/")) {
|
||||
@@ -108,3 +152,39 @@
|
||||
status: "ok",
|
||||
});
|
||||
});
|
||||
+
|
||||
+// cluster#50 — Express error tail. Any unhandled error in middleware /
|
||||
+// handlers funnels here; we count it so the uncaught-throw alert fires.
|
||||
+app.use(
|
||||
+ (
|
||||
+ err: unknown,
|
||||
+ _req: express.Request,
|
||||
+ res: express.Response,
|
||||
+ next: express.NextFunction,
|
||||
+ ) => {
|
||||
+ mcpUncaughtThrowTotal.inc({ hop: HOP_NAME, component: "express" });
|
||||
+ traceLog("error", "event=uncaught_throw", {
|
||||
+ error: err instanceof Error ? err.message : String(err),
|
||||
+ });
|
||||
+ if (res.headersSent) return next(err);
|
||||
+ res.status(500).json({ error: "internal error" });
|
||||
+ },
|
||||
+);
|
||||
+
|
||||
+// cluster#50 — last-resort process traps so unhandled rejections /
|
||||
+// uncaught exceptions still feed the alert. Counting only; existing
|
||||
+// behaviour (log + continue) is preserved.
|
||||
+process.on("unhandledRejection", (err) => {
|
||||
+ mcpUncaughtThrowTotal.inc({ hop: HOP_NAME, component: "unhandledRejection" });
|
||||
+ traceLog("error", "event=uncaught_throw", {
|
||||
+ component: "unhandledRejection",
|
||||
+ error: err instanceof Error ? err.message : String(err),
|
||||
+ });
|
||||
+});
|
||||
+process.on("uncaughtException", (err) => {
|
||||
+ mcpUncaughtThrowTotal.inc({ hop: HOP_NAME, component: "uncaughtException" });
|
||||
+ traceLog("error", "event=uncaught_throw", {
|
||||
+ component: "uncaughtException",
|
||||
+ error: err.message,
|
||||
+ });
|
||||
+});
|
||||
diff -urN metamcp-2.4.22.orig/apps/backend/src/lib/observability/metrics.ts metamcp-2.4.22/apps/backend/src/lib/observability/metrics.ts
|
||||
--- a/apps/backend/src/lib/observability/metrics.ts 1970-01-01 03:00:00.000000000 +0300
|
||||
+++ b/apps/backend/src/lib/observability/metrics.ts 2026-05-23 23:49:42.080145070 +0300
|
||||
@@ -0,0 +1,160 @@
|
||||
+// Hand-rolled Prometheus metrics for the metamcp aggregator.
|
||||
+//
|
||||
+// We avoid adding `prom-client` to package.json so the pnpm lockfile and
|
||||
+// the Nix `pnpmDeps` hash stay untouched (cluster#50). This module
|
||||
+// emits the canonical schema from Specs/mcp-metric-schema:
|
||||
+// mcp_hop_duration_seconds (histogram)
|
||||
+// mcp_cancellation_total (counter)
|
||||
+// mcp_uncaught_throw_total (counter)
|
||||
+// mcp_traceparent_synthesized_total (counter)
|
||||
+//
|
||||
+// Exposition is text format v0.0.4. The /metrics endpoint is registered
|
||||
+// in index.ts.
|
||||
+
|
||||
+type LabelSet = Record<string, string>;
|
||||
+
|
||||
+function escapeLabel(v: string): string {
|
||||
+ return v.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
||||
+}
|
||||
+
|
||||
+function renderLabels(labels: LabelSet): string {
|
||||
+ const parts: string[] = [];
|
||||
+ for (const k of Object.keys(labels).sort()) {
|
||||
+ parts.push(`${k}="${escapeLabel(labels[k])}"`);
|
||||
+ }
|
||||
+ return parts.length ? `{${parts.join(",")}}` : "";
|
||||
+}
|
||||
+
|
||||
+function labelKey(labels: LabelSet): string {
|
||||
+ // Stable key for the labels map. Sort keys so the order is deterministic.
|
||||
+ const ks = Object.keys(labels).sort();
|
||||
+ return ks.map((k) => `${k}=${labels[k]}`).join("\x1f");
|
||||
+}
|
||||
+
|
||||
+export class Counter {
|
||||
+ private values = new Map<string, { labels: LabelSet; value: number }>();
|
||||
+
|
||||
+ constructor(
|
||||
+ public readonly name: string,
|
||||
+ public readonly help: string,
|
||||
+ public readonly labelNames: readonly string[],
|
||||
+ ) {}
|
||||
+
|
||||
+ inc(labels: LabelSet, by: number = 1): void {
|
||||
+ const key = labelKey(labels);
|
||||
+ const cur = this.values.get(key);
|
||||
+ if (cur) {
|
||||
+ cur.value += by;
|
||||
+ } else {
|
||||
+ this.values.set(key, { labels: { ...labels }, value: by });
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ render(): string {
|
||||
+ const lines: string[] = [];
|
||||
+ lines.push(`# HELP ${this.name} ${this.help}`);
|
||||
+ lines.push(`# TYPE ${this.name} counter`);
|
||||
+ for (const { labels, value } of this.values.values()) {
|
||||
+ lines.push(`${this.name}${renderLabels(labels)} ${value}`);
|
||||
+ }
|
||||
+ return lines.join("\n");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+export class Histogram {
|
||||
+ private series = new Map<
|
||||
+ string,
|
||||
+ {
|
||||
+ labels: LabelSet;
|
||||
+ bucketCounts: number[]; // counts per bucket boundary (cumulative on render)
|
||||
+ sum: number;
|
||||
+ count: number;
|
||||
+ }
|
||||
+ >();
|
||||
+
|
||||
+ constructor(
|
||||
+ public readonly name: string,
|
||||
+ public readonly help: string,
|
||||
+ public readonly labelNames: readonly string[],
|
||||
+ public readonly buckets: readonly number[],
|
||||
+ ) {}
|
||||
+
|
||||
+ observe(labels: LabelSet, value: number): void {
|
||||
+ const key = labelKey(labels);
|
||||
+ let s = this.series.get(key);
|
||||
+ if (!s) {
|
||||
+ s = {
|
||||
+ labels: { ...labels },
|
||||
+ bucketCounts: new Array(this.buckets.length).fill(0),
|
||||
+ sum: 0,
|
||||
+ count: 0,
|
||||
+ };
|
||||
+ this.series.set(key, s);
|
||||
+ }
|
||||
+ for (let i = 0; i < this.buckets.length; i++) {
|
||||
+ if (value <= this.buckets[i]) s.bucketCounts[i] += 1;
|
||||
+ }
|
||||
+ s.sum += value;
|
||||
+ s.count += 1;
|
||||
+ }
|
||||
+
|
||||
+ render(): string {
|
||||
+ const lines: string[] = [];
|
||||
+ lines.push(`# HELP ${this.name} ${this.help}`);
|
||||
+ lines.push(`# TYPE ${this.name} histogram`);
|
||||
+ for (const s of this.series.values()) {
|
||||
+ for (let i = 0; i < this.buckets.length; i++) {
|
||||
+ const le = String(this.buckets[i]);
|
||||
+ const lbls = renderLabels({ ...s.labels, le });
|
||||
+ lines.push(`${this.name}_bucket${lbls} ${s.bucketCounts[i]}`);
|
||||
+ }
|
||||
+ const lblsInf = renderLabels({ ...s.labels, le: "+Inf" });
|
||||
+ lines.push(`${this.name}_bucket${lblsInf} ${s.count}`);
|
||||
+ lines.push(`${this.name}_sum${renderLabels(s.labels)} ${s.sum}`);
|
||||
+ lines.push(`${this.name}_count${renderLabels(s.labels)} ${s.count}`);
|
||||
+ }
|
||||
+ return lines.join("\n");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+// --- Schema instances --------------------------------------------------------
|
||||
+
|
||||
+const DURATION_BUCKETS = [
|
||||
+ 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 300,
|
||||
+];
|
||||
+
|
||||
+export const mcpHopDurationSeconds = new Histogram(
|
||||
+ "mcp_hop_duration_seconds",
|
||||
+ "Wall time spent in this hop processing one MCP request",
|
||||
+ ["hop", "tool_name", "status"],
|
||||
+ DURATION_BUCKETS,
|
||||
+);
|
||||
+
|
||||
+export const mcpCancellationTotal = new Counter(
|
||||
+ "mcp_cancellation_total",
|
||||
+ "Cancellations observed per hop",
|
||||
+ ["hop", "source"],
|
||||
+);
|
||||
+
|
||||
+export const mcpUncaughtThrowTotal = new Counter(
|
||||
+ "mcp_uncaught_throw_total",
|
||||
+ "Uncaught exceptions per component (today's smoking gun, see cluster#44)",
|
||||
+ ["hop", "component"],
|
||||
+);
|
||||
+
|
||||
+export const mcpTraceparentSynthesizedTotal = new Counter(
|
||||
+ "mcp_traceparent_synthesized_total",
|
||||
+ "Inbound MCP requests missing a traceparent header",
|
||||
+ ["hop"],
|
||||
+);
|
||||
+
|
||||
+export function renderMetricsExposition(): string {
|
||||
+ return (
|
||||
+ [
|
||||
+ mcpHopDurationSeconds.render(),
|
||||
+ mcpCancellationTotal.render(),
|
||||
+ mcpUncaughtThrowTotal.render(),
|
||||
+ mcpTraceparentSynthesizedTotal.render(),
|
||||
+ ].join("\n") + "\n"
|
||||
+ );
|
||||
+}
|
||||
diff -urN metamcp-2.4.22.orig/apps/backend/src/lib/observability/trace.ts metamcp-2.4.22/apps/backend/src/lib/observability/trace.ts
|
||||
--- a/apps/backend/src/lib/observability/trace.ts 1970-01-01 03:00:00.000000000 +0300
|
||||
+++ b/apps/backend/src/lib/observability/trace.ts 2026-05-23 23:49:11.700817887 +0300
|
||||
@@ -0,0 +1,127 @@
|
||||
+// W3C Trace Context propagation for the metamcp aggregator.
|
||||
+//
|
||||
+// Implements the contract from cluster#45 (Specs/mcp-request-id):
|
||||
+// traceparent header: 00-<trace-id:32-hex>-<span-id:16-hex>-<flags:2-hex>
|
||||
+// Bound per-request via AsyncLocalStorage. Inbound HTTP header is read by
|
||||
+// the express middleware in index.ts; outbound JSON-RPC to stdio children
|
||||
+// reads currentTraceparentForUpstream() and injects it under
|
||||
+// params._meta.traceparent (process-managed-transport.send).
|
||||
+
|
||||
+import { AsyncLocalStorage } from "node:async_hooks";
|
||||
+import { randomBytes } from "node:crypto";
|
||||
+
|
||||
+export const HOP_NAME = "metamcp";
|
||||
+
|
||||
+export interface TraceCtx {
|
||||
+ trace_id: string;
|
||||
+ span_id: string;
|
||||
+ parent_span_id: string;
|
||||
+ hop: string;
|
||||
+ tool_name: string;
|
||||
+}
|
||||
+
|
||||
+const TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
|
||||
+
|
||||
+export const traceStore = new AsyncLocalStorage<TraceCtx>();
|
||||
+
|
||||
+export function newTraceId(): string {
|
||||
+ return randomBytes(16).toString("hex");
|
||||
+}
|
||||
+
|
||||
+export function newSpanId(): string {
|
||||
+ return randomBytes(8).toString("hex");
|
||||
+}
|
||||
+
|
||||
+export type ParsedTraceparent = {
|
||||
+ trace_id: string;
|
||||
+ parent_span_id: string;
|
||||
+ malformed: boolean;
|
||||
+};
|
||||
+
|
||||
+export function parseTraceparent(raw: string | undefined): ParsedTraceparent {
|
||||
+ if (typeof raw !== "string") {
|
||||
+ return { trace_id: "", parent_span_id: "", malformed: false };
|
||||
+ }
|
||||
+ const m = TRACEPARENT_RE.exec(raw.trim());
|
||||
+ if (!m) {
|
||||
+ return { trace_id: newTraceId(), parent_span_id: "", malformed: true };
|
||||
+ }
|
||||
+ return { trace_id: m[1], parent_span_id: m[2], malformed: false };
|
||||
+}
|
||||
+
|
||||
+export type BindResult = {
|
||||
+ ctx: TraceCtx;
|
||||
+ // One of: "" (normal), "traceparent_synthesized", "traceparent_malformed".
|
||||
+ // Caller emits the reserved event log so the dashboard can count it.
|
||||
+ event: "" | "traceparent_synthesized" | "traceparent_malformed";
|
||||
+};
|
||||
+
|
||||
+export function bindFromHeader(
|
||||
+ raw: string | undefined,
|
||||
+ tool_name: string = "",
|
||||
+): BindResult {
|
||||
+ let event: BindResult["event"] = "";
|
||||
+ let trace_id = "";
|
||||
+ let parent_span_id = "";
|
||||
+ if (raw == null) {
|
||||
+ trace_id = newTraceId();
|
||||
+ event = "traceparent_synthesized";
|
||||
+ } else {
|
||||
+ const p = parseTraceparent(raw);
|
||||
+ trace_id = p.trace_id;
|
||||
+ parent_span_id = p.parent_span_id;
|
||||
+ if (p.malformed) event = "traceparent_malformed";
|
||||
+ }
|
||||
+ const ctx: TraceCtx = {
|
||||
+ trace_id,
|
||||
+ span_id: newSpanId(),
|
||||
+ parent_span_id,
|
||||
+ hop: HOP_NAME,
|
||||
+ tool_name,
|
||||
+ };
|
||||
+ return { ctx, event };
|
||||
+}
|
||||
+
|
||||
+// Build a traceparent literal for outbound calls. Our span-id becomes the
|
||||
+// downstream hop's parent_span_id (standard W3C propagation). Falls back
|
||||
+// to synthesizing a fresh context when called outside any bound request
|
||||
+// (e.g. session-pool keepalive paths that originate inside the server).
|
||||
+export function currentTraceparentForUpstream(): string {
|
||||
+ let ctx = traceStore.getStore();
|
||||
+ if (!ctx || !ctx.trace_id) {
|
||||
+ ctx = {
|
||||
+ trace_id: newTraceId(),
|
||||
+ span_id: newSpanId(),
|
||||
+ parent_span_id: "",
|
||||
+ hop: HOP_NAME,
|
||||
+ tool_name: "",
|
||||
+ };
|
||||
+ }
|
||||
+ return `00-${ctx.trace_id}-${ctx.span_id}-01`;
|
||||
+}
|
||||
+
|
||||
+// Single structured log line stamped with the current trace fields. Use
|
||||
+// from any code path that has a bound trace context. Outside a context,
|
||||
+// emits with empty fields so existing call sites do not crash.
|
||||
+export function traceLog(
|
||||
+ level: "info" | "warn" | "error",
|
||||
+ msg: string,
|
||||
+ extra: Record<string, unknown> = {},
|
||||
+): void {
|
||||
+ const ctx = traceStore.getStore();
|
||||
+ const rec: Record<string, unknown> = {
|
||||
+ ts: Date.now() / 1000,
|
||||
+ level,
|
||||
+ msg,
|
||||
+ hop: HOP_NAME,
|
||||
+ trace_id: ctx?.trace_id ?? "",
|
||||
+ span_id: ctx?.span_id ?? "",
|
||||
+ parent_span_id: ctx?.parent_span_id ?? "",
|
||||
+ tool_name: ctx?.tool_name ?? "",
|
||||
+ ...extra,
|
||||
+ };
|
||||
+ const line = JSON.stringify(rec);
|
||||
+ if (level === "error") console.error(line);
|
||||
+ else if (level === "warn") console.warn(line);
|
||||
+ else console.log(line);
|
||||
+}
|
||||
diff -urN metamcp-2.4.22.orig/apps/backend/src/lib/stdio-transport/process-managed-transport.ts metamcp-2.4.22/apps/backend/src/lib/stdio-transport/process-managed-transport.ts
|
||||
--- a/apps/backend/src/lib/stdio-transport/process-managed-transport.ts 2026-05-23 23:45:36.315112630 +0300
|
||||
+++ b/apps/backend/src/lib/stdio-transport/process-managed-transport.ts 2026-05-23 23:51:05.898047455 +0300
|
||||
@@ -6,6 +6,7 @@
|
||||
import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
||||
import spawn from "cross-spawn";
|
||||
|
||||
+import { currentTraceparentForUpstream } from "../observability/trace";
|
||||
import { ReadBuffer, serializeMessage } from "./shared";
|
||||
|
||||
export type StdioServerParameters = {
|
||||
@@ -279,7 +280,32 @@
|
||||
throw new Error("Not connected");
|
||||
}
|
||||
|
||||
- const json = serializeMessage(message);
|
||||
+ // cluster#49 — propagate W3C trace context to the stdio child via
|
||||
+ // params._meta.traceparent (the MCP spec's standard escape hatch
|
||||
+ // for transport-agnostic metadata). Only stamp on messages with
|
||||
+ // params (notifications/requests); JSON-RPC responses ride back
|
||||
+ // up so don't need it. Idempotent: existing _meta is preserved.
|
||||
+ const tracedMessage = (() => {
|
||||
+ const m = message as JSONRPCMessage & {
|
||||
+ params?: Record<string, unknown>;
|
||||
+ };
|
||||
+ if (!m || typeof m !== "object" || !("params" in m)) return message;
|
||||
+ if (m.params == null || typeof m.params !== "object") return message;
|
||||
+ const meta = (m.params as Record<string, unknown>)._meta;
|
||||
+ const traceparent = currentTraceparentForUpstream();
|
||||
+ return {
|
||||
+ ...m,
|
||||
+ params: {
|
||||
+ ...m.params,
|
||||
+ _meta: {
|
||||
+ ...(typeof meta === "object" && meta !== null ? meta : {}),
|
||||
+ traceparent,
|
||||
+ },
|
||||
+ },
|
||||
+ } as JSONRPCMessage;
|
||||
+ })();
|
||||
+
|
||||
+ const json = serializeMessage(tracedMessage);
|
||||
if (this._process.stdin.write(json)) {
|
||||
resolve();
|
||||
} else {
|
||||
@@ -0,0 +1,209 @@
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
makeWrapper,
|
||||
nodejs_20,
|
||||
pnpm_10,
|
||||
fetchPnpmDeps,
|
||||
pnpmConfigHook,
|
||||
postgresql,
|
||||
}:
|
||||
|
||||
# MetaMCP — MCP aggregator/orchestrator.
|
||||
#
|
||||
# Upstream is a pnpm monorepo (Turbo) with two apps:
|
||||
# - apps/backend : Express/tRPC API, built with tsup -> dist/index.js
|
||||
# port 12009 (internal), runs drizzle-kit migrations at boot
|
||||
# - apps/frontend : Next.js 15, port 12008 (public), launched via `next start`
|
||||
#
|
||||
# Both require runtime node_modules (Next.js non-standalone; drizzle-kit is
|
||||
# a devDep invoked at runtime by docker-entrypoint.sh). We ship the whole
|
||||
# built tree under $out/lib/metamcp and provide launcher scripts.
|
||||
#
|
||||
# The upstream Dockerfile patches Next.js' proxy timeout (30s -> 600s) by
|
||||
# sed-editing two files inside node_modules/.pnpm/next@.../...; we replicate
|
||||
# that in postBuild so the behaviour matches the official image.
|
||||
#
|
||||
# Build is single-derivation: pnpm fetch -> pnpm build -> install. First
|
||||
# build prints the right pnpmDeps hash; paste it back here and rebuild.
|
||||
|
||||
let
|
||||
pname = "metamcp";
|
||||
version = "2.4.22";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "metatool-ai";
|
||||
repo = "metamcp";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-EEb3RUjsaJ5ZSHSIkAxfdV/BAjZEAvw3rtjALM4RpSc=";
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
inherit pname version src;
|
||||
|
||||
pnpmDeps = fetchPnpmDeps {
|
||||
inherit pname version src;
|
||||
fetcherVersion = 3;
|
||||
hash = "sha256-nHHLLu5wBzzP4i/oTnOkuIiPQvvvBAIIVtKdfpDiXQw=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
nodejs_20
|
||||
pnpm_10
|
||||
pnpmConfigHook
|
||||
makeWrapper
|
||||
];
|
||||
|
||||
# Upstream pins `packageManager: pnpm@9.0.0`; nixpkgs ships pnpm 10. The
|
||||
# lockfile is v9 (forward-compatible). Rewrite the pin to the pnpm we
|
||||
# actually have so pnpm 10 doesn't try to network-fetch pnpm@9 and Turbo
|
||||
# (which *requires* the field) still finds it.
|
||||
postPatch = ''
|
||||
${nodejs_20}/bin/node -e '
|
||||
const fs = require("fs");
|
||||
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
||||
pkg.packageManager = "pnpm@${pnpm_10.version}";
|
||||
if (pkg.engines) delete pkg.engines.pnpm;
|
||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 2) + "\n");
|
||||
'
|
||||
|
||||
# cluster#49, cluster#50 — observability patch: W3C traceparent
|
||||
# propagation (HTTP header → params._meta.traceparent on stdio
|
||||
# children) + hand-rolled Prometheus metrics on /metrics, no new
|
||||
# dependency so pnpmDeps stays untouched. Two new source files
|
||||
# under apps/backend/src/lib/observability/ and minimal edits to
|
||||
# apps/backend/src/index.ts and process-managed-transport.ts.
|
||||
# Retire when this lands upstream.
|
||||
patch -p1 < ${./metamcp-observability.patch}
|
||||
'';
|
||||
|
||||
# pnpmConfigHook places node_modules; build the workspace with Turbo.
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
# Match the upstream Dockerfile sed-patch on Next.js proxy timeout.
|
||||
# Files live under the pnpm virtual store; glob to tolerate minor
|
||||
# next/react version bumps on later tags.
|
||||
for f in \
|
||||
node_modules/.pnpm/next@*/node_modules/next/dist/server/lib/router-utils/proxy-request.js \
|
||||
node_modules/.pnpm/next@*/node_modules/next/dist/esm/server/lib/router-utils/proxy-request.js; do
|
||||
if [ -f "$f" ]; then
|
||||
sed -i -e "s/30000/600000/" "$f"
|
||||
fi
|
||||
done
|
||||
|
||||
pnpm build
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/lib/metamcp $out/bin
|
||||
|
||||
# Ship the whole built workspace. This is bulky but reliable:
|
||||
# `next start` needs .next + node_modules + package.json side-by-side,
|
||||
# and the backend's runtime invokes `pnpm exec drizzle-kit migrate`.
|
||||
cp -r \
|
||||
apps \
|
||||
packages \
|
||||
node_modules \
|
||||
package.json \
|
||||
pnpm-workspace.yaml \
|
||||
pnpm-lock.yaml \
|
||||
turbo.json \
|
||||
$out/lib/metamcp/
|
||||
|
||||
# Next.js standalone post-processing: `output: "standalone"` produces
|
||||
# apps/frontend/.next/standalone/apps/frontend/server.js but does NOT
|
||||
# copy .next/static or public/ into the standalone tree. Do it here
|
||||
# so the runtime UI has its CSS, JS chunks and static assets.
|
||||
SA=$out/lib/metamcp/apps/frontend/.next/standalone/apps/frontend
|
||||
cp -r $out/lib/metamcp/apps/frontend/.next/static $SA/.next/static
|
||||
cp -r $out/lib/metamcp/apps/frontend/public $SA/public
|
||||
|
||||
# Launcher: re-implementation of upstream's docker-entrypoint.sh.
|
||||
# Cleaner than substituteInPlace-ing the upstream script (the original
|
||||
# has overlapping `/app` substrings that break naive replacement) and
|
||||
# gives us a single place to keep the orchestration logic in sync.
|
||||
cat > $out/bin/metamcp <<EOF
|
||||
#!${stdenv.shell}
|
||||
set -e
|
||||
|
||||
export PATH=${
|
||||
lib.makeBinPath [
|
||||
nodejs_20
|
||||
pnpm_10
|
||||
postgresql # for pg_isready
|
||||
]
|
||||
}:\$PATH
|
||||
|
||||
ROOT=$out/lib/metamcp
|
||||
: "\''${POSTGRES_HOST:=127.0.0.1}"
|
||||
: "\''${POSTGRES_PORT:=5432}"
|
||||
: "\''${POSTGRES_USER:=postgres}"
|
||||
|
||||
echo "Waiting for PostgreSQL at \$POSTGRES_HOST:\$POSTGRES_PORT..."
|
||||
until pg_isready -h "\$POSTGRES_HOST" -p "\$POSTGRES_PORT" -U "\$POSTGRES_USER"; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Running drizzle migrations..."
|
||||
cd "\$ROOT/apps/backend"
|
||||
pnpm exec drizzle-kit migrate
|
||||
|
||||
echo "Starting backend on :12009..."
|
||||
PORT=12009 node "\$ROOT/apps/backend/dist/index.js" &
|
||||
BACKEND_PID=\$!
|
||||
sleep 3
|
||||
kill -0 \$BACKEND_PID 2>/dev/null || { echo "Backend died"; exit 1; }
|
||||
|
||||
echo "Starting frontend on :12008..."
|
||||
cd "\$ROOT/apps/frontend/.next/standalone/apps/frontend"
|
||||
# Next.js standalone uses \$HOSTNAME as the bind address. The shell
|
||||
# inherits HOSTNAME=<system-hostname>, leaving the server unreachable
|
||||
# on 127.0.0.1 — force 0.0.0.0 unless overridden via METAMCP_HOSTNAME.
|
||||
PORT=12008 HOSTNAME="\''${METAMCP_HOSTNAME:-0.0.0.0}" node server.js &
|
||||
FRONTEND_PID=\$!
|
||||
sleep 3
|
||||
kill -0 \$FRONTEND_PID 2>/dev/null || { kill \$BACKEND_PID 2>/dev/null; echo "Frontend died"; exit 1; }
|
||||
|
||||
trap 'kill \$BACKEND_PID \$FRONTEND_PID 2>/dev/null || true' TERM INT
|
||||
wait \$BACKEND_PID \$FRONTEND_PID
|
||||
EOF
|
||||
chmod +x $out/bin/metamcp
|
||||
|
||||
# Direct sub-launchers (handy for systemd "two-unit" deployments if
|
||||
# you ever want to skip the bundled orchestrator).
|
||||
makeWrapper ${nodejs_20}/bin/node $out/bin/metamcp-backend \
|
||||
--add-flags "$out/lib/metamcp/apps/backend/dist/index.js" \
|
||||
--set NODE_ENV production
|
||||
|
||||
cat > $out/bin/metamcp-frontend <<EOF
|
||||
#!${stdenv.shell}
|
||||
cd $out/lib/metamcp/apps/frontend/.next/standalone/apps/frontend
|
||||
exec env HOSTNAME="\''${METAMCP_HOSTNAME:-0.0.0.0}" ${nodejs_20}/bin/node server.js "\$@"
|
||||
EOF
|
||||
chmod +x $out/bin/metamcp-frontend
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
# The pnpm/turbo build writes into $HOME; give it a writable one.
|
||||
preBuild = ''
|
||||
export HOME=$TMPDIR
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "MetaMCP — MCP aggregator, orchestrator, middleware, gateway";
|
||||
homepage = "https://github.com/metatool-ai/metamcp";
|
||||
license = lib.licenses.mit;
|
||||
mainProgram = "metamcp";
|
||||
platforms = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
];
|
||||
};
|
||||
})
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
python3Packages,
|
||||
fetchurl,
|
||||
}:
|
||||
|
||||
let
|
||||
mkWheel =
|
||||
{
|
||||
pname,
|
||||
version,
|
||||
url,
|
||||
sha256,
|
||||
}:
|
||||
python3Packages.buildPythonPackage {
|
||||
inherit pname version;
|
||||
src = fetchurl { inherit url sha256; };
|
||||
format = "wheel";
|
||||
doCheck = false;
|
||||
dontCheckRuntimeDeps = true;
|
||||
};
|
||||
in
|
||||
{
|
||||
xontrib-prompt-starship = mkWheel {
|
||||
pname = "xontrib-prompt-starship";
|
||||
version = "0.3.7";
|
||||
url = "https://files.pythonhosted.org/packages/94/71/968450a151d003bb80ed6fbfb190f22cfac076cbd4442dc58b48c5545644/xontrib_prompt_starship-0.3.7-py3-none-any.whl";
|
||||
sha256 = "12a213fc454c9547c6426d19c4a43982be48500019378267bdab465d0749dccd";
|
||||
};
|
||||
|
||||
xontrib-broot = mkWheel {
|
||||
pname = "xontrib-broot";
|
||||
version = "0.2.1";
|
||||
url = "https://files.pythonhosted.org/packages/a8/97/a595ef322a40bcfaf885af8538d48cd0c506b8e06e3834458909c6b9bf90/xontrib_broot-0.2.1-py3-none-any.whl";
|
||||
sha256 = "5eee2af0740fcc0355937b1365a15bcfebbe7c045215f0a597a519ce05cca96f";
|
||||
};
|
||||
|
||||
xontrib-term-integrations = mkWheel {
|
||||
pname = "xontrib-term-integrations";
|
||||
version = "0.2.0";
|
||||
url = "https://files.pythonhosted.org/packages/0e/df/76f0d98fb67267124f498b0da6b598939e85d5ef9aad49c3476929395ea0/xontrib_term_integrations-0.2.0-py3-none-any.whl";
|
||||
sha256 = "cc55f4a1885361349d1a5a39aa6f44abc080e318355335bc14e7ea28c5b30776";
|
||||
};
|
||||
|
||||
xontrib-direnv = mkWheel {
|
||||
pname = "xonsh-direnv";
|
||||
version = "1.6.5";
|
||||
url = "https://files.pythonhosted.org/packages/f4/c3/47e5e0fa9db04f8d42272d8e1ffe9367e1643da7c24b944757fc1b18369a/xonsh_direnv-1.6.5-py3-none-any.whl";
|
||||
sha256 = "e67e5c4f4328d9c8ee388476c83c1eb86d75d1b53b8595a4a6022641e7fb0214";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- a/src/bootstrap/src/lib.rs
|
||||
+++ b/src/bootstrap/src/lib.rs
|
||||
@@ -1990,7 +1990,7 @@ impl Build {
|
||||
use std::os::unix::fs::symlink as symlink_file;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::symlink_file;
|
||||
- if !self.config.dry_run() { symlink_file(src.as_ref(), link.as_ref()) } else { Ok(()) }
|
||||
+ if !self.config.dry_run() { let _ = std::fs::remove_file(link.as_ref()); let _ = std::fs::remove_dir_all(link.as_ref()); symlink_file(src.as_ref(), link.as_ref()) } else { Ok(()) }
|
||||
}
|
||||
|
||||
/// Returns if config.ninja is enabled, and checks for ninja existence,
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
# Update flow for gitea-local-fork driven by passthru.updateScript.
|
||||
# Invoked by `nix-update --use-update-script gitea-local-fork`.
|
||||
#
|
||||
# Source fork: /home/oleks/projects/gitea
|
||||
# Source remote: https://git.oleks.space/oleks/gitea.git (named `oleks` locally)
|
||||
# Tracked branch: oleks/main (formerly `feat/projects-api`, renamed 2026-05-13)
|
||||
#
|
||||
# nix-update with --version=branch=X constructs the new version as
|
||||
# `<latest-tag>-unstable-<date>` where <latest-tag> is the most recent
|
||||
# tag on the remote. If we leave an old `v1.26.0-unstable-<date>` tag
|
||||
# from a previous run on the gitea fork, nix-update will pick that
|
||||
# as <latest-tag> and we end up with double suffixes. So:
|
||||
#
|
||||
# 0. Delete any prior `*-unstable-*` tag on the gitea fork (local + remote).
|
||||
# 1. Run nix-update default flow: it now sees only clean semver tags
|
||||
# (v1.26.0 etc.), constructs `1.26.0-unstable-<commit-date>`.
|
||||
# 2. Tag the gitea fork at the new rev with v<version>, push the tag.
|
||||
# 3. Commit flake-hub's package.nix change, push to origin.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
FLAKE_HUB="${FLAKE_HUB:-$HOME/projects/nix-customs/flake-hub}"
|
||||
GITEA_REPO="${GITEA_REPO:-$HOME/projects/gitea}"
|
||||
PKG_FILE="$FLAKE_HUB/packages/gitea-local-fork.nix"
|
||||
|
||||
log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
|
||||
|
||||
# ── 0. Wipe prior unstable tags from gitea fork ───────────────────────────
|
||||
cd "$GITEA_REPO"
|
||||
log "Cleaning up previous unstable tags on gitea fork (so nix-update sees only clean semver tags)"
|
||||
mapfile -t PREV_TAGS < <(git ls-remote --tags oleks 'refs/tags/v*-unstable-*' |
|
||||
awk '{print $2}' |
|
||||
sed 's|refs/tags/||;s|\^{}$||' |
|
||||
sort -u)
|
||||
for tag in "${PREV_TAGS[@]}"; do
|
||||
[ -z "$tag" ] && continue
|
||||
log " deleting $tag"
|
||||
git tag -d "$tag" 2>/dev/null || true
|
||||
git push oleks ":refs/tags/$tag" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# ── 1. nix-update default flow ────────────────────────────────────────────
|
||||
# Also normalize the in-file version to its bare semver baseline so
|
||||
# nix-update doesn't see a stale unstable suffix from the local file.
|
||||
cd "$FLAKE_HUB"
|
||||
log "Normalizing in-file version baseline"
|
||||
sed -i -E 's/(version = "[^"]+?)-unstable-[0-9]{4}-[0-9]{2}-[0-9]{2}"/\1"/' "$PKG_FILE"
|
||||
|
||||
log "Running nix-update against oleks/main tip on the gitea fork"
|
||||
nix run nixpkgs#nix-update -- \
|
||||
--flake gitea-local-fork \
|
||||
--version=branch=main \
|
||||
--build
|
||||
|
||||
# ── 2. Tag gitea fork ─────────────────────────────────────────────────────
|
||||
NEW_REV=$(grep -oP 'rev = "\K[a-f0-9]{40}' "$PKG_FILE" | head -1)
|
||||
NEW_VER=$(grep -oP 'version = "\K[^"]+' "$PKG_FILE" | head -1)
|
||||
TAG="v${NEW_VER}"
|
||||
log "New version: $NEW_VER (rev ${NEW_REV:0:12})"
|
||||
|
||||
cd "$GITEA_REPO"
|
||||
if git rev-parse --verify --quiet "refs/tags/$TAG" >/dev/null; then
|
||||
log "Tag $TAG already exists locally — skipping"
|
||||
else
|
||||
log "Tagging gitea fork: $TAG at ${NEW_REV:0:12}"
|
||||
git -c commit.gpgsign=false tag -a "$TAG" "$NEW_REV" \
|
||||
-m "Pinned by nix-customs/flake-hub gitea-local-fork derivation"
|
||||
fi
|
||||
log "Pushing tag to oleks remote"
|
||||
git push oleks "$TAG" 2>/dev/null || log " tag already on remote"
|
||||
|
||||
# ── 3. Commit + push flake-hub changes ────────────────────────────────────
|
||||
cd "$FLAKE_HUB"
|
||||
if git diff --quiet -- "$PKG_FILE"; then
|
||||
log "flake-hub: no changes to commit"
|
||||
else
|
||||
log "flake-hub: committing version + hashes bump"
|
||||
git -c commit.gpgsign=false add "$PKG_FILE"
|
||||
git -c commit.gpgsign=false commit -m "gitea-local-fork: refresh to $NEW_VER
|
||||
|
||||
Pin to gitea fork rev ${NEW_REV:0:12} (tag $TAG).
|
||||
Refreshed by nix-update via passthru.updateScript."
|
||||
fi
|
||||
|
||||
if git rev-parse '@{u}' >/dev/null 2>&1; then
|
||||
log "flake-hub: pushing"
|
||||
git push
|
||||
else
|
||||
log "flake-hub: no upstream tracking — skipping push"
|
||||
fi
|
||||
|
||||
log "Done. New version $NEW_VER live at:"
|
||||
log " gitea tag: https://git.oleks.space/oleks/gitea/src/tag/$TAG"
|
||||
log " derivation: $PKG_FILE"
|
||||
Reference in New Issue
Block a user