Compare commits
1 Commits
final-befo
...
448fe0d07a
| Author | SHA1 | Date | |
|---|---|---|---|
|
448fe0d07a
|
@@ -1,10 +1,10 @@
|
|||||||
name: Build
|
name: Build and Deploy
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
deploy:
|
||||||
runs-on: nix
|
runs-on: nix
|
||||||
steps:
|
steps:
|
||||||
- uses: https://github.com/actions/checkout@v4
|
- uses: https://github.com/actions/checkout@v4
|
||||||
@@ -19,20 +19,37 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
nix build .#nixosConfigurations.yarn.config.system.build.toplevel -L
|
nix build .#nixosConfigurations.yarn.config.system.build.toplevel -L
|
||||||
|
|
||||||
- name: Record yarn store path for pull-update
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
mkdir -p /var/lib/dotfiles-deploy
|
|
||||||
readlink -f result > /var/lib/dotfiles-deploy/yarn
|
|
||||||
nix-store --add-root /var/lib/dotfiles-deploy/yarn-gcroot -r "$(readlink -f result)"
|
|
||||||
|
|
||||||
- name: Build NixOS configuration (mreow)
|
- name: Build NixOS configuration (mreow)
|
||||||
run: |
|
run: |
|
||||||
nix build .#nixosConfigurations.mreow.config.system.build.toplevel -L
|
nix build .#nixosConfigurations.mreow.config.system.build.toplevel -L
|
||||||
|
|
||||||
- name: Record mreow store path
|
- name: Deploy to desktop
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /var/lib/dotfiles-deploy
|
eval $(ssh-agent -s)
|
||||||
readlink -f result > /var/lib/dotfiles-deploy/mreow
|
ssh-add /run/agenix/ci-deploy-key
|
||||||
nix-store --add-root /var/lib/dotfiles-deploy/mreow-gcroot -r "$(readlink -f result)"
|
if ssh -i /run/agenix/ci-deploy-key -o StrictHostKeyChecking=no -o ConnectTimeout=10 root@desktop "echo reachable" 2>/dev/null; then
|
||||||
|
nix run github:serokell/deploy-rs -- .#yarn --ssh-opts="-o StrictHostKeyChecking=no"
|
||||||
|
echo "Deploy to desktop succeeded"
|
||||||
|
else
|
||||||
|
echo "Desktop unreachable - skipping deploy. Build succeeded."
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Notify success
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
curl -sf -X POST \
|
||||||
|
"https://ntfy.sigkill.computer/deployments" \
|
||||||
|
-H "Title: [dotfiles] Build succeeded" \
|
||||||
|
-H "Priority: default" \
|
||||||
|
-H "Tags: white_check_mark" \
|
||||||
|
-d "dotfiles built from commit ${GITHUB_SHA::8}"
|
||||||
|
|
||||||
|
- name: Notify failure
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
curl -sf -X POST \
|
||||||
|
"https://ntfy.sigkill.computer/deployments" \
|
||||||
|
-H "Title: [dotfiles] Build FAILED" \
|
||||||
|
-H "Priority: urgent" \
|
||||||
|
-H "Tags: rotating_light" \
|
||||||
|
-d "dotfiles build failed at commit ${GITHUB_SHA::8}"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
NixOS dotfiles for two hosts using Nix flakes + home-manager:
|
NixOS dotfiles for two hosts using Nix flakes + home-manager:
|
||||||
- **mreow** — Framework 13 AMD AI 300 laptop, niri WM, greetd, swaylock
|
- **mreow** — Framework 13 AMD AI 300 laptop, niri WM, greetd, swaylock
|
||||||
- **yarn** — Desktop, Jovian-NixOS (Steam deck mode), impermanence, sddm, pull-based updates from CI
|
- **yarn** — Desktop, Jovian-NixOS (Steam deck mode), impermanence, sddm, deploy-rs target
|
||||||
|
|
||||||
Secrets in `system/secrets/` and `home-manager/secrets/` are encrypted with git-crypt. **Never read or write files in those directories.**
|
Secrets in `system/secrets/` and `home-manager/secrets/` are encrypted with git-crypt. **Never read or write files in those directories.**
|
||||||
|
|
||||||
@@ -21,10 +21,8 @@ Secrets in `system/secrets/` and `home-manager/secrets/` are encrypted with git-
|
|||||||
nix build .#nixosConfigurations.mreow.config.system.build.toplevel -L
|
nix build .#nixosConfigurations.mreow.config.system.build.toplevel -L
|
||||||
nix build .#nixosConfigurations.yarn.config.system.build.toplevel -L
|
nix build .#nixosConfigurations.yarn.config.system.build.toplevel -L
|
||||||
|
|
||||||
# yarn pulls updates automatically on boot from the binary cache.
|
# Remote deploy to yarn via deploy-rs
|
||||||
# CI builds the yarn closure, records the store path, and Harmonia serves it.
|
deploy .#yarn
|
||||||
# To manually trigger the pull on yarn:
|
|
||||||
systemctl start pull-update
|
|
||||||
|
|
||||||
# Format all Nix files (uses nixfmt-tree, declared in flake.nix)
|
# Format all Nix files (uses nixfmt-tree, declared in flake.nix)
|
||||||
nix fmt
|
nix fmt
|
||||||
|
|||||||
205
flake.lock
generated
205
flake.lock
generated
@@ -12,11 +12,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776249299,
|
"lastModified": 1771437256,
|
||||||
"narHash": "sha256-Dt9t1TGRmJFc0xVYhttNBD6QsAgHOHCArqGa0AyjrJY=",
|
"narHash": "sha256-bLqwib+rtyBRRVBWhMuBXPCL/OThfokA+j6+uH7jDGU=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "blueprint",
|
"repo": "blueprint",
|
||||||
"rev": "56131e8628f173d24a27f6d27c0215eff57e40dd",
|
"rev": "06ee7190dc2620ea98af9eb225aa9627b68b0e33",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -46,16 +46,15 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776182890,
|
"lastModified": 1770895533,
|
||||||
"narHash": "sha256-+/VOe8XGq5klpU+I19D+3TcaR7o+Cwbq67KNF7mcFak=",
|
"narHash": "sha256-v3QaK9ugy9bN9RXDnjw0i2OifKmz2NnKM82agtqm/UY=",
|
||||||
"owner": "Mic92",
|
"owner": "nix-community",
|
||||||
"repo": "bun2nix",
|
"repo": "bun2nix",
|
||||||
"rev": "648d293c51e981aec9cb07ba4268bc19e7a8c575",
|
"rev": "c843f477b15f51151f8c6bcc886954699440a6e1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "Mic92",
|
"owner": "nix-community",
|
||||||
"ref": "catalog-support",
|
|
||||||
"repo": "bun2nix",
|
"repo": "bun2nix",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -63,11 +62,11 @@
|
|||||||
"cachyos-kernel": {
|
"cachyos-kernel": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776183001,
|
"lastModified": 1775145950,
|
||||||
"narHash": "sha256-lvLKB5dTqjO1S/YonS9ZyWemEjO6QXtN4D76rYEYy4s=",
|
"narHash": "sha256-AfVja9nvYHm0BHbuTvn+K8rKfLmPl5QjoiNecp9HOJU=",
|
||||||
"owner": "CachyOS",
|
"owner": "CachyOS",
|
||||||
"repo": "linux-cachyos",
|
"repo": "linux-cachyos",
|
||||||
"rev": "4224303b6d7a50dd1cc3ffa78864050cc9536eec",
|
"rev": "b91624f68ceaf5394ef1571f60290dca6ba22b45",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -79,11 +78,11 @@
|
|||||||
"cachyos-kernel-patches": {
|
"cachyos-kernel-patches": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776355454,
|
"lastModified": 1775157685,
|
||||||
"narHash": "sha256-b9Hc0sTxjEzDbphzS9yQqxVha/7bsPIs2cQQQvaG45E=",
|
"narHash": "sha256-g8HgH7gADoEnrBN30BK3pz7+M2pT/p3xtfRFEuEov5w=",
|
||||||
"owner": "CachyOS",
|
"owner": "CachyOS",
|
||||||
"repo": "kernel-patches",
|
"repo": "kernel-patches",
|
||||||
"rev": "b5e029226df5cc30c103651072d49a7af2878202",
|
"rev": "c1ba300617a12d257b5721572b9bbe28efae182f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -107,6 +106,28 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"deploy-rs": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"utils": "utils"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1770019181,
|
||||||
|
"narHash": "sha256-hwsYgDnby50JNVpTRYlF3UR/Rrpt01OrxVuryF40CFY=",
|
||||||
|
"owner": "serokell",
|
||||||
|
"repo": "deploy-rs",
|
||||||
|
"rev": "77c906c0ba56aabdbc72041bf9111b565cdd6171",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "serokell",
|
||||||
|
"repo": "deploy-rs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"disko": {
|
"disko": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -131,11 +152,11 @@
|
|||||||
"doomemacs": {
|
"doomemacs": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776400245,
|
"lastModified": 1776049930,
|
||||||
"narHash": "sha256-RuQB1PxazI4DOw3O+rEVU2FPT0vP0Xb+Gp/M6Yqer20=",
|
"narHash": "sha256-6nuelk+8qSRsh5Ryj9EpWOWXeeh/g3lI5mZsBfiFZQI=",
|
||||||
"owner": "doomemacs",
|
"owner": "doomemacs",
|
||||||
"repo": "doomemacs",
|
"repo": "doomemacs",
|
||||||
"rev": "860a91aaac235701f30b70fdc74259d438818968",
|
"rev": "5eb006f455f0558c97210ba7579880aacb045b67",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -154,11 +175,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776478519,
|
"lastModified": 1776074175,
|
||||||
"narHash": "sha256-4TWCOVYe0iWEKuW7OH93nRI4Z7u68wNT6k9UJn0FZ5w=",
|
"narHash": "sha256-8e7+uLslLDZRD8p5QyJV8QSivpB2qMy2XuAcVYbW1f4=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "emacs-overlay",
|
"repo": "emacs-overlay",
|
||||||
"rev": "513e332b074507e1b46992952e7d83f329f2c22c",
|
"rev": "000ca2cd866f7c37a8c0cc96dd2aff457ee4c865",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -175,11 +196,11 @@
|
|||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "pkgs/firefox-addons",
|
"dir": "pkgs/firefox-addons",
|
||||||
"lastModified": 1776398575,
|
"lastModified": 1776052978,
|
||||||
"narHash": "sha256-WArU6WOdWxzbzGqYk4w1Mucg+bw/SCl6MoSp+/cZMio=",
|
"narHash": "sha256-WR0Svwg/JreBNW006qjHET6RRRmmjWCMfrkS5JmDZK8=",
|
||||||
"owner": "rycee",
|
"owner": "rycee",
|
||||||
"repo": "nur-expressions",
|
"repo": "nur-expressions",
|
||||||
"rev": "05815686caf4e3678f5aeb5fd36e567886ab0d30",
|
"rev": "6c0e7f01d9315f4806a187c2ec58d0f3b6961876",
|
||||||
"type": "gitlab"
|
"type": "gitlab"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -190,6 +211,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1733328505,
|
||||||
|
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-compat_2": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767039857,
|
"lastModified": 1767039857,
|
||||||
@@ -205,7 +242,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-compat_2": {
|
"flake-compat_3": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767039857,
|
"lastModified": 1767039857,
|
||||||
@@ -262,7 +299,7 @@
|
|||||||
},
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"systems": "systems_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710146030,
|
"lastModified": 1710146030,
|
||||||
@@ -307,11 +344,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776454077,
|
"lastModified": 1776114641,
|
||||||
"narHash": "sha256-7zSUFWsU0+jlD7WB3YAxQ84Z/iJurA5hKPm8EfEyGJk=",
|
"narHash": "sha256-VJMt3n9zGRzupzvlhcKIz4SpWflKh0rWfYTgmkmun0Q=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "565e5349208fe7d0831ef959103c9bafbeac0681",
|
"rev": "2de7205ce6e10b031151033e69b7ef89708dc282",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -366,11 +403,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776428236,
|
"lastModified": 1775841957,
|
||||||
"narHash": "sha256-+0SyQglnT2xUiyY07155G+O7aUWISELwqtTnfURufRU=",
|
"narHash": "sha256-oHxj9I82v+axW1lj+jUj2t8V++E6A9x54K5lq+liNAk=",
|
||||||
"owner": "Jovian-Experiments",
|
"owner": "Jovian-Experiments",
|
||||||
"repo": "Jovian-NixOS",
|
"repo": "Jovian-NixOS",
|
||||||
"rev": "eac78fc379ca47f7e21be8539c405e5fb489a857",
|
"rev": "67d55e61fe5e4d88d3fb90c0888cfced04a0589d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -412,11 +449,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776248416,
|
"lastModified": 1775866084,
|
||||||
"narHash": "sha256-TC6yzbCAex1pDfqUZv9u8fVm8e17ft5fNrcZ0JRDOIQ=",
|
"narHash": "sha256-mWn8D/oXXAaqeFFFRorKHvTLw5V9M8eYzAWRr4iffag=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "lanzaboote",
|
"repo": "lanzaboote",
|
||||||
"rev": "18e9e64bae15b828c092658335599122a6db939b",
|
"rev": "29d2cca7fc3841708c1d48e2d1272f79db1538b6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -433,15 +470,15 @@
|
|||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
"systems": "systems_2",
|
"systems": "systems_3",
|
||||||
"treefmt-nix": "treefmt-nix"
|
"treefmt-nix": "treefmt-nix"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776482297,
|
"lastModified": 1776092696,
|
||||||
"narHash": "sha256-KmsWPwtbO8vrlH/R9stIun0LKZ4PFSCCEdqWDeLgbTE=",
|
"narHash": "sha256-4MQq9lP6b9nBHozK1LHQNXPv72XAgGt8Drb4mQPqd7s=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "llm-agents.nix",
|
"repo": "llm-agents.nix",
|
||||||
"rev": "66c76393570f8fc4730caa2dc2d2c470fe33a3c9",
|
"rev": "215193474714ad712668bda23eef596c2656af6b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -464,11 +501,11 @@
|
|||||||
"xwayland-satellite-unstable": "xwayland-satellite-unstable"
|
"xwayland-satellite-unstable": "xwayland-satellite-unstable"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776435348,
|
"lastModified": 1776109195,
|
||||||
"narHash": "sha256-qsZnMThxTqxCJZ7DEKu3DD3KjIPcuUBvZ0C9a2uIvaQ=",
|
"narHash": "sha256-yug5v5OI5ixCYyAiqCbNrxfiyfvxvlsMr/tj3uyH51c=",
|
||||||
"owner": "sodiboo",
|
"owner": "sodiboo",
|
||||||
"repo": "niri-flake",
|
"repo": "niri-flake",
|
||||||
"rev": "55b5b1fc9481ab267603a1099e5d4b4ebc7394d7",
|
"rev": "8fcfcef0fc05ee826adf66225b27716131ed74af",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -497,11 +534,11 @@
|
|||||||
"niri-unstable": {
|
"niri-unstable": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776432730,
|
"lastModified": 1775561155,
|
||||||
"narHash": "sha256-Pq1ZVvRGq/IFiFH6vkNwMfZEpWk23NjgGdX50COdj/c=",
|
"narHash": "sha256-TK2IrqQivRcwqJa0suZMbcsN17CtA8Uu0v7CDnLATb0=",
|
||||||
"owner": "YaLTeR",
|
"owner": "YaLTeR",
|
||||||
"repo": "niri",
|
"repo": "niri",
|
||||||
"rev": "c814c656c53ea9d69f5afb45c88f4dc4d25338cd",
|
"rev": "599db847f857b8a7ff78ce02f15acab5d5d9fee1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -514,18 +551,18 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"cachyos-kernel": "cachyos-kernel",
|
"cachyos-kernel": "cachyos-kernel",
|
||||||
"cachyos-kernel-patches": "cachyos-kernel-patches",
|
"cachyos-kernel-patches": "cachyos-kernel-patches",
|
||||||
"flake-compat": "flake-compat_2",
|
"flake-compat": "flake-compat_3",
|
||||||
"flake-parts": "flake-parts_2",
|
"flake-parts": "flake-parts_2",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776386586,
|
"lastModified": 1775239578,
|
||||||
"narHash": "sha256-eVAUaL/6n8mnmBiPpEVW1NDNVSKLWhYVfycG+P0SvWU=",
|
"narHash": "sha256-MKJmDHlaxwBcnfCUEA89AwKOOONjOjbjHNNWdSdg5RA=",
|
||||||
"owner": "xddxdd",
|
"owner": "xddxdd",
|
||||||
"repo": "nix-cachyos-kernel",
|
"repo": "nix-cachyos-kernel",
|
||||||
"rev": "c65c3faf90ae07bae101c15ef502f0bcb06c5d74",
|
"rev": "beaf7a533ae106c2681de2624da94707f9857f1f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -544,14 +581,14 @@
|
|||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
"systems": "systems_3"
|
"systems": "systems_4"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776419397,
|
"lastModified": 1776078956,
|
||||||
"narHash": "sha256-vmWJwNYtQFexLG6r/v8Dlou/5z8FbFCLo3QqZ/stLYQ=",
|
"narHash": "sha256-+JCa+VodMqySrmSBWwNxuUcgLFSDvFA8rR0sY6YC7t0=",
|
||||||
"owner": "marienz",
|
"owner": "marienz",
|
||||||
"repo": "nix-doom-emacs-unstraightened",
|
"repo": "nix-doom-emacs-unstraightened",
|
||||||
"rev": "7623dd4adbdf5f8a8464ecc5fd089e5c5cb5dada",
|
"rev": "f4abf68bf74d506ff56af64e7a0b29e1a72d8b57",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -615,11 +652,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776169885,
|
"lastModified": 1775710090,
|
||||||
"narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=",
|
"narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9",
|
"rev": "4c1018dae018162ec878d42fec712642d214fdfa",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -652,11 +689,11 @@
|
|||||||
"noctalia-qs": "noctalia-qs"
|
"noctalia-qs": "noctalia-qs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776302695,
|
"lastModified": 1776043445,
|
||||||
"narHash": "sha256-xZc9o1JLQpmWn2Dqui323+Tq2Ai4sSdtdvbFZCs4qLo=",
|
"narHash": "sha256-ie3vFwg0eZTTHBDCRm+ee/PecbtdPn/pyL6hlotAfeQ=",
|
||||||
"owner": "noctalia-dev",
|
"owner": "noctalia-dev",
|
||||||
"repo": "noctalia-shell",
|
"repo": "noctalia-shell",
|
||||||
"rev": "a7c724181fca5d1aff2d47b18fa733504cfdbda2",
|
"rev": "e56a9db57ed61ea248f109edd60965faf56d3da2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -671,7 +708,7 @@
|
|||||||
"noctalia",
|
"noctalia",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
"systems": "systems_4",
|
"systems": "systems_5",
|
||||||
"treefmt-nix": "treefmt-nix_2"
|
"treefmt-nix": "treefmt-nix_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
@@ -690,7 +727,7 @@
|
|||||||
},
|
},
|
||||||
"pre-commit": {
|
"pre-commit": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat_2",
|
||||||
"gitignore": "gitignore",
|
"gitignore": "gitignore",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"lanzaboote",
|
"lanzaboote",
|
||||||
@@ -713,6 +750,7 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"deploy-rs": "deploy-rs",
|
||||||
"disko": "disko",
|
"disko": "disko",
|
||||||
"emacs-overlay": "emacs-overlay",
|
"emacs-overlay": "emacs-overlay",
|
||||||
"firefox-addons": "firefox-addons",
|
"firefox-addons": "firefox-addons",
|
||||||
@@ -740,11 +778,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776481912,
|
"lastModified": 1776050130,
|
||||||
"narHash": "sha256-Xq7p+Ex3YHFAd+fFFLOYw2Wv67582X7SAmrEDtIDZQ4=",
|
"narHash": "sha256-/f/6/1WOfBJaGMfqV3VxWD9lpFRbPpF+Cx4MO+0mGok=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "e611106c527e8ab0adbb641183cda284411d575c",
|
"rev": "3c27f4c92a7d977556dd2c10bb564d9c61b375e9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -799,6 +837,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems_4": {
|
"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": {
|
"locked": {
|
||||||
"lastModified": 1689347949,
|
"lastModified": 1689347949,
|
||||||
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
|
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
|
||||||
@@ -856,6 +909,24 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
"xwayland-satellite-stable": {
|
"xwayland-satellite-stable": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
@@ -899,11 +970,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776403742,
|
"lastModified": 1776091817,
|
||||||
"narHash": "sha256-ZmGY9XiOsuMS/THsSNkgp2fnc3asXQX/xRrQpWnY9nA=",
|
"narHash": "sha256-Vwmi3P4LAUmOrE2zc9JpnRrNxNwamDN46hqcXpWTkp0=",
|
||||||
"owner": "0xc000022070",
|
"owner": "0xc000022070",
|
||||||
"repo": "zen-browser-flake",
|
"repo": "zen-browser-flake",
|
||||||
"rev": "ca7077bea5c830470437ea878da2a1940773324c",
|
"rev": "4f2e98c1125ab4be758cd1b51b526ad998e9618f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
16
flake.nix
16
flake.nix
@@ -63,6 +63,12 @@
|
|||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
inputs.home-manager.follows = "home-manager";
|
inputs.home-manager.follows = "home-manager";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
deploy-rs = {
|
||||||
|
url = "github:serokell/deploy-rs";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
jovian-nixos = {
|
jovian-nixos = {
|
||||||
url = "github:Jovian-Experiments/Jovian-NixOS";
|
url = "github:Jovian-Experiments/Jovian-NixOS";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
@@ -95,6 +101,7 @@
|
|||||||
lanzaboote,
|
lanzaboote,
|
||||||
nixos-hardware,
|
nixos-hardware,
|
||||||
home-manager,
|
home-manager,
|
||||||
|
deploy-rs,
|
||||||
jovian-nixos,
|
jovian-nixos,
|
||||||
...
|
...
|
||||||
}@inputs:
|
}@inputs:
|
||||||
@@ -151,5 +158,14 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
) { } hostnames;
|
) { } hostnames;
|
||||||
|
|
||||||
|
# Deploy-rs configuration for yarn host only
|
||||||
|
deploy.nodes.yarn = {
|
||||||
|
hostname = "desktop";
|
||||||
|
profiles.system = {
|
||||||
|
sshUser = "root";
|
||||||
|
path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.yarn;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
# niri wayland compositor
|
# niri wayland compositor
|
||||||
./progs/niri.nix
|
./progs/niri.nix
|
||||||
|
|
||||||
|
# statusbar
|
||||||
|
# ./progs/eww/eww.nix
|
||||||
|
|
||||||
# lockscreen
|
# lockscreen
|
||||||
./progs/swaylock.nix
|
./progs/swaylock.nix
|
||||||
|
|
||||||
@@ -26,4 +29,5 @@
|
|||||||
# used by /etc/nixos logic to launch niri
|
# used by /etc/nixos logic to launch niri
|
||||||
config.programs.niri.package
|
config.programs.niri.package
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
109
home-manager/progs/eww/config/eww.scss
Normal file
109
home-manager/progs/eww/config/eww.scss
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
$background: #1e1e2e;
|
||||||
|
$pink: #f5c2e7;
|
||||||
|
$lavendar: #b4befe;
|
||||||
|
$red: #f38ba8;
|
||||||
|
$maroon: #eba0ac;
|
||||||
|
$peach: #fab387;
|
||||||
|
$yellow: #f9e2af;
|
||||||
|
$green: #a6e3a1;
|
||||||
|
$text: #cdd6f4;
|
||||||
|
$subtext: #a6adc8;
|
||||||
|
$surface: #585b70;
|
||||||
|
|
||||||
|
* {
|
||||||
|
color: $text;
|
||||||
|
font-family: CaskaydiaCove Nerd Font Mono;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 10pt;
|
||||||
|
padding: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maroon {
|
||||||
|
color: $maroon;
|
||||||
|
}
|
||||||
|
|
||||||
|
.peach {
|
||||||
|
color: $peach;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow {
|
||||||
|
color: $yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
color: $green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lavendar {
|
||||||
|
color: $lavendar;
|
||||||
|
}
|
||||||
|
|
||||||
|
.symbol {
|
||||||
|
color: $lavendar;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
* {
|
||||||
|
all: unset;
|
||||||
|
margin: 0 5px;
|
||||||
|
font-size: 14pt;
|
||||||
|
transition: color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover * {
|
||||||
|
color: $pink;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bluetooth * {
|
||||||
|
font-size: 10pt;
|
||||||
|
padding: 0 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padded>*:not(:last-child) {
|
||||||
|
padding: 0 10px;
|
||||||
|
border-right: 1px solid $surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background {
|
||||||
|
border: 1px solid $pink;
|
||||||
|
background-color: $background;
|
||||||
|
border-radius: 12px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
scale trough {
|
||||||
|
margin: 0 10px;
|
||||||
|
border: none;
|
||||||
|
background-color: #FFF;
|
||||||
|
min-height: 3px;
|
||||||
|
min-width: 100px;
|
||||||
|
|
||||||
|
& slider {
|
||||||
|
box-shadow: none;
|
||||||
|
background-image: none;
|
||||||
|
border: none;
|
||||||
|
background-color: $pink;
|
||||||
|
min-width: 5pt;
|
||||||
|
min-height: 5pt;
|
||||||
|
margin: -5pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
& highlight {
|
||||||
|
border: none;
|
||||||
|
background-color: $lavendar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clipboard {
|
||||||
|
color: $subtext;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
1
home-manager/progs/eww/config/eww.yuck
Normal file
1
home-manager/progs/eww/config/eww.yuck
Normal file
@@ -0,0 +1 @@
|
|||||||
|
(include "./statusbar.yuck")
|
||||||
17
home-manager/progs/eww/config/scripts/currentWindow.sh
Executable file
17
home-manager/progs/eww/config/scripts/currentWindow.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
niri_data=$(niri msg --json focused-window)
|
||||||
|
|
||||||
|
if [[ "$niri_data" == "null" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
name=$(echo "$niri_data" | jq -r '.["app_id"], .["title"]' | tr '\n' ' ' | sed 's/.$//')
|
||||||
|
proc_name=$(echo "$name" | head -c 55)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO! fix this logic, add a '...' at the end
|
||||||
|
if [[ "$name" != "$proc_name" ]]; then
|
||||||
|
proc_name="$proc_name..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$proc_name"
|
||||||
3
home-manager/progs/eww/config/scripts/currentWorkspace.sh
Executable file
3
home-manager/progs/eww/config/scripts/currentWorkspace.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env fish
|
||||||
|
|
||||||
|
niri msg --json workspaces | jq -r '.[] | select(.is_focused == true) | .["id"]'
|
||||||
58
home-manager/progs/eww/config/scripts/power_bat.rs
Executable file
58
home-manager/progs/eww/config/scripts/power_bat.rs
Executable file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env rust-script
|
||||||
|
|
||||||
|
use std::{fmt, fs::read_to_string, str::FromStr};
|
||||||
|
|
||||||
|
const BASE_PATH: &str = "/sys/class/power_supply/BAT1/";
|
||||||
|
const CURRENT_NOW_PATH: &str = "current_now";
|
||||||
|
const VOLTAGE_NOW_PATH: &str = "voltage_now";
|
||||||
|
const STATUS_PATH: &str = "status";
|
||||||
|
const FACTOR: f32 = 1e6_f32;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Status {
|
||||||
|
Charging,
|
||||||
|
Discharging,
|
||||||
|
NotCharging,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Status {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(input: &str) -> Result<Status, Self::Err> {
|
||||||
|
match input {
|
||||||
|
"Charging" => Ok(Status::Charging),
|
||||||
|
"Discharging" => Ok(Status::Discharging),
|
||||||
|
"Not charging" => Ok(Status::NotCharging),
|
||||||
|
_ => Err("unknown state"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Status {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_and_trim_into<T: FromStr<Err = impl fmt::Debug>>(path: &str) -> T {
|
||||||
|
let mut content = read_to_string(BASE_PATH.to_owned() + path).unwrap();
|
||||||
|
content.pop();
|
||||||
|
T::from_str(&content).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_bat_info(path: &str) -> f32 {
|
||||||
|
let value: f32 = fetch_and_trim_into(path);
|
||||||
|
value / FACTOR
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let current_now: f32 = fetch_bat_info(CURRENT_NOW_PATH);
|
||||||
|
let voltage_now: f32 = fetch_bat_info(VOLTAGE_NOW_PATH);
|
||||||
|
let watts: f32 = current_now * voltage_now;
|
||||||
|
let status: Status = fetch_and_trim_into(STATUS_PATH);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"voltage: {:.4}\ncurrent: {:.4}\nwatts: {:.4}\nstatus: {}",
|
||||||
|
voltage_now, current_now, watts, status
|
||||||
|
);
|
||||||
|
}
|
||||||
2
home-manager/progs/eww/config/scripts/sound/getSink.sh
Executable file
2
home-manager/progs/eww/config/scripts/sound/getSink.sh
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
wpctl inspect @DEFAULT_SINK@ | grep -E "^ +\* node\.description" | cut -d' ' -f6- | tr -d '"'
|
||||||
22
home-manager/progs/eww/config/scripts/sound/getVolume.sh
Executable file
22
home-manager/progs/eww/config/scripts/sound/getVolume.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
output=$(wpctl get-volume @DEFAULT_SINK@ | cut -d' ' -f2- | sed -E 's/\.//g' | sed 's/^0*//g')
|
||||||
|
count=$(echo "$output" | awk -F, '{print $1+0}')
|
||||||
|
muted=$(echo "$output" | cut -d'[' -f2 | cut -d ']' -f1)
|
||||||
|
|
||||||
|
# if not muted, set to empty string
|
||||||
|
if [ "$muted" == "$count" ]; then
|
||||||
|
muted=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
color="green"
|
||||||
|
if ((count > 75)); then color="yellow"; fi
|
||||||
|
if ((count > 90)); then color="peach"; fi
|
||||||
|
if ((count > 100)); then color="maroon"; fi
|
||||||
|
if ((count > 110)); then color="red"; fi
|
||||||
|
|
||||||
|
output="${count}%"
|
||||||
|
if [ "$muted" != "" ]; then
|
||||||
|
output="${output} [${muted}]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "{\"count\":\"${output}\", \"color\":\"${color}\"}"
|
||||||
23
home-manager/progs/eww/config/scripts/wifiInfo.zsh
Executable file
23
home-manager/progs/eww/config/scripts/wifiInfo.zsh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env zsh
|
||||||
|
export CHARSET=ASCII
|
||||||
|
case $1 in
|
||||||
|
name)
|
||||||
|
nmcli -f IN-USE,SSID d w | grep '*' | sed 's/[\* ]//g' | cat
|
||||||
|
exit 0;;
|
||||||
|
strength)
|
||||||
|
str=$(nmcli -f ACTIVE,BARS d w | grep 'yes' | tr -d ' yesno')
|
||||||
|
case ${str: 0:-1} in
|
||||||
|
'****')
|
||||||
|
icon=""; colour="green";;
|
||||||
|
'***')
|
||||||
|
icon=""; colour="yellow";;
|
||||||
|
'**')
|
||||||
|
icon=""; colour="peach";;
|
||||||
|
'*')
|
||||||
|
icon=""; colour="maroon";;
|
||||||
|
*)
|
||||||
|
icon=""; colour="red";;
|
||||||
|
esac
|
||||||
|
echo "{\"icon\":\"$icon\",\"colour\":\"$colour\"}"
|
||||||
|
exit 0;;
|
||||||
|
esac
|
||||||
130
home-manager/progs/eww/config/statusbar.yuck
Normal file
130
home-manager/progs/eww/config/statusbar.yuck
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
(defwindow statusbar
|
||||||
|
:monitor 0
|
||||||
|
:stacking "fg"
|
||||||
|
:exclusive true
|
||||||
|
:geometry (geometry
|
||||||
|
:y "0.5%"
|
||||||
|
:width "100%"
|
||||||
|
:height "24px"
|
||||||
|
:anchor "top center")
|
||||||
|
(statusbar))
|
||||||
|
|
||||||
|
(defwidget statusbar []
|
||||||
|
(centerbox
|
||||||
|
(box :space-evenly false :halign 'start' :class 'padded'
|
||||||
|
(window-title))
|
||||||
|
(time)
|
||||||
|
(box :space-evenly false :halign 'end' :class 'padded'
|
||||||
|
(brightness-ctl)
|
||||||
|
(brightness-ctl-opener)
|
||||||
|
(volume)
|
||||||
|
(battery)
|
||||||
|
(bluetooth)
|
||||||
|
(wifi))))
|
||||||
|
|
||||||
|
(defwidget cmd-slider [?symbol value command max color]
|
||||||
|
(box :space-evenly false
|
||||||
|
(label :text symbol :class "symbol")
|
||||||
|
(scale
|
||||||
|
:min 0 :max max
|
||||||
|
:value value
|
||||||
|
:round-digits 0
|
||||||
|
:timeout "200ms"
|
||||||
|
:onchange command)
|
||||||
|
(label :text "${value}%" :class color)))
|
||||||
|
|
||||||
|
(defpoll windowtitle :interval "1s" `scripts/currentWindow.sh`)
|
||||||
|
|
||||||
|
(defwidget window-title []
|
||||||
|
(label
|
||||||
|
:text {windowtitle == "" ? "" : "(${windowtitle})"}))
|
||||||
|
|
||||||
|
(defwidget brightness-ctl []
|
||||||
|
(box :visible brightnessctl-open
|
||||||
|
(cmd-slider :symbol "" :value brightness
|
||||||
|
:command `brightnessctl set {}%`
|
||||||
|
:max 101 :color {
|
||||||
|
brightness >= 80 ? "green" :
|
||||||
|
brightness >= 50 ? "yellow" :
|
||||||
|
brightness >= 30 ? "peach" :
|
||||||
|
brightness >= 10 ? "maroon" : "red"
|
||||||
|
})))
|
||||||
|
|
||||||
|
|
||||||
|
(defpoll brightness :interval "1s" :run-while brightnessctl-open `brightnessctl -m | awk -F, '{print $4+0}'`)
|
||||||
|
|
||||||
|
|
||||||
|
(defvar brightnessctl-open false)
|
||||||
|
(defwidget brightness-ctl-opener []
|
||||||
|
(eventbox :class "button"
|
||||||
|
(button
|
||||||
|
:onclick `${EWW_CMD} update brightnessctl-open=${!brightnessctl-open}`
|
||||||
|
"")))
|
||||||
|
|
||||||
|
(defwidget wifi []
|
||||||
|
(eventbox
|
||||||
|
:class "button ${wifi-strength.colour}"
|
||||||
|
(label
|
||||||
|
:text {wifi-strength.icon}
|
||||||
|
:tooltip "Connected To: ${wifi-name}")))
|
||||||
|
|
||||||
|
(defpoll wifi-strength :interval "10s" `scripts/wifiInfo.zsh strength`)
|
||||||
|
(defpoll wifi-name :interval "1m" `scripts/wifiInfo.zsh name`)
|
||||||
|
|
||||||
|
(defwidget bluetooth []
|
||||||
|
(eventbox
|
||||||
|
:class "bluetooth button ${ bluetooth-name != "" ? "green" : "lavendar" }"
|
||||||
|
:onclick `blueman-manager &`
|
||||||
|
(label
|
||||||
|
:text "${bluetooth-name} ")))
|
||||||
|
|
||||||
|
; `FNR == 1 + head -c 30` so the name doesn't explode the screen
|
||||||
|
(defpoll bluetooth-name :interval "10s" `bluetoothctl devices Connected | awk '$1 == "Device" {print $0}' | cut -d' ' -f3-`)
|
||||||
|
|
||||||
|
(defwidget time []
|
||||||
|
(box
|
||||||
|
:space-evenly false
|
||||||
|
:class "time"
|
||||||
|
:tooltip {time.long}
|
||||||
|
(label :class "yellow" :text {time.hour})
|
||||||
|
(label :text ":")
|
||||||
|
(label :class "yellow" :text {time.minute})))
|
||||||
|
|
||||||
|
(defpoll time :interval "1s" `date +'{"long":"%a %b %e %H:%M:%S %Z %Y","hour":"%H","minute":"%M"}'`)
|
||||||
|
|
||||||
|
(defpoll powerstats :interval "2s" `power_bat`)
|
||||||
|
|
||||||
|
(defwidget battery []
|
||||||
|
(box :space-evenly false
|
||||||
|
:tooltip powerstats
|
||||||
|
(label
|
||||||
|
:text {EWW_BATTERY.BAT1.status == "Charging" ? "" :
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 90 ? "" :
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 80 ? "" :
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 70 ? "" :
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 60 ? "" :
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 50 ? "" :
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 40 ? "" :
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 30 ? "" :
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 20 ? "" :
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 10 ? "" : ""
|
||||||
|
}
|
||||||
|
:class {
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 80 ? "green" :
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 50 ? "yellow" :
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 30 ? "peach" :
|
||||||
|
EWW_BATTERY.BAT1.capacity >= 10 ? "maroon" : "red"
|
||||||
|
})
|
||||||
|
(label :text "${EWW_BATTERY.BAT1.capacity}%" :class "yellow")))
|
||||||
|
|
||||||
|
|
||||||
|
(defpoll volumevalue :interval "1s" `scripts/sound/getVolume.sh`)
|
||||||
|
(defpoll volumesink :interval "1s" `scripts/sound/getSink.sh`)
|
||||||
|
|
||||||
|
(defwidget volume []
|
||||||
|
(eventbox :tooltip volumesink
|
||||||
|
:onclick `pwvucontrol &`
|
||||||
|
(label :text "${volumevalue.count}" :class {volumevalue.color})))
|
||||||
|
|
||||||
|
|
||||||
|
(defpoll currentworkspace :interval "1s" `scripts/currentWorkspace.sh`)
|
||||||
40
home-manager/progs/eww/eww.nix
Normal file
40
home-manager/progs/eww/eww.nix
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
zsh
|
||||||
|
bluez
|
||||||
|
brightnessctl
|
||||||
|
(callPackage ./power_bat.nix { })
|
||||||
|
];
|
||||||
|
|
||||||
|
programs.eww = {
|
||||||
|
enable = true;
|
||||||
|
configDir = ./config;
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.niri.settings.spawn-at-startup = [
|
||||||
|
{
|
||||||
|
command = [
|
||||||
|
(lib.getExe config.programs.eww.package)
|
||||||
|
"-c"
|
||||||
|
"${config.programs.eww.configDir}"
|
||||||
|
"open"
|
||||||
|
"statusbar"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
# swaybg works on more than just sway (sets a wallpaper)
|
||||||
|
{
|
||||||
|
command = [
|
||||||
|
(lib.getExe pkgs.swaybg)
|
||||||
|
"-i"
|
||||||
|
"${../wallpaper.png}"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
4
home-manager/progs/eww/power_bat.nix
Normal file
4
home-manager/progs/eww/power_bat.nix
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{ pkgs, lib, ... }:
|
||||||
|
pkgs.writeShellScriptBin "power_bat" ''
|
||||||
|
exec ${lib.getExe pkgs.rust-script} ${./config/scripts/power_bat.rs} "$@"
|
||||||
|
''
|
||||||
@@ -10,10 +10,10 @@ let
|
|||||||
# librarian/explore/quick → smol/commit = haiku
|
# librarian/explore/quick → smol/commit = haiku
|
||||||
ompSettings = {
|
ompSettings = {
|
||||||
modelRoles = {
|
modelRoles = {
|
||||||
default = "anthropic/claude-opus-4-7:high";
|
default = "anthropic/claude-opus-4-6:high";
|
||||||
smol = "anthropic/claude-haiku-4-5:low";
|
smol = "anthropic/claude-haiku-4-5:low";
|
||||||
slow = "anthropic/claude-opus-4-7:xhigh";
|
slow = "anthropic/claude-opus-4-6:xhigh";
|
||||||
plan = "anthropic/claude-opus-4-7:high";
|
plan = "anthropic/claude-opus-4-6:high";
|
||||||
commit = "anthropic/claude-haiku-4-5:low";
|
commit = "anthropic/claude-haiku-4-5:low";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -38,7 +38,7 @@ in
|
|||||||
{
|
{
|
||||||
home.packages = [
|
home.packages = [
|
||||||
(inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.omp.overrideAttrs (old: {
|
(inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.omp.overrideAttrs (old: {
|
||||||
patches = (old.patches or [ ]) ++ [ ];
|
patches = (old.patches or [ ]) ++ [ ../../patches/omp-fix-auth.patch ];
|
||||||
}))
|
}))
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
298
patches/omp-fix-auth.patch
Normal file
298
patches/omp-fix-auth.patch
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
commit 02b80dd74e2f962fffc9b0076bb7966eea4e45ff
|
||||||
|
Author: Simon Gardling <titaniumtown@proton.me>
|
||||||
|
Date: Wed Apr 8 13:05:09 2026 -0400
|
||||||
|
|
||||||
|
Fix key reauth with parallel sessions
|
||||||
|
|
||||||
|
diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md
|
||||||
|
index 3f50b7bd4..248dacbff 100644
|
||||||
|
--- a/packages/ai/CHANGELOG.md
|
||||||
|
+++ b/packages/ai/CHANGELOG.md
|
||||||
|
@@ -5,6 +5,10 @@
|
||||||
|
|
||||||
|
- Removed `coerceNullStrings` function and its automatic null-string coercion behavior from JSON parsing
|
||||||
|
|
||||||
|
+### Fixed
|
||||||
|
+
|
||||||
|
+- Fixed concurrent OAuth refresh token rotation race: when multiple instances share the same credential DB, one instance refreshing a token no longer causes other instances to permanently disable the credential on `invalid_grant` ([#607](https://github.com/can1357/oh-my-pi/issues/607))
|
||||||
|
+
|
||||||
|
## [13.19.0] - 2026-04-05
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
diff --git a/packages/ai/src/auth-storage.ts b/packages/ai/src/auth-storage.ts
|
||||||
|
index 9fdb4473b..fc81a6f3b 100644
|
||||||
|
--- a/packages/ai/src/auth-storage.ts
|
||||||
|
+++ b/packages/ai/src/auth-storage.ts
|
||||||
|
@@ -1865,7 +1865,35 @@ export class AuthStorage {
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isDefinitiveFailure) {
|
||||||
|
- // Permanently disable invalid credentials with an explicit cause for inspection/debugging
|
||||||
|
+ // Before permanently disabling, check if another instance already refreshed
|
||||||
|
+ // the token in the shared DB. Concurrent instances hold stale in-memory
|
||||||
|
+ // copies; refresh token rotation by one instance invalidates the token
|
||||||
|
+ // that other instances still hold. Re-read from DB before giving up.
|
||||||
|
+ if (/invalid_grant/i.test(errorMsg)) {
|
||||||
|
+ const entries = this.#getStoredCredentials(provider);
|
||||||
|
+ const entry = entries[selection.index];
|
||||||
|
+ if (entry) {
|
||||||
|
+ const dbCredentials = this.#store.listAuthCredentials(provider);
|
||||||
|
+ const dbEntry = dbCredentials.find(row => row.id === entry.id);
|
||||||
|
+ if (
|
||||||
|
+ dbEntry?.credential.type === "oauth" &&
|
||||||
|
+ dbEntry.credential.refresh !== selection.credential.refresh
|
||||||
|
+ ) {
|
||||||
|
+ // DB has a newer refresh token — another instance refreshed.
|
||||||
|
+ // Update in-memory state and retry with the fresh credential.
|
||||||
|
+ logger.warn("Concurrent refresh detected, syncing from DB and retrying", {
|
||||||
|
+ provider,
|
||||||
|
+ index: selection.index,
|
||||||
|
+ rowId: entry.id,
|
||||||
|
+ });
|
||||||
|
+ const updated = [...entries];
|
||||||
|
+ updated[selection.index] = { id: entry.id, credential: dbEntry.credential };
|
||||||
|
+ this.#setStoredCredentials(provider, updated);
|
||||||
|
+ return this.getApiKey(provider, sessionId, options);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ // Genuinely invalid — disable the credential
|
||||||
|
this.#disableCredentialAt(provider, selection.index, `oauth refresh failed: ${errorMsg}`);
|
||||||
|
if (this.#getCredentialsForProvider(provider).some(credential => credential.type === "oauth")) {
|
||||||
|
return this.getApiKey(provider, sessionId, options);
|
||||||
|
diff --git a/packages/ai/test/auth-storage-refresh-race.test.ts b/packages/ai/test/auth-storage-refresh-race.test.ts
|
||||||
|
new file mode 100644
|
||||||
|
index 000000000..5a8098a18
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/packages/ai/test/auth-storage-refresh-race.test.ts
|
||||||
|
@@ -0,0 +1,230 @@
|
||||||
|
+import { Database } from "bun:sqlite";
|
||||||
|
+import { afterEach, beforeEach, describe, expect, test, vi } from "bun:test";
|
||||||
|
+import * as fs from "node:fs/promises";
|
||||||
|
+import * as os from "node:os";
|
||||||
|
+import * as path from "node:path";
|
||||||
|
+import { AuthCredentialStore, AuthStorage, type OAuthCredential } from "../src/auth-storage";
|
||||||
|
+import * as oauthUtils from "../src/utils/oauth";
|
||||||
|
+import type { OAuthCredentials } from "../src/utils/oauth/types";
|
||||||
|
+
|
||||||
|
+/**
|
||||||
|
+ * Tests for the concurrent OAuth refresh token rotation race condition.
|
||||||
|
+ *
|
||||||
|
+ * When multiple omp instances share the same SQLite credential DB but hold
|
||||||
|
+ * independent in-memory caches, refresh token rotation by one instance
|
||||||
|
+ * invalidates the token that other instances still hold. Without the fix,
|
||||||
|
+ * the stale instance permanently disables the credential on `invalid_grant`
|
||||||
|
+ * even though a valid refresh token exists in the DB.
|
||||||
|
+ */
|
||||||
|
+
|
||||||
|
+function readDisabledCauses(dbPath: string, provider: string): string[] {
|
||||||
|
+ const db = new Database(dbPath, { readonly: true });
|
||||||
|
+ try {
|
||||||
|
+ const rows = db
|
||||||
|
+ .prepare(
|
||||||
|
+ "SELECT disabled_cause FROM auth_credentials WHERE provider = ? AND disabled_cause IS NOT NULL ORDER BY id ASC",
|
||||||
|
+ )
|
||||||
|
+ .all(provider) as Array<{ disabled_cause?: string | null }>;
|
||||||
|
+ return rows.flatMap(row => (typeof row.disabled_cause === "string" ? [row.disabled_cause] : []));
|
||||||
|
+ } finally {
|
||||||
|
+ db.close();
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+function countActiveCredentials(dbPath: string, provider: string): number {
|
||||||
|
+ const db = new Database(dbPath, { readonly: true });
|
||||||
|
+ try {
|
||||||
|
+ const row = db
|
||||||
|
+ .prepare("SELECT COUNT(*) AS count FROM auth_credentials WHERE provider = ? AND disabled_cause IS NULL")
|
||||||
|
+ .get(provider) as { count?: number } | undefined;
|
||||||
|
+ return row?.count ?? 0;
|
||||||
|
+ } finally {
|
||||||
|
+ db.close();
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+const EXPIRED_TOKEN_EXPIRES = Date.now() - 60_000;
|
||||||
|
+const FRESH_TOKEN_EXPIRES = Date.now() + 3_600_000;
|
||||||
|
+
|
||||||
|
+function makeOAuthCredential(suffix: string, opts?: { expires?: number }): OAuthCredential {
|
||||||
|
+ return {
|
||||||
|
+ type: "oauth",
|
||||||
|
+ access: `access-${suffix}`,
|
||||||
|
+ refresh: `refresh-${suffix}`,
|
||||||
|
+ expires: opts?.expires ?? FRESH_TOKEN_EXPIRES,
|
||||||
|
+ accountId: "acct-shared",
|
||||||
|
+ email: "user@example.com",
|
||||||
|
+ };
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+describe("AuthStorage concurrent OAuth refresh token rotation race", () => {
|
||||||
|
+ let tempDir = "";
|
||||||
|
+ let dbPath = "";
|
||||||
|
+ let storeA: AuthCredentialStore | null = null;
|
||||||
|
+ let storeB: AuthCredentialStore | null = null;
|
||||||
|
+ let authStorageA: AuthStorage | null = null;
|
||||||
|
+ let authStorageB: AuthStorage | null = null;
|
||||||
|
+
|
||||||
|
+ beforeEach(async () => {
|
||||||
|
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-ai-auth-refresh-race-"));
|
||||||
|
+ dbPath = path.join(tempDir, "agent.db");
|
||||||
|
+
|
||||||
|
+ // Both stores point at the same SQLite DB — simulates two omp instances
|
||||||
|
+ storeA = await AuthCredentialStore.open(dbPath);
|
||||||
|
+ storeB = await AuthCredentialStore.open(dbPath);
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ afterEach(async () => {
|
||||||
|
+ vi.restoreAllMocks();
|
||||||
|
+ storeA?.close();
|
||||||
|
+ storeB?.close();
|
||||||
|
+ storeA = null;
|
||||||
|
+ storeB = null;
|
||||||
|
+ authStorageA = null;
|
||||||
|
+ authStorageB = null;
|
||||||
|
+ if (tempDir) {
|
||||||
|
+ await fs.rm(tempDir, { recursive: true, force: true });
|
||||||
|
+ tempDir = "";
|
||||||
|
+ }
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ test("instance B recovers from invalid_grant when instance A already refreshed", async () => {
|
||||||
|
+ if (!storeA || !storeB) throw new Error("test setup failed");
|
||||||
|
+
|
||||||
|
+ // Store an expired OAuth credential — both instances will need to refresh
|
||||||
|
+ const staleCredential = makeOAuthCredential("v1", { expires: EXPIRED_TOKEN_EXPIRES });
|
||||||
|
+ authStorageA = new AuthStorage(storeA);
|
||||||
|
+ await authStorageA.set("anthropic", [staleCredential]);
|
||||||
|
+
|
||||||
|
+ // Instance B loads same credential from DB into its own in-memory cache
|
||||||
|
+ authStorageB = new AuthStorage(storeB);
|
||||||
|
+ await authStorageB.reload();
|
||||||
|
+
|
||||||
|
+ // The refreshed credential that instance A will produce
|
||||||
|
+ const refreshedCredential: OAuthCredentials = {
|
||||||
|
+ access: "access-v2",
|
||||||
|
+ refresh: "refresh-v2",
|
||||||
|
+ expires: FRESH_TOKEN_EXPIRES,
|
||||||
|
+ accountId: "acct-shared",
|
||||||
|
+ email: "user@example.com",
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ // Mock both refresh paths:
|
||||||
|
+ // - refreshOAuthToken: called by pre-refresh in #resolveOAuthApiKey for expired tokens
|
||||||
|
+ // - getOAuthApiKey: called by #tryOAuthCredential for the actual token exchange
|
||||||
|
+ const refreshSpy = vi.spyOn(oauthUtils, "refreshOAuthToken");
|
||||||
|
+ const getApiKeySpy = vi.spyOn(oauthUtils, "getOAuthApiKey");
|
||||||
|
+
|
||||||
|
+ // Step 1: Instance A refreshes successfully
|
||||||
|
+ refreshSpy.mockImplementation(async (_provider, credential) => {
|
||||||
|
+ return { ...credential, ...refreshedCredential };
|
||||||
|
+ });
|
||||||
|
+ getApiKeySpy.mockImplementation(async (_provider, credentials) => {
|
||||||
|
+ const cred = credentials.anthropic as OAuthCredentials | undefined;
|
||||||
|
+ if (!cred) return null;
|
||||||
|
+ return { newCredentials: refreshedCredential, apiKey: "sk-ant-new-key" };
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ const keyA = await authStorageA.getApiKey("anthropic", "session-a");
|
||||||
|
+ expect(keyA).toBe("sk-ant-new-key");
|
||||||
|
+
|
||||||
|
+ // DB now has refreshed credential from instance A.
|
||||||
|
+ // Instance B still has stale refresh-v1 in memory.
|
||||||
|
+
|
||||||
|
+ // Step 2: Instance B tries to refresh with stale token.
|
||||||
|
+ // The pre-refresh (refreshOAuthToken) runs first, then #tryOAuthCredential calls getOAuthApiKey.
|
||||||
|
+ // Both throw invalid_grant for stale tokens. After DB re-read, retry with fresh token succeeds.
|
||||||
|
+ let getApiKeyCallCount = 0;
|
||||||
|
+ refreshSpy.mockImplementation(async (_provider, credential) => {
|
||||||
|
+ if (credential.refresh === "refresh-v1") {
|
||||||
|
+ throw new Error("invalid_grant: Refresh token not found or invalid");
|
||||||
|
+ }
|
||||||
|
+ return { ...credential, ...refreshedCredential };
|
||||||
|
+ });
|
||||||
|
+ getApiKeySpy.mockImplementation(async (_provider, credentials) => {
|
||||||
|
+ const cred = credentials.anthropic as OAuthCredentials | undefined;
|
||||||
|
+ if (!cred) return null;
|
||||||
|
+ getApiKeyCallCount++;
|
||||||
|
+
|
||||||
|
+ if (cred.refresh === "refresh-v1") {
|
||||||
|
+ // Stale token — Anthropic would return invalid_grant
|
||||||
|
+ throw new Error("invalid_grant: Refresh token not found or invalid");
|
||||||
|
+ }
|
||||||
|
+ if (cred.refresh === "refresh-v2") {
|
||||||
|
+ // Fresh token from instance A — success
|
||||||
|
+ return { newCredentials: refreshedCredential, apiKey: "sk-ant-new-key-b" };
|
||||||
|
+ }
|
||||||
|
+ return null;
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ const keyB = await authStorageB.getApiKey("anthropic", "session-b");
|
||||||
|
+
|
||||||
|
+ // The credential must NOT be disabled — instance B should have recovered
|
||||||
|
+ expect(keyB).toBeDefined();
|
||||||
|
+ expect(typeof keyB).toBe("string");
|
||||||
|
+
|
||||||
|
+ // DB should still have exactly 1 active credential, none disabled
|
||||||
|
+ expect(countActiveCredentials(dbPath, "anthropic")).toBe(1);
|
||||||
|
+ expect(readDisabledCauses(dbPath, "anthropic")).toEqual([]);
|
||||||
|
+ // The getOAuthApiKey mock must have been called at least twice: once with stale, once with fresh
|
||||||
|
+ expect(getApiKeyCallCount).toBeGreaterThanOrEqual(2);
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ test("credential is still disabled when DB has same stale token (genuine failure)", async () => {
|
||||||
|
+ if (!storeA || !storeB) throw new Error("test setup failed");
|
||||||
|
+
|
||||||
|
+ // Store an expired credential — only one instance, no concurrent refresh
|
||||||
|
+ const staleCredential = makeOAuthCredential("v1", { expires: EXPIRED_TOKEN_EXPIRES });
|
||||||
|
+ authStorageA = new AuthStorage(storeA);
|
||||||
|
+ await authStorageA.set("anthropic", [staleCredential]);
|
||||||
|
+
|
||||||
|
+ // Mock: always fail with invalid_grant (genuinely revoked token)
|
||||||
|
+ vi.spyOn(oauthUtils, "refreshOAuthToken").mockImplementation(async () => {
|
||||||
|
+ throw new Error("invalid_grant: Refresh token not found or invalid");
|
||||||
|
+ });
|
||||||
|
+ vi.spyOn(oauthUtils, "getOAuthApiKey").mockImplementation(async () => {
|
||||||
|
+ throw new Error("invalid_grant: Refresh token not found or invalid");
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ const key = await authStorageA.getApiKey("anthropic", "session-a");
|
||||||
|
+
|
||||||
|
+ // Should return undefined — no valid credentials
|
||||||
|
+ expect(key).toBeUndefined();
|
||||||
|
+
|
||||||
|
+ // Credential should be disabled in DB
|
||||||
|
+ const causes = readDisabledCauses(dbPath, "anthropic");
|
||||||
|
+ expect(causes.length).toBe(1);
|
||||||
|
+ expect(causes[0]).toContain("invalid_grant");
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ test("terminates when DB token matches stale token (no concurrent refresh)", async () => {
|
||||||
|
+ if (!storeA || !storeB) throw new Error("test setup failed");
|
||||||
|
+
|
||||||
|
+ // Both instances share the same stale credential — nobody refreshed
|
||||||
|
+ const staleCredential = makeOAuthCredential("v1", { expires: EXPIRED_TOKEN_EXPIRES });
|
||||||
|
+ authStorageA = new AuthStorage(storeA);
|
||||||
|
+ await authStorageA.set("anthropic", [staleCredential]);
|
||||||
|
+
|
||||||
|
+ // Instance B loads same stale credential
|
||||||
|
+ authStorageB = new AuthStorage(storeB);
|
||||||
|
+ await authStorageB.reload();
|
||||||
|
+
|
||||||
|
+ // Mock: always fail — the token is genuinely revoked, nobody refreshed
|
||||||
|
+ vi.spyOn(oauthUtils, "refreshOAuthToken").mockImplementation(async () => {
|
||||||
|
+ throw new Error("invalid_grant: Refresh token not found or invalid");
|
||||||
|
+ });
|
||||||
|
+ vi.spyOn(oauthUtils, "getOAuthApiKey").mockImplementation(async () => {
|
||||||
|
+ throw new Error("invalid_grant: Refresh token not found or invalid");
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ // Instance B fails. DB has the same token (nobody refreshed), so the
|
||||||
|
+ // fix correctly falls through to disable instead of retrying forever.
|
||||||
|
+ const keyB = await authStorageB.getApiKey("anthropic", "session-b");
|
||||||
|
+ expect(keyB).toBeUndefined();
|
||||||
|
+
|
||||||
|
+ // Credential should be disabled — the fix did not prevent a genuine failure
|
||||||
|
+ const causes = readDisabledCauses(dbPath, "anthropic");
|
||||||
|
+ expect(causes.length).toBe(1);
|
||||||
|
+ expect(causes[0]).toContain("invalid_grant");
|
||||||
|
+ });
|
||||||
|
+});
|
||||||
@@ -100,17 +100,6 @@
|
|||||||
# kernel options
|
# kernel options
|
||||||
boot = {
|
boot = {
|
||||||
|
|
||||||
# cachyos kernel: bore scheduler, full lto, x86_64-v3 (common to zen 3 + zen 5)
|
|
||||||
kernelPackages =
|
|
||||||
let
|
|
||||||
helpers = pkgs.callPackage "${inputs.nix-cachyos-kernel}/helpers.nix" { };
|
|
||||||
kernel = pkgs.cachyosKernels.linux-cachyos-bore-lto.override {
|
|
||||||
lto = "full";
|
|
||||||
processorOpt = "x86_64-v3";
|
|
||||||
};
|
|
||||||
in
|
|
||||||
helpers.kernelModuleLLVMOverride (pkgs.linuxKernel.packagesFor kernel);
|
|
||||||
|
|
||||||
# disable legacy subsystems neither host will ever use
|
# disable legacy subsystems neither host will ever use
|
||||||
kernelPatches = [
|
kernelPatches = [
|
||||||
{
|
{
|
||||||
@@ -121,7 +110,7 @@
|
|||||||
PCMCIA = lib.mkForce no;
|
PCMCIA = lib.mkForce no;
|
||||||
PCCARD = lib.mkForce no;
|
PCCARD = lib.mkForce no;
|
||||||
PARPORT = lib.mkForce no;
|
PARPORT = lib.mkForce no;
|
||||||
GAMEPORT = lib.mkForce module;
|
GAMEPORT = lib.mkForce no;
|
||||||
FIREWIRE = lib.mkForce no;
|
FIREWIRE = lib.mkForce no;
|
||||||
AGP = lib.mkForce no;
|
AGP = lib.mkForce no;
|
||||||
|
|
||||||
@@ -143,8 +132,6 @@
|
|||||||
PHONET = lib.mkForce no;
|
PHONET = lib.mkForce no;
|
||||||
IEEE802154 = lib.mkForce no;
|
IEEE802154 = lib.mkForce no;
|
||||||
"6LOWPAN" = lib.mkForce no;
|
"6LOWPAN" = lib.mkForce no;
|
||||||
NET_9P = lib.mkForce no;
|
|
||||||
BATMAN_ADV = lib.mkForce no;
|
|
||||||
|
|
||||||
# tv tuners / digital video broadcasting
|
# tv tuners / digital video broadcasting
|
||||||
MEDIA_ANALOG_TV_SUPPORT = lib.mkForce no;
|
MEDIA_ANALOG_TV_SUPPORT = lib.mkForce no;
|
||||||
@@ -153,116 +140,17 @@
|
|||||||
|
|
||||||
# hypervisor guest support (bare metal only)
|
# hypervisor guest support (bare metal only)
|
||||||
HYPERV = lib.mkForce no;
|
HYPERV = lib.mkForce no;
|
||||||
XEN = lib.mkForce no;
|
|
||||||
VMWARE_VMCI = lib.mkForce no;
|
VMWARE_VMCI = lib.mkForce no;
|
||||||
VMWARE_BALLOON = lib.mkForce no;
|
|
||||||
VMWARE_PVSCSI = lib.mkForce no;
|
|
||||||
VMWARE_VMCI_VSOCKETS = lib.mkForce no;
|
|
||||||
VMXNET3 = lib.mkForce no;
|
|
||||||
DRM_VMWGFX = lib.mkForce no;
|
|
||||||
VBOXGUEST = lib.mkForce no;
|
|
||||||
VBOXSF_FS = lib.mkForce no;
|
|
||||||
|
|
||||||
# staging drivers (experimental/unmaintained)
|
# staging drivers (experimental/unmaintained)
|
||||||
STAGING = lib.mkForce no;
|
STAGING = lib.mkForce no;
|
||||||
# SND_PCI stays — SND_HDA_INTEL (AMD HDA audio) lives under it
|
|
||||||
ACCESSIBILITY = lib.mkForce no;
|
|
||||||
MTD = lib.mkForce no;
|
|
||||||
MEDIA_RC_SUPPORT = lib.mkForce no;
|
|
||||||
|
|
||||||
# legacy storage (AHCI for modern SATA is independent)
|
|
||||||
ATA_SFF = lib.mkForce no;
|
|
||||||
SCSI_LOWLEVEL = lib.mkForce no;
|
|
||||||
FUSION = lib.mkForce no;
|
|
||||||
|
|
||||||
# misc legacy
|
# misc legacy
|
||||||
MOST = lib.mkForce no;
|
MOST = lib.mkForce no;
|
||||||
PPDEV = lib.mkForce no;
|
PPDEV = lib.mkForce no;
|
||||||
PHANTOM = lib.mkForce no;
|
PHANTOM = lib.mkForce no;
|
||||||
|
W1 = lib.mkForce no;
|
||||||
X86_ANDROID_TABLETS = lib.mkForce no;
|
X86_ANDROID_TABLETS = lib.mkForce no;
|
||||||
# CHROME_PLATFORMS stays — Framework laptops use CrOS EC
|
|
||||||
SURFACE_PLATFORMS = lib.mkForce no;
|
|
||||||
MCTP = lib.mkForce no;
|
|
||||||
GPIB = lib.mkForce no;
|
|
||||||
SIOX = lib.mkForce no;
|
|
||||||
SLIMBUS = lib.mkForce no;
|
|
||||||
WWAN = lib.mkForce no;
|
|
||||||
|
|
||||||
# nvidia gpu
|
|
||||||
DRM_NOUVEAU = lib.mkForce no;
|
|
||||||
|
|
||||||
# other gpus not present
|
|
||||||
DRM_RADEON = lib.mkForce no;
|
|
||||||
DRM_GMA500 = lib.mkForce no;
|
|
||||||
DRM_AST = lib.mkForce no;
|
|
||||||
DRM_MGAG200 = lib.mkForce no;
|
|
||||||
DRM_HISI_HIBMC = lib.mkForce no;
|
|
||||||
DRM_APPLETBDRM = lib.mkForce no;
|
|
||||||
|
|
||||||
# intel gpu
|
|
||||||
DRM_I915 = lib.mkForce no;
|
|
||||||
DRM_XE = lib.mkForce no;
|
|
||||||
|
|
||||||
# intel cpu / platform
|
|
||||||
INTEL_IOMMU = lib.mkForce no;
|
|
||||||
INTEL_IDLE = lib.mkForce no;
|
|
||||||
INTEL_HFI_THERMAL = lib.mkForce no;
|
|
||||||
INTEL_TCC_COOLING = lib.mkForce no;
|
|
||||||
INTEL_SOC_DTS_THERMAL = lib.mkForce no;
|
|
||||||
INTEL_PCH_THERMAL = lib.mkForce no;
|
|
||||||
INTEL_POWERCLAMP = lib.mkForce no;
|
|
||||||
X86_PKG_TEMP_THERMAL = lib.mkForce no;
|
|
||||||
X86_INTEL_LPSS = lib.mkForce no;
|
|
||||||
INTEL_MEI = lib.mkForce no;
|
|
||||||
INTEL_TH = lib.mkForce no;
|
|
||||||
INTEL_VSEC = lib.mkForce no;
|
|
||||||
INTEL_IDXD = lib.mkForce no;
|
|
||||||
INTEL_IOATDMA = lib.mkForce no;
|
|
||||||
EDAC_E752X = lib.mkForce no;
|
|
||||||
EDAC_I82975X = lib.mkForce no;
|
|
||||||
EDAC_I3000 = lib.mkForce no;
|
|
||||||
EDAC_I3200 = lib.mkForce no;
|
|
||||||
EDAC_IE31200 = lib.mkForce no;
|
|
||||||
EDAC_X38 = lib.mkForce no;
|
|
||||||
EDAC_I5400 = lib.mkForce no;
|
|
||||||
EDAC_I7CORE = lib.mkForce no;
|
|
||||||
EDAC_I5100 = lib.mkForce no;
|
|
||||||
EDAC_I7300 = lib.mkForce no;
|
|
||||||
EDAC_SBRIDGE = lib.mkForce no;
|
|
||||||
EDAC_SKX = lib.mkForce no;
|
|
||||||
EDAC_I10NM = lib.mkForce no;
|
|
||||||
EDAC_IMH = lib.mkForce no;
|
|
||||||
EDAC_PND2 = lib.mkForce no;
|
|
||||||
EDAC_IGEN6 = lib.mkForce no;
|
|
||||||
|
|
||||||
# intel audio
|
|
||||||
SND_SOC_SOF_INTEL_TOPLEVEL = lib.mkForce no;
|
|
||||||
SND_SOC_INTEL_SST_TOPLEVEL = lib.mkForce no;
|
|
||||||
|
|
||||||
# mellanox networking
|
|
||||||
MLX4_CORE = lib.mkForce no;
|
|
||||||
MLX5_CORE = lib.mkForce no;
|
|
||||||
MLXSW_CORE = lib.mkForce no;
|
|
||||||
MLX_PLATFORM = lib.mkForce no;
|
|
||||||
|
|
||||||
# fpga
|
|
||||||
FPGA = lib.mkForce no;
|
|
||||||
|
|
||||||
# old x86 cpufreq / platform (both systems are modern Zen)
|
|
||||||
AMD_NUMA = lib.mkForce no;
|
|
||||||
X86_POWERNOW_K8 = lib.mkForce no;
|
|
||||||
X86_P4_CLOCKMOD = lib.mkForce no;
|
|
||||||
X86_SPEEDSTEP_LIB = lib.mkForce no;
|
|
||||||
|
|
||||||
# cxl (datacenter memory expansion)
|
|
||||||
CXL_BUS = lib.mkForce no;
|
|
||||||
|
|
||||||
# embedded SoC peripherals (not present on desktop/laptop)
|
|
||||||
INPUT_TOUCHSCREEN = lib.mkForce no;
|
|
||||||
INPUT_TABLET = lib.mkForce no;
|
|
||||||
INPUT_JOYSTICK = lib.mkForce no;
|
|
||||||
MEDIA_PLATFORM_DRIVERS = lib.mkForce no;
|
|
||||||
MEDIA_TEST_SUPPORT = lib.mkForce no;
|
|
||||||
|
|
||||||
# deprecated userland compat
|
# deprecated userland compat
|
||||||
SGETMASK_SYSCALL = lib.mkForce no;
|
SGETMASK_SYSCALL = lib.mkForce no;
|
||||||
@@ -277,9 +165,6 @@
|
|||||||
lib.filter (m: m != "aes_generic") options.boot.initrd.luks.cryptoModules.default
|
lib.filter (m: m != "aes_generic") options.boot.initrd.luks.cryptoModules.default
|
||||||
);
|
);
|
||||||
|
|
||||||
# some default initrd modules (ata_piix etc) don't exist with ATA_SFF=n
|
|
||||||
initrd.allowMissingModules = true;
|
|
||||||
|
|
||||||
lanzaboote = {
|
lanzaboote = {
|
||||||
enable = true;
|
enable = true;
|
||||||
# TODO: proper secrets management so this is not stored in nix store
|
# TODO: proper secrets management so this is not stored in nix store
|
||||||
|
|||||||
@@ -14,6 +14,17 @@
|
|||||||
inputs.nixos-hardware.nixosModules.framework-amd-ai-300-series
|
inputs.nixos-hardware.nixosModules.framework-amd-ai-300-series
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# cachyos kernel: bore scheduler, full lto, zen 4 (amd ai 340)
|
||||||
|
boot.kernelPackages =
|
||||||
|
let
|
||||||
|
helpers = pkgs.callPackage "${inputs.nix-cachyos-kernel}/helpers.nix" { };
|
||||||
|
kernel = pkgs.cachyosKernels.linux-cachyos-bore-lto.override {
|
||||||
|
lto = "full";
|
||||||
|
processorOpt = "zen4";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
helpers.kernelModuleLLVMOverride (pkgs.linuxKernel.packagesFor kernel);
|
||||||
|
|
||||||
hardware.framework.laptop13.audioEnhancement.rawDeviceName =
|
hardware.framework.laptop13.audioEnhancement.rawDeviceName =
|
||||||
lib.mkDefault "alsa_output.pci-0000_c1_00.6.analog-stereo";
|
lib.mkDefault "alsa_output.pci-0000_c1_00.6.analog-stereo";
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,22 @@
|
|||||||
inputs.jovian-nixos.nixosModules.default
|
inputs.jovian-nixos.nixosModules.default
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# cachyos kernel: bore scheduler, full lto, zen 3 (5800x)
|
||||||
|
boot.kernelPackages =
|
||||||
|
let
|
||||||
|
helpers = pkgs.callPackage "${inputs.nix-cachyos-kernel}/helpers.nix" { };
|
||||||
|
kernel = pkgs.cachyosKernels.linux-cachyos-bore-lto.override {
|
||||||
|
lto = "full";
|
||||||
|
processorOpt = "x86_64-v3";
|
||||||
|
structuredExtraConfig = with lib.kernel; {
|
||||||
|
# x86_64-v3 is the ISA level; pin to zen 3 for microarch tuning
|
||||||
|
GENERIC_CPU = lib.mkForce no;
|
||||||
|
MZEN3 = lib.mkForce yes;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
helpers.kernelModuleLLVMOverride (pkgs.linuxKernel.packagesFor kernel);
|
||||||
|
|
||||||
fileSystems."/media/games" = {
|
fileSystems."/media/games" = {
|
||||||
device = "/dev/disk/by-uuid/1878136e-765d-4784-b204-3536ab4fdac8";
|
device = "/dev/disk/by-uuid/1878136e-765d-4784-b204-3536ab4fdac8";
|
||||||
fsType = "f2fs";
|
fsType = "f2fs";
|
||||||
@@ -75,54 +91,12 @@
|
|||||||
# LACT (Linux AMDGPU Configuration Tool): https://github.com/ilya-zlobintsev/LACT
|
# LACT (Linux AMDGPU Configuration Tool): https://github.com/ilya-zlobintsev/LACT
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
lact
|
lact
|
||||||
jovian-stubs
|
|
||||||
];
|
];
|
||||||
systemd.packages = with pkgs; [ lact ];
|
systemd.packages = with pkgs; [ lact ];
|
||||||
systemd.services.lactd.wantedBy = [ "multi-user.target" ];
|
systemd.services.lactd.wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
systemd.services.lactd.serviceConfig.ExecStartPre = "${lib.getExe pkgs.bash} -c \"sleep 3s\"";
|
systemd.services.lactd.serviceConfig.ExecStartPre = "${lib.getExe pkgs.bash} -c \"sleep 3s\"";
|
||||||
|
|
||||||
# root-level service that applies a pending update. Triggered by
|
|
||||||
# steamos-update (via systemctl start) when the user accepts an update.
|
|
||||||
# Runs as root so it can write the system profile and boot entry.
|
|
||||||
systemd.services.pull-update-apply = {
|
|
||||||
description = "Apply pending NixOS update pulled from binary cache";
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
ExecStart = pkgs.writeShellScript "pull-update-apply" ''
|
|
||||||
set -uo pipefail
|
|
||||||
export PATH=${
|
|
||||||
pkgs.lib.makeBinPath [
|
|
||||||
pkgs.curl
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.nix
|
|
||||||
]
|
|
||||||
}
|
|
||||||
STORE_PATH=$(curl -sf --max-time 30 "https://nix-cache.sigkill.computer/deploy/yarn" || true)
|
|
||||||
if [ -z "$STORE_PATH" ]; then
|
|
||||||
echo "server unreachable"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "applying $STORE_PATH"
|
|
||||||
nix-store -r "$STORE_PATH" || { echo "fetch failed"; exit 1; }
|
|
||||||
nix-env -p /nix/var/nix/profiles/system --set "$STORE_PATH" || { echo "profile set failed"; exit 1; }
|
|
||||||
"$STORE_PATH/bin/switch-to-configuration" boot || { echo "boot entry failed"; exit 1; }
|
|
||||||
echo "update applied; reboot required"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Allow primary user to start pull-update-apply.service without a password
|
|
||||||
security.polkit.extraConfig = ''
|
|
||||||
polkit.addRule(function(action, subject) {
|
|
||||||
if (action.id == "org.freedesktop.systemd1.manage-units" &&
|
|
||||||
action.lookup("unit") == "pull-update-apply.service" &&
|
|
||||||
subject.user == "${username}") {
|
|
||||||
return polkit.Result.YES;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
'';
|
|
||||||
|
|
||||||
nixpkgs.config.allowUnfreePredicate =
|
nixpkgs.config.allowUnfreePredicate =
|
||||||
pkg:
|
pkg:
|
||||||
builtins.elem (lib.getName pkg) [
|
builtins.elem (lib.getName pkg) [
|
||||||
@@ -138,123 +112,65 @@
|
|||||||
# This prevents Steam from requesting reboots for "system updates"
|
# This prevents Steam from requesting reboots for "system updates"
|
||||||
# Steam client updates will still work normally
|
# Steam client updates will still work normally
|
||||||
nixpkgs.overlays = [
|
nixpkgs.overlays = [
|
||||||
(
|
(final: prev: {
|
||||||
final: prev:
|
jovian-stubs = prev.stdenv.mkDerivation {
|
||||||
let
|
name = "jovian-stubs-no-update";
|
||||||
deploy-url = "https://nix-cache.sigkill.computer/deploy/yarn";
|
dontUnpack = true;
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
|
||||||
steamos-update-script = final.writeShellScript "steamos-update" ''
|
# steamos-update: always report "no update available" (exit 7)
|
||||||
export PATH=${
|
# This disables the kernel mismatch check that triggers reboot prompts
|
||||||
final.lib.makeBinPath [
|
cat > $out/bin/steamos-update << 'STUB'
|
||||||
final.curl
|
#!/bin/sh
|
||||||
final.coreutils
|
>&2 echo "[JOVIAN] $0: stub called with: $* (system updates disabled)"
|
||||||
final.systemd
|
exit 7
|
||||||
]
|
STUB
|
||||||
}
|
|
||||||
|
|
||||||
STORE_PATH=$(curl -sf --max-time 30 "${deploy-url}" || true)
|
# steamos-reboot: reboot the system
|
||||||
|
cat > $out/bin/steamos-reboot << 'STUB'
|
||||||
|
#!/bin/sh
|
||||||
|
>&2 echo "[JOVIAN] $0: stub called with: $*"
|
||||||
|
systemctl reboot
|
||||||
|
STUB
|
||||||
|
|
||||||
if [ -z "$STORE_PATH" ]; then
|
# steamos-select-branch: no-op stub
|
||||||
>&2 echo "[steamos-update] server unreachable"
|
cat > $out/bin/steamos-select-branch << 'STUB'
|
||||||
exit 7
|
#!/bin/sh
|
||||||
fi
|
>&2 echo "[JOVIAN] $0: stub called with: $*"
|
||||||
|
exit 0
|
||||||
|
STUB
|
||||||
|
|
||||||
CURRENT=$(readlink -f /nix/var/nix/profiles/system)
|
# steamos-factory-reset-config: no-op stub
|
||||||
if [ "$CURRENT" = "$STORE_PATH" ]; then
|
cat > $out/bin/steamos-factory-reset-config << 'STUB'
|
||||||
>&2 echo "[steamos-update] no update available"
|
#!/bin/sh
|
||||||
exit 0
|
>&2 echo "[JOVIAN] $0: stub called with: $*"
|
||||||
fi
|
exit 0
|
||||||
|
STUB
|
||||||
|
|
||||||
# check-only mode: just report that an update exists
|
# steamos-firmware-update: no-op stub
|
||||||
if [ "''${1:-}" = "check" ] || [ "''${1:-}" = "--check-only" ]; then
|
cat > $out/bin/steamos-firmware-update << 'STUB'
|
||||||
>&2 echo "[steamos-update] update available"
|
#!/bin/sh
|
||||||
exit 0
|
>&2 echo "[JOVIAN] $0: stub called with: $*"
|
||||||
fi
|
exit 0
|
||||||
|
STUB
|
||||||
|
|
||||||
# apply: trigger the root-running systemd service to install the update
|
# pkexec: pass through to real pkexec
|
||||||
>&2 echo "[steamos-update] applying update..."
|
cat > $out/bin/pkexec << 'STUB'
|
||||||
if systemctl start --wait pull-update-apply.service; then
|
#!/bin/sh
|
||||||
>&2 echo "[steamos-update] update installed, reboot to apply"
|
exec /run/wrappers/bin/pkexec "$@"
|
||||||
exit 0
|
STUB
|
||||||
else
|
|
||||||
>&2 echo "[steamos-update] apply failed; see 'journalctl -u pull-update-apply'"
|
# sudo: pass through to doas
|
||||||
exit 1
|
cat > $out/bin/sudo << 'STUB'
|
||||||
fi
|
#!/bin/sh
|
||||||
|
exec /run/wrappers/bin/doas "$@"
|
||||||
|
STUB
|
||||||
|
|
||||||
|
chmod 755 $out/bin/*
|
||||||
'';
|
'';
|
||||||
in
|
};
|
||||||
{
|
})
|
||||||
jovian-stubs = prev.stdenv.mkDerivation {
|
|
||||||
name = "jovian-stubs";
|
|
||||||
dontUnpack = true;
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/bin
|
|
||||||
ln -s ${steamos-update-script} $out/bin/steamos-update
|
|
||||||
ln -s ${steamos-update-script} $out/bin/steamos-mandatory-update
|
|
||||||
|
|
||||||
# jupiter-initial-firmware-update: no-op (not a real steam deck)
|
|
||||||
cat > $out/bin/jupiter-initial-firmware-update << 'STUB'
|
|
||||||
#!/bin/sh
|
|
||||||
exit 0
|
|
||||||
STUB
|
|
||||||
|
|
||||||
# jupiter-biosupdate: no-op (not a real steam deck)
|
|
||||||
cat > $out/bin/jupiter-biosupdate << 'STUB'
|
|
||||||
#!/bin/sh
|
|
||||||
exit 0
|
|
||||||
STUB
|
|
||||||
|
|
||||||
# steamos-reboot: reboot the system
|
|
||||||
cat > $out/bin/steamos-reboot << 'STUB'
|
|
||||||
#!/bin/sh
|
|
||||||
>&2 echo "[JOVIAN] $0: stub called with: $*"
|
|
||||||
systemctl reboot
|
|
||||||
STUB
|
|
||||||
|
|
||||||
# steamos-select-branch: no-op stub
|
|
||||||
cat > $out/bin/steamos-select-branch << 'STUB'
|
|
||||||
#!/bin/sh
|
|
||||||
>&2 echo "[JOVIAN] $0: stub called with: $*"
|
|
||||||
exit 0
|
|
||||||
STUB
|
|
||||||
|
|
||||||
# steamos-factory-reset-config: no-op stub
|
|
||||||
cat > $out/bin/steamos-factory-reset-config << 'STUB'
|
|
||||||
#!/bin/sh
|
|
||||||
>&2 echo "[JOVIAN] $0: stub called with: $*"
|
|
||||||
exit 0
|
|
||||||
STUB
|
|
||||||
|
|
||||||
# steamos-firmware-update: no-op stub
|
|
||||||
cat > $out/bin/steamos-firmware-update << 'STUB'
|
|
||||||
#!/bin/sh
|
|
||||||
>&2 echo "[JOVIAN] $0: stub called with: $*"
|
|
||||||
exit 0
|
|
||||||
STUB
|
|
||||||
|
|
||||||
# pkexec: pass through to real pkexec
|
|
||||||
cat > $out/bin/pkexec << 'STUB'
|
|
||||||
#!/bin/sh
|
|
||||||
exec /run/wrappers/bin/pkexec "$@"
|
|
||||||
STUB
|
|
||||||
|
|
||||||
# sudo: strip flags and run the command directly (no escalation).
|
|
||||||
# privileged ops are delegated to root systemd services via systemctl.
|
|
||||||
cat > $out/bin/sudo << 'STUB'
|
|
||||||
#!/bin/sh
|
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
case "$1" in
|
|
||||||
-*) shift ;;
|
|
||||||
*) break ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
exec "$@"
|
|
||||||
STUB
|
|
||||||
|
|
||||||
find $out/bin -type f -exec chmod 755 {} +
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
];
|
];
|
||||||
|
|
||||||
jovian = {
|
jovian = {
|
||||||
|
|||||||
Reference in New Issue
Block a user