Compare commits
69
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5b41ba6fd | ||
|
|
7041dfebfa | ||
|
|
4d6ad47837 | ||
|
|
f471d226e0 | ||
|
|
10f713103c | ||
|
|
cc6cb24c78 | ||
|
|
240facdbbb | ||
|
|
c1456decaf | ||
|
|
e06495ae69 | ||
|
|
6868182ef5 | ||
|
|
90a57ab73b | ||
|
|
75f4e22624 | ||
|
|
cf96fec63e | ||
|
|
3cdf4d4e54 | ||
|
|
f61a206977 | ||
|
|
1d5a5adbcc | ||
|
|
4029866ed4 | ||
|
|
66b27517ba | ||
|
|
6b43e76457 | ||
|
|
cbf2fdac42 | ||
|
|
1fdd048eed | ||
|
|
9b72a81d43 | ||
|
|
2f0302d66e | ||
|
|
256a9a9745 | ||
|
|
b746d58812 | ||
|
|
67963ed0e0 | ||
|
|
7bcc5feb35 | ||
|
|
9759cb70cf | ||
|
|
4d27b29233 | ||
|
|
dbc30b4b0e | ||
|
|
86ef677f2f | ||
|
|
a65771ccac | ||
|
|
89e55f4365 | ||
|
|
fee2f66385 | ||
|
|
72770a4ddb | ||
|
|
6d9e4443e1 | ||
|
|
7d504e68be | ||
|
|
432a00fb35 | ||
|
|
2125dd7aac | ||
|
|
0ff75654ce | ||
|
|
51df3473ca | ||
|
|
6ea5183f0e | ||
|
|
00ad68a5be | ||
|
|
bd309f38a2 | ||
|
|
50e2b68a23 | ||
|
|
e0fc1021ea | ||
|
|
10c64c77f1 | ||
|
|
0dbf33d476 | ||
|
|
677cefdb52 | ||
|
|
0e47006bdb | ||
|
|
cf8ec786bd | ||
|
|
f6d379efcc | ||
|
|
474c5436c8 | ||
|
|
d62a23680a | ||
|
|
734be2a727 | ||
|
|
ad12062cde | ||
|
|
e72d007a7d | ||
|
|
8d016a546a | ||
|
|
a93ca2c04d | ||
|
|
dcd6fa6be3 | ||
|
|
f4a9e638a5 | ||
|
|
4fd26b1662 | ||
|
|
665703fbe6 | ||
|
|
ad6dac634e | ||
|
|
9b7a9fa9b9 | ||
|
|
33278d9ed2 | ||
|
|
40aef99289 | ||
|
|
b191d8883c | ||
|
|
1df7bec2d7 |
+28
-16
@@ -1,15 +1,21 @@
|
||||
# Flake CI: full `nix flake check` (formatting + deadnix + statix + pre-commit)
|
||||
# plus an explicit per-host evaluation pass for granular output.
|
||||
# Flake CI. Formatting (treefmt) runs on *every* PR; the heavier Nix work
|
||||
# (deadnix/statix/pre-commit lints + per-host evaluation) runs only when the
|
||||
# change can affect it.
|
||||
name: CI
|
||||
|
||||
# Deliberately no `paths:` filter. This job is a required status check on main,
|
||||
# and a path-filtered workflow is *skipped* (never runs) for PRs that touch no
|
||||
# matching file -- which leaves the required check pending forever and blocks the
|
||||
# merge (e.g. a .renovaterc.json-only change). So the workflow always runs and
|
||||
# always reports. To avoid burning a full Nix evaluation on changes that can't
|
||||
# affect it, the "detect" step below diffs the PR and the heavy steps run only
|
||||
# when a .nix file, flake.lock, or this workflow changed; otherwise they skip and
|
||||
# the job still passes. The required check is therefore always green-reportable.
|
||||
# always reports.
|
||||
#
|
||||
# Two tiers of checks:
|
||||
# * Formatting always runs. treefmt covers Markdown, YAML, and JSON as well as
|
||||
# Nix and shell, so a docs- or config-only PR must be format-checked too. It
|
||||
# is cheap (no host evaluation).
|
||||
# * The heavy steps (full `nix flake check` + host evals) run only when a .nix
|
||||
# file, flake.lock, or this workflow changed; otherwise they skip and the job
|
||||
# still passes, keeping the required check green-reportable.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
@@ -20,16 +26,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
# Full history so the detect step can diff the PR against its base.
|
||||
fetch-depth: 0
|
||||
|
||||
# Decide whether the Nix steps need to run. On a pull_request, diff the PR
|
||||
# against its base and look for files that can affect the flake: any .nix,
|
||||
# the lockfile, or this workflow. On any other event (push to main) always
|
||||
# run. The job itself always succeeds, so the required status check is
|
||||
# reported even when the heavy steps are skipped.
|
||||
# Decide whether the *heavy* Nix steps need to run. On a pull_request, diff
|
||||
# against the base for files that can affect them: any .nix, the lockfile,
|
||||
# or this workflow. On any other event (push to main) always run. The
|
||||
# formatting step below is unaffected -- it always runs.
|
||||
- name: Detect Nix-relevant changes
|
||||
id: detect
|
||||
run: |
|
||||
@@ -45,16 +50,16 @@ jobs:
|
||||
echo "Changed files:"
|
||||
echo "$changed"
|
||||
if echo "$changed" | grep -Eq '(\.nix$|^flake\.lock$|^\.gitea/workflows/ci\.yaml$)'; then
|
||||
echo "Nix-relevant changes found: running checks."
|
||||
echo "Nix-relevant changes found: running heavy checks."
|
||||
echo "run=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "No Nix-relevant changes: skipping checks (job still passes)."
|
||||
echo "No Nix-relevant changes: heavy checks skip (formatting still runs)."
|
||||
echo "run=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Nix drives the formatting check, so install it unconditionally.
|
||||
- name: Install Nix
|
||||
if: steps.detect.outputs.run == 'true'
|
||||
uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31
|
||||
uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31
|
||||
with:
|
||||
extra_nix_config: |
|
||||
experimental-features = nix-command flakes
|
||||
@@ -62,6 +67,13 @@ jobs:
|
||||
substituters = https://cache.nixos.org https://nix-community.cachix.org
|
||||
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=
|
||||
|
||||
# Always run: treefmt formats Markdown/YAML/JSON (docs + config) as well as
|
||||
# Nix and shell, so documentation-only PRs are format-checked too. This is
|
||||
# the cheap gate (no host evaluation) and pre-builds the `formatting`
|
||||
# derivation that the flake check below reuses from cache.
|
||||
- name: Formatting check
|
||||
run: nix build --print-build-logs '.#checks.x86_64-linux.formatting'
|
||||
|
||||
# Runs every flake check: treefmt formatting, deadnix, statix, and the
|
||||
# pre-commit hooks (so a --no-verify commit can't ship unlinted).
|
||||
- name: Flake check
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Working on this flake
|
||||
|
||||
Project notes for changes to this repository. Persona and memory rules live in
|
||||
the user-global config; this file is about the flake's checks and conventions.
|
||||
|
||||
## Before you commit: run the formatter
|
||||
|
||||
Formatting and linting are driven by the flake. CI (`.gitea/workflows/ci.yaml`)
|
||||
runs `nix flake check`, which fails the build if any file is unformatted or trips
|
||||
a lint. From the repo root:
|
||||
|
||||
- `nix fmt` — format the whole tree (writes changes).
|
||||
- `nix flake check` — run every check read-only (what CI runs).
|
||||
- `nix develop` — dev shell; its `shellHook` installs the git pre-commit hooks so
|
||||
the same gates run on `git commit`.
|
||||
|
||||
Never commit with `--no-verify`. A bypassed commit ships unformatted content and
|
||||
turns CI red on the next push to `main` (see "Docs are checked too").
|
||||
|
||||
## What gets checked
|
||||
|
||||
Defined in `flake.nix` (the `treefmt`, `pre-commit`, and `checks` blocks) and
|
||||
`statix.toml`:
|
||||
|
||||
| Check | Tool | Covers |
|
||||
| ------------ | --------------------------------- | ------------------------------------------------------- |
|
||||
| `formatting` | treefmt → `nixfmt` | all `*.nix` |
|
||||
| `formatting` | treefmt → `shfmt` | shell scripts |
|
||||
| `formatting` | treefmt → `prettier` | **Markdown, YAML, JSON** (incl. `README.md`, this file) |
|
||||
| `deadnix` | deadnix | dead Nix bindings (`--no-lambda-pattern-names`) |
|
||||
| `statix` | statix | Nix antipatterns (config in `statix.toml`) |
|
||||
| pre-commit | nixfmt-rfc-style, deadnix, statix | the same gates, run on commit |
|
||||
|
||||
Excluded from formatting: `*/hardware-configuration.nix` (generated by
|
||||
`nixos-generate-config`) and `flake.lock`. Editor defaults (indent, EOL, final
|
||||
newline) are in `.editorconfig`; note Markdown keeps trailing whitespace, which
|
||||
encodes hard line breaks.
|
||||
|
||||
## Docs are checked too
|
||||
|
||||
prettier formats `*.md`, so **documentation edits must be run through `nix fmt`**
|
||||
exactly like code. prettier re-aligns Markdown tables in particular; hand-editing
|
||||
a table almost always leaves it non-conformant and fails the `formatting` check.
|
||||
|
||||
The CI `formatting` step runs on **every** PR — including docs- and config-only
|
||||
changes — so a Markdown/YAML/JSON edit is format-checked before merge, not just
|
||||
after it lands on `main`. (The heavier `deadnix`/`statix`/`pre-commit` lints and
|
||||
the per-host evaluation still run only when a `.nix` file, `flake.lock`, or the
|
||||
workflow changed; see `.gitea/workflows/ci.yaml`.) Run `nix fmt` before you
|
||||
commit and the formatting check stays green.
|
||||
|
||||
## Host evaluation
|
||||
|
||||
CI also evaluates every `nixosConfigurations` / `darwinConfigurations` host's
|
||||
toplevel (eval only, no build) on an x86_64 runner, so eval errors fail cheaply.
|
||||
Reproduce locally:
|
||||
|
||||
```sh
|
||||
nix eval --raw ".#nixosConfigurations.<host>.config.system.build.toplevel.drvPath"
|
||||
```
|
||||
|
||||
Host lists are discovered from the flake, so adding or removing a host needs no
|
||||
change to the workflow.
|
||||
@@ -30,7 +30,7 @@ profiles. The full module catalogue is below.
|
||||
flake.nix # inputs, mkHost/mkDarwinHost, the host tables, dev shell + checks
|
||||
flake.lock # pinned input revisions (Renovate keeps this fresh)
|
||||
modules/ # reusable NixOS system modules (see "Module catalogue")
|
||||
home/ # home-manager profile: shell, git, editor, claude, desktop, sway
|
||||
home/ # home-manager profile: shell, git, editor, claude, secret-service, desktop, sway
|
||||
users/ # identity registry + per-user home extras (see "Users")
|
||||
hosts/<Name>/ # per-machine config: configuration.nix + hardware-configuration.nix
|
||||
lib/ # small pure helpers (currently the Catppuccin Mocha palette)
|
||||
@@ -94,8 +94,9 @@ Per-user home extras live under `users/<name>/`:
|
||||
- [`users/lyrathorpe/home.nix`](./users/lyrathorpe/home.nix) — personal extras
|
||||
(an ssh host shortcut, gammastep coordinates); imported on Lyra's hosts.
|
||||
- [`users/emmathorpe/work.nix`](./users/emmathorpe/work.nix) — the work
|
||||
toolchain (kubectl/helm/az/etc.), work-only LSP servers, and the corporate ssh
|
||||
handling; imports
|
||||
toolchain (kubectl/helm/az/etc.), work-only LSP servers, the corporate ssh
|
||||
handling, and the headless Secret Service that gcx needs for its keychain
|
||||
tokens (see [`home/secret-service.nix`](./home/secret-service.nix)); imports
|
||||
[`users/emmathorpe/renovate-review.nix`](./users/emmathorpe/renovate-review.nix),
|
||||
the daily headless Renovate-PR review timer (EDaaS only).
|
||||
|
||||
|
||||
Generated
+46
-68
@@ -3,16 +3,16 @@
|
||||
"brew-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1781226006,
|
||||
"narHash": "sha256-w4ZTuOnhYiDxjaynrMTASzp802QblBWmo3wpB8wVN4Y=",
|
||||
"lastModified": 1785710351,
|
||||
"narHash": "sha256-DTL5T9+HlblsnXCEdxRpEo/2NBHD3t48BS7r+PTf090=",
|
||||
"owner": "Homebrew",
|
||||
"repo": "brew",
|
||||
"rev": "109191be4988470b51a60a5ef1998520aa24c01b",
|
||||
"rev": "7b0f22a4ab77567edef114c8dfc423fb96e2fbaa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Homebrew",
|
||||
"ref": "6.0.1",
|
||||
"ref": "6.0.15",
|
||||
"repo": "brew",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -25,11 +25,11 @@
|
||||
},
|
||||
"locked": {
|
||||
"dir": "pkgs/firefox-addons",
|
||||
"lastModified": 1782014564,
|
||||
"narHash": "sha256-F/royQHyJAyKWKrV8AaG4Yf1yjzxa+PFk5xvTdvBrzk=",
|
||||
"lastModified": 1786248171,
|
||||
"narHash": "sha256-ALbtZKFam+O2ONRk7XhYsXmXxmhR0Y3Bj+qZkTA8zCg=",
|
||||
"owner": "rycee",
|
||||
"repo": "nur-expressions",
|
||||
"rev": "d6668e34bbce788459883a1097bf0ee170f49c61",
|
||||
"rev": "2e85abc79236d031845a0e2be3ebbd1759cea715",
|
||||
"type": "gitlab"
|
||||
},
|
||||
"original": {
|
||||
@@ -93,11 +93,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778716662,
|
||||
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
||||
"lastModified": 1785627969,
|
||||
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
||||
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -130,17 +130,16 @@
|
||||
"git-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781733627,
|
||||
"narHash": "sha256-U3yTuGBnmXvXoQI3qkpfEDsn9RovQPAjN7ndRco+3u0=",
|
||||
"lastModified": 1784288435,
|
||||
"narHash": "sha256-ReRHaLgr/uVqdD8afFSn+myXIfpHeOhP0yYe0TJqAA8=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "3bbec39bc90eadfa031e6f3b77272f3f60803e39",
|
||||
"rev": "43b3c1ab9d40fb1dbb008f451988a91e375825e9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -149,27 +148,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"git-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"home-manager": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -177,11 +155,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781981105,
|
||||
"narHash": "sha256-/1nNBbA7PrSQpTc9Qazkhl4kIPg+TNl0CjxS3UQJKlw=",
|
||||
"lastModified": 1785119570,
|
||||
"narHash": "sha256-Rgs2xKnGLFWQscxUaXX07oyZeuMDOHEbqDOsgliLFGM=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "7bfff44b465909f69a442701293bc0badcf476dc",
|
||||
"rev": "d4fd24667c8cbef124bb70a20380cab75ec8474d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -214,11 +192,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781772065,
|
||||
"narHash": "sha256-xIbRSwDB1GBAUsWsQZUjudGfAGQt3BOpsWaO/ugVa4w=",
|
||||
"lastModified": 1783744694,
|
||||
"narHash": "sha256-2cp6N3rrwnGYLTx9l6N+NI+kwrCWxvJUbj5WJhvB29A=",
|
||||
"owner": "nix-darwin",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "adda04f0bf4819575b1978c2f8d78401b3c2be12",
|
||||
"rev": "c3e90c89649b07d1a96e4b9dd6cd0d6e44b91a74",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -233,11 +211,11 @@
|
||||
"brew-src": "brew-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781389246,
|
||||
"narHash": "sha256-ORqLAo/hoJdsZC7UPAuEHev6S0+XIqKEC7vjo5prz1k=",
|
||||
"lastModified": 1786217209,
|
||||
"narHash": "sha256-rusAj5QBnbSwXG6csns6dwxTeCOGRcj08VmlnhUQ6ZY=",
|
||||
"owner": "zhaofengli",
|
||||
"repo": "nix-homebrew",
|
||||
"rev": "de7953a08ed4bb9245be043e468561c17b89130d",
|
||||
"rev": "486357ea434dc0061ea52121ff99b1d33096c811",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -253,11 +231,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1782030356,
|
||||
"narHash": "sha256-h4WpMr455AfRub0FXBaon6Vcpe0waUyJ4GivIW6oyd4=",
|
||||
"lastModified": 1786249295,
|
||||
"narHash": "sha256-Y2mSr+HLKYoOsjiackgilxkHXe8gkJ3z4hFFekjQX3I=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-index-database",
|
||||
"rev": "3017088b49efd404f78e3b104f553b97e4af786b",
|
||||
"rev": "14d55b8069119e3b88da7aa2f6c97f86a2cd3cd6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -274,11 +252,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781520503,
|
||||
"narHash": "sha256-XuqQQG1qRyc3o8ld937sDLQNx+QrGV852KJ0dNglJDg=",
|
||||
"lastModified": 1785426242,
|
||||
"narHash": "sha256-QHAP8KsJQmI+dpNS/wfWAtEBQ0Zby+B0Ty+qZIO/U2w=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixos-apple-silicon",
|
||||
"rev": "43043ad207529650f9fa68e1705f7cf9c08bfdeb",
|
||||
"rev": "66d8dd2c27f99bd5420c99938b60695aac1785c4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -294,11 +272,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781622756,
|
||||
"narHash": "sha256-JrPh4M6S7aPsEE9tOENuZrxC6o2szSLlK+t4+nLke9s=",
|
||||
"lastModified": 1785232496,
|
||||
"narHash": "sha256-65EQYIRRpTdpH8lUiB6Mvo5uBkG60aBIzAJuALfx+O0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixos-hardware",
|
||||
"rev": "08018c72174a4df5657f8d94178ac69fb9c243e5",
|
||||
"rev": "2e790b0a6be8ec2b76174ac0931b8ff11919ec98",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -315,11 +293,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781182279,
|
||||
"narHash": "sha256-V5EQQbDnmdiXGQXrEF1PEL7QYsFqfH8N1E89Z5ONwFk=",
|
||||
"lastModified": 1784642409,
|
||||
"narHash": "sha256-hcbDqFuySAJawljt5r0sKBCJKYnbtGD0T/ZIozH1Dq0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "NixOS-WSL",
|
||||
"rev": "5675822ba756e6e56f8f6a5a76e90e0da2ece94d",
|
||||
"rev": "eaeb18da90024448a60eb1ec7132eafa4003404e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -330,11 +308,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1781216227,
|
||||
"narHash": "sha256-9mUW6gNwoN2SWc/l0fW4svPNOulXLl8ijqKyeSOGgJE=",
|
||||
"lastModified": 1786201459,
|
||||
"narHash": "sha256-CiOTEjmwAmG2AWnaIno9YaCJJmpca2FXPhMAsnrolCg=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a0374025a863d007d98e3297f6aa46cc3141c2f0",
|
||||
"rev": "8b8c811c7c2541c30382c5de7ed26be055569c60",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -346,11 +324,11 @@
|
||||
},
|
||||
"nixpkgs-unstable": {
|
||||
"locked": {
|
||||
"lastModified": 1781577229,
|
||||
"narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=",
|
||||
"lastModified": 1786106723,
|
||||
"narHash": "sha256-zDSUbpoeo/9ZmD2+wXnzxoo1+uhL8vxc0b8yuYMKYq0=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f",
|
||||
"rev": "f13ff45afd1bb73e640eaa08a7066dbed07e3238",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -369,11 +347,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781971008,
|
||||
"narHash": "sha256-T2u2RQZWKvD1J+TgcxjiJr8IymBr/PrUNeAGhMZFZU4=",
|
||||
"lastModified": 1782919967,
|
||||
"narHash": "sha256-pRwjfB5HQJ3m8J8bOR43pPHtHI7VUJSqwLA3P06cOY0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixvim",
|
||||
"rev": "7afca458f064f166d3a9c98db3b41a984fe46492",
|
||||
"rev": "667c8471f4a0fb24d702d1a61af8609f1a5f1ba6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -424,11 +402,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1780220602,
|
||||
"narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=",
|
||||
"lastModified": 1785945821,
|
||||
"narHash": "sha256-NLSyTCW4K4ofhNBllt3omPasm6QpralXH1DBZOc91Dw=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "db947814a175b7ca6ded66e21383d938df01c227",
|
||||
"rev": "ae7910970dddc408fe6ab1c8e4b277bb21d72dc0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -84,7 +84,9 @@
|
||||
flake-parts.lib.mkFlake { inherit inputs; } (
|
||||
{ lib, ... }:
|
||||
let
|
||||
# claude-code tracks nixpkgs-unstable regardless of the pinned nixpkgs.
|
||||
# These track nixpkgs-unstable regardless of the pinned nixpkgs.
|
||||
# gcx: 26.05 ships 0.2.14, which predates the stacks/contexts config
|
||||
# model and the agento11y commands the tooling expects.
|
||||
overlays = [
|
||||
(_final: prev: {
|
||||
inherit
|
||||
@@ -93,8 +95,19 @@
|
||||
config.allowUnfree = true;
|
||||
})
|
||||
claude-code
|
||||
gcx
|
||||
;
|
||||
})
|
||||
# commitizen 4.13.9's regression test for the invalid-command error
|
||||
# message asserts argparse's older, unquoted "invalid choice" wording;
|
||||
# the argparse in Python 3.13 quotes each choice, so the fixture no
|
||||
# longer matches and the checkPhase fails. The package itself is fine
|
||||
# -- deselect just that test. Drop once nixpkgs updates the fixture.
|
||||
(_final: prev: {
|
||||
commitizen = prev.commitizen.overridePythonAttrs (old: {
|
||||
disabledTests = (old.disabledTests or [ ]) ++ [ "test_invalid_command" ];
|
||||
});
|
||||
})
|
||||
];
|
||||
|
||||
# Unfree packages permitted to be built (replaces blanket allowUnfree).
|
||||
@@ -422,6 +435,7 @@
|
||||
git = ./home/git.nix;
|
||||
editor = ./home/editor.nix;
|
||||
claude = ./home/claude.nix;
|
||||
secret-service = ./home/secret-service.nix;
|
||||
desktop = ./home/desktop.nix;
|
||||
sway = ./home/sway.nix;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
- [User name](user_name.md) — address the user as Lyra
|
||||
- [Soviet engineer persona](persona_soviet_engineer.md) — terse, dry, pragmatic; no emojis; technical accuracy over voice
|
||||
- [Git conventions](git_conventions.md) — never commit to main, always a branch; Conventional Commits branches and messages; inspect repo style first; commit at logical checkpoints
|
||||
- [Git network ops](git_network_ops.md) — GitHub pushable in-sandbox (agent key; just sandbox off); Gitea code.emmathe.dev needs hand-off
|
||||
- [Git commit signing](git_commit_signing.md) — signs in-sandbox via ssh-agent (allowAllUnixSockets + inlined pubkey)
|
||||
- [Git conventions](git_conventions.md) — never commit to main, always a branch; EVERY commit is `type(<TICKET-ID>): summary` using the live ticket, overrides repo's bare-prefix style; watch for scope decay on follow-up commits; grep to verify before pushing
|
||||
- [Git network ops](git_network_ops.md) — GitHub and Gitea (code.emmathe.dev) both pushable in-sandbox (sandbox off, agent key); raise Gitea PRs via tea CLI
|
||||
- [Git commit signing](git_commit_signing.md) — signs in-sandbox via ssh-agent (allowAllUnixSockets + inlined pubkey); sig=N without allowedSignersFile is cosmetic, still signed
|
||||
- [Git check state first](git_check_state.md) — always check branch/status/divergence before git work; Lyra edits repos between sessions
|
||||
- [Keep docs updated](docs_keep_updated.md) — update docs in the same pass as code/config changes; stale docs are a defect
|
||||
- [SIBO Workabout MX project](sibo_workabout_mx_scanner.md) — RE + barcode-inventory project state; scanner is an OO DYL object (oscanner), blocked on on-device ordinal capture; resume via code/inventory/CONTINUATION.md
|
||||
- [Jira tooling](jira_tooling.md) — comments are Markdown not wiki; transitions may need assignee; link direction; WSP transition IDs
|
||||
- [Jira WSP fields](jira_wsp_fields.md) — WSP field map: issue-type IDs, required Bug fields with allowed values/IDs, Task shortcut, relevant components
|
||||
- [Review and comments workflow](workflow_review_and_comments.md) — show PR body and non-trivial Jira comments before posting; terse IaC code comments; PR body content rules
|
||||
- [Code comment style](code_comment_style.md) — reviewer feedback: no ticket IDs in comments by default, concise, explain non-obvious why; Helm needs `#` not `{{/* */}}` to render
|
||||
- [Copilot review false positives](copilot_review_false_positives.md) — verify Copilot "this breaks X" claims against spec/live config before acting; two recorded Terraform false positives
|
||||
- [Sandbox prompts](feedback_sandbox_prompts.md) — don't prompt for sandbox-disable or routine read-only shell ops; broaden permissions instead
|
||||
- [Dev clusters disposable](dev_clusters_disposable.md) — Lyra's dev clusters are recreatable; mutate/break freely, no confirmation needed
|
||||
- [Nix shell tooling](nix_shell_tooling.md) — any nixpkgs tool runs ad hoc via `nix run`/`nix shell nixpkgs#<pkg>`; a missing command is never a dead end
|
||||
- [WSP local build and test](wsp_local_build_and_test.md) — core-services-cloud on this box: dotnet via nix, artifactory creds from `~/.artifactoryenv` sourced per command, how to tell auth failure from a code failure
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: code_comment_style
|
||||
description: "Code/comment style from PR review feedback: no ticket IDs in comments by default, concise, explain the non-obvious why"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: feedback
|
||||
originSessionId: 59e09a3f-1429-4f68-a5fb-9af4390e9b0d
|
||||
---
|
||||
|
||||
Recurring PR-review feedback from human reviewers (Tom Wilkins, Andrew Hyde, Gilberto Pestanarosa) on the `multicluster` and `unified-helm` repos, on how to write comments in code and IaC:
|
||||
|
||||
- **No Jira/WSP ticket IDs in code comments or WAF `msg:` strings by default.** Add a ticket ref only when there is a specific reason to. Never duplicate the id, and never put a ticket URL in a comment. Tracking/rationale belongs in the PR description and the Jira ticket, not in `.tf`, `.tftpl`, or `.yaml`. (Flagged repeatedly — PRs #1735, #1762.)
|
||||
- **Comment the non-obvious "why", not the obvious "what".** Drop comments that restate what the code or file plainly does (e.g. a header on `namespace.yaml` re-announcing that it defines a namespace). If a reviewer can't tell why a comment exists, it shouldn't.
|
||||
- **Keep it short and readable.** No multi-line block where one line does; if a comment isn't clear after a couple of reads, rewrite it plainer. Prefer trimming to the single load-bearing sentence over hedged prose. (PRs #1745, #216.)
|
||||
- **In Helm charts, use `#` YAML comments — not `{{/* */}}` — for anything that must appear in the rendered manifest.** Helm template comments are stripped before render, so port/label explanations meant for the live chart have to be `#`. (PR #216.)
|
||||
|
||||
**Why:** Multiple human reviewers, across multiple PRs, consistently push back on verbose comments and gratuitous ticket references. Terse, purpose-driven comments clear review faster.
|
||||
|
||||
**How to apply:** When writing or editing comments in code/IaC, default to: no ticket id, one line, non-obvious "why" only. This supersedes the "one-liner + WSP ticket reference" phrasing that used to live in [[workflow-review-and-comments]]. Relates to [[docs_keep_updated]].
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: copilot_review_false_positives
|
||||
description: "Verify Copilot PR-review 'this breaks X' claims against spec/live config before acting; two recorded false positives"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: feedback
|
||||
originSessionId: 59e09a3f-1429-4f68-a5fb-9af4390e9b0d
|
||||
---
|
||||
|
||||
The Copilot reviewer on the `multicluster` / `unified-helm` repos raises blocking-sounding "this will fail" claims that are sometimes wrong. Verify against the language spec and the live/`master` config before treating one as real or applying its fix.
|
||||
|
||||
Recorded false positives (both Terraform, both Emma-flagged "for future reference"):
|
||||
|
||||
- **PR #1742** — claimed `var.map.hyphenated-key` dot access is parsed as subtraction and breaks `terraform plan`. False: HCL2 identifiers may contain hyphens (`ID_Start (ID_Continue | "-")*`), and the same pattern is already live on `master` in prod. Bracket indexing was adopted anyway as marginally clearer, not as a fix.
|
||||
- **PR #1745** — claimed the `aks_pools` per-pool `max_surge` lookup was off-by-one and should use `count.index + 1`. False: every config attribute on that resource indexes with `count.index`; only the cosmetic `name`/`az_nodepool` label uses `+1`. Applying `+1` would have introduced a real bug (wrong pool, and out-of-bounds on the last pool).
|
||||
|
||||
**Why:** Blindly applying a plausible-but-wrong Copilot suggestion can introduce a real defect or waste review cycles.
|
||||
|
||||
**How to apply:** For any Copilot claim that code is broken or unsafe, confirm it against the relevant spec and the existing working config first; if it's wrong, say so plainly on the PR and leave the code. Genuine Copilot catches (over-broad WAF `@beginsWith`, missing input validation, doc/behaviour drift) still get fixed. Relates to [[workflow-review-and-comments]] and [[code_comment_style]].
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: git-commit-signing
|
||||
description: "Commits sign in-sandbox via ssh-agent — needs `allowAllUnixSockets: true` in settings, plus pubkey inlined in user.signingkey."
|
||||
description: "Commits sign in-sandbox via ssh-agent (allowAllUnixSockets + inlined pubkey); local verify shows sig=N without an allowedSignersFile but the commit IS signed."
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: feedback
|
||||
@@ -19,4 +19,6 @@ Lyra's git is configured to SSH-sign commits (`commit.gpgsign=true`, `gpg.format
|
||||
|
||||
**How to apply:** Commit normally with `git commit`. If signing fails with `Couldn't load public key`, check (a) `git config --get user.signingkey` starts with `key::ssh-ed25519 AAAA...` (not literal `$(...)`), (b) `ssh-add -l` from in-sandbox lists keys (if it says "Operation not permitted", the sandbox config didn't take effect — restart Claude Code), (c) the ssh-agent on the host actually has the key loaded (`ssh-add -l` outside the sandbox). Do NOT use `--no-gpg-sign` to bypass — the repo's `ReleaseWorkflow-Commit` check enforces signed commits.
|
||||
|
||||
**Verifying — the recurring trap:** `git log --show-signature` and the `%G?` format both report `N` and print `error: gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification`. This does NOT mean the commit is unsigned — it means git has no local allowed-signers file to check it against. The signature is present. Confirm the real state with `git cat-file commit <ref> | grep -i '^gpgsig'`: an `-----BEGIN SSH SIGNATURE-----` block means signed. So `N` here is cosmetic, not a signing failure — do not "fix" it by re-committing. To make local verification actually pass, set `gpg.ssh.allowedSignersFile` to a file mapping the signer to the pubkey (a line like `emma.thorpe@cloud.com ssh-ed25519 AAAA...`); Gitea/CI verifies server-side regardless.
|
||||
|
||||
Related: [[git-network-ops]], [[git-conventions]].
|
||||
|
||||
@@ -11,8 +11,34 @@ metadata:
|
||||
|
||||
**Branch naming:** Follow the repo's existing convention — inspect with `git branch -a` or `git for-each-ref` before creating. Prefer Conventional Commits prefixes (`feat/`, `fix/`, `chore/`, `docs/`, `refactor/`). Format: `<prefix>/<TICKET-ID>-<kebab-summary>`. Only ask if no convention is discoverable.
|
||||
|
||||
**Commit messages:** Conventional Commits. Subject line: `<type>(<TICKET-ID>): <imperative summary>` — ticket ID as the scope. Use additional `-m` flags for rationale/body. Commit at logical checkpoints, not one giant final commit.
|
||||
**Commit messages — every commit, without exception:** `<type>(<TICKET-ID>): <imperative summary>`. The ticket ID goes in the scope. Use additional `-m` flags for rationale/body. Commit at logical checkpoints, not one giant final commit.
|
||||
|
||||
**Why:** Lyra's standard workflow for traceability and clean history.
|
||||
**`<TICKET-ID>` is the real ticket for the work in hand.** It is a symbol to substitute, never a literal — if a commit subject ever reaches git still containing `<TICKET-ID>`, or a made-up number, that is a defect. Establish the actual ID before the first commit, in this order:
|
||||
|
||||
**How to apply:** Whenever creating a branch or committing in any repo. Inspect existing branches/log first so you match the repo's actual style; the format above is the default when nothing else is established.
|
||||
1. The ticket Lyra named in the request.
|
||||
2. The current branch name — `task/WSP-32542/remove-wspgov-terraform` gives `WSP-32542`. Extract it: `git branch --show-current | grep -oE '[A-Z]{2,}-[0-9]+'`.
|
||||
3. The ticket the branch's existing commits already use.
|
||||
|
||||
If none of those yield an ID, ask which ticket to file the work under. Do not guess, do not reuse the ID from an unrelated earlier task in the session, and do not invent a plausible-looking number. Every commit in a branch normally carries the same ID; if the work genuinely spans two tickets, split the commits accordingly rather than picking one at random.
|
||||
|
||||
**Exception — repos with no issue tracker.** Personal repos such as `nixfiles` have no Jira project. There the scope is the area of the change, not a ticket: `chore(claude): ...`, `chore(deps): ...`, `feat(hosts): ...`. Conventional form is still required; only the ticket scope is dropped. Never invent a WSP number to satisfy the rule in a repo that has no tickets. The ticket requirement applies to the work repos under `~/code` that are backed by the WSP Jira project and gated by CI.
|
||||
|
||||
**This format is mandatory and overrides the repo's existing log style.** Many repos (`multicluster`, `core-services-cloud`) have histories full of bare `<TICKET-ID>: summary` subjects written by other people. Do not copy that. Match repo style for _branch names_ only; commit subjects are always full Conventional Commits with the ticket scope. CI enforces this, and a failure means Lyra rebases the history by hand.
|
||||
|
||||
**Known failure mode — scope decay across a session.** The first commit gets `fix(<TICKET-ID>): ...` correctly, then follow-up commits in the same sitting degrade to bare `test: add tests for class`, `refactor: hoist middleware`, `chore: tidy`. This has caused real rebase work in `core-services-cloud`. The second, third and fifth commits need the ticket scope exactly as much as the first. Re-read the subject against the format before every single `git commit`.
|
||||
|
||||
**Merge commits count too.** Prefer `git rebase origin/<base>` over `git merge` so none is created. If unavoidable, set the message explicitly: `git merge --no-ff -m "<TICKET-ID>: merge master into <branch>"`. Keep the ID uppercase; the check is case-sensitive.
|
||||
|
||||
**Before pushing, verify — do not skip this:**
|
||||
|
||||
```
|
||||
git log --format=%s origin/<base>..HEAD | grep -vE '^[a-z]+(\([A-Z]{2,}-[0-9]+\))!?: '
|
||||
```
|
||||
|
||||
Must print nothing. Writing each subject carefully is not a substitute for running it.
|
||||
|
||||
**Auditing past behaviour is unreliable.** If Lyra has already rebased to fix a bad subject, the log shows her corrected version, not what was originally written. A clean `git log` is not evidence that nothing was wrong. Check author date vs committer date (`--format="%ad %cd"`) — a mismatch means history was rewritten. Never argue from a clean log that the fault did not occur.
|
||||
|
||||
**Why:** Lyra's standard workflow for traceability, and a hard CI gate. A malformed subject is manual rebase work for her, not just a red build.
|
||||
|
||||
**How to apply:** Conventional form on every commit in every repo; the ticket scope additionally on every commit in a Jira-backed work repo. Format first, repo style second. Run the verification grep before every push. Relates to [[git_check_state]].
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: git-network-ops
|
||||
description: Push/pull is remote-specific — GitHub is agent-pushable in-sandbox; Gitea (code.emmathe.dev) needs hand-off to Lyra.
|
||||
description: Push/pull is remote-specific — both GitHub and Gitea (code.emmathe.dev) are agent-pushable in-sandbox (sandbox off); raise Gitea PRs with the tea CLI.
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: feedback
|
||||
@@ -11,8 +11,8 @@ Whether a network op can run depends on which key the remote needs:
|
||||
|
||||
**GitHub remotes (e.g. csg-citrix-storefront/\*): pushable in-sandbox by the agent.** ssh-agent holds the decrypted `~/.ssh/id_ed25519` (`emma.thorpe@cloud.com`), which is authorized on GitHub. Only requirement now is `dangerouslyDisableSandbox: true` (network); plain `git push`/`ls-remote` works. Probe non-mutatively with `git ls-remote` first. (Historically also needed `ssh -F /dev/null` to dodge a broken NixOS-WSL system ssh_config include — that's fixed in nixfiles via `programs.ssh.systemd-ssh-proxy.enable = false`, merged and rebuilt 2026-06, so the workaround is no longer needed.)
|
||||
|
||||
**Gitea (`code.emmathe.dev`, e.g. nixfiles): hand off to Lyra.** Needs `~/.ssh/code.emmathe.dev`, which is passphrase-protected and NOT in the agent, so `git push`/`pull`/`fetch` there will fail/hang. Pause, give Lyra the exact command (she runs `ssh-add ~/.ssh/code.emmathe.dev` once, then pushes).
|
||||
**Gitea (`code.emmathe.dev`, e.g. nixfiles): pushable in-sandbox by the agent (as of 2026-07-14).** The ssh-agent now holds the `code.emmathe.dev` key (`git@code.emmathe.dev`), so `git push` works with `dangerouslyDisableSandbox: true` — it needs the agent socket plus `~/.ssh/known_hosts`, both reachable with sandbox off. Probe with `git ls-remote` first. Raise PRs with the `tea` CLI, which is installed and logged in to `code.emmathe.dev` (user `lyrathorpe`): `tea pr create --login code.emmathe.dev --repo lyrathorpe/nixfiles --base main --head <branch> --title "..." --description "..."`. Only fall back to hand-off if `ssh-add -l` (sandbox off) does NOT list the `code.emmathe.dev` key — then it dropped from the agent and Lyra must re-add it (`ssh-add ~/.ssh/code.emmathe.dev`, passphrase-protected).
|
||||
|
||||
**Fine to run locally:** `git branch`, `git rebase`, `git reset`, `git status`, `git log`, `git diff`. `git commit` works in-sandbox via ssh-agent signing — see [[git-commit-signing]].
|
||||
|
||||
**How to apply:** Check the remote host before a network op. GitHub → just do it (sandbox off). Gitea → hand off. Related: [[git-conventions]].
|
||||
**How to apply:** Both remotes → do it with sandbox off; probe with `git ls-remote` first, and raise Gitea PRs via `tea`. Hand off only if the Gitea key is missing from the agent. Related: [[git-conventions]].
|
||||
|
||||
@@ -9,6 +9,10 @@ metadata:
|
||||
|
||||
**Transitions:** `transitionJiraIssue` may fail if the issue lacks an assignee. Set assignee first via `editJiraIssue` when a transition errors on assignee requirement.
|
||||
|
||||
**Transition required fields (WSP):** the same target status can enforce different required fields per issue type — e.g. `Cancelled` on a Story requires `Resolution` + `Justification`, but on an Epic requires neither (so an Epic can land in Cancelled while still reading Unresolved). Fetch requirements with `getTransitionsForJiraIssue` + `expand=transitions.fields` before transitioning. Cancel/won't-do resolution values: `Won't Fix` (10068), `Canceled` (10070), `Obsolete` (10073 — use for superseded-by-another-ticket).
|
||||
|
||||
**ADF-only custom fields:** the WSP `Justification` field (`customfield_10070`) advertises schema `textarea` (string) but the API rejects a plain string — it requires an Atlassian Document Format object (`{type:"doc",version:1,content:[...]}`). If a transition/edit errors with "Operation value must be an Atlassian Document", wrap the text in ADF.
|
||||
|
||||
**Issue link direction:** For `createIssueLink`, "X is blocked by Y" means `inwardIssue=Y` (the blocker), `outwardIssue=X` (the blocked), `type.name="Blocks"`. Inward = the side the link points _from_; outward = the side it points _to_.
|
||||
|
||||
**WSP project transition IDs:**
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: jira-wsp-fields
|
||||
description: WSP Jira project field map — issue-type IDs, required Bug fields with allowed values/IDs, and the Task shortcut for fast ticket creation
|
||||
metadata:
|
||||
type: reference
|
||||
---
|
||||
|
||||
Field map for the **WSP (Workspace Platform)** Jira project, to create tickets without trial-and-error. Site `citrix.atlassian.net`, cloudId `70cbc59a-06d2-4508-a9a6-61f1dbc2057f`, project key `WSP`, project id `10061`. See also [[jira-tooling]].
|
||||
|
||||
**Issue-type IDs:** Epic `10000`, Story `10004`, Task `10008`, Bug `10123`, Sub-task `10009`.
|
||||
|
||||
**Fast path — use Task, not Bug.** A `Task` requires only `summary` (project/issuetype auto, reporter defaults to caller). A `Bug` requires six extra fields (below), so only pick Bug when it must be a Bug. The sibling infra/remediation tickets in WSP are Tasks.
|
||||
|
||||
**Bug required fields** (enforced by the create validator; note `createmeta` omits `versions` but the API rejects without it):
|
||||
|
||||
| Field | Key | Shape | Allowed values (value = id) |
|
||||
| ------------------- | ------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| Severity | `customfield_10061` | `{"value":"S2"}` | S1=10643, S2=10644, S3=10645, S4=10646 |
|
||||
| Affects Environment | `customfield_10116` | `{"value":"Production"}` | Production=11031, Staging=11032, Integration=11033, Development=11034, Test=11035 |
|
||||
| Defect Source | `customfield_10138` | `{"value":"Internal - Manual"}` | Internal - Manual=12699, Internal - Automation=12700, Customer=12702, Security Review=12704 |
|
||||
| Regression | `customfield_10141` | `{"value":"No"}` | Yes - Previous Build=12705, Yes - Previous Release=12706, No=12707 |
|
||||
| Components | `components` | `[{"name":"Workspace Configuration"}]` | 109 options; relevant ones below |
|
||||
| Affects versions | `versions` | `[{"name":"<version>"}]` | not in requiredFieldsOnly createmeta — fetch current list from full createmeta or project versions before setting |
|
||||
|
||||
Select customfields (`...customfieldtypes:select`) accept `{"value":"..."}` or `{"id":"..."}`. Components/versions accept `[{"name":...}]` or `[{"id":...}]`.
|
||||
|
||||
**Relevant Components (name=id):** Workspace Configuration=12052, Workspace-Platform=12060, Multicluster Platform=12023, WSP Core Ingress=12037, Microservice Infrastructure=12020, Infrastructure=34375, Custom Domain Proxy=12021, Custom Domain Ingress Manager=11968, Custom Domain Infrastructure=11975, StoreFrontConfiguration=12042, StoreFront=12050, Test Infrastructure=12012, WSP Release Infrastructure=12011.
|
||||
|
||||
**Other create notes:** pass `description`/`commentBody` with `contentFormat: markdown`; set labels via `additional_fields {"labels":[...]}`; attach to an epic with the top-level `parent` param (`parent: "WSP-32494"` works for epic→Task). WSP transition IDs live in [[jira-tooling]].
|
||||
|
||||
Example Bug `additional_fields`:
|
||||
`{"customfield_10061":{"value":"S2"},"customfield_10116":{"value":"Production"},"customfield_10138":{"value":"Internal - Manual"},"customfield_10141":{"value":"No"},"components":[{"name":"Workspace Configuration"}],"versions":[{"name":"<version>"}],"labels":["..."]}`
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: nix-shell-tooling
|
||||
description: "Any nixpkgs tool can be run ad hoc via nix run / nix shell — a missing command is never a dead end during development"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: feedback
|
||||
originSessionId: dfb56b58-518b-4daf-b531-7119bb4a9534
|
||||
---
|
||||
|
||||
Any tool in nixpkgs can be run without installing it into the environment. If a
|
||||
command is missing during development, pull it from nixpkgs on the fly instead
|
||||
of working around its absence or reporting the tool as unavailable.
|
||||
|
||||
**Why:** Lyra runs NixOS; the ambient PATH is deliberately minimal, but the full
|
||||
nixpkgs set is always one command away. "command not found" is not a blocker.
|
||||
|
||||
**How to apply:**
|
||||
|
||||
- One-off run: `nix run nixpkgs#<pkg> -- <args>` (e.g. `nix run nixpkgs#jq -- .`).
|
||||
- Tools on PATH for a session: `nix shell nixpkgs#<pkg> [nixpkgs#<pkg2> ...]`,
|
||||
then run commands normally.
|
||||
- Legacy form also works: `nix-shell -p <pkg> --run '<cmd>'`.
|
||||
- Prefer this over hand-rolling a substitute for a tool that exists in nixpkgs.
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: sibo-workabout-mx-scanner
|
||||
description: State of the Psion Workabout MX reverse-engineering / barcode-inventory project and how to resume it
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
originSessionId: 74de014e-9cf4-47f6-92f4-c34197ac1858
|
||||
---
|
||||
|
||||
Long-running project (July 2026) reverse-engineering the **Psion Workabout MX** (SIBO OS, NEC V30MX, TopSpeed C) to build a barcode **inventory demo** (scan UPC → DBF database file; add stock, consume by a quantity unit) and, alongside, **complete device programming documentation**. Repo: Gitea **lyrathorpe/sibo-playground**, working branch **`feat/inventory-phase1-scan`** (unmerged). Gitea needs hand-off / the contents API for pushes — see [[git-network-ops]]; [[git-conventions]] for branch/PR rules.
|
||||
|
||||
**Committed on the branch (durable, survive reboot):**
|
||||
|
||||
- `docs/reference/00-08` + index — the SIBO/MX programming reference (building apps, system/OS, I/O devices, PLIB core, file system & DBF, UI, hardware, and RE'd boot/OS-call internals).
|
||||
- `code/inventory/` — app scaffold: `upc.c/.h` (UPC-A check-digit validation, correct), `bcode.c/.h`, `scan.c` (Phase-1 diagnostics), `README.md`, **`SCANNER-API.md`** (all scanner findings), **`CONTINUATION.md`** (the on-device debugging procedure to finish).
|
||||
- `docs/mx-re/toolchain-and-plan.md` — the RE toolchain.
|
||||
- The **ROM `w2mx_v7.20f_eng.bin`** and the full **SDK + HDK** (manuals as `docs/*.txt`; headers/libs/`bar*.ldd` under `code/SIBOSDK/`; HDK under `code/HDK/`) are on the branch. `/tmp/claude/sibo/` working files (ROM slices, MAME rom dir, Ghidra/decomp output) are transient and reproducible from the toolchain doc.
|
||||
|
||||
**Scanner — key result:** the integral laser is driven as an **OO library object** in `SCANNER.DYL` (category token **`oscanner`**) via `p_getlibh` → `p_newsend`/`f_newsend` → `p_send`, over **LIBMANAGER (INT 0x84)** / **MESSMANAGER (INT 0x83)** — NOT raw device I/O. Confirmed on the physical device: `p_open("WL2:D")` + control ops **6** then **7** (`p_iow(chan,6); p_iow(chan,7)`) fire the laser to a good decode (green LED). Default Symbol2 11-byte param block: `04 3f 01 15 06 04 1e 80 0d 0a 06` (decoded output is CR/LF-terminated). Dead ends (do not retry): raw `TTY:D` reads, and the wand `BAR:` / `bar*.ldd` decoders (probe expansion slots → `-41`).
|
||||
|
||||
**Blocked on / next step:** the OO **message ordinals + parameter structs** for init / set-params / trigger / read. OLIB assigns ordinals dynamically across the class hierarchy (base classes in `olib`/`hwim`), so they resolve only at runtime — capture them with the **SIBO Debugger on the physical device** (remote debug over serial; it supports breakpoints inside DYLs). MAME cannot inject a barcode, so the last mile must be on hardware. Full step-by-step is in `code/inventory/CONTINUATION.md`.
|
||||
|
||||
**RE toolchain (reproducible):** the ROM is MAME machine **`psionwamx`**; run its debugger headless via `xvfb-run -a mame psionwamx -rompath roms -debug -debugscript CMDS -sound none -seconds_to_run N` (MAME lua input injection into the keyboard matrix does NOT work headless — a known limitation). Static: **radare2** (16-bit x86). Decompile: **Ghidra headless** (processor `x86:LE:16:Real Mode`, a Java GhidraScript — Ghidra 12 has no bundled Python). Get MAME/radare2/Ghidra via `nix-shell -p ...`. Details in `docs/mx-re/toolchain-and-plan.md`.
|
||||
|
||||
**Fallback to deliver value now:** Phase 2 (the DBF inventory: add stock, consume by quantity) can be built with keyboard UPC entry against `docs/reference/05-filesystem-dbf.md`, dropping the scanner in behind the same interface once retrieval is finished. [[docs-keep-updated]]
|
||||
@@ -11,7 +11,7 @@ metadata:
|
||||
|
||||
**Show non-trivial Jira comments before posting:** Same rule for any non-trivial public Jira comment — paste the proposed body in chat first when there is any doubt about content.
|
||||
|
||||
**Code comments stay terse:** One-liner saying what a thing is for, plus the WSP ticket reference. Full rationale lives in the Jira ticket or commit/PR description — not in `.tf`, `.tftpl`, or `.yaml` files. See [[git-conventions]].
|
||||
**Code comments stay terse:** One line on the non-obvious _why_, and **no Jira/WSP ticket id by default** — add one only when specifically warranted. Full rationale lives in the Jira ticket or commit/PR description, not in `.tf`, `.tftpl`, or `.yaml` files. Reviewers repeatedly strip gratuitous ticket refs and verbose comments; see [[code_comment_style]] for the full rule set and [[git-conventions]].
|
||||
|
||||
**PR body content:** Do NOT mention `terraform plan` output or terraform-version mismatch caveats. Stick to: what changed, why, and validation results.
|
||||
|
||||
@@ -19,4 +19,4 @@ metadata:
|
||||
|
||||
**Why:** Lyra reviews everything Claude publishes externally before it goes out; terraform-version noise in PR descriptions is unhelpful clutter.
|
||||
|
||||
**How to apply:** Before any GitHub PR creation or substantive Jira comment, show the draft. When writing code comments in IaC files, keep to one-liner + ticket ref.
|
||||
**How to apply:** Before any GitHub PR creation or substantive Jira comment, show the draft. When writing code comments in IaC files, keep to a one-line non-obvious _why_ with no ticket id by default ([[code_comment_style]]).
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
name: wsp-local-build-and-test
|
||||
description: "How to compile and test core-services-cloud locally on Lyra's NixOS/WSL box: dotnet via nix, artifactory creds from ~/.artifactoryenv, sourced per command"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: reference
|
||||
---
|
||||
|
||||
Canonical build/test commands for `core-services-cloud` live in the repo at
|
||||
`.ai/agents.md` and `.ai/component-tests.md` — read those rather than guessing.
|
||||
The repo docs assume Windows/PowerShell paths; this box is NixOS under WSL, so
|
||||
the environment deltas below are what actually make them run.
|
||||
|
||||
**dotnet is not on PATH.** Get it from nixpkgs — see [[nix-shell-tooling]]:
|
||||
|
||||
```sh
|
||||
nix shell nixpkgs#dotnet-sdk_8 --command dotnet build
|
||||
```
|
||||
|
||||
`global.json` pins SDK 8 with `rollForward: minor`, so `dotnet-sdk_8` is the
|
||||
right attribute.
|
||||
|
||||
**Every restore needs artifactory credentials.** They live in
|
||||
`~/.artifactoryenv` (mode 0600) as `ARTIFACTORY_READ_ACCESS_USER` and
|
||||
`ARTIFACTORY_READ_ACCESS_TOKEN`, consumed by `nuget.config`. Shell state does
|
||||
not persist between tool calls, so source them inside each command:
|
||||
|
||||
```sh
|
||||
set -a; . ~/.artifactoryenv; set +a
|
||||
```
|
||||
|
||||
**Check the credentials before blaming the code.** A failed restore reports
|
||||
`NU1301: Unable to load the service index`, which looks like a network fault but
|
||||
is usually auth. Confirm which it is:
|
||||
|
||||
```sh
|
||||
curl -s -o /dev/null -w '%{http_code}\n' \
|
||||
-u "$ARTIFACTORY_READ_ACCESS_USER:$ARTIFACTORY_READ_ACCESS_TOKEN" \
|
||||
https://repo.citrite.net/api/nuget/v3/stf-virtual-nuget/index.json
|
||||
```
|
||||
|
||||
200 means the credentials are good. 401 means the token is the problem, not the
|
||||
change under test. `https://repo.citrite.net/api/system/ping` returning `OK`
|
||||
proves reachability independently of auth.
|
||||
|
||||
**Component tests** need Docker plus the same credentials, and are driven by
|
||||
`./service.ps1` — PowerShell, so `nix shell nixpkgs#powershell` if `pwsh` is
|
||||
missing. Log in to the image registry first:
|
||||
|
||||
```sh
|
||||
echo "$ARTIFACTORY_READ_ACCESS_TOKEN" | docker login stf-virtual-docker.repo.citrite.net \
|
||||
--username "$ARTIFACTORY_READ_ACCESS_USER" --password-stdin
|
||||
```
|
||||
|
||||
Two Docker Desktop leftovers break this box, both fatal and both easy to miss:
|
||||
|
||||
1. `/usr/bin/docker` is a dangling symlink into an absent Docker Desktop WSL
|
||||
mount, and it shadows the working NixOS docker inside `pwsh`. The script dies
|
||||
with `Program 'docker' failed to run ... No such file`.
|
||||
2. `~/.docker/config.json` sets `"credsStore": "desktop.exe"`, a helper that does
|
||||
not exist. `docker login` reports success while storing nothing, then pulls
|
||||
fail with `error getting credentials - err: exit status 1`. Remove the
|
||||
`credsStore` key and log in again; docker then writes the auth into
|
||||
`config.json` itself.
|
||||
|
||||
Put the real docker first when invoking anything that shells out to it, and note
|
||||
`$PATH` must expand _inside_ the nix shell or dotnet drops off the path:
|
||||
|
||||
```sh
|
||||
nix shell nixpkgs#dotnet-sdk_8 --command sh -c \
|
||||
'export PATH="/run/current-system/sw/bin:$PATH"; dotnet test ...'
|
||||
```
|
||||
|
||||
A feature canary used by a component test must also be registered in
|
||||
`Automation/Component/ComponentTests/src/Citrix.Wsp.Test.Mocks/WspComprehensive/__files/unleash/unleash-test-environment.json`,
|
||||
or `SetFeatureFlag` fails the test as inconclusive rather than failing loudly.
|
||||
@@ -20,6 +20,25 @@ report? If the latter, rewrite. Retain all software-engineering capability and t
|
||||
- Refer to the user as "comrade Lyra" when it reads naturally; do not force it into every line.
|
||||
- No emojis.
|
||||
|
||||
## Length and form (the voice fails here first)
|
||||
|
||||
Terseness is structural, not just tonal. A dry register wrapped in report furniture —
|
||||
headers, tables, a full status recap every turn — is the failure mode, and it passes a
|
||||
tone-only self-check. Enforce:
|
||||
|
||||
- Default ceiling around 150 words. Longer only when the content genuinely needs it:
|
||||
a real analysis, a comparison of options, a requested writeup.
|
||||
- Headers and tables only for four or more distinct items. Two facts are two sentences.
|
||||
- Report the delta since the last message, never the accumulated state. Assume Lyra
|
||||
remembers what she was told.
|
||||
- State each caveat once per session. Repeating a settled limitation is filler.
|
||||
- Do the obvious next action and report it. Do not present a menu of options for a
|
||||
decision that has an obvious answer.
|
||||
- Do not restate the request, or narrate what is about to be done.
|
||||
|
||||
Self-check before sending: is this the delta, at the shortest length that stays accurate?
|
||||
If it reads like a status report, cut it to the three facts that changed.
|
||||
|
||||
## Scope
|
||||
|
||||
The persona lives in PROSE ONLY — explanations, summaries, status, discussion. It must NEVER
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
./git.nix
|
||||
./editor.nix
|
||||
./claude.nix
|
||||
# Declares services.headlessSecretService; opt-in, off by default. Graphical
|
||||
# hosts should prefer home-manager's own services.gnome-keyring.
|
||||
./secret-service.nix
|
||||
];
|
||||
|
||||
# Manage the XDG base-directory layout and ~/.config files. Tools above
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
pkgs.element-desktop
|
||||
pkgs.legcord
|
||||
pkgs.nemo # file manager (launched via Mod+e, see ./sway.nix)
|
||||
pkgs.darktable
|
||||
#pkgs.plex-desktop
|
||||
#pkgs.plexamp
|
||||
];
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
# Headless Secret Service (org.freedesktop.secrets) on the user session bus,
|
||||
# for CLI tools that keep credentials in the system keychain rather than in a
|
||||
# config file of their own.
|
||||
#
|
||||
# Current consumer: gcx, the Grafana Cloud CLI (users/emmathorpe/work.nix). gcx
|
||||
# stores its OAuth access and refresh tokens in the keychain unconditionally --
|
||||
# its config file holds only opaque `keychain:gcx:v2:...` handles -- and offers
|
||||
# no plaintext fallback (there is no environment variable or config key to
|
||||
# select a file-backed store). With nothing owning org.freedesktop.secrets,
|
||||
# `gcx login` authenticates against Grafana successfully and then dies writing
|
||||
# its config: "The name is not activatable".
|
||||
#
|
||||
# home-manager already ships services.gnome-keyring, but it does not fit a
|
||||
# headless host on two counts:
|
||||
#
|
||||
# * it is WantedBy graphical-session-pre.target, which never activates
|
||||
# without a desktop session, so the service would simply never start; and
|
||||
# * it cannot unlock the login keyring (it passes no --unlock). An unlocked
|
||||
# collection is mandatory: writing to a locked one blocks on a GUI prompter
|
||||
# (gcr) that does not exist here, so the caller hangs rather than fails.
|
||||
#
|
||||
# Security posture, stated plainly: the login keyring is encrypted at rest, but
|
||||
# the password unlocking it is readable by the same user on the same machine.
|
||||
# That protects the tokens from something reading the keyring file directly; it
|
||||
# protects them from nothing already running as this user. It is the same
|
||||
# posture as the existing ~/.jenkinsenv and ~/.splunkenv token files, and it is
|
||||
# the price of unattended operation -- systemd --user timers start with no
|
||||
# human present to type a passphrase.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.headlessSecretService;
|
||||
|
||||
# Where the generated unlock password lives when no external passwordFile is
|
||||
# supplied. Under $XDG_DATA_HOME rather than the nix store, which is
|
||||
# world-readable.
|
||||
defaultPasswordFile = "${config.xdg.dataHome}/gnome-keyring/login-password";
|
||||
|
||||
passwordFile = if cfg.passwordFile != null then cfg.passwordFile else defaultPasswordFile;
|
||||
|
||||
keyringDaemon = pkgs.writeShellApplication {
|
||||
name = "headless-secret-service";
|
||||
runtimeInputs = [
|
||||
pkgs.gnome-keyring
|
||||
pkgs.coreutils
|
||||
];
|
||||
text = ''
|
||||
pwfile=${lib.escapeShellArg passwordFile}
|
||||
|
||||
if [ ! -s "$pwfile" ]; then
|
||||
echo "headless-secret-service: no keyring password at $pwfile" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The daemon takes the whole of stdin as the password, so a trailing
|
||||
# newline would silently become part of it. Strip it, so a hand-written or
|
||||
# agenix-managed file unlocks the same keyring the generated one created.
|
||||
#
|
||||
# --components=secrets ONLY. The ssh component must stay off: it would
|
||||
# claim SSH_AUTH_SOCK and displace services.ssh-agent, breaking SSH auth
|
||||
# and signed commits. pkcs11 is not needed by anything here.
|
||||
tr -d '\n' <"$pwfile" |
|
||||
exec gnome-keyring-daemon --foreground --components=secrets --unlock
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
options.services.headlessSecretService = {
|
||||
enable = lib.mkEnableOption ''
|
||||
a headless gnome-keyring serving org.freedesktop.secrets on the user
|
||||
session bus, with the login keyring unlocked at service start'';
|
||||
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/run/agenix/gnome-keyring-login";
|
||||
description = ''
|
||||
Path to a file holding the login keyring password. It is read at service
|
||||
start, not at build time, so it need not exist when the system is built
|
||||
-- this is the seam for an agenix-managed secret.
|
||||
|
||||
When null, a random 32-byte password is generated on first activation at
|
||||
${defaultPasswordFile} (mode 0600) and reused from then on.
|
||||
|
||||
Pointing this at a different file after the login keyring already exists
|
||||
does NOT re-key the keyring: the daemon will fail to unlock it. To
|
||||
change the password, delete ~/.local/share/keyrings and re-authenticate
|
||||
every tool that stored a secret there.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# secret-tool, for inspecting or repairing the keyring by hand when a stored
|
||||
# credential misbehaves (`secret-tool search --all service gcx`).
|
||||
home.packages = [ pkgs.libsecret ];
|
||||
|
||||
# Generate the unlock password on first activation. Guarded on us owning it:
|
||||
# an externally supplied passwordFile is never created or written here.
|
||||
home.activation = lib.mkIf (cfg.passwordFile == null) {
|
||||
headlessSecretServicePassword = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
pwfile=${lib.escapeShellArg defaultPasswordFile}
|
||||
if [ ! -s "$pwfile" ]; then
|
||||
run mkdir -p "$(dirname "$pwfile")"
|
||||
# Create the file empty at 0600 first, then fill it: the redirect
|
||||
# keeps the existing mode, so the password is never briefly readable.
|
||||
run install -m 600 /dev/null "$pwfile"
|
||||
run ${pkgs.bash}/bin/sh -c \
|
||||
'head -c 32 /dev/urandom | base64 -w0 > "$1"' sh "$pwfile"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.user.services.headless-secret-service = {
|
||||
Unit = {
|
||||
Description = "GNOME Keyring (Secret Service, headless)";
|
||||
Documentation = "man:gnome-keyring-daemon(1)";
|
||||
# The daemon claims its name on the user session bus.
|
||||
Requires = [ "dbus.socket" ];
|
||||
After = [ "dbus.socket" ];
|
||||
};
|
||||
|
||||
Service = {
|
||||
Type = "simple";
|
||||
ExecStart = lib.getExe keyringDaemon;
|
||||
Restart = "on-failure";
|
||||
RestartSec = 2;
|
||||
};
|
||||
|
||||
# default.target, not graphical-session-pre.target: there is no graphical
|
||||
# session on this host. With `linger` enabled (see the host table in
|
||||
# flake.nix) default.target is reached at boot, so the keyring is also up
|
||||
# for unattended systemd --user timers, not just interactive logins.
|
||||
Install.WantedBy = [ "default.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -96,6 +96,15 @@ in
|
||||
# runs before oh-my-zsh/compinit so the exec replaces the shell before
|
||||
# that setup is wasted. Guards, each preventing a real breakage:
|
||||
# interactive only -> don't hijack scp / `ssh host cmd` / scripted shells
|
||||
# stdout is a tty -> VS Code (macOS) resolves the shell environment on
|
||||
# startup by running an interactive login shell with
|
||||
# stdout piped, no controlling terminal. Without this
|
||||
# guard `exec tmux` runs there, fails ("open terminal
|
||||
# failed: not a terminal"), exits non-zero, and VS
|
||||
# Code reports "Unable to resolve your shell
|
||||
# environment". A real terminal always has a tty here.
|
||||
# not VS Code env -> also skip VS Code's env-resolution probe explicitly,
|
||||
# in case a future version allocates a pty for it.
|
||||
# $TMUX empty -> a pane's zsh won't re-exec tmux (infinite loop)
|
||||
# not SSH -> don't force inbound SSH logins into a server tmux
|
||||
# not VS Code -> its integrated terminal manages itself
|
||||
@@ -103,6 +112,8 @@ in
|
||||
# $NO_TMUX unset -> escape hatch: `NO_TMUX=1 <term>` opens a bare shell
|
||||
(lib.mkOrder 200 ''
|
||||
if [[ $- == *i* ]] \
|
||||
&& [[ -t 1 ]] \
|
||||
&& [[ -z "$VSCODE_RESOLVING_ENVIRONMENT" ]] \
|
||||
&& [[ -z "$TMUX" ]] \
|
||||
&& [[ -z "$NO_TMUX" ]] \
|
||||
&& [[ -z "$SSH_CONNECTION" && -z "$SSH_TTY" ]] \
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
"lld@21"
|
||||
"python@3.14"
|
||||
"dosbox-staging"
|
||||
"mole"
|
||||
];
|
||||
# GUI applications. macOS app bundles are managed as casks; nixpkgs darwin
|
||||
# GUI support is unreliable, so these stay on brew for continuity.
|
||||
@@ -111,6 +112,7 @@
|
||||
"bitwarden"
|
||||
"citrix-workspace"
|
||||
"curseforge"
|
||||
"darktable"
|
||||
"discord"
|
||||
"firefox"
|
||||
"freecad"
|
||||
@@ -131,7 +133,6 @@
|
||||
"signal"
|
||||
"steam"
|
||||
"thunderbird"
|
||||
"virtualbox"
|
||||
"visual-studio-code"
|
||||
"vnc-viewer"
|
||||
"vscodium"
|
||||
|
||||
@@ -38,6 +38,40 @@ the daily headless **Renovate PR review** timer firing — defined in
|
||||
(imported only from `work.nix`, so it exists on this machine alone). See that
|
||||
file's header for the auth (Vertex AI ADC), triage policy, and caveats.
|
||||
|
||||
## Secret Service (keychain)
|
||||
|
||||
`work.nix` sets `services.headlessSecretService.enable = true`, which runs
|
||||
`gnome-keyring` as a `systemd --user` service owning `org.freedesktop.secrets`
|
||||
on the session bus, with the login keyring unlocked at start.
|
||||
|
||||
This exists for **gcx**, the Grafana Cloud CLI. gcx stores its OAuth access and
|
||||
refresh tokens in the keychain unconditionally (its config keeps only opaque
|
||||
`keychain:gcx:v2:...` handles) and has no plaintext fallback, so without a
|
||||
Secret Service `gcx login` authenticates and then fails to persist with "The
|
||||
name is not activatable".
|
||||
|
||||
Home-manager's own `services.gnome-keyring` does not work here: it is
|
||||
`WantedBy=graphical-session-pre.target`, which never activates on this headless
|
||||
box, and it cannot unlock the keyring. See
|
||||
[`../../home/secret-service.nix`](../../home/secret-service.nix) for the full
|
||||
rationale and the security trade-off of an auto-unlocked keyring.
|
||||
|
||||
Only the `secrets` component is started. The `ssh` component is deliberately off
|
||||
— it would claim `SSH_AUTH_SOCK` and displace `services.ssh-agent`, breaking SSH
|
||||
auth and signed commits.
|
||||
|
||||
Checking it:
|
||||
|
||||
```sh
|
||||
systemctl --user status headless-secret-service
|
||||
busctl --user list | grep secrets # expect org.freedesktop.secrets
|
||||
secret-tool search --all service gcx # inspect what gcx stored
|
||||
gcx config check # end-to-end
|
||||
```
|
||||
|
||||
If the keyring password is ever lost or changed, the login keyring cannot be
|
||||
unlocked: delete `~/.local/share/keyrings` and re-run `gcx login`.
|
||||
|
||||
## stateVersion
|
||||
|
||||
`system.stateVersion = "24.11"` — the release this box was first installed on.
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
];
|
||||
};
|
||||
|
||||
# Explicit rather than relying on the module default (which upstream will stop
|
||||
# defaulting to true; the eval warns otherwise).
|
||||
hardware.asahi.enable = true;
|
||||
|
||||
# Apple peripheral firmware (Wi-Fi/Bluetooth). The directory is gitignored and
|
||||
# populated out-of-band -- see README.
|
||||
hardware.asahi.peripheralFirmwareDirectory = ../../modules/firmware;
|
||||
|
||||
@@ -46,11 +46,30 @@
|
||||
pkgs.tflint # Terraform linter (catches what terraformls won't)
|
||||
pkgs.terraform-docs # generate Terraform module docs
|
||||
pkgs.yq-go # jq for YAML
|
||||
pkgs.gcx # Grafana Cloud CLI (dashboards, SLOs, synthetics, alerts)
|
||||
];
|
||||
services.ssh-agent.enable = true;
|
||||
|
||||
# gcx (above) keeps its OAuth tokens in the system keychain and has no
|
||||
# plaintext fallback, so this WSL box needs something owning
|
||||
# org.freedesktop.secrets. See home/secret-service.nix for why
|
||||
# home-manager's services.gnome-keyring cannot be used on a headless host,
|
||||
# and for the security trade-off of an auto-unlocked keyring.
|
||||
services.headlessSecretService.enable = true;
|
||||
home.shellAliases = {
|
||||
docker = "/run/current-system/sw/bin/docker";
|
||||
};
|
||||
|
||||
# Source the (nix-unmanaged) Jenkins credentials file into every zsh, so the
|
||||
# JENKINS_UCE_/JENKINS_STF_ tokens are exported for all shells and anything they
|
||||
# launch -- the Jenkins MCP servers read them via ${JENKINS_*} expansion.
|
||||
# envExtra lands in ~/.zshenv, which zsh sources for login, interactive, and
|
||||
# non-interactive shells alike. Guarded so a missing file never breaks a shell;
|
||||
# the file holds secrets, so it is kept out of the world-readable nix store.
|
||||
programs.zsh.envExtra = ''
|
||||
[ -f "$HOME/.jenkinsenv" ] && . "$HOME/.jenkinsenv"
|
||||
[ -f "$HOME/.splunkenv" ] && . "$HOME/.splunkenv"
|
||||
'';
|
||||
programs.tmux = {
|
||||
# kube context/namespace in the status line. kube-tmux is pinned as a flake
|
||||
# input (it is not in nixpkgs), so the script is always present in the store.
|
||||
|
||||
Reference in New Issue
Block a user