Compare commits
3 Commits
34b89af77f
...
6978396646
| Author | SHA1 | Date | |
|---|---|---|---|
| 6978396646 | |||
| fba5a7a2aa | |||
| f02606902c |
+1
-1
@@ -3,7 +3,7 @@ keys:
|
||||
- &host_14900k age1elk6zwmcylwfk7gd4pjda7g29upftjvxys8py42s8d42jklnyv7s7dm9z2
|
||||
- &host_uConsole age193gw802ytal7h5p5q37kpd9079k2vsflzmnvupcwfxh2kjdrwqtsk3g6rm
|
||||
- &host_t2mbp age1yr7vurfxc3w8ewfw9djfm54atw6ayze69qglamecuft5q0n9gu2sadsa2m
|
||||
- &host_ideapad age1m30m9xzszmcawte35m0yymz42gfx3x84w7d5l67mtdtajhgpfgssuc2plm
|
||||
- &host_ideapad age1hya7pgpe8zal52w3pjf036tpapmehedatfm4r84h30t4wuh079ssedfd37
|
||||
- &host_nix-server age1p05z980kdtngk9mw67hfev72h7xhslplpxfk9yskgmf0hl4lu3ls04zht9
|
||||
creation_rules:
|
||||
- path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
|
||||
|
||||
Generated
+79
-79
@@ -23,11 +23,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776686554,
|
||||
"narHash": "sha256-TaJnAbwSnfJ9My8Df8o6MKswuc3dq6qGF+zTZF906Eg=",
|
||||
"lastModified": 1779537840,
|
||||
"narHash": "sha256-IS3aolEKgyL0VuMfd/QX2AHvur1YukCTa6eZdxQWe1A=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "c1a35069ee41595f8549fb529d29647565a30e75",
|
||||
"revCount": 99,
|
||||
"rev": "8d9c19f98abf47aa4504efa8d2233730b4afed50",
|
||||
"revCount": 109,
|
||||
"type": "git",
|
||||
"url": "https://git.chiasson.cloud/Olivier/cursor-nixos-flake"
|
||||
},
|
||||
@@ -63,11 +63,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776181116,
|
||||
"narHash": "sha256-aUNKF+jzGY+jRlR7Bp82v/zNHdI9bFELLuYYWbaM6fo=",
|
||||
"lastModified": 1778679554,
|
||||
"narHash": "sha256-zoPgnxIlDja91/4TmnCui+Fzc/xU/1jdJFu9bovtOk8=",
|
||||
"owner": "AvengeMedia",
|
||||
"repo": "dgop",
|
||||
"rev": "e2078a7c5620be2e4897e7dabc08ade6dac9a454",
|
||||
"rev": "06574b54fa4878a93d8605962d50b13e9528a4ca",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -84,11 +84,11 @@
|
||||
"quickshell": "quickshell"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775588644,
|
||||
"narHash": "sha256-iYBdSBvcW7bJtc84G6k5TFJEbPHQrif9KzZyE9Lbq8M=",
|
||||
"lastModified": 1777431599,
|
||||
"narHash": "sha256-g6r/Gx8PTDzO3jCNzzySA+Ff1lmLF9nDlMCNyyoQjoE=",
|
||||
"owner": "AvengeMedia",
|
||||
"repo": "DankMaterialShell",
|
||||
"rev": "9798d78300d402178896f6ee1c370baed490158a",
|
||||
"rev": "eb5afcdc40ea5446c27e18552ff4a19f9daf9484",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -132,11 +132,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775087534,
|
||||
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
|
||||
"lastModified": 1778716662,
|
||||
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
|
||||
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -150,11 +150,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775087534,
|
||||
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
|
||||
"lastModified": 1778716662,
|
||||
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
|
||||
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -209,11 +209,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776721614,
|
||||
"narHash": "sha256-zGuW7C4tsScib2560yE5VV6lY/MdRs30aU9cbg3RP+U=",
|
||||
"lastModified": 1779507042,
|
||||
"narHash": "sha256-7wOwi8B6D0BYsieZCnHZZj2sNUzgJhLoIVSfkwB7lxQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "c555a4a34a260493be5adb795c54e013c58f2d34",
|
||||
"rev": "509ed3c603349a9d43de9e2ae6613baea6bd5b34",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -230,11 +230,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776184304,
|
||||
"narHash": "sha256-No6QGBmIv5ChiwKCcbkxjdEQ/RO2ZS1gD7SFy6EZ7rc=",
|
||||
"lastModified": 1778805320,
|
||||
"narHash": "sha256-nGFJ01m2CTBKD4ABtcY4vLhHrRN91LKr/pn41PcU78A=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "3c7524c68348ef79ce48308e0978611a050089b2",
|
||||
"rev": "9846abe15e7d0d36b8acbd4d05f2b87461744c92",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -245,11 +245,11 @@
|
||||
},
|
||||
"import-tree": {
|
||||
"locked": {
|
||||
"lastModified": 1773693634,
|
||||
"narHash": "sha256-BtZ2dtkBdSUnFPPFc+n0kcMbgaTxzFNPv2iaO326Ffg=",
|
||||
"lastModified": 1778781969,
|
||||
"narHash": "sha256-Jjuz5CmSkur8KvLDoGa+vylEp+RkQtv4mt/qcMznpH0=",
|
||||
"owner": "vic",
|
||||
"repo": "import-tree",
|
||||
"rev": "c41e7d58045f9057880b0d85e1152d6a4430dbf1",
|
||||
"rev": "d321337efd0f23a9eb14a42adb7b2c29313ab274",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -299,11 +299,11 @@
|
||||
"nixpkgs-nixcord": "nixpkgs-nixcord"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777125640,
|
||||
"narHash": "sha256-jKmRu5PknoI0pk3WEqMhVReosUubUCq3M/izEQWzb+4=",
|
||||
"lastModified": 1779498537,
|
||||
"narHash": "sha256-6LQjFDS69JufrN4sVsMNsXxeSF6BbDzMSbN7sVApsaA=",
|
||||
"owner": "KaylorBen",
|
||||
"repo": "nixcord",
|
||||
"rev": "0e738683dd7551a9cbfa343397b1592dfd785b7e",
|
||||
"rev": "45a98c17b0d9e695bdee92ab00c76657eddf47e7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -348,11 +348,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775857096,
|
||||
"narHash": "sha256-+eSij7C0oMqz76rGnB99RuWptBuEkJBm9vgb5fIwRrg=",
|
||||
"lastModified": 1779023229,
|
||||
"narHash": "sha256-MInilg7B/06c34SwOuGSBho4l0H1EZcmvxTkSWCs5pE=",
|
||||
"owner": "nvmd",
|
||||
"repo": "nixos-raspberrypi",
|
||||
"rev": "1dc4ca5f93587932383c0b61e1753f5eed1c3bba",
|
||||
"rev": "06c6e3513e1ee64b651913193fc6ac38aa4963f5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -364,11 +364,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1776548001,
|
||||
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
|
||||
"lastModified": 1779357205,
|
||||
"narHash": "sha256-cCO8aTqss5x9Ky8GWkpY0Hy5fyTZEbtifSUV8QjSzic=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
|
||||
"rev": "f83fc3c307e74bc5fd5adb7eb6b8b13ffd2a36e1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -380,11 +380,11 @@
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1774748309,
|
||||
"narHash": "sha256-+U7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ=",
|
||||
"lastModified": 1777168982,
|
||||
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "333c4e0545a6da976206c74db8773a1645b5870a",
|
||||
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -395,11 +395,11 @@
|
||||
},
|
||||
"nixpkgs-lib_2": {
|
||||
"locked": {
|
||||
"lastModified": 1774748309,
|
||||
"narHash": "sha256-+U7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ=",
|
||||
"lastModified": 1777168982,
|
||||
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "333c4e0545a6da976206c74db8773a1645b5870a",
|
||||
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -410,11 +410,11 @@
|
||||
},
|
||||
"nixpkgs-nixcord": {
|
||||
"locked": {
|
||||
"lastModified": 1776734388,
|
||||
"narHash": "sha256-vl3dkhlE5gzsItuHoEMVe+DlonsK+0836LIRDnm6MXQ=",
|
||||
"lastModified": 1779102034,
|
||||
"narHash": "sha256-vZJZjLo513IeI8hjzHFc6TDezUd4uCE2Eq4SNO3DNNg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "10e7ad5bbcb421fe07e3a4ad53a634b0cd57ffac",
|
||||
"rev": "687f05a9184cad4eaf905c48b63649e3a86f5433",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -426,11 +426,11 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1775579569,
|
||||
"narHash": "sha256-/m3yyS/EnXqoPGBJYVy4jTOsirdgsEZ3JdN2gGkBr14=",
|
||||
"lastModified": 1778869304,
|
||||
"narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "dfd9566f82a6e1d55c30f861879186440614696e",
|
||||
"rev": "d233902339c02a9c334e7e593de68855ad26c4cb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -448,11 +448,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776720544,
|
||||
"narHash": "sha256-SjaFRV8Oqu3LtEGxr1q5K+bMPbxPPjc7z1adadC8yE8=",
|
||||
"lastModified": 1779493406,
|
||||
"narHash": "sha256-70dCjL6KdsNG+hPHqUsrTF/gQtnucRMo2B/oGvf8aOw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "NUR",
|
||||
"rev": "fe8c1a700dbbfb474f7e80f6ca6223d0bd61d79d",
|
||||
"rev": "e27d8a76f2167da18bd37ab38f463c13daf2bc21",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -471,11 +471,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1773135655,
|
||||
"narHash": "sha256-eb4/TZEU1cMpUPtUuxcr2sfiCciHtesBtPHzS1zh2Uo=",
|
||||
"lastModified": 1778402771,
|
||||
"narHash": "sha256-WS8hQ8Yk4M1rfkp2aUCaUkGVBU0ppCYAhklBk5kBdFU=",
|
||||
"owner": "robertjakub",
|
||||
"repo": "oom-hardware",
|
||||
"rev": "ad592fd988ee7a7c1bd68ff8b819973e1ae900ef",
|
||||
"rev": "9f338e9250b7c01ac97750851867fc8158e8f54c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -493,16 +493,16 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1766725085,
|
||||
"narHash": "sha256-O2aMFdDUYJazFrlwL7aSIHbUSEm3ADVZjmf41uBJfHs=",
|
||||
"lastModified": 1776854048,
|
||||
"narHash": "sha256-lLbV66V3RMNp1l8/UelmR4YzoJ5ONtgvEtiUMJATH/o=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "41828c4180fb921df7992a5405f5ff05d2ac2fff",
|
||||
"revCount": 715,
|
||||
"rev": "783c953987dc56ff0601abe6845ed96f1d00495a",
|
||||
"revCount": 806,
|
||||
"type": "git",
|
||||
"url": "https://git.outfoxxed.me/quickshell/quickshell"
|
||||
},
|
||||
"original": {
|
||||
"rev": "41828c4180fb921df7992a5405f5ff05d2ac2fff",
|
||||
"rev": "783c953987dc56ff0601abe6845ed96f1d00495a",
|
||||
"type": "git",
|
||||
"url": "https://git.outfoxxed.me/quickshell/quickshell"
|
||||
}
|
||||
@@ -540,11 +540,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776119890,
|
||||
"narHash": "sha256-Zm6bxLNnEOYuS/SzrAGsYuXSwk3cbkRQZY0fJnk8a5M=",
|
||||
"lastModified": 1777944972,
|
||||
"narHash": "sha256-VfGRo1qTBKOe3s2gOv8LSoA6Fk19PvBlwQ1ECN0Evn8=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "d4971dd58c6627bfee52a1ad4237637c0a2fb0cd",
|
||||
"rev": "c591bf665727040c6cc5cb409079acb22dcce33c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -561,11 +561,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776578704,
|
||||
"narHash": "sha256-4+JHYCweZ/SSrMcu2nJ5gc7gop2scBk0JIIfaUKuTaQ=",
|
||||
"lastModified": 1779000518,
|
||||
"narHash": "sha256-wdtytSnzMe85J/qeXJALMzSLRFTZ1gBHwn81l1PtT8k=",
|
||||
"owner": "Gerg-L",
|
||||
"repo": "spicetify-nix",
|
||||
"rev": "73f6d24b4f5bdacc3b41ddcf9965bef2781f97dd",
|
||||
"rev": "5dde76b38418892ccb3d99e99bed7f8a43ac294c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -582,11 +582,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777652580,
|
||||
"narHash": "sha256-CO4RXrd0eQ2INc8/S2CTWCIHUdvVkqwKZ/9o7a/pcFg=",
|
||||
"lastModified": 1779059662,
|
||||
"narHash": "sha256-PkBItyS1oZ4MJ+eEgF5iLKxx28rmSyk/bHp63tjW/5g=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "42575990389388d8d07da6fb4110d77ea7493159",
|
||||
"revCount": 75,
|
||||
"rev": "030da2f52d3cbe3c577ce12b5abbd35e90e1f093",
|
||||
"revCount": 79,
|
||||
"type": "git",
|
||||
"url": "https://git.chiasson.cloud/Olivier/SwiftShare"
|
||||
},
|
||||
@@ -632,11 +632,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775302822,
|
||||
"narHash": "sha256-QoK8SYoIc0d/PoRdIUo+fkDNAHZIP2+AJ6PDM9ehGiY=",
|
||||
"lastModified": 1777412856,
|
||||
"narHash": "sha256-WrcIo3y9uFCuzgzbxc465FBS3zAZMQlfYszefkOUCWc=",
|
||||
"owner": "GnomedDev",
|
||||
"repo": "T2FanRD",
|
||||
"rev": "5b1c0c10785b8e8dfe124a4d6aaa7c2becdac65c",
|
||||
"rev": "48baf962697ec3d4d969c74cf601ee8e15b7aeaa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -648,11 +648,11 @@
|
||||
"t2linux-patches": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1776111571,
|
||||
"narHash": "sha256-1neTptNNPtwbBYSQOE48GM8CYx780eI5JQTFYmwN0og=",
|
||||
"lastModified": 1779369552,
|
||||
"narHash": "sha256-vDcWjgjhYAQcXZH40QN17ZV9BS0zqZeme9APXBqjlHs=",
|
||||
"owner": "t2linux",
|
||||
"repo": "linux-t2-patches",
|
||||
"rev": "76589a89790c33c137d173f2d98b6096cd16b132",
|
||||
"rev": "716093d3244566cd708362661de269ab7e67ff0f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -682,11 +682,11 @@
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776464146,
|
||||
"narHash": "sha256-XwLFfJDz71vIF7BAhnbLhrzQjmDC2uXdo7N0oHUrYzA=",
|
||||
"lastModified": 1779297405,
|
||||
"narHash": "sha256-VFoBwH7ZjVxCnvZTb5ODRXt70sLtWMxstive0N+RS50=",
|
||||
"owner": "BirdeeHub",
|
||||
"repo": "nix-wrapper-modules",
|
||||
"rev": "75febede14a0845f4ef429da692a0698bf433600",
|
||||
"rev": "e7ed7a1205945befdf2e0d73ba7df91d935e5af1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -703,11 +703,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776663782,
|
||||
"narHash": "sha256-qzBBuxZbn7vPD9ZDl3xmCBGa6qEc8Q//76Cbx4W0tE4=",
|
||||
"lastModified": 1779455631,
|
||||
"narHash": "sha256-svU6Ro4xiMxMA1KJGwQ/nfKwz3yXE/SONCw2Z1qTXHA=",
|
||||
"owner": "0xc000022070",
|
||||
"repo": "zen-browser-flake",
|
||||
"rev": "b93be06dc91630bf0ced69c54d0e1e05e56ae460",
|
||||
"rev": "5bcdfcef664bf62831dcb4b947004d9c5fbf7201",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
+16
-2
@@ -58,6 +58,14 @@
|
||||
xdg.portal.extraPortals =
|
||||
[ pkgs.xdg-desktop-portal-gtk ]
|
||||
++ lib.optionals cfg.plasma.enable [ pkgs.kdePackages.xdg-desktop-portal-kde ];
|
||||
|
||||
# Backlight control: `brightnessctl` + its udev rule (auto-loaded from the package's
|
||||
# `lib/udev/rules.d`). The rule grants the `video` group write access to
|
||||
# `/sys/class/backlight/*/brightness`; the user catalog adds desktop users to `video`.
|
||||
# The Hyprland defaults already bind XF86MonBrightness* to brightnessctl, and the Niri
|
||||
# base config does the same — this guarantees the binary is actually present.
|
||||
environment.systemPackages = [ pkgs.brightnessctl ];
|
||||
services.udev.packages = [ pkgs.brightnessctl ];
|
||||
})
|
||||
(lib.mkIf (guiEnabled && !useGreeter) {
|
||||
services.displayManager.sddm = {
|
||||
@@ -118,8 +126,14 @@
|
||||
(lib.mkIf (guiEnabled && cfg.keyring.enable && hmAvailable) {
|
||||
"home-manager".sharedModules = [
|
||||
({ lib, pkgs, ... }: {
|
||||
services.gnome-keyring.enable = lib.mkDefault true;
|
||||
home.packages = [ pkgs.gcr ];
|
||||
services.gnome-keyring = {
|
||||
enable = lib.mkDefault true;
|
||||
components = [ "secrets" ];
|
||||
};
|
||||
home.packages = [
|
||||
pkgs.gcr
|
||||
pkgs.libsecret
|
||||
];
|
||||
})
|
||||
];
|
||||
})
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
}:
|
||||
let
|
||||
hyprlandEnabled = osConfig.chiasson.desktop.hyprland.enable or false;
|
||||
keyringEnabled = osConfig.chiasson.desktop.keyring.enable or false;
|
||||
# nixpkgs hyprland-plugins pin is stale for current Hyprland — override to a known-good rev.
|
||||
hyprbarsPatched =
|
||||
let
|
||||
@@ -66,6 +67,11 @@
|
||||
'';
|
||||
|
||||
settings = lib.mkMerge [
|
||||
(lib.mkIf keyringEnabled {
|
||||
exec-once = lib.mkBefore [
|
||||
"dbus-update-activation-environment --systemd --all"
|
||||
];
|
||||
})
|
||||
{
|
||||
monitor = [ ",preferred,auto,auto" ];
|
||||
general = {
|
||||
|
||||
@@ -113,23 +113,38 @@ let
|
||||
"toggle"
|
||||
];
|
||||
|
||||
# Backlight: relies on `pkgs.brightnessctl` being on PATH (provided by `desktopGui` when
|
||||
# any GUI session is enabled) and the user being in the `video` group (catalog default).
|
||||
"XF86MonBrightnessUp".spawn = [ "brightnessctl" "set" "+5%" ];
|
||||
"XF86MonBrightnessDown".spawn = [ "brightnessctl" "set" "5%-" ];
|
||||
|
||||
Print.screenshot = _: { };
|
||||
"Ctrl+Print"."screenshot-screen" = _: { };
|
||||
"Alt+Print"."screenshot-window" = _: { };
|
||||
};
|
||||
};
|
||||
|
||||
keyringNiriStartupKdl = ''
|
||||
spawn-at-startup "dbus-update-activation-environment" "--systemd" "--all"
|
||||
'';
|
||||
|
||||
mergeNiriSettings =
|
||||
pkgs: niriCfg:
|
||||
pkgs: niriCfg: keyringEnable:
|
||||
let
|
||||
lib = pkgs.lib;
|
||||
pi5 = self.lib.pi5NiriKdl;
|
||||
rpi5Extra = lib.optionalString (niriCfg.raspberryPi5DrmWorkaround or false) pi5.drmExtraConfig;
|
||||
base = niriBaseSettings pkgs;
|
||||
userExtra = niriCfg.extraSettings or { };
|
||||
extraConfigMerged = rpi5Extra + (userExtra.extraConfig or "");
|
||||
keyringExtra = lib.optionalString keyringEnable keyringNiriStartupKdl;
|
||||
extraConfigMerged = keyringExtra + rpi5Extra + (userExtra.extraConfig or "");
|
||||
windowRules = (base.window-rules or [ ]) ++ (userExtra.window-rules or [ ]);
|
||||
in
|
||||
lib.recursiveUpdate (niriBaseSettings pkgs) (
|
||||
userExtra
|
||||
lib.recursiveUpdate base (
|
||||
lib.removeAttrs userExtra [ "window-rules" "extraConfig" ]
|
||||
// lib.optionalAttrs (windowRules != [ ]) {
|
||||
window-rules = windowRules;
|
||||
}
|
||||
// lib.optionalAttrs (rpi5Extra != "" || (userExtra.extraConfig or "") != "") {
|
||||
extraConfig = extraConfigMerged;
|
||||
}
|
||||
@@ -141,7 +156,8 @@ in
|
||||
let
|
||||
niriOs = osConfig.chiasson.desktop.niri or { };
|
||||
niriEnabled = osConfig.chiasson.desktop.niri.enable or false;
|
||||
mergedSettings = mergeNiriSettings pkgs niriOs;
|
||||
keyringEnabled = osConfig.chiasson.desktop.keyring.enable or false;
|
||||
mergedSettings = mergeNiriSettings pkgs niriOs keyringEnabled;
|
||||
niriConfigPkg = inputs.wrapper-modules.wrappers.niri.wrap {
|
||||
inherit pkgs;
|
||||
settings = mergedSettings;
|
||||
|
||||
@@ -752,6 +752,14 @@ in {
|
||||
"trailing_diamond": "\ue0b4",
|
||||
"type": "session"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"style": "diamond",
|
||||
"background": "#3A456E",
|
||||
"foreground": "#ffbebc",
|
||||
"template": "\u007B\u007B if .Env.IN_NIX_SHELL \u007D\u007Din nix-shell\u007B\u007B end \u007D\u007D",
|
||||
"trailing_diamond": "\ue0b4"
|
||||
},
|
||||
{
|
||||
"background": "#3A456E",
|
||||
"foreground": "#bc93ff",
|
||||
|
||||
@@ -4,46 +4,27 @@
|
||||
|
||||
#TODO[epic=Moderate] Clean this up, move to host's configuration.nix.
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
gpuPassthrough = config.chiasson.system.vm.gpuPassthrough.enable;
|
||||
in
|
||||
{
|
||||
chiasson.desktop.niri.extraSettings = {
|
||||
extraConfig =
|
||||
if gpuPassthrough then
|
||||
''
|
||||
output "DP-1" {
|
||||
mode "2560x1080@144"
|
||||
scale 1.0
|
||||
position x=1920 y=0
|
||||
focus-at-startup
|
||||
}
|
||||
output "HDMI-A-2" {
|
||||
mode "2560x1080@60"
|
||||
scale 1.0
|
||||
position x=0 y=0
|
||||
}
|
||||
''
|
||||
else
|
||||
''
|
||||
output "DP-2" {
|
||||
mode "2560x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=0
|
||||
focus-at-startup
|
||||
}
|
||||
output "HDMI-A-3" {
|
||||
mode "1920x1080@60"
|
||||
scale 1.0
|
||||
position x=-1920 y=0
|
||||
}
|
||||
output "DP-4" {
|
||||
mode "1920x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=-1080
|
||||
}
|
||||
extraConfig = ''
|
||||
output "DP-2" {
|
||||
mode "2560x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=0
|
||||
focus-at-startup
|
||||
}
|
||||
output "HDMI-A-3" {
|
||||
mode "1920x1080@60"
|
||||
scale 1.0
|
||||
position x=-1920 y=0
|
||||
}
|
||||
output "DP-4" {
|
||||
mode "1920x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=-1080
|
||||
}
|
||||
|
||||
'';
|
||||
'';
|
||||
|
||||
binds."XF86Tools".spawn = [
|
||||
"wpctl"
|
||||
@@ -55,43 +36,22 @@ in
|
||||
|
||||
chiasson.desktop.hyprland.settings = lib.mkIf config.chiasson.desktop.hyprland.enable (
|
||||
let
|
||||
monitorList =
|
||||
if gpuPassthrough then
|
||||
[
|
||||
"DP-1, 2560x1080@144, 0x0, 1"
|
||||
"HDMI-A-2, 1920x1080@60, auto-up, 1"
|
||||
]
|
||||
else
|
||||
[
|
||||
"DP-2, 2560x1080@144, 0x0, 1"
|
||||
"DP-4, 1920x1080@144, 0x-1080, 1"
|
||||
"HDMI-A-3, 1920x1080@60, -1920x0, 1"
|
||||
];
|
||||
workspaceList =
|
||||
if gpuPassthrough then
|
||||
[
|
||||
"1, monitor:DP-1, default:true"
|
||||
"2, monitor:DP-1"
|
||||
"3, monitor:DP-1"
|
||||
"4, monitor:DP-1"
|
||||
"5, monitor:HDMI-A-2, default:true"
|
||||
"6, monitor:HDMI-A-2"
|
||||
"7, monitor:HDMI-A-2"
|
||||
"8, monitor:HDMI-A-2"
|
||||
"9, monitor:DP-1"
|
||||
]
|
||||
else
|
||||
[
|
||||
"1, monitor:DP-3, default:true"
|
||||
"2, monitor:DP-3"
|
||||
"3, monitor:DP-3"
|
||||
"4, monitor:Unknown-2, default:true"
|
||||
"5, monitor:Unknown-2"
|
||||
"6, monitor:Unknown-2"
|
||||
"7, monitor:DP-4"
|
||||
"8, monitor:DP-4"
|
||||
"9, monitor:DP-4"
|
||||
];
|
||||
monitorList = [
|
||||
"DP-2, 2560x1080@144, 0x0, 1"
|
||||
"DP-4, 1920x1080@144, 0x-1080, 1"
|
||||
"HDMI-A-3, 1920x1080@60, -1920x0, 1"
|
||||
];
|
||||
workspaceList = [
|
||||
"1, monitor:DP-3, default:true"
|
||||
"2, monitor:DP-3"
|
||||
"3, monitor:DP-3"
|
||||
"4, monitor:Unknown-2, default:true"
|
||||
"5, monitor:Unknown-2"
|
||||
"6, monitor:Unknown-2"
|
||||
"7, monitor:DP-4"
|
||||
"8, monitor:DP-4"
|
||||
"9, monitor:DP-4"
|
||||
];
|
||||
in
|
||||
{
|
||||
monitor = lib.mkBefore monitorList;
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# Export large Jellyfin media trees to nix-server. Local path must already exist
|
||||
# (e.g. /mnt/test/jellyfin/{movies,tv}). On nix-server this is mounted at /mnt/nixdesk-jellyfin.
|
||||
# NFS exports from nixdesk (14900k) to nix-server (192.168.2.238):
|
||||
# - /mnt/deep/jellyfin → nix-server /mnt/nixdesk-jellyfin (Jellyfin bulk libraries)
|
||||
#
|
||||
# After deploy: ensure Jellyfin can read files over NFS — typical fix:
|
||||
# chmod -R a+rX /mnt/test/jellyfin
|
||||
{ ... }:
|
||||
# Jellyfin root on nixdesk uses owner olivier + group nfsmedia (990); dirs here are 2775 so
|
||||
# local writes and NFS all_squash (anonuid=olivier, anongid=990) get rwx via owner or group.
|
||||
#
|
||||
# Legacy trees may still need a one-time `chgrp -R nfsmedia` / `chmod -R g+rwX` on deep folders.
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
olivierUid = config.users.users.olivier.uid or 1000;
|
||||
in
|
||||
{
|
||||
# Avoid UID/GID mismatches across machines: map all NFS writes from nix-server to a single
|
||||
# local system user/group on this server.
|
||||
@@ -14,10 +19,23 @@
|
||||
group = "nfsmedia";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.settings."14900k-jellyfin-media-dirs" = {
|
||||
"/mnt/test/jellyfin"."d" = { mode = "2775"; user = "nfsmedia"; group = "nfsmedia"; };
|
||||
"/mnt/test/jellyfin/movies"."d" = { mode = "2775"; user = "nfsmedia"; group = "nfsmedia"; };
|
||||
"/mnt/test/jellyfin/tv"."d" = { mode = "2775"; user = "nfsmedia"; group = "nfsmedia"; };
|
||||
# olivier: owner for local use; nfsmedia: group used by NFS all_squash (990).
|
||||
systemd.tmpfiles.settings."14900k-nfs-export-paths" = {
|
||||
"/mnt/deep/jellyfin"."d" = { mode = "2775"; user = "olivier"; group = "nfsmedia"; };
|
||||
};
|
||||
|
||||
# After exports are up, ensure group nfsmedia can write throughout library roots (idempotent;
|
||||
# scoped to library folders only — not whole disks). Runs on each `nixos-rebuild switch`.
|
||||
system.activationScripts.nfs-export-group-write = {
|
||||
deps = [ "specialfs" ];
|
||||
text = ''
|
||||
for d in /mnt/deep/jellyfin
|
||||
do
|
||||
[ -d "$d" ] || continue
|
||||
${pkgs.acl}/bin/setfacl -R -m g:nfsmedia:rwx "$d" 2>/dev/null || true
|
||||
${pkgs.acl}/bin/setfacl -R -d -m g:nfsmedia:rwx "$d" 2>/dev/null || true
|
||||
done
|
||||
'';
|
||||
};
|
||||
|
||||
# Fixed ports so the firewall can allow NFS v3 helpers (see networking.firewall below).
|
||||
@@ -26,8 +44,11 @@
|
||||
mountdPort = 4000;
|
||||
lockdPort = 4001;
|
||||
statdPort = 4002;
|
||||
# fsid= unique per export tree (avoids client ESTALE when multiple paths are exported).
|
||||
# Squash nix-server clients to olivier:nfsmedia so Jellyfin can write .nfo/posters into
|
||||
# existing olivier-owned library folders (990-only squash was "other" r-x on typical 755 trees).
|
||||
exports = ''
|
||||
/mnt/test/jellyfin 192.168.2.238(rw,sync,no_subtree_check,crossmnt,root_squash,all_squash,anonuid=990,anongid=990)
|
||||
/mnt/deep/jellyfin 192.168.2.238(rw,sync,no_subtree_check,crossmnt,root_squash,all_squash,anonuid=${toString olivierUid},anongid=990,fsid=1)
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Extra local disks. Declared here, not in hardware.nix (hardware.nix is generated).
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
# Stable UID so NTFS `uid=` matches `users.users.olivier` (override if your account is not 1000).
|
||||
olivierUid = config.users.users.olivier.uid or 1000;
|
||||
in
|
||||
{
|
||||
users.users.olivier.uid = lib.mkDefault 1000;
|
||||
# LABEL="MediaLibrary" (btrfs on sda1 by UUID). No subvol=@ — this disk has no @ subvolume.
|
||||
fileSystems."/mnt/2nd" = {
|
||||
device = "/dev/disk/by-uuid/17d8a981-db3b-415e-a0f7-7dbc519e04ab";
|
||||
fsType = "btrfs";
|
||||
options = [
|
||||
"compress=zstd"
|
||||
"noatime"
|
||||
"nofail"
|
||||
"x-systemd.device-timeout=30"
|
||||
];
|
||||
};
|
||||
|
||||
#new deep storage unit
|
||||
fileSystems."/mnt/deep" = {
|
||||
device = "/dev/disk/by-uuid/64fb08fe-da5d-4405-afa3-1603a411e9e5";
|
||||
fsType = "btrfs";
|
||||
options = [
|
||||
"compress=zstd"
|
||||
"noatime"
|
||||
"nofail"
|
||||
"x-systemd.device-timeout=30"
|
||||
];
|
||||
};
|
||||
|
||||
# LABEL="Deep Storage Unit". Owner olivier, group nfsmedia (990) so:
|
||||
# - local logins write as user 1000 (owner rwx);
|
||||
# - NFS (all_squash → uid/gid 990) matches group 990 → rwx (see jellyfin-nfs-export).
|
||||
#fileSystems."/mnt/test" = {
|
||||
# device = "/dev/disk/by-uuid/BC12E55E12E51DE0";
|
||||
# fsType = "ntfs-3g";
|
||||
# options = [
|
||||
# "rw"
|
||||
# "force"
|
||||
# "uid=${toString olivierUid}"
|
||||
# "gid=990"
|
||||
# "umask=0002"
|
||||
# ];
|
||||
#};
|
||||
}
|
||||
@@ -1,26 +1,19 @@
|
||||
# NVIDIA for host desktop; when `chiasson.system.vm.gpuPassthrough` is enabled, drop NVIDIA for VFIO (port later).
|
||||
# NVIDIA for host desktop.
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
passthrough = config.chiasson.system.vm.gpuPassthrough.enable;
|
||||
in
|
||||
{
|
||||
boot.kernelParams = [ "snd_hda_core.gpu_bind=0" ];
|
||||
boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
|
||||
|
||||
services.xserver.videoDrivers = if passthrough then [ "modesetting" ] else [ "nvidia" ];
|
||||
services.xserver.videoDrivers = [ "nvidia" ];
|
||||
|
||||
hardware.nvidia =
|
||||
if passthrough then
|
||||
lib.mkForce { }
|
||||
else {
|
||||
modesetting.enable = true;
|
||||
powerManagement.enable = false;
|
||||
powerManagement.finegrained = false;
|
||||
open = true;
|
||||
nvidiaSettings = true;
|
||||
package = config.boot.kernelPackages.nvidiaPackages.stable;
|
||||
};
|
||||
hardware.nvidia = {
|
||||
modesetting.enable = true;
|
||||
powerManagement.enable = false;
|
||||
powerManagement.finegrained = false;
|
||||
open = true;
|
||||
nvidiaSettings = true;
|
||||
package = config.boot.kernelPackages.nvidiaPackages.latest;
|
||||
};
|
||||
|
||||
# Needed for `docker compose` GPU passthrough (e.g. `--gpus all` / DEVICE=gpu).
|
||||
hardware.nvidia-container-toolkit.enable = !passthrough;
|
||||
hardware.nvidia-container-toolkit.enable = true;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
hardware.enableAllFirmware = true;
|
||||
hardware.cpu.intel.updateMicrocode = true;
|
||||
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
./_private/peripherals.nix
|
||||
# ./_private/printing-epson.nix
|
||||
./_private/displays.nix
|
||||
./_private/media-disk.nix
|
||||
./_private/jellyfin-nfs-export.nix
|
||||
];
|
||||
|
||||
@@ -44,6 +45,7 @@ services.cloudflare-warp.enable = true;
|
||||
# This fixes common NixOS issues like `vaInitialize failed` and missing QSV encoders in apps.
|
||||
hardware.graphics = {
|
||||
enable = true;
|
||||
enable32Bit = true; # Required by Wine/DXVK for 32-bit Vulkan userspace.
|
||||
extraPackages = with pkgs; [
|
||||
intel-media-driver # iHD (Gen8+)
|
||||
vpl-gpu-rt # oneVPL runtime (QSV)
|
||||
@@ -83,23 +85,22 @@ services.cloudflare-warp.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
chiasson.system.chromiumHevc.enable = true;
|
||||
|
||||
chiasson.system = {
|
||||
# libvirt/QEMU + VFIO; host uses Intel iGPU for Niri while NVIDIA is passed through (see
|
||||
# `_private/nvidia.nix`, `_private/displays.nix`). If your GPU is not RTX 2070-class IDs, set
|
||||
# `chiasson.system.vm.gpuPassthrough.vfioIds` from `lspci -nn` (GPU + HDA functions in the same group).
|
||||
vm = {
|
||||
enable = true;
|
||||
gpuPassthrough.enable = false;
|
||||
};
|
||||
ytDlpTelequebecPatch.enable = true;
|
||||
|
||||
audio.enable = true;
|
||||
docker.enable = true;
|
||||
gaming.enable = true;
|
||||
gaming.launchers.enableBottles = false;
|
||||
gaming.gamescope.enable = true;
|
||||
gaming.steam.steamTinkerLaunch.enable = true;
|
||||
|
||||
monitorInput.enable = true;
|
||||
|
||||
flatpak.enable = true;
|
||||
flatpak.flathub.appIds = [ "com.usebottles.bottles" ];
|
||||
|
||||
palera1n.enable = true;
|
||||
uconsoleKernelBuilder.enable = true;
|
||||
@@ -114,8 +115,15 @@ services.cloudflare-warp.enable = true;
|
||||
# Native install (avoid flatpak sandbox issues for QSV/VAAPI).
|
||||
handbrake
|
||||
|
||||
qbittorrent
|
||||
|
||||
# Diagnostics
|
||||
libva-utils # vainfo
|
||||
vlc
|
||||
element-desktop
|
||||
thunderbird
|
||||
|
||||
prismlauncher
|
||||
];
|
||||
|
||||
|
||||
@@ -134,6 +142,8 @@ services.cloudflare-warp.enable = true;
|
||||
self.homeManagerModules.wisdomBrowsersEdge
|
||||
self.homeManagerModules.wisdomBrowsersFlow
|
||||
self.homeManagerModules.wisdomBrowsersOrion
|
||||
self.homeManagerModules.wisdomBrowsersZen
|
||||
self.homeManagerModules.wisdomBrowsersChromiumHevc
|
||||
self.homeManagerModules.wisdomEditorsCursor
|
||||
self.homeManagerModules.wisdomEditorsObsidian
|
||||
self.homeManagerModules.wisdomShellYazi
|
||||
@@ -168,6 +178,12 @@ services.cloudflare-warp.enable = true;
|
||||
browsers.edge.enable = true;
|
||||
browsers.flow.enable = false;
|
||||
browsers.orion.enable = true;
|
||||
browsers.zen.enable = true;
|
||||
browsers.chromiumHevc = {
|
||||
enable = true;
|
||||
packages = [ "google-chrome" ];
|
||||
vaapi.gpu = "intel"; # Chromium + NVIDIA VA-API → frame pool errors in Jellyfin cuz chrome is proprietary rats nests, gecko engine might support NVIDIA VA-API
|
||||
};
|
||||
|
||||
editors.cursor.enable = true;
|
||||
editors.obsidian.enable = true;
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
# Cameras on the Lenovo Duet 3 (`lenovo-wormdingler`) — TODO
|
||||
|
||||
The front and rear cameras do **not** work on the current Mobile NixOS image.
|
||||
This file is a starting point for picking the work back up later, so we don't
|
||||
have to re-diagnose from scratch.
|
||||
|
||||
## Current state (May 2026)
|
||||
|
||||
Empirical, taken on the running device:
|
||||
|
||||
```
|
||||
$ uname -a
|
||||
Linux ideapad 6.5.0 #1-mobile-nixos SMP Tue Jan 1 00:00:00 UTC 1980 aarch64 GNU/Linux
|
||||
|
||||
$ ls /dev/video*
|
||||
/dev/video0 ← Qualcomm Venus video decoder (h.264/h.265 dec)
|
||||
/dev/video1 ← Qualcomm Venus video encoder (h.264/h.265 enc)
|
||||
|
||||
$ ls /dev/media* # nothing — no media controller graph at all
|
||||
$ lsmod | grep -i camss # nothing
|
||||
$ wpctl status | grep -A1 Sources # nothing — PipeWire has no camera sources
|
||||
```
|
||||
|
||||
Relevant kernel config bits in `/proc/config.gz` on the running image:
|
||||
|
||||
```
|
||||
CONFIG_MEDIA_CAMERA_SUPPORT=y ← media framework is on
|
||||
# CONFIG_VIDEO_QCOM_CAMSS is not set ← Qualcomm Camera Subsystem driver is OFF
|
||||
# CONFIG_VIDEO_OV5675 is not set ← every relevant sensor driver is OFF
|
||||
# CONFIG_VIDEO_OV13858 is not set
|
||||
# CONFIG_VIDEO_HI556 is not set
|
||||
# (full grep of `VIDEO_OV*` / `VIDEO_HI*` is all `not set`)
|
||||
```
|
||||
|
||||
So the *media framework* is enabled, but the *ISP driver* (CAMSS) and every
|
||||
plausible sensor driver are off, and no out-of-tree modules are shipped. The
|
||||
two `/dev/video*` nodes are the Venus codecs, not cameras — that's why
|
||||
`snapshot` reports "no camera found."
|
||||
|
||||
## Why this is non-trivial
|
||||
|
||||
1. **Kernel rebuild required.** `mobile-nixos` builds its own kernel for this
|
||||
device (see `mobile.kernel.structuredConfig` in `hardware.nix`, where we
|
||||
already enable `CIFS` and `EXFAT_FS`). Adding camera support means adding:
|
||||
- `VIDEO_QCOM_CAMSS = module;`
|
||||
- The right sensor driver(s) — and we don't currently know which ones the
|
||||
Duet 3 actually uses. The `wormdingler` Chromium-OS DT references a pair
|
||||
of OV-series sensors but the exact part numbers can vary by SKU and
|
||||
production batch.
|
||||
- Their dependencies (`I2C`, `V4L2`, `MEDIA_CONTROLLER`, etc. — most are
|
||||
already pulled in by `MEDIA_CAMERA_SUPPORT`).
|
||||
|
||||
2. **Device-tree wiring.** Even with the drivers compiled in, the device tree
|
||||
has to describe the CCI bus, the sensor I²C addresses, the regulators, the
|
||||
reset/enable GPIOs, and the CSI port mapping. Mobile NixOS' DT for
|
||||
wormdingler may or may not include these nodes — needs verification by
|
||||
reading the upstream device file at
|
||||
`${inputs.mobile-nixos}/devices/lenovo-wormdingler/`.
|
||||
|
||||
3. **libcamera is the user-space side.** CAMSS is not a "single v4l2 device"
|
||||
driver — it exposes a media-controller graph that has to be configured by
|
||||
libcamera (per-sensor IPA, format negotiation, etc.). Apps then talk to
|
||||
libcamera via the `libcamerasrc` PipeWire module or directly. So the
|
||||
user-space stack is:
|
||||
- `pkgs.libcamera` (system-wide)
|
||||
- `pkgs.pipewire` already running, but needs the libcamera module enabled
|
||||
(`services.pipewire.libcamera = …` or equivalent — check current option
|
||||
name)
|
||||
- GUI: `snapshot`, `gnome-camera`, or anything that talks to PipeWire
|
||||
video sources.
|
||||
|
||||
## Investigation checklist
|
||||
|
||||
When picking this up again, do these in order:
|
||||
|
||||
1. **Identify the actual sensors.** The cleanest way:
|
||||
- Read the upstream Mobile NixOS device file for wormdingler
|
||||
(`devices/lenovo-wormdingler/default.nix` and any `.dts` overlays).
|
||||
- Cross-check with the Chromium OS overlay at
|
||||
<https://chromium.googlesource.com/chromiumos/overlays/board-overlays/+/refs/heads/main/overlay-trogdor/>
|
||||
and the upstream Linux DTS at
|
||||
`arch/arm64/boot/dts/qcom/sc7180-trogdor-wormdingler-*.dts`.
|
||||
- As a runtime cross-check, when CAMSS is eventually loaded, `dmesg | grep -i
|
||||
-E 'cci|sensor|isp'` will print the I²C probe attempts.
|
||||
|
||||
2. **Enable kernel options** in `modules/hosts/ideapad/hardware.nix` under
|
||||
`mobile.kernel.structuredConfig`:
|
||||
|
||||
```nix
|
||||
(helpers: with helpers; {
|
||||
VIDEO_QCOM_CAMSS = module;
|
||||
# plus whatever sensors step 1 identified, e.g.:
|
||||
# VIDEO_OV5675 = module;
|
||||
# VIDEO_OV13858 = module;
|
||||
})
|
||||
```
|
||||
|
||||
Cross-build on the 14900k via the existing flow (binfmt aarch64 + push back).
|
||||
Reboot, then check:
|
||||
|
||||
```
|
||||
ls /dev/media* # expect at least /dev/media0
|
||||
sudo dmesg | grep -i -E 'camss|sensor|isp|cci' # probe history
|
||||
sudo modprobe -v qcom-camss # if not auto-loaded
|
||||
```
|
||||
|
||||
3. **Install diagnostic tools** for this round of work (do **not** keep these
|
||||
in the long-term config unless cameras actually work):
|
||||
|
||||
```nix
|
||||
environment.systemPackages = with pkgs; [
|
||||
v4l-utils # provides v4l2-ctl, media-ctl
|
||||
libcamera # provides `cam`, `qcam`
|
||||
];
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
v4l2-ctl --list-devices
|
||||
media-ctl -p # dumps the full media-controller graph
|
||||
cam -l # libcamera's view of available cameras
|
||||
```
|
||||
|
||||
4. **Wire libcamera into PipeWire.** Once `cam -l` shows at least one camera,
|
||||
enable PipeWire's libcamera module (option name may have shifted; current
|
||||
nixpkgs typically has `services.pipewire.wireplumber.extraConfig` or
|
||||
similar). Then `wpctl status` should show new Sources under "Video", and
|
||||
`snapshot` will see them.
|
||||
|
||||
5. **Re-add a camera GUI** to `configuration.nix`. `snapshot` is the simplest
|
||||
touch-first option; `gnome-camera` and `cheese` are alternatives.
|
||||
|
||||
## Why nothing else is being touched right now
|
||||
|
||||
Steps 1–4 above are speculative — there's no guarantee the Duet 3 cameras have
|
||||
working mainline-Linux sensor drivers at all. The conservative move is to
|
||||
leave the config tablet-usable without them, document the dead end, and revisit
|
||||
when there's time for a real spike.
|
||||
@@ -0,0 +1,48 @@
|
||||
{ pkgs, ... }: {
|
||||
# ─────────────────────── Power & thermal ───────────────────────
|
||||
# Snapdragon 7c (sc7180) on a tablet form factor: aim for battery life. `schedutil` is the
|
||||
# right modern cpufreq governor on ARM (responsive + power-aware); use it instead of
|
||||
# `powersave` to avoid pinning the CPU at minimum frequency under interactive load.
|
||||
powerManagement.cpuFreqGovernor = "schedutil";
|
||||
powerManagement.enable = true;
|
||||
|
||||
# ─────────────────────── logind: lid & power button ───────────────────────
|
||||
# Tablet form factor: lid close = suspend (even on AC), short power-press = suspend, long
|
||||
# power-press = poweroff. Niri's own power-key handler must stay disabled — see the
|
||||
# `input.disable-power-key-handling` flag in `_private/touch-tablet.nix` — otherwise niri's
|
||||
# `block` inhibitor on `handle-power-key` pre-empts logind and turns the wake-from-suspend
|
||||
# press (which the EC re-delivers as KEY_POWER) into an immediate re-suspend loop
|
||||
# (https://github.com/niri-wm/niri/issues/2233).
|
||||
services.logind.settings.Login = {
|
||||
HandleLidSwitch = "suspend";
|
||||
HandleLidSwitchExternalPower = "suspend";
|
||||
HandleLidSwitchDocked = "ignore";
|
||||
HandlePowerKey = "suspend";
|
||||
HandlePowerKeyLongPress = "poweroff";
|
||||
};
|
||||
|
||||
# ─────────────────────── Idle / suspend tuning ───────────────────────
|
||||
# Allow suspend-to-RAM but disable hibernate (ARM swap-resume is unreliable, and we don't
|
||||
# have a swap device by default anyway).
|
||||
systemd.sleep.settings.Sleep = {
|
||||
AllowHibernation = "no";
|
||||
AllowHybridSleep = "no";
|
||||
AllowSuspendThenHibernate = "no";
|
||||
};
|
||||
|
||||
# upower picks the right battery percentages for low/critical out of the box; just make
|
||||
# sure the action on critical is hibernate-then-poweroff fallback (we disabled hibernate
|
||||
# so it'll go straight to poweroff). DMS reads upower for the bar widget.
|
||||
services.upower = {
|
||||
enable = true;
|
||||
criticalPowerAction = "PowerOff";
|
||||
percentageLow = 15;
|
||||
percentageCritical = 7;
|
||||
percentageAction = 3;
|
||||
};
|
||||
|
||||
# ─────────────────────── Bluetooth audio quality of life ───────────────────────
|
||||
# Duet has limited mic/speaker hw — keep wpa_supplicant power-save off so audio doesn't crackle
|
||||
# over Bluetooth when CPU is idle. (Wi-Fi + BT share the chip; aggressive power-save = stutter.)
|
||||
networking.networkmanager.wifi.powersave = false;
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
# Host-only: ideapad tablet ergonomics — touchscreen calibration, IIO sensors, virtual keyboard,
|
||||
# touch-controller resume fix, and per-session helper daemons (tablet-mode toggle + auto-rotation
|
||||
# via iio-sensor-proxy) for both Niri and Hyprland. Lives at the NixOS layer because the hardware
|
||||
# bits are system-wide; the per-compositor autostart hooks are gated on `chiasson.desktop.<wm>.enable`
|
||||
# so they stay dormant if you pick the other session at the greeter.
|
||||
#
|
||||
# Hyprland uses CW transforms via `hyprctl`; Niri uses CCW transforms via `niri msg output`.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
# ─────────────────────── Hyprland helpers ───────────────────────
|
||||
ideapadTabletModeDaemon = pkgs.writeShellScriptBin "ideapad-tablet-mode-daemon" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
HYPRCTL="${pkgs.hyprland}/bin/hyprctl"
|
||||
JQ="${pkgs.jq}/bin/jq"
|
||||
PKILL="${pkgs.procps}/bin/pkill"
|
||||
PGREP="${pkgs.procps}/bin/pgrep"
|
||||
WVKBD="${pkgs.wvkbd}/bin/wvkbd-mobintl"
|
||||
|
||||
monitor_name="DSI-1"
|
||||
keyboard_scale="1.25"
|
||||
tablet_scale="1.6"
|
||||
state_file="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}/ideapad-input-mode.state"
|
||||
|
||||
current_transform() {
|
||||
"$HYPRCTL" -j monitors 2>/dev/null | "$JQ" -r --arg mon "$monitor_name" '
|
||||
([.[] | select(.name == $mon)][0].transform // 1 | tostring)
|
||||
' 2>/dev/null || echo "1"
|
||||
}
|
||||
|
||||
has_attached_pogo_dock() {
|
||||
"$HYPRCTL" -j devices 2>/dev/null | "$JQ" -e '
|
||||
any((([.keyboards[]?.name] + [.mice[]?.name])[]?); (ascii_downcase | test("google-inc\\.-hammer")))
|
||||
' >/dev/null
|
||||
}
|
||||
|
||||
# wvkbd is no longer auto-spawned at session start — this daemon owns its lifecycle so we
|
||||
# only have a virtual keyboard surface in memory when the pogo cover is detached.
|
||||
start_wvkbd() {
|
||||
"$PGREP" -x wvkbd-mobintl >/dev/null 2>&1 && return 0
|
||||
"$WVKBD" --non-exclusive -H 520 -L 360 --fn 'DejaVu Sans 18' >/dev/null 2>&1 &
|
||||
}
|
||||
stop_wvkbd() {
|
||||
"$PKILL" -x wvkbd-mobintl >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
apply_mode() {
|
||||
mode="$1"
|
||||
transform="$(current_transform)"
|
||||
if [ "$mode" = "tablet" ]; then
|
||||
"$HYPRCTL" keyword monitor "$monitor_name,1200x2000@60.0,0x0,$tablet_scale, transform, $transform" >/dev/null 2>&1 || true
|
||||
start_wvkbd
|
||||
else
|
||||
"$HYPRCTL" keyword monitor "$monitor_name,1200x2000@60.0,0x0,$keyboard_scale, transform, $transform" >/dev/null 2>&1 || true
|
||||
stop_wvkbd
|
||||
fi
|
||||
"$HYPRCTL" keyword input:touchdevice:output "$monitor_name" >/dev/null 2>&1 || true
|
||||
"$HYPRCTL" keyword input:touchdevice:transform "$transform" >/dev/null 2>&1 || true
|
||||
printf "%s\n" "$mode" > "$state_file"
|
||||
}
|
||||
|
||||
# Always reapply on startup — the cached state file may lie (e.g., session restart while
|
||||
# pogo state changed) and wvkbd needs to be (re)spawned since nothing else launches it now.
|
||||
previous_mode=""
|
||||
|
||||
while true; do
|
||||
if has_attached_pogo_dock; then
|
||||
mode="keyboard"
|
||||
else
|
||||
mode="tablet"
|
||||
fi
|
||||
|
||||
if [ "$mode" != "$previous_mode" ]; then
|
||||
apply_mode "$mode"
|
||||
previous_mode="$mode"
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
|
||||
ideapadAutoRotateDaemon = pkgs.writeShellScriptBin "ideapad-autorotate-daemon" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
HYPRCTL="${pkgs.hyprland}/bin/hyprctl"
|
||||
MONITOR_SENSOR="${pkgs.iio-sensor-proxy}/bin/monitor-sensor"
|
||||
|
||||
monitor_name="DSI-1"
|
||||
keyboard_scale="1.25"
|
||||
tablet_scale="1.6"
|
||||
state_file="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}/ideapad-input-mode.state"
|
||||
|
||||
scale_for_mode() {
|
||||
mode="$(cat "$state_file" 2>/dev/null || echo keyboard)"
|
||||
if [ "$mode" = "tablet" ]; then
|
||||
printf "%s\n" "$tablet_scale"
|
||||
else
|
||||
printf "%s\n" "$keyboard_scale"
|
||||
fi
|
||||
}
|
||||
|
||||
transform_for_orientation() {
|
||||
orientation="$1"
|
||||
case "$orientation" in
|
||||
normal) printf "0\n" ;;
|
||||
bottom-up) printf "2\n" ;;
|
||||
left-up) printf "1\n" ;;
|
||||
right-up) printf "3\n" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
apply_orientation() {
|
||||
orientation="$1"
|
||||
transform="$(transform_for_orientation "$orientation")" || return 0
|
||||
scale="$(scale_for_mode)"
|
||||
"$HYPRCTL" keyword monitor "$monitor_name,1200x2000@60.0,0x0,$scale, transform, $transform" >/dev/null 2>&1 || true
|
||||
"$HYPRCTL" keyword input:touchdevice:output "$monitor_name" >/dev/null 2>&1 || true
|
||||
"$HYPRCTL" keyword input:touchdevice:transform "$transform" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
while true; do
|
||||
"$MONITOR_SENSOR" --accel 2>/dev/null | while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*normal*) apply_orientation "normal" ;;
|
||||
*bottom-up*) apply_orientation "bottom-up" ;;
|
||||
*left-up*) apply_orientation "left-up" ;;
|
||||
*right-up*) apply_orientation "right-up" ;;
|
||||
esac
|
||||
done
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
|
||||
# ─────────────────────── Niri helpers ───────────────────────
|
||||
# `niri msg output DSI-1 transform <T>` accepts: normal | 90 | 180 | 270.
|
||||
#
|
||||
# Empirical mapping for this exact device + ACCEL_MOUNT_MATRIX (lenovo-wormdingler family):
|
||||
# physical position iio orientation correct niri transform
|
||||
# ────────────────── ──────────────── ──────────────────────
|
||||
# keyboard down (LS) left-up normal
|
||||
# keyboard up (LS) right-up 180
|
||||
# keyboard right (P) bottom-up 90
|
||||
# keyboard left (P) normal 270
|
||||
# iio's "normal" is *not* the natural landscape pose here — the panel-mount matrix in the
|
||||
# mobile-nixos device file biases it toward portrait-with-keyboard-left. Don't trust the
|
||||
# textbook iio→Wayland convention; trust the table above.
|
||||
ideapadNiriTabletModeDaemon = pkgs.writeShellScriptBin "ideapad-niri-tablet-mode-daemon" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
NIRI="${pkgs.niri}/bin/niri"
|
||||
JQ="${pkgs.jq}/bin/jq"
|
||||
PKILL="${pkgs.procps}/bin/pkill"
|
||||
PGREP="${pkgs.procps}/bin/pgrep"
|
||||
WVKBD="${pkgs.wvkbd}/bin/wvkbd-mobintl"
|
||||
|
||||
output_name="DSI-1"
|
||||
keyboard_scale="1.25"
|
||||
tablet_scale="1.6"
|
||||
state_file="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}/ideapad-input-mode.state"
|
||||
|
||||
has_attached_pogo_dock() {
|
||||
# Niri doesn't expose input devices via IPC, so we look in `/dev/input/by-id` for the
|
||||
# USB-class enumeration of the Google Hammer / Whiskers pogo keyboard. The directory
|
||||
# contains symlinks; presence of either string means the cover is attached.
|
||||
# NOTE: `find` ships in findutils, not coreutils — using the wrong package here makes
|
||||
# the pipeline fail silently (stderr is dropped) and pogo always reads as detached.
|
||||
${pkgs.findutils}/bin/find /dev/input/by-id -maxdepth 1 -type l 2>/dev/null \
|
||||
| ${pkgs.gnugrep}/bin/grep -i -E 'google.*hammer|google.*whiskers' >/dev/null
|
||||
}
|
||||
|
||||
# wvkbd is owned by this daemon: spawn on pogo detach, kill on pogo attach. The DMS bar
|
||||
# `pkill -SIGRTMIN -x wvkbd-mobintl` toggle still works while wvkbd is running (i.e., in
|
||||
# tablet mode). When the pogo cover is on, the pill is a no-op — that's intentional, the
|
||||
# physical keyboard is the input.
|
||||
start_wvkbd() {
|
||||
"$PGREP" -x wvkbd-mobintl >/dev/null 2>&1 && return 0
|
||||
"$WVKBD" --non-exclusive -H 520 -L 360 --fn 'DejaVu Sans 18' >/dev/null 2>&1 &
|
||||
}
|
||||
stop_wvkbd() {
|
||||
"$PKILL" -x wvkbd-mobintl >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
apply_mode() {
|
||||
mode="$1"
|
||||
if [ "$mode" = "tablet" ]; then
|
||||
"$NIRI" msg output "$output_name" scale "$tablet_scale" >/dev/null 2>&1 || true
|
||||
start_wvkbd
|
||||
else
|
||||
"$NIRI" msg output "$output_name" scale "$keyboard_scale" >/dev/null 2>&1 || true
|
||||
stop_wvkbd
|
||||
fi
|
||||
printf "%s\n" "$mode" > "$state_file"
|
||||
}
|
||||
|
||||
# Always reapply on startup — see Hyprland daemon's identical comment.
|
||||
previous_mode=""
|
||||
|
||||
while true; do
|
||||
if has_attached_pogo_dock; then
|
||||
mode="keyboard"
|
||||
else
|
||||
mode="tablet"
|
||||
fi
|
||||
|
||||
if [ "$mode" != "$previous_mode" ]; then
|
||||
apply_mode "$mode"
|
||||
previous_mode="$mode"
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
|
||||
ideapadNiriAutoRotateDaemon = pkgs.writeShellScriptBin "ideapad-niri-autorotate-daemon" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
NIRI="${pkgs.niri}/bin/niri"
|
||||
MONITOR_SENSOR="${pkgs.iio-sensor-proxy}/bin/monitor-sensor"
|
||||
|
||||
output_name="DSI-1"
|
||||
|
||||
transform_for_orientation() {
|
||||
orientation="$1"
|
||||
case "$orientation" in
|
||||
normal) printf "270\n" ;;
|
||||
bottom-up) printf "90\n" ;;
|
||||
left-up) printf "normal\n" ;;
|
||||
right-up) printf "180\n" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
apply_orientation() {
|
||||
orientation="$1"
|
||||
transform="$(transform_for_orientation "$orientation")" || return 0
|
||||
"$NIRI" msg output "$output_name" transform "$transform" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
while true; do
|
||||
"$MONITOR_SENSOR" --accel 2>/dev/null | while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*normal*) apply_orientation "normal" ;;
|
||||
*bottom-up*) apply_orientation "bottom-up" ;;
|
||||
*left-up*) apply_orientation "left-up" ;;
|
||||
*right-up*) apply_orientation "right-up" ;;
|
||||
esac
|
||||
done
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
in
|
||||
{
|
||||
# ─────────────────────── Hardware ───────────────────────
|
||||
hardware.sensor.iio.enable = true;
|
||||
|
||||
# Touchscreen calibration — identity matrix is correct: hardware coordinates are already aligned
|
||||
# with the panel-native frame, and per-orientation rotation is handled by `niri msg output`,
|
||||
# not by re-tuning this matrix. Rotate the *output*, never this matrix.
|
||||
services.udev.extraRules = ''
|
||||
SUBSYSTEM=="input", ENV{ID_INPUT_TOUCHSCREEN}=="1", ENV{LIBINPUT_CALIBRATION_MATRIX}="1 0 0 0 1 0"
|
||||
'';
|
||||
|
||||
# ─────────────────────── Touch controller resume fix ───────────────────────
|
||||
# The hid-over-i2c touch controller at i2c bus 4-0001 wedges across S3 suspend: after resume it
|
||||
# re-enumerates with the correct capabilities but reports zero events on touch. Cycling the
|
||||
# `i2c_hid_of` driver (unbind + bind) un-wedges it. systemd-sleep runs every executable in
|
||||
# `/etc/systemd/system-sleep/` with `$1 = pre|post`; we only act on `post`. Driver name is
|
||||
# discovered at runtime so a future kernel rename to `i2c_hid` doesn't break this.
|
||||
environment.etc."systemd/system-sleep/ideapad-touch-rebind".source =
|
||||
pkgs.writeShellScript "ideapad-touch-rebind" ''
|
||||
set -eu
|
||||
[ "$1" = post ] || exit 0
|
||||
dev=4-0001
|
||||
dev_dir=/sys/bus/i2c/devices/$dev
|
||||
[ -L "$dev_dir/driver" ] || exit 0
|
||||
drv=$(${pkgs.coreutils}/bin/basename "$(${pkgs.coreutils}/bin/readlink "$dev_dir/driver")")
|
||||
echo "ideapad-touch-rebind: cycling $drv for $dev" >&2
|
||||
echo "$dev" > "/sys/bus/i2c/drivers/$drv/unbind" || true
|
||||
${pkgs.coreutils}/bin/sleep 0.3
|
||||
echo "$dev" > "/sys/bus/i2c/drivers/$drv/bind"
|
||||
'';
|
||||
|
||||
# ─────────────────────── User-facing tools ───────────────────────
|
||||
# System-wide so any user session (Niri or Hyprland) can launch wvkbd / hyprctl / niri-msg helpers.
|
||||
environment.systemPackages = [
|
||||
pkgs.wvkbd
|
||||
pkgs.iio-sensor-proxy
|
||||
ideapadTabletModeDaemon
|
||||
ideapadAutoRotateDaemon
|
||||
ideapadNiriTabletModeDaemon
|
||||
ideapadNiriAutoRotateDaemon
|
||||
];
|
||||
|
||||
# ─────────────────────── Niri session autostart ───────────────────────
|
||||
# Set on the NixOS layer; the `desktopNiri` HM module merges this into per-user `niri/config.kdl`.
|
||||
# Touch input is glued to DSI-1 so it follows whatever transform the autorotate daemon sets.
|
||||
# wvkbd is intentionally NOT spawned here — `ideapad-niri-tablet-mode-daemon` starts/kills it
|
||||
# in response to pogo-cover attach/detach so we don't keep a virtual-keyboard surface alive
|
||||
# while a physical keyboard is plugged in.
|
||||
chiasson.desktop.niri.extraSettings = lib.mkIf config.chiasson.desktop.niri.enable {
|
||||
input.touch.map-to-output = "DSI-1";
|
||||
|
||||
# Required for logind's `HandlePowerKey` in `_private/platform.nix` to take effect: otherwise
|
||||
# niri grabs a `block` inhibitor on `handle-power-key` and suspends via D-Bus, including on
|
||||
# the EC's wake-from-suspend KEY_POWER event → instant re-suspend loop.
|
||||
# https://github.com/niri-wm/niri/issues/2233
|
||||
input."disable-power-key-handling" = _: { };
|
||||
|
||||
# wrapper-modules schema: each entry is a `command argv` list of strings (or a single string).
|
||||
spawn-at-startup = [
|
||||
[ "ideapad-niri-autorotate-daemon" ]
|
||||
[ "ideapad-niri-tablet-mode-daemon" ]
|
||||
];
|
||||
};
|
||||
|
||||
# ─────────────────────── Hyprland session autostart ───────────────────────
|
||||
# Same lifecycle policy as Niri: the tablet-mode daemon owns wvkbd, no exec-once for it.
|
||||
chiasson.desktop.hyprland.settings = lib.mkIf config.chiasson.desktop.hyprland.enable {
|
||||
exec-once = lib.mkAfter [
|
||||
"${pkgs.procps}/bin/pkill -x ideapad-tablet-mode-daemon >/dev/null 2>&1 || true; ideapad-tablet-mode-daemon &"
|
||||
"${pkgs.procps}/bin/pkill -x ideapad-autorotate-daemon >/dev/null 2>&1 || true; ideapad-autorotate-daemon &"
|
||||
];
|
||||
bind = lib.mkAfter [
|
||||
"Super, K, exec, pkill -SIGRTMIN -x wvkbd-mobintl"
|
||||
];
|
||||
monitor = lib.mkAfter [
|
||||
"DSI-1,1200x2000@60.0,0x0,1.25, transform, 1"
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
{ self, inputs, ... }: {
|
||||
|
||||
# Lenovo Chromebook Duet 3 (`lenovo-wormdingler`) on Mobile NixOS.
|
||||
# Full V2 stack: mobile-nixos device + Niri/Hyprland/DMS, DankGreeter, wvkbd, IIO sensors,
|
||||
# touchscreen calibration + resume-rebind, attic cache, sops, and the standard user catalog.
|
||||
# Host-only quirks live in `_private/touch-tablet.nix` and `_private/platform.nix`.
|
||||
flake.nixosModules.ideapadConfiguration =
|
||||
{
|
||||
self,
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
# Mobile NixOS device + family + depthcharge system-type.
|
||||
(import "${inputs.mobile-nixos}/lib/configuration.nix" {
|
||||
device = "lenovo-wormdingler";
|
||||
})
|
||||
|
||||
self.nixosModules.ideapadHardware
|
||||
|
||||
inputs.home-manager.nixosModules.home-manager
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
|
||||
self.nixosModules.system
|
||||
self.nixosModules.desktop
|
||||
self.nixosModules.users
|
||||
|
||||
self.nixosModules."client-services"
|
||||
|
||||
# Host-only: IIO + touchscreen calibration + per-compositor tablet/autorotate helpers.
|
||||
./_private/touch-tablet.nix
|
||||
|
||||
# Host-only: cpufreq, lid/power-button policy, upower thresholds.
|
||||
./_private/platform.nix
|
||||
];
|
||||
|
||||
# ─────────────────────── Sops ───────────────────────
|
||||
# `host_ideapad` recipient in `.sops.yaml` derives from the new ed25519 host key (post-reflash).
|
||||
sops = {
|
||||
defaultSopsFile = ../../../secrets/secrets.yaml;
|
||||
defaultSopsFormat = "yaml";
|
||||
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||
};
|
||||
|
||||
sops.secrets."users/olivier/hashedPassword".neededForUsers = true;
|
||||
sops.secrets."caching/attic/token" = {
|
||||
owner = "olivier";
|
||||
group = "users";
|
||||
mode = "0400";
|
||||
};
|
||||
sops.secrets."swiftshare/API_KEY" = {
|
||||
owner = "olivier";
|
||||
group = "users";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
# ─────────────────────── Mobile NixOS / firmware ───────────────────────
|
||||
# mruby's test-suite breaks on aarch64 in the Nix sandbox; the overlay strips checks and
|
||||
# rebuilds Mobile NixOS' script-loader against the patched mruby.
|
||||
chiasson.system.ideapadMrubyOverlay.enable = true;
|
||||
|
||||
# Wi-Fi modem (qcom-wcn3990) + Bluetooth (QCA crnv32) need binary blobs.
|
||||
nixpkgs.config.allowUnfreePredicate =
|
||||
pkg: builtins.elem (lib.getName pkg) [
|
||||
"chromeos-sc7180-unredistributable-firmware"
|
||||
"chromeos-sc7180-unredistributable-firmware-zstd"
|
||||
];
|
||||
hardware.firmware = [ pkgs.chromeos-sc7180-unredistributable-firmware ];
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
# ─────────────────────── Attic (substitution + push + CLI token) ───────
|
||||
chiasson.system.caching.attic = {
|
||||
enable = true;
|
||||
cacheName = "nixos-new";
|
||||
endpoint = "http://192.168.2.238:8080/";
|
||||
publicKey = "nixos-new:8NySIcT0HP7KvGQKgBRWoWESxxRA8BVYo8S85UNpNX0=";
|
||||
tokenFile = config.sops.secrets."caching/attic/token".path;
|
||||
push.enable = true;
|
||||
userCli.enable = true;
|
||||
};
|
||||
|
||||
# ─────────────────────── System bits ───────────────────────
|
||||
chiasson.system = {
|
||||
audio.enable = true;
|
||||
networking = {
|
||||
hostName = "ideapad";
|
||||
networkManager = {
|
||||
enable = true;
|
||||
unmanaged = [ ];
|
||||
};
|
||||
wifi.tools.enabled = true;
|
||||
};
|
||||
extraPackages = with pkgs; [
|
||||
gitMinimal
|
||||
sops
|
||||
ssh-to-age
|
||||
];
|
||||
};
|
||||
|
||||
# ─────────────────────── Desktop ───────────────────────
|
||||
# Both compositors are enabled — DankGreeter picks at login, V2 default is Niri.
|
||||
# Per-session tablet-mode / autorotate daemons live in `_private/touch-tablet.nix`.
|
||||
chiasson.desktop = {
|
||||
niri.enable = true;
|
||||
hyprland.enable = true;
|
||||
|
||||
defaultSession = "niri";
|
||||
shell = "dms";
|
||||
shells.dms = {
|
||||
enableWvkbdToggle = true;
|
||||
enableRbwLockToggle = true;
|
||||
# Cross-build on the 14900k via binfmt and push back over LAN — much faster than
|
||||
# rebuilding aarch64 closure on the Snapdragon. Mirrors the old NixOS-New flow:
|
||||
# ssh out to nixdesk, run nixos-rebuild --target-host pointing back at us.
|
||||
rebuildCommand = [
|
||||
"bash"
|
||||
"-lc"
|
||||
''
|
||||
ssh -t olivier@nixdesk \
|
||||
"nixos-rebuild switch --flake path:/home/olivier/NixOS-V2#ideapad --target-host olivier@ideapad --sudo --ask-sudo-password 2>&1"
|
||||
''
|
||||
];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
# ─────────────────────── Users / HM ───────────────────────
|
||||
chiasson.users.enabled = [ "olivier" ];
|
||||
|
||||
# Touch-friendly application set, mirroring uConsole's selection (no heavy IDEs / gaming).
|
||||
chiasson.users.extraModules.olivier = [
|
||||
self.homeManagerModules.wisdomFilebrowsersDolphin
|
||||
self.homeManagerModules.wisdomTerminalsKitty
|
||||
self.homeManagerModules.wisdomBrowsersZen
|
||||
self.homeManagerModules.wisdomEditorsKate
|
||||
self.homeManagerModules.wisdomEditorsCursor
|
||||
self.homeManagerModules.wisdomShellFish
|
||||
self.homeManagerModules.wisdomShellOhMyPosh
|
||||
self.homeManagerModules.wisdomAppsSpotify
|
||||
self.homeManagerModules.wisdomAppsLocalsend
|
||||
self.homeManagerModules.wisdomDesktopScreenshot
|
||||
{
|
||||
chiasson.home = {
|
||||
shell = {
|
||||
fish.enable = true;
|
||||
ohMyPosh.enable = true;
|
||||
};
|
||||
terminals.kitty.enable = true;
|
||||
filebrowsers.dolphin.enable = true;
|
||||
browsers.zen.enable = true;
|
||||
editors.kate.enable = true;
|
||||
editors.cursor.enable = true;
|
||||
apps.spotify.enable = true;
|
||||
apps.localsend.enable = true;
|
||||
desktop = {
|
||||
screenshot = {
|
||||
enable = true;
|
||||
swiftshareApiKeyFile = "/run/secrets/swiftshare/API_KEY"; #TODO[epic=sops] redo this by passing sops file output directly
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
# Tablet-class apps: kept inline rather than promoting to wisdom modules — these aren't
|
||||
# part of the broader catalog (no use on uConsole / 14900k / servers) and adding a wisdom
|
||||
# module per single-host package would just be ceremony. If a second tablet host ever
|
||||
# appears, factor them out then.
|
||||
#
|
||||
# NOTE on cameras: no v4l2/libcamera GUI is installed. The Mobile NixOS kernel for
|
||||
# `lenovo-wormdingler` ships with `CONFIG_VIDEO_QCOM_CAMSS` disabled and no
|
||||
# `VIDEO_OV*`/`VIDEO_HI*` sensor drivers, so `/dev/video0`-`/dev/video1` only expose
|
||||
# the Qualcomm Venus codecs (h.264/h.265 enc/dec) and there is no camera source for
|
||||
# PipeWire / libcamera to pick up. See `_private/CAMERA-TODO.md` for the steps that
|
||||
# would (potentially) bring the front/rear cameras online — it's a kernel-rebuild +
|
||||
# device-tree + libcamera project, not a config tweak.
|
||||
(
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
home.packages = with pkgs; [
|
||||
# PDF viewer — fits the existing KDE app set (Dolphin + Kate).
|
||||
kdePackages.okular
|
||||
# ePub reader, GTK4, large touch targets.
|
||||
foliate
|
||||
];
|
||||
}
|
||||
)
|
||||
];
|
||||
|
||||
system.stateVersion = "26.05";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{ self, inputs, ... }: {
|
||||
|
||||
flake.nixosConfigurations.ideapad = inputs.nixpkgs.lib.nixosSystem {
|
||||
system = "aarch64-linux";
|
||||
specialArgs = {
|
||||
inherit self inputs;
|
||||
host = "ideapad";
|
||||
system = "aarch64-linux";
|
||||
};
|
||||
modules = [
|
||||
self.nixosModules.ideapadConfiguration
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{ ... }: {
|
||||
flake.nixosModules.ideapadHardware =
|
||||
# Mobile NixOS' depthcharge system-type wires up the disk image, kernel partitions and
|
||||
# rootfs entirely; we don't need a generated `hardware-configuration.nix`. This module is a
|
||||
# placeholder so the host follows the standard `<host>Configuration` / `<host>Hardware` shape
|
||||
# and gives us a place to drop kernel-config knobs that aren't covered by the device family.
|
||||
{ ... }:
|
||||
{
|
||||
# Useful on a portable: mounting USB sticks (often exFAT) and SMB shares.
|
||||
boot.supportedFilesystems = [ "exfat" "cifs" ];
|
||||
|
||||
# Mobile NixOS builds its own kernel — the regular `boot.kernelModules` won't help if the
|
||||
# module isn't compiled in. `mobile.kernel.structuredConfig` lives upstream in
|
||||
# `modules/system-types/depthcharge/kernel/` and is the right layer to add features.
|
||||
mobile.kernel.structuredConfig = [
|
||||
(helpers: with helpers; {
|
||||
CIFS = module;
|
||||
EXFAT_FS = module;
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
sops = {
|
||||
templates."atticd.env" = {
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
content = ''
|
||||
ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64=${config.sops.placeholder."attic/server-token-rs256-secret-base64"}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets."attic/server-token-rs256-secret-base64" = {
|
||||
sopsFile = ../../../../secrets/attic-secrets.yaml;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
# SQLite on disk was the main source of random multi-minute stalls (see attic#113).
|
||||
# NAR blobs stay in /var/lib/atticd/storage; only metadata moves to Postgres.
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
ensureDatabases = [ "atticd" ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "atticd";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.atticd = {
|
||||
enable = true;
|
||||
environmentFile = config.sops.templates."atticd.env".path;
|
||||
settings = {
|
||||
listen = "0.0.0.0:8080";
|
||||
jwt = { };
|
||||
# Use a libpq socket URI format accepted by Attic's parser.
|
||||
database.url = "postgresql:///atticd?host=/run/postgresql&user=atticd";
|
||||
chunking = {
|
||||
nar-size-threshold = 65536;
|
||||
min-size = 16384;
|
||||
avg-size = 65536;
|
||||
max-size = 262144;
|
||||
};
|
||||
storage = {
|
||||
type = "local";
|
||||
path = "/var/lib/atticd/storage";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.atticd = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkForce "always";
|
||||
RestartSec = lib.mkForce 5;
|
||||
# Large closures; default limits can wedge uploads under load.
|
||||
LimitNOFILE = 1048576;
|
||||
};
|
||||
};
|
||||
|
||||
chiasson.system.networking.firewall.allowedTCPPorts = [ 8080 ];
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# DDRM Flask backend (Widevine / PlayReady decrypt). Extension URL: http://<host>:58239
|
||||
{ pkgs, inputs, ... }:
|
||||
{
|
||||
services.ddrm-media-server = {
|
||||
enable = true;
|
||||
port = 58239;
|
||||
listenAddress = "0.0.0.0";
|
||||
openFirewall = true;
|
||||
package = inputs.ddrm.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||
# State: /var/lib/ddrm-media (venv + configs + CDMs on first run — needs network for pip).
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# Dispatcharr — IPTV / M3U / EPG / HDHomeRun-style proxy (Docker, AIO image).
|
||||
# Docs: https://dispatcharr.github.io/Dispatcharr-Docs/
|
||||
# Compose reference: https://github.com/Dispatcharr/Dispatcharr/blob/main/docker/docker-compose.aio.yml
|
||||
#
|
||||
# Web UI: http://<host>:9191
|
||||
# After deploy: create an admin user, add playlists / EPG; point Jellyfin at the tuner/M3U URLs
|
||||
# Dispatcharr shows in its UI.
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
systemd.tmpfiles.settings."nix-server-dispatcharr-data" = {
|
||||
"/var/lib/dispatcharr"."d" = {
|
||||
mode = "0755";
|
||||
user = "root";
|
||||
group = "root";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.docker-dispatcharr.preStart = lib.mkBefore ''
|
||||
${pkgs.coreutils}/bin/mkdir -p /var/lib/dispatcharr
|
||||
'';
|
||||
|
||||
virtualisation.oci-containers.containers.dispatcharr = {
|
||||
image = "ghcr.io/dispatcharr/dispatcharr:latest";
|
||||
ports = [ "9191:9191" ];
|
||||
volumes = [
|
||||
"/var/lib/dispatcharr:/data"
|
||||
];
|
||||
environment = {
|
||||
DISPATCHARR_ENV = "aio";
|
||||
REDIS_HOST = "localhost";
|
||||
CELERY_BROKER_URL = "redis://localhost:6379/0";
|
||||
DISPATCHARR_LOG_LEVEL = "info";
|
||||
TZ = "America/Moncton";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 9191 ];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{ ... }:
|
||||
{
|
||||
# FlareSolverr (Cloudflare / JS challenge solver for some indexers).
|
||||
# Typically used by Prowlarr as an HTTP proxy.
|
||||
#
|
||||
# UI/endpoint: http://<host>:8191
|
||||
services.flaresolverr.enable = true;
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 8191 ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
secretFilePath = ../secrets.yaml;
|
||||
in
|
||||
{
|
||||
sops.secrets."immich/database-password".sopsFile = secretFilePath;
|
||||
|
||||
# Placeholders are expanded only inside template `content` (not in arbitrary Nix strings).
|
||||
sops.templates."immich-db.env" = {
|
||||
content = ''
|
||||
POSTGRES_PASSWORD=${config.sops.placeholder."immich/database-password"}
|
||||
DB_PASSWORD=${config.sops.placeholder."immich/database-password"}
|
||||
'';
|
||||
};
|
||||
|
||||
chiasson.system.services.immich = {
|
||||
enable = true;
|
||||
host = "0.0.0.0";
|
||||
port = 2283;
|
||||
timezone = "America/Moncton";
|
||||
uploadLocation = "/var/lib/immich/library";
|
||||
environmentFiles = [ config.sops.templates."immich-db.env".path ];
|
||||
postgres = {
|
||||
user = "postgres";
|
||||
#password = ""; # Defined in sops.templates."immich-db.env"
|
||||
database = "immich";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
# Jellyfin (native NixOS service). Local media: /var/lib/media (group `media`; jellyfin + server).
|
||||
# Dashboard: Movies → /var/lib/media/movies, Shows → /var/lib/media/tv (see nixdesk-nfs-client.nix
|
||||
# for bulk libraries on nixdesk at /mnt/nixdesk-jellyfin/{movies,tv}).
|
||||
# Do not use "Mixed Movies and Shows" (deprecated): https://jellyfin.org/docs/general/server/media/mixed-movies-and-shows
|
||||
# Dedicated disk: fileSystems."/var/lib/media" in hardware.nix, then fix ownership.
|
||||
{ lib, ... }:
|
||||
{
|
||||
nixpkgs.overlays = [
|
||||
(final: prev: {
|
||||
jellyfin-web = prev.jellyfin-web.overrideAttrs (oldAttrs: {
|
||||
postInstall =
|
||||
(oldAttrs.postInstall or "")
|
||||
+ ''
|
||||
# Blank default Jellyfin banner assets (read-only store otherwise). Wildcards
|
||||
# track hashed filenames across jellyfin-web releases; bump if layout changes.
|
||||
find "$out" -type f \( -name 'banner-light.*.png' -o -name 'banner-dark.*.png' \) \
|
||||
-exec truncate -s 0 {} \;
|
||||
'';
|
||||
});
|
||||
})
|
||||
];
|
||||
|
||||
users.groups.media = { };
|
||||
|
||||
users.users.jellyfin.extraGroups = [ "media" ];
|
||||
users.users.server.extraGroups = [ "media" ];
|
||||
|
||||
systemd.tmpfiles.settings."nix-server-var-lib-media" = {
|
||||
"/var/lib/media"."d" = {
|
||||
mode = "0775";
|
||||
user = "root";
|
||||
group = "media";
|
||||
};
|
||||
"/var/lib/media/movies"."d" = {
|
||||
mode = "0775";
|
||||
user = "root";
|
||||
group = "media";
|
||||
};
|
||||
"/var/lib/media/tv"."d" = {
|
||||
mode = "0775";
|
||||
user = "root";
|
||||
group = "media";
|
||||
};
|
||||
};
|
||||
|
||||
services.jellyfin = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
};
|
||||
|
||||
# `users.users.jellyfin.extraGroups` does not affect systemd; the service must list
|
||||
# supplementary groups explicitly. Without `media`, directories mode 775 root:media are
|
||||
# not writable by uid jellyfin (it only had group `jellyfin`), so deletes fail.
|
||||
systemd.services.jellyfin.serviceConfig = {
|
||||
SupplementaryGroups = [ "media" ];
|
||||
# Jellyfin libraries on NFS (e.g. /mnt/nixdesk-jellyfin). PrivateUsers breaks
|
||||
# uid mapping for NFS auth in practice; disable so metadata writes use the real jellyfin uid
|
||||
# (squashed to olivier:nfsmedia on nixdesk exports).
|
||||
PrivateUsers = lib.mkForce false;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# NFS mounts of nixdesk (14900k) bulk storage for nix-server. Exports live in
|
||||
# modules/hosts/14900k/_private/jellyfin-nfs-export.nix
|
||||
#
|
||||
# Jellyfin library paths (see also services/jellyfin.nix):
|
||||
# Movies → /mnt/nixdesk-jellyfin/movies
|
||||
# Shows → /mnt/nixdesk-jellyfin/tv
|
||||
#
|
||||
# If you see "Stale file handle" under /mnt after changing exports or fsid on nixdesk, drop the
|
||||
# old client mount and let automount reattach, e.g.:
|
||||
# sudo umount -l /mnt/nixdesk-jellyfin
|
||||
# ls /mnt/nixdesk-jellyfin
|
||||
# (or reboot nix-server.)
|
||||
{ ... }:
|
||||
let
|
||||
nfsExportHost = "192.168.2.25";
|
||||
# nfsvers+tcp: predictable Linux↔Linux; lookupcache=none: fewer stale dentries after export changes.
|
||||
nfsClientOpts = [
|
||||
"rw"
|
||||
"noatime"
|
||||
"nofail"
|
||||
"_netdev"
|
||||
"nfsvers=3"
|
||||
"tcp"
|
||||
"lookupcache=none"
|
||||
"x-systemd.automount"
|
||||
"x-systemd.idle-timeout=3600"
|
||||
];
|
||||
in
|
||||
{
|
||||
fileSystems."/mnt/nixdesk-jellyfin" = {
|
||||
device = "${nfsExportHost}:/mnt/deep/jellyfin";
|
||||
fsType = "nfs";
|
||||
options = nfsClientOpts;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
# Organizr — homelab dashboard (Docker). UI: http://<host>:8888
|
||||
# Official image: https://github.com/organizr/docker-organizr
|
||||
#
|
||||
# Wizard errors like "API … /default/ not writable" are almost always host permissions on
|
||||
# `/var/lib/organizr`: the first container run may leave root-owned files under `/config`.
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
users.groups.organizr = { gid = 950; };
|
||||
users.users.organizr = {
|
||||
isSystemUser = true;
|
||||
uid = 950;
|
||||
group = "organizr";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.settings."nix-server-organizr-config" = {
|
||||
"/var/lib/organizr"."d" = {
|
||||
mode = "0755";
|
||||
user = "organizr";
|
||||
group = "organizr";
|
||||
};
|
||||
};
|
||||
|
||||
# Recursively reset ownership (handles root-owned files from an earlier container run).
|
||||
systemd.tmpfiles.settings."nix-server-organizr-config-perms" = {
|
||||
"/var/lib/organizr"."Z" = {
|
||||
mode = "0755";
|
||||
user = "organizr";
|
||||
group = "organizr";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.docker-organizr.preStart = lib.mkBefore ''
|
||||
${pkgs.coreutils}/bin/mkdir -p /var/lib/organizr
|
||||
${pkgs.coreutils}/bin/chown -R organizr:organizr /var/lib/organizr
|
||||
'';
|
||||
|
||||
virtualisation.oci-containers.containers.organizr = {
|
||||
image = "ghcr.io/organizr/organizr:latest";
|
||||
ports = [ "8888:80" ];
|
||||
volumes = [
|
||||
"/var/lib/organizr:/config"
|
||||
];
|
||||
environment = {
|
||||
PUID = "950";
|
||||
PGID = "950";
|
||||
TZ = "America/Moncton";
|
||||
# v2-master / master are stable v2; optional override:
|
||||
# branch = "v2-master";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 8888 ];
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{config, ...}: {
|
||||
virtualisation = {
|
||||
docker.enable = true;
|
||||
oci-containers = {
|
||||
backend = "docker";
|
||||
containers = {
|
||||
portainer = {
|
||||
image = "portainer/portainer-ce:latest";
|
||||
ports = [ "9443:9443" ];
|
||||
volumes = [
|
||||
"/var/run/docker.sock:/var/run/docker.sock"
|
||||
"/var/lib/portainer:/data"
|
||||
];
|
||||
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 9443 ];
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
# Prowlarr (indexer manager). UI: http://<host>:9696
|
||||
# Data dir is /var/lib/prowlarr (see systemd unit ExecStart -data=…), not ~/.config/Prowlarr.
|
||||
services.prowlarr.enable = true;
|
||||
|
||||
# Useful when Prowlarr/Sonarr/Radarr need to write into shared areas (downloads, etc.).
|
||||
users.groups.prowlarr = { };
|
||||
users.users.prowlarr = {
|
||||
isSystemUser = true;
|
||||
group = "prowlarr";
|
||||
extraGroups = [ "media" ];
|
||||
};
|
||||
|
||||
systemd.services.prowlarr.preStart = lib.mkBefore ''
|
||||
mkdir -p /var/lib/prowlarr/Definitions/Custom
|
||||
ln -sf ${./prowlarr/torrent9-custom.yml} /var/lib/prowlarr/Definitions/Custom/torrent9-custom.yml
|
||||
'';
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 9696 ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
---
|
||||
id: torrent9-custom
|
||||
name: Torrent9 (Custom URL)
|
||||
description: "Torrent9 is a FRENCH Public site for MOVIES / TV / GENERAL"
|
||||
language: fr-FR
|
||||
type: public
|
||||
encoding: UTF-8
|
||||
followredirect: true
|
||||
testlinktorrent: false
|
||||
links:
|
||||
- https://www.torrent9.club/
|
||||
- https://www6.torrent9.to/
|
||||
legacylinks:
|
||||
- https://www.torrent9.pl/ # this is a proxy for torrent9clone
|
||||
- https://torrent9.black-mirror.xyz/ # this is a proxy for torrent9clone
|
||||
- https://torrent9.unblocked.casa/ # this is a proxy for torrent9clone
|
||||
- https://torrent9.proxyportal.fun/ # this is a proxy for torrent9clone
|
||||
- https://torrent9.uk-unblock.xyz/ # this is a proxy for torrent9clone
|
||||
- https://torrent9.ind-unblock.xyz/ # this is a proxy for torrent9clone
|
||||
- https://ww1.torrent9.to/
|
||||
- https://www.torrent9.is/
|
||||
- https://torrent9.li/ # not a proxy for torrent9 or torrent9clone
|
||||
- https://www.oxtorrent.me/
|
||||
- https://www.torrent9.gg/
|
||||
- https://www.torrent9.fi/ # this is the torrent9clone domain
|
||||
- https://www.torrent9.fm/
|
||||
- https://torrent9.se/ # redirect to www.
|
||||
- https://torrent9.ninjaproxy1.com/ # no response data
|
||||
- https://torrent9.proxyninja.org/ # Error 1007
|
||||
- https://www.torrent9.se/
|
||||
- https://torrent9.unblockninja.com/ # 403 forbidden
|
||||
- https://ww1.torrent9.fm/
|
||||
- https://www.torrent9.zone/ # clone? details links are broken
|
||||
- https://torrent9.to/
|
||||
- https://ww2.torrent9.to/
|
||||
- https://www5.torrent9.to/
|
||||
|
||||
caps:
|
||||
# dont forget to update the search fields category case block
|
||||
categorymappings:
|
||||
- {id: films, cat: Movies, desc: "Movies"}
|
||||
- {id: series, cat: TV, desc: "TV"}
|
||||
- {id: musique, cat: Audio, desc: "Music"}
|
||||
- {id: ebook, cat: Books, desc: "Books"}
|
||||
- {id: logiciels, cat: PC, desc: "Software"}
|
||||
- {id: jeux-pc, cat: PC/Games, desc: "PC Games"}
|
||||
- {id: other, cat: Other, desc: "Other"} # dummy cat for results missing icon
|
||||
|
||||
modes:
|
||||
search: [q]
|
||||
tv-search: [q, season, ep]
|
||||
movie-search: [q]
|
||||
music-search: [q]
|
||||
book-search: [q]
|
||||
allowrawsearch: true
|
||||
|
||||
settings:
|
||||
- name: info_flaresolverr
|
||||
type: info_flaresolverr
|
||||
- name: multilang
|
||||
type: checkbox
|
||||
label: Replace MULTi by another language in release name
|
||||
default: false
|
||||
- name: multilanguage
|
||||
type: select
|
||||
label: Replace MULTi by this language
|
||||
default: FRENCH
|
||||
options:
|
||||
FRENCH: FRENCH
|
||||
MULTi FRENCH: MULTi FRENCH
|
||||
ENGLISH: ENGLISH
|
||||
MULTi ENGLISH: MULTi ENGLISH
|
||||
VOSTFR: VOSTFR
|
||||
MULTi VOSTFR: MULTi VOSTFR
|
||||
- name: vostfr
|
||||
type: checkbox
|
||||
label: Replace VOSTFR and SUBFRENCH with ENGLISH
|
||||
default: false
|
||||
- name: sort
|
||||
type: select
|
||||
label: Sort requested from site (Only works for searches with Keywords)
|
||||
default: ".html"
|
||||
options:
|
||||
".html": best
|
||||
".html,trie-date-d": created desc
|
||||
".html,trie-date-a": created asc
|
||||
".html,trie-seeds-d": seeders desc
|
||||
".html,trie-seeds-a": seeders asc
|
||||
".html,trie-poid-d": size desc
|
||||
".html,trie-poid-a": size asc
|
||||
".html,trie-nom-d": title desc
|
||||
".html,trie-nom-a": title asc
|
||||
|
||||
download:
|
||||
selectors:
|
||||
- selector: a[href^="magnet:?"]
|
||||
attribute: href
|
||||
- selector: script:contains("magnet:?")
|
||||
filters:
|
||||
- name: regexp
|
||||
args: "\\s'(magnet:\\?.+?)';"
|
||||
|
||||
search:
|
||||
paths:
|
||||
- path: "{{ if .Keywords }}search_torrent/{{ .Keywords }}{{ .Config.sort }}{{ else }}home/{{ end }}"
|
||||
keywordsfilters:
|
||||
# if searching for season packs with S01 to saison 1 #9712
|
||||
- name: re_replace
|
||||
args: ["(?i)(S0)(\\d{1,2})$", "saison $2"]
|
||||
- name: re_replace
|
||||
args: ["(?i)(S)(\\d{1,3})$", "saison $2"]
|
||||
- name: replace
|
||||
args: [" ", "-"]
|
||||
|
||||
headers:
|
||||
# site blocks all Linux User-Agents, so use a slightly altered Windows Jackett UA here (e.g. Safari/537.36 > Safari/537.35)
|
||||
User-Agent: ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.35"]
|
||||
|
||||
rows:
|
||||
selector: table.table-striped > tbody > tr
|
||||
filters:
|
||||
- name: andmatch
|
||||
|
||||
fields:
|
||||
category_optional:
|
||||
selector: td:nth-child(1) i
|
||||
optional: true
|
||||
case:
|
||||
i[class="fa fa-video-camera"]: films
|
||||
i[class="fa fa-tv"]: series # search by name
|
||||
i[class="fa fa-desktop"]: series # keywordless search
|
||||
i[class="fa fa-music"]: musique
|
||||
i[class="fa fa-gamepad"]: jeux-pc
|
||||
i[class="fa fa-laptop"]: logiciels
|
||||
i[class="fa fa-book"]: ebook
|
||||
category:
|
||||
text: "{{ if .Result.category_optional }}{{ .Result.category_optional }}{{ else }}other{{ end }}"
|
||||
title_default:
|
||||
selector: td:nth-child(1) a
|
||||
title_optional:
|
||||
selector: td:nth-child(1) a[title]
|
||||
attribute: title
|
||||
optional: true
|
||||
title_phase1:
|
||||
text: "{{ if .Result.title_optional }}{{ .Result.title_optional }}{{ else }}{{ .Result.title_default }}{{ end }}"
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: ["(?i)\\b(FRENCH|MULTI|TRUEFRENCH|VOSTFR|SUBFRENCH)\\b(.+?)(\\b((19|20)\\d{2})\\b)$", "$3 $1$2"]
|
||||
title_vostfr:
|
||||
text: "{{ .Result.title_phase1 }}"
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: ["(?i)\\b(vostfr|subfrench)\\b", "ENGLISH"]
|
||||
title_phase2:
|
||||
text: "{{ if .Config.vostfr }}{{ .Result.title_vostfr }}{{ else }}{{ .Result.title_phase1 }}{{ end }}"
|
||||
title_multilang:
|
||||
text: "{{ .Result.title_phase2 }}"
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: ["(?i)\\b(MULTI(?!.*(?:FRENCH|ENGLISH|VOSTFR)))\\b", "{{ .Config.multilanguage }}"]
|
||||
title:
|
||||
text: "{{ if .Config.multilang }}{{ .Result.title_multilang }}{{ else }}{{ .Result.title_phase2 }}{{ end }}"
|
||||
details:
|
||||
selector: td:nth-child(1) a
|
||||
attribute: href
|
||||
download:
|
||||
selector: td:nth-child(1) a
|
||||
attribute: href
|
||||
date:
|
||||
selector: td:nth-child(2):contains("/")
|
||||
optional: true
|
||||
default: now
|
||||
filters:
|
||||
- name: dateparse
|
||||
args: "dd/MM/yyyy"
|
||||
size:
|
||||
selector: "{{ if .Keywords }}td:nth-child(3){{ else }}td:nth-child(2){{ end }}"
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: ["(\\w)o", "$1B"]
|
||||
seeders:
|
||||
selector: "{{ if .Keywords }}td:nth-child(4){{ else }}td:nth-child(3){{ end }}"
|
||||
optional: true
|
||||
default: 0
|
||||
leechers:
|
||||
selector: "{{ if .Keywords }}td:nth-child(5){{ else }}td:nth-child(4){{ end }}"
|
||||
optional: true
|
||||
default: 0
|
||||
downloadvolumefactor:
|
||||
text: 0
|
||||
uploadvolumefactor:
|
||||
text: 1
|
||||
# engine n/a
|
||||
@@ -0,0 +1,36 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
webPort = 8081;
|
||||
btPort = 51413;
|
||||
downloadsDir = "/var/lib/downloads";
|
||||
in
|
||||
{
|
||||
# qBittorrent (headless). Web UI: http://<host>:8081
|
||||
services.qbittorrent = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
webuiPort = webPort;
|
||||
# Prefer a stable port for NAT/firewall and for easier debugging.
|
||||
torrentingPort = btPort;
|
||||
};
|
||||
|
||||
users.groups.qbittorrent = { };
|
||||
users.users.qbittorrent = {
|
||||
isSystemUser = true;
|
||||
group = "qbittorrent";
|
||||
extraGroups = [ "media" ];
|
||||
};
|
||||
|
||||
systemd.tmpfiles.settings."nix-server-downloads-dir" = {
|
||||
"${downloadsDir}"."d" = {
|
||||
mode = "2775";
|
||||
user = "root";
|
||||
group = "media";
|
||||
};
|
||||
};
|
||||
|
||||
# Some NixOS versions don't open UDP for torrenting even when openFirewall=true.
|
||||
networking.firewall.allowedTCPPorts = [ webPort btPort ];
|
||||
networking.firewall.allowedUDPPorts = [ btPort ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{ ... }:
|
||||
{
|
||||
# Radarr (movie automation). UI: http://<host>:7878
|
||||
services.radarr.enable = true;
|
||||
|
||||
# Keep permissions aligned with Jellyfin (/var/lib/media via group `media`).
|
||||
users.groups.radarr = { };
|
||||
users.users.radarr = {
|
||||
isSystemUser = true;
|
||||
group = "radarr";
|
||||
extraGroups = [ "media" ];
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 7878 ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{ ... }:
|
||||
{
|
||||
# Blank default Seerr branding assets (read-only store otherwise).
|
||||
nixpkgs.overlays = [
|
||||
(final: prev: {
|
||||
seerr = prev.seerr.overrideAttrs (oldAttrs: {
|
||||
postInstall =
|
||||
(oldAttrs.postInstall or "")
|
||||
+ ''
|
||||
find "$out/share/public" -maxdepth 1 -type f \( -name 'logo_full.svg' -o -name 'logo_stacked.svg' \) \
|
||||
-exec truncate -s 0 {} \;
|
||||
'';
|
||||
});
|
||||
})
|
||||
];
|
||||
|
||||
# "Seerr" request management. For Jellyfin, Jellyseerr is the right choice.
|
||||
# UI: http://<host>:5055
|
||||
services.seerr.enable = true;
|
||||
|
||||
users.groups.jellyseerr = { };
|
||||
users.users.jellyseerr = {
|
||||
isSystemUser = true;
|
||||
group = "jellyseerr";
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 5055 ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{ ... }:
|
||||
{
|
||||
# Sonarr (TV automation). UI: http://<host>:8989
|
||||
services.sonarr.enable = true;
|
||||
|
||||
# Ensure Sonarr can manage the same libraries as Jellyfin.
|
||||
users.groups.sonarr = { };
|
||||
users.users.sonarr = {
|
||||
isSystemUser = true;
|
||||
group = "sonarr";
|
||||
extraGroups = [ "media" ];
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 8989 ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
secretFilePath = ../secrets.yaml;
|
||||
in
|
||||
{
|
||||
sops.secrets."swiftshare/ghcr-token".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/database-password".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/oauth-discord-client-secret".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/oauth-github-client-secret".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/auth-secret".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/oauth-google-client-id".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/oauth-google-client-secret".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/smtp-pass".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/minio-access-key".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/minio-secret-key".sopsFile = secretFilePath;
|
||||
|
||||
# Docker `--env-file` expects `KEY=value`. Separate snippets for DB/MinIO so only `swiftshare.env` hits the app container.
|
||||
sops.templates."swiftshare-postgres.env" = {
|
||||
content = ''
|
||||
POSTGRES_PASSWORD=${config.sops.placeholder."swiftshare/database-password"}
|
||||
'';
|
||||
};
|
||||
|
||||
sops.templates."swiftshare-minio.env" = {
|
||||
content = ''
|
||||
MINIO_ROOT_USER=${config.sops.placeholder."swiftshare/minio-access-key"}
|
||||
MINIO_ROOT_PASSWORD=${config.sops.placeholder."swiftshare/minio-secret-key"}
|
||||
'';
|
||||
};
|
||||
|
||||
sops.templates."swiftshare.env" = {
|
||||
content = ''
|
||||
DATABASE_URL=postgresql://swiftshare:${config.sops.placeholder."swiftshare/database-password"}@swiftshare-db:5432/swiftshare
|
||||
AUTH_SECRET=${config.sops.placeholder."swiftshare/auth-secret"}
|
||||
AUTH_DISCORD_SECRET=${config.sops.placeholder."swiftshare/oauth-discord-client-secret"}
|
||||
AUTH_GITHUB_SECRET=${config.sops.placeholder."swiftshare/oauth-github-client-secret"}
|
||||
AUTH_GOOGLE_SECRET=${config.sops.placeholder."swiftshare/oauth-google-client-secret"}
|
||||
AUTH_GOOGLE_ID=${config.sops.placeholder."swiftshare/oauth-google-client-id"}
|
||||
SMTP_PASS=${config.sops.placeholder."swiftshare/smtp-pass"}
|
||||
STORAGE_ACCESS_KEY=${config.sops.placeholder."swiftshare/minio-access-key"}
|
||||
STORAGE_SECRET_KEY=${config.sops.placeholder."swiftshare/minio-secret-key"}
|
||||
'';
|
||||
};
|
||||
|
||||
services.swiftshare = {
|
||||
enable = true;
|
||||
|
||||
app = {
|
||||
image = "ghcr.io/olivierchiasson/swiftshare:main";
|
||||
ghcr = {
|
||||
username = "olivierchiasson";
|
||||
passwordFile = config.sops.secrets."swiftshare/ghcr-token".path;
|
||||
};
|
||||
|
||||
origin = "https://swiftshare.cloud";
|
||||
port = 3000;
|
||||
uploadBodySizeLimit = "100mb";
|
||||
disableTelemetry = true;
|
||||
environmentFiles = [ config.sops.templates."swiftshare.env".path ];
|
||||
};
|
||||
|
||||
database = {
|
||||
user = "swiftshare";
|
||||
#password = ""; # Defined in sops.templates."swiftshare-postgres.env"
|
||||
name = "swiftshare";
|
||||
environmentFiles = [ config.sops.templates."swiftshare-postgres.env".path ];
|
||||
#exposePort.enable = true;
|
||||
};
|
||||
|
||||
auth = {
|
||||
#secret = "";
|
||||
|
||||
discord = {
|
||||
clientId = "1400660345068191855";
|
||||
#clientSecret = ""; # Defined in sops.templates."swiftshare.env"
|
||||
};
|
||||
|
||||
# GitHub OAuth App (https://github.com/settings/developers) — replace placeholders.
|
||||
github = {
|
||||
clientId = "Ov23lifcVKR6B1iYDicU";
|
||||
#clientSecret = ""; # Defined in sops.templates."swiftshare.env"
|
||||
};
|
||||
|
||||
# Google Cloud OAuth 2.0 client — replace placeholders.
|
||||
#google = {
|
||||
# clientId = ""; # Defined in sops.templates."swiftshare.env"
|
||||
# clientSecret = ""; # Defined in sops.templates."swiftshare.env"
|
||||
#};
|
||||
|
||||
# SMTP for Better Auth email verification / password reset.
|
||||
smtp = {
|
||||
host = "smtp.purelymail.com";
|
||||
port = 465;
|
||||
secure = true;
|
||||
user = "noreply@swiftshare.cloud";
|
||||
#pass = ""; # Defined in sops.templates."swiftshare.env"
|
||||
from = "noreply@swiftshare.cloud";
|
||||
};
|
||||
};
|
||||
|
||||
minio = {
|
||||
#accessKey = ""; # Defined in sops.templates."swiftshare-minio.env"
|
||||
#secretKey = ""; # Defined in sops.templates."swiftshare-minio.env"
|
||||
bucketName = "swiftshare-assets";
|
||||
environmentFiles = [ config.sops.templates."swiftshare-minio.env".path ];
|
||||
};
|
||||
|
||||
umami = {
|
||||
websiteId = "b4e1240d-a9d8-4075-b64d-0d3e0329cac8";
|
||||
scriptUrl = "https://analytics.chiasson.cloud/script.js";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -9,14 +9,26 @@
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
inputs.ddrm.nixosModules.default
|
||||
self.nixosModules.nix-serverHardware
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
self.nixosModules.system
|
||||
self.nixosModules.users
|
||||
./_services/attic-cache-server.nix
|
||||
./_services/portainer.nix
|
||||
./_services/organizr.nix
|
||||
./_services/swiftshare.nix
|
||||
./_services/immich.nix
|
||||
./_services/jellyfin.nix
|
||||
./_services/nixdesk-nfs-client.nix
|
||||
./_services/ddrm-media-server.nix
|
||||
./_services/sonarr.nix
|
||||
./_services/prowlarr.nix
|
||||
./_services/flaresolverr.nix
|
||||
./_services/radarr.nix
|
||||
./_services/qbittorrent.nix
|
||||
./_services/seerr.nix
|
||||
./_services/dispatcharr.nix
|
||||
];
|
||||
|
||||
boot.loader.grub = {
|
||||
|
||||
@@ -95,6 +95,9 @@
|
||||
};
|
||||
defaultSession = "niri";
|
||||
shell = "dms";
|
||||
shells.dms = {
|
||||
enableRbwLockToggle = true;
|
||||
};
|
||||
};
|
||||
|
||||
chiasson.system = {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
# cockpit-file-sharing expects a live Samba stack: /etc/samba/smb.conf, smbd, and
|
||||
# `include = registry` in [global] for net registry share management.
|
||||
services.samba = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
winbindd.enable = false;
|
||||
settings.global = {
|
||||
workgroup = "WORKGROUP";
|
||||
"server string" = config.networking.hostName;
|
||||
include = "registry";
|
||||
};
|
||||
};
|
||||
|
||||
services.nfs.server = {
|
||||
enable = true;
|
||||
mountdPort = 4000;
|
||||
lockdPort = 4001;
|
||||
statdPort = 4002;
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
111
|
||||
2049
|
||||
4000
|
||||
4001
|
||||
4002
|
||||
];
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
111
|
||||
2049
|
||||
4000
|
||||
4001
|
||||
4002
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
acl,
|
||||
bash,
|
||||
coreutils,
|
||||
dpkg,
|
||||
fetchurl,
|
||||
findutils,
|
||||
hostname,
|
||||
iproute2,
|
||||
jq,
|
||||
lib,
|
||||
nfs-utils,
|
||||
python3Packages,
|
||||
samba,
|
||||
stdenv,
|
||||
systemd,
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation {
|
||||
pname = "cockpit-file-sharing";
|
||||
version = "4.5.6-1";
|
||||
|
||||
src = fetchurl {
|
||||
url = "https://github.com/45Drives/cockpit-file-sharing/releases/download/v4.5.6-1/cockpit-file-sharing_4.5.6-1jammy_all.deb";
|
||||
hash = "sha256-ViTdhiCmqwuBvAfzT8hr2kqZqyWkV9OZ9FEPD10ajF8=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ dpkg ];
|
||||
|
||||
unpackPhase = "dpkg-deb -x $src source";
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out/share/cockpit
|
||||
cp -r source/usr/share/cockpit/file-sharing $out/share/cockpit/
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
passthru.cockpitPath = [
|
||||
acl
|
||||
bash
|
||||
coreutils
|
||||
findutils
|
||||
hostname
|
||||
iproute2
|
||||
jq
|
||||
nfs-utils
|
||||
python3Packages.botocore
|
||||
samba
|
||||
systemd
|
||||
];
|
||||
|
||||
meta = {
|
||||
description = "Cockpit plugin to manage Samba and NFS file sharing (45Drives)";
|
||||
homepage = "https://github.com/45Drives/cockpit-file-sharing";
|
||||
license = lib.licenses.gpl3Plus;
|
||||
platforms = lib.platforms.linux;
|
||||
sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
cockpitFileSharing = pkgs.callPackage ./cockpit-file-sharing/package.nix { };
|
||||
in
|
||||
{
|
||||
imports = [ ./cockpit-file-sharing-services.nix ];
|
||||
|
||||
services.cockpit = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
allowed-origins = [
|
||||
"https://${config.networking.hostName}:${toString config.services.cockpit.port}"
|
||||
"https://192.168.2.99:${toString config.services.cockpit.port}"
|
||||
];
|
||||
plugins = with pkgs; [
|
||||
cockpit-files
|
||||
cockpit-machines
|
||||
cockpit-podman
|
||||
cockpitFileSharing
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# Pi 5 / CM5 onboard Wi-Fi (brcmfmac). Without a regulatory domain the driver scans
|
||||
# disallowed channels and spams: brcmf_set_channel: set chanspec … fail, reason -52
|
||||
# (open raspberrypi/linux#6049). Wi-Fi often still works; logs and scans are noisy.
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
wifiCountry = "CA"; # change if you are not in Canada
|
||||
in
|
||||
{
|
||||
boot.kernelParams = [
|
||||
"cfg80211.ieee80211_regdom=${wifiCountry}"
|
||||
];
|
||||
|
||||
# Firmware country code (config.txt) — picked up before userspace regdom.
|
||||
hardware.raspberry-pi.extra-config = ''
|
||||
country=${wifiCountry}
|
||||
'';
|
||||
|
||||
# Apply regdom before NetworkManager starts scanning.
|
||||
systemd.services.wifi-regulatory-domain = {
|
||||
description = "Set Wi-Fi regulatory domain for brcmfmac";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "NetworkManager.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
${pkgs.iw}/bin/iw reg set ${wifiCountry}
|
||||
'';
|
||||
};
|
||||
|
||||
networking.networkmanager.wifi.powersave = false;
|
||||
networking.networkmanager.wifi.scanRandMacAddress = false;
|
||||
networking.networkmanager.connectionConfig."wifi.bgscan" = "0";
|
||||
}
|
||||
@@ -29,7 +29,9 @@
|
||||
|
||||
self.nixosModules."client-services"
|
||||
./_private/platform.nix
|
||||
./_private/wifi-brcmfmac.nix
|
||||
./_private/services.nix
|
||||
./_private/cockpit.nix
|
||||
./_private/activation.nix
|
||||
./_private/4g/default.nix
|
||||
];
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
};
|
||||
|
||||
ideapad = {
|
||||
hostName = "192.168.2.113";
|
||||
hostName = "192.168.2.229";
|
||||
aliases = [ "ideapad" ];
|
||||
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQwaaI90xIMjZ46EcMyO8kBwGCxf7qVL75IYhw8Ssze ideapad";
|
||||
};
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py
|
||||
index 1a29a93ed..94434d5e7 100644
|
||||
--- a/yt_dlp/extractor/_extractors.py
|
||||
+++ b/yt_dlp/extractor/_extractors.py
|
||||
@@ -2034,6 +2034,7 @@
|
||||
TeleQuebecEmissionIE,
|
||||
TeleQuebecIE,
|
||||
TeleQuebecLiveIE,
|
||||
+ TeleQuebecSeasonIE,
|
||||
TeleQuebecSquatIE,
|
||||
TeleQuebecVideoIE,
|
||||
)
|
||||
diff --git a/yt_dlp/extractor/telequebec.py b/yt_dlp/extractor/telequebec.py
|
||||
index 7f5d5d29b..be927d6c5 100644
|
||||
--- a/yt_dlp/extractor/telequebec.py
|
||||
+++ b/yt_dlp/extractor/telequebec.py
|
||||
@@ -1,7 +1,12 @@
|
||||
+import json
|
||||
+import re
|
||||
+
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
+ ExtractorError,
|
||||
int_or_none,
|
||||
smuggle_url,
|
||||
+ traverse_obj,
|
||||
try_get,
|
||||
unified_timestamp,
|
||||
)
|
||||
@@ -28,81 +33,174 @@ class TeleQuebecIE(TeleQuebecBaseIE):
|
||||
)/(?P<id>\d+)
|
||||
'''
|
||||
_TESTS = [{
|
||||
- # available till 01.01.2023
|
||||
- 'url': 'http://zonevideo.telequebec.tv/media/37578/un-petit-choc-et-puis-repart/un-chef-a-la-cabane',
|
||||
- 'info_dict': {
|
||||
- 'id': '6155972771001',
|
||||
- 'ext': 'mp4',
|
||||
- 'title': 'Un petit choc et puis repart!',
|
||||
- 'description': 'md5:b04a7e6b3f74e32d7b294cffe8658374',
|
||||
- 'timestamp': 1589262469,
|
||||
- 'uploader_id': '6150020952001',
|
||||
- 'upload_date': '20200512',
|
||||
- },
|
||||
- 'add_ie': ['BrightcoveNew'],
|
||||
+ 'url': 'https://zonevideo.telequebec.tv/media/1/exemple',
|
||||
+ 'only_matching': True,
|
||||
}, {
|
||||
- 'url': 'https://zonevideo.telequebec.tv/media/55267/le-soleil/passe-partout',
|
||||
+ 'url': 'https://coucou.telequebec.tv/videos/1004958/top-cornichon/l-anniversaire-de-top-cornichon',
|
||||
'info_dict': {
|
||||
- 'id': '6167180337001',
|
||||
+ 'id': '6370144678112',
|
||||
'ext': 'mp4',
|
||||
- 'title': 'Le soleil',
|
||||
- 'description': 'md5:64289c922a8de2abbe99c354daffde02',
|
||||
+ 'title': 'L\'anniversaire de Top Cornichon',
|
||||
+ 'description': 'md5:fc4fb2967dcea0baa8b6d39a11da917b',
|
||||
'uploader_id': '6150020952001',
|
||||
- 'upload_date': '20200625',
|
||||
- 'timestamp': 1593090307,
|
||||
+ 'duration': 360.107,
|
||||
+ 'thumbnail': 'md5:027b0d8b371bc86d5ac9c024acfeb6f2',
|
||||
+ 'timestamp': 1742217124,
|
||||
+ 'upload_date': '20250317',
|
||||
+ 'series': 'Top Cornichon',
|
||||
+ 'episode': 'L\'anniversaire de Top Cornichon',
|
||||
+ },
|
||||
+ 'params': {
|
||||
+ 'skip_download': True,
|
||||
},
|
||||
'add_ie': ['BrightcoveNew'],
|
||||
- }, {
|
||||
- # no description
|
||||
- 'url': 'http://zonevideo.telequebec.tv/media/30261',
|
||||
- 'only_matching': True,
|
||||
- }, {
|
||||
- 'url': 'https://coucou.telequebec.tv/videos/41788/idee-de-genie/l-heure-du-bain',
|
||||
- 'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
media_id = self._match_id(url)
|
||||
- media = self._download_json(
|
||||
+ meta = self._download_json(
|
||||
'https://mnmedias.api.telequebec.tv/api/v3/media/' + media_id,
|
||||
- media_id)['media']
|
||||
- source_id = next(source_info['sourceId'] for source_info in media['streamInfos'] if source_info.get('source') == 'Brightcove')
|
||||
- info = self._brightcove_result(source_id, '22gPKdt7f')
|
||||
- product = media.get('product') or {}
|
||||
- season = product.get('season') or {}
|
||||
+ media_id, fatal=False)
|
||||
+ media = meta.get('media') if meta else None
|
||||
+ stream_infos = try_get(media, lambda m: m['streamInfos']) or []
|
||||
+
|
||||
+ if media and any(si and si.get('source') == 'Brightcove' for si in stream_infos):
|
||||
+ source_id = next(
|
||||
+ si['sourceId'] for si in stream_infos
|
||||
+ if si and si.get('source') == 'Brightcove')
|
||||
+ info = self._brightcove_result(source_id, '22gPKdt7f')
|
||||
+ product = media.get('product') or {}
|
||||
+ season = product.get('season') or {}
|
||||
+ info.update({
|
||||
+ 'description': try_get(media, lambda x: x['descriptions'][-1]['text'], str),
|
||||
+ 'series': try_get(season, lambda x: x['serie']['titre']),
|
||||
+ 'season': season.get('name'),
|
||||
+ 'season_number': int_or_none(season.get('seasonNo')),
|
||||
+ 'episode': product.get('titre'),
|
||||
+ 'episode_number': int_or_none(product.get('episodeNo')),
|
||||
+ })
|
||||
+ return info
|
||||
+
|
||||
+ # Coucou Next.js: Brightcove id is in __NEXT_DATA__ when mnmedias has no catalogue row.
|
||||
+ webpage = self._download_webpage(url, media_id)
|
||||
+ next_json = self._search_regex(
|
||||
+ r'<script id="__NEXT_DATA__" type="application/json">(?P<json>[\s\S]+?)</script>',
|
||||
+ webpage, '__NEXT_DATA__')
|
||||
+ next_data = self._parse_json(next_json, media_id)
|
||||
+ media_obj = traverse_obj(next_data, ('props', 'pageProps', 'media')) or {}
|
||||
+ bc_video_id = media_obj.get('mediaId')
|
||||
+ if not bc_video_id:
|
||||
+ raise ExtractorError('Unable to extract Brightcove video id')
|
||||
+
|
||||
+ info = self._brightcove_result(str(bc_video_id), '22gPKdt7f')
|
||||
+ hero = traverse_obj(next_data, ('props', 'pageProps', 'hero')) or {}
|
||||
info.update({
|
||||
- 'description': try_get(media, lambda x: x['descriptions'][-1]['text'], str),
|
||||
- 'series': try_get(season, lambda x: x['serie']['titre']),
|
||||
- 'season': season.get('name'),
|
||||
- 'season_number': int_or_none(season.get('seasonNo')),
|
||||
- 'episode': product.get('titre'),
|
||||
- 'episode_number': int_or_none(product.get('episodeNo')),
|
||||
+ 'description': media_obj.get('description'),
|
||||
+ 'episode': media_obj.get('titre'),
|
||||
+ 'series': hero.get('nom'),
|
||||
})
|
||||
return info
|
||||
|
||||
|
||||
-class TeleQuebecSquatIE(InfoExtractor):
|
||||
- _VALID_URL = r'https?://squat\.telequebec\.tv/videos/(?P<id>\d+)'
|
||||
+class TeleQuebecSeasonIE(InfoExtractor):
|
||||
+ """telequebec.tv/contenu/{slug}/saison/{n} — expands to episode /regarder/ URLs via GraphQL."""
|
||||
+
|
||||
+ _VALID_URL = r'https?://(?:www\.)?telequebec\.tv/contenu/(?P<slug>[^/?#]+)/saison/(?P<season>\d+)'
|
||||
_TESTS = [{
|
||||
- 'url': 'https://squat.telequebec.tv/videos/9314',
|
||||
+ 'url': 'https://telequebec.tv/contenu/macaroni-tout-garni/saison/1',
|
||||
+ 'playlist_mincount': 15,
|
||||
'info_dict': {
|
||||
- 'id': 'd59ae78112d542e793d83cc9d3a5b530',
|
||||
- 'ext': 'mp4',
|
||||
- 'title': 'Poupeflekta',
|
||||
- 'description': 'md5:2f0718f8d2f8fece1646ee25fb7bce75',
|
||||
- 'duration': 1351,
|
||||
- 'timestamp': 1569057600,
|
||||
- 'upload_date': '20190921',
|
||||
- 'series': 'Miraculous : Les Aventures de Ladybug et Chat Noir',
|
||||
- 'season': 'Saison 3',
|
||||
- 'season_number': 3,
|
||||
- 'episode_number': 57,
|
||||
+ 'id': 'macaroni-tout-garni-s1',
|
||||
+ 'title': 'Macaroni tout garni',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
+ }, {
|
||||
+ 'url': 'https://www.telequebec.tv/contenu/macaroni-tout-garni/saison/1/',
|
||||
+ 'only_matching': True,
|
||||
}]
|
||||
|
||||
+ def _real_extract(self, url):
|
||||
+ mobj = self._match_valid_url(url)
|
||||
+ slug, season = mobj.group('slug'), int(mobj.group('season'))
|
||||
+ playlist_id = f'{slug}-s{season}'
|
||||
+
|
||||
+ query = '''query ($slug: String!, $season: Int!) {
|
||||
+ productPage(rootProductSlug: $slug, seasonNumber: $season) {
|
||||
+ ... on ArtisanPage {
|
||||
+ blocks {
|
||||
+ ... on ArtisanBlocksProductPlayableProductsStrip {
|
||||
+ blockConfiguration {
|
||||
+ rootProduct { title }
|
||||
+ season { title }
|
||||
+ products { title videoCanonicalUrl }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }'''
|
||||
+
|
||||
+ resp = self._download_json(
|
||||
+ 'https://api.pc-cms.tele.quebec/graphql', playlist_id,
|
||||
+ data=json.dumps({
|
||||
+ 'query': query,
|
||||
+ 'variables': {'slug': slug, 'season': season},
|
||||
+ }, separators=(',', ':')).encode(),
|
||||
+ headers={
|
||||
+ 'Content-Type': 'application/json',
|
||||
+ 'Accept': 'application/json',
|
||||
+ 'Origin': 'https://telequebec.tv',
|
||||
+ })
|
||||
+
|
||||
+ errs = traverse_obj(resp, ('errors', ..., 'message', {str}))
|
||||
+ if errs:
|
||||
+ raise ExtractorError(', '.join(errs), expected=True)
|
||||
+
|
||||
+ playlist_title = None
|
||||
+ products = []
|
||||
+ for block in traverse_obj(resp, ('data', 'productPage', 'blocks')) or []:
|
||||
+ cfg = traverse_obj(block, 'blockConfiguration')
|
||||
+ if not cfg:
|
||||
+ continue
|
||||
+ prods = cfg.get('products')
|
||||
+ if not prods:
|
||||
+ continue
|
||||
+ products = prods
|
||||
+ playlist_title = traverse_obj(cfg, ('season', 'title')) or traverse_obj(cfg, ('rootProduct', 'title'))
|
||||
+ break
|
||||
+
|
||||
+ if not products:
|
||||
+ raise ExtractorError('No playable episodes in this season', expected=True)
|
||||
+
|
||||
+ def _episode_sort_key(p):
|
||||
+ vu = p.get('videoCanonicalUrl') or ''
|
||||
+ m = re.search(r'/(\d+)/?(?:[?#]|$)', vu)
|
||||
+ return int(m.group(1)) if m else 0
|
||||
+
|
||||
+ products = sorted(products, key=_episode_sort_key)
|
||||
+
|
||||
+ entries = [
|
||||
+ self.url_result(
|
||||
+ p['videoCanonicalUrl'],
|
||||
+ ie=TeleQuebecEmissionIE.ie_key(),
|
||||
+ video_title=p.get('title'))
|
||||
+ for p in products
|
||||
+ if p.get('videoCanonicalUrl')
|
||||
+ ]
|
||||
+
|
||||
+ return self.playlist_result(
|
||||
+ entries,
|
||||
+ playlist_id=playlist_id,
|
||||
+ playlist_title=playlist_title or slug)
|
||||
+
|
||||
+
|
||||
+class TeleQuebecSquatIE(InfoExtractor):
|
||||
+ _WORKING = False
|
||||
+ _VALID_URL = r'https?://squat\.telequebec\.tv/videos/(?P<id>\d+)'
|
||||
+ _TESTS = []
|
||||
+
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
@@ -136,25 +234,47 @@ class TeleQuebecEmissionIE(InfoExtractor):
|
||||
)
|
||||
(?P<id>[^?#&]+)
|
||||
'''
|
||||
+
|
||||
+ @classmethod
|
||||
+ def suitable(cls, url):
|
||||
+ if re.match(r'https?://(?:www\.)?telequebec\.tv/contenu/[^/?#]+/saison/\d+', url):
|
||||
+ return False
|
||||
+ return super().suitable(url)
|
||||
+
|
||||
_TESTS = [{
|
||||
- 'url': 'http://lindicemcsween.telequebec.tv/emissions/100430013/des-soins-esthetiques-a-377-d-interets-annuels-ca-vous-tente',
|
||||
+ 'url': 'https://telequebec.tv/regarder/donner-lgout/2/6',
|
||||
'info_dict': {
|
||||
- 'id': '6154476028001',
|
||||
+ 'id': 'ref:100832979',
|
||||
'ext': 'mp4',
|
||||
- 'title': 'Des soins esthétiques à 377 % d’intérêts annuels, ça vous tente?',
|
||||
- 'description': 'md5:cb4d378e073fae6cce1f87c00f84ae9f',
|
||||
- 'upload_date': '20200505',
|
||||
- 'timestamp': 1588713424,
|
||||
+ 'title': 'La Grèce à Québec',
|
||||
+ 'description': 'md5:c506e07b90426ad391e18a753c021516',
|
||||
'uploader_id': '6150020952001',
|
||||
+ 'duration': 2695.083,
|
||||
+ 'thumbnail': 'md5:17aead23a395fb3f56a376524eb9f23c',
|
||||
+ 'timestamp': 1777782475,
|
||||
+ 'upload_date': '20260503',
|
||||
},
|
||||
+ 'params': {
|
||||
+ 'skip_download': True,
|
||||
+ },
|
||||
+ 'add_ie': ['BrightcoveNew'],
|
||||
+ }, {
|
||||
+ 'url': 'https://telequebec.tv/regarder/donner-lgout/2/1',
|
||||
+ 'only_matching': True,
|
||||
+ }, {
|
||||
+ 'url': 'https://telequebec.tv/regarder/kamikazes/2/7',
|
||||
+ 'only_matching': True,
|
||||
}, {
|
||||
- 'url': 'http://bancpublic.telequebec.tv/emissions/emission-49/31986/jeunes-meres-sous-pression',
|
||||
+ 'url': 'https://telequebec.tv/regarder/5050',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
- 'url': 'http://www.telequebec.tv/masha-et-michka/epi059masha-et-michka-3-053-078',
|
||||
+ 'url': 'https://telequebec.tv/regarder/les-magnetiques',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
- 'url': 'http://www.telequebec.tv/documentaire/bebes-sur-mesure/',
|
||||
+ 'url': 'https://telequebec.tv/regarder/les-dalton/2/107',
|
||||
+ 'only_matching': True,
|
||||
+ }, {
|
||||
+ 'url': 'https://telequebec.tv/regarder/macaroni-tout-garni/1/1',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
@@ -164,31 +284,33 @@ def _real_extract(self, url):
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
|
||||
media_id = self._search_regex(
|
||||
- r'mediaId\s*:\s*(?P<id>\d+)', webpage, 'media id')
|
||||
+ (
|
||||
+ r'mediaId\s*:\s*(?P<id>\d+)',
|
||||
+ r'"mediaId"\s*,\s*"(?P<id>\d+)"',
|
||||
+ r'"mediaId"\s*,\s*(?P<id>\d+)',
|
||||
+ r'mediaId\\"\s*,\s*\\"(?P<id>\d+)',
|
||||
+ ),
|
||||
+ webpage, 'media id')
|
||||
+
|
||||
+ meta = self._download_json(
|
||||
+ 'https://mnmedias.api.telequebec.tv/api/v3/media/' + media_id,
|
||||
+ media_id, fatal=False)
|
||||
+ media = meta.get('media') if meta else None
|
||||
+ stream_infos = try_get(media, lambda m: m['streamInfos']) or []
|
||||
+ if media and any(si and si.get('source') == 'Brightcove' for si in stream_infos):
|
||||
+ return self.url_result(
|
||||
+ 'http://zonevideo.telequebec.tv/media/' + media_id,
|
||||
+ TeleQuebecIE.ie_key())
|
||||
|
||||
- return self.url_result(
|
||||
- 'http://zonevideo.telequebec.tv/media/' + media_id,
|
||||
- TeleQuebecIE.ie_key())
|
||||
+ # New telequebec.tv stack no longer mirrors catalogue ids into mnmedias; Brightcove loads
|
||||
+ # videoId=ref:{mediaId} (see web player bundle: videoId:`ref:${mediaId}`).
|
||||
+ return TeleQuebecBaseIE._brightcove_result(f'ref:{media_id}', 'ja7RtbSne')
|
||||
|
||||
|
||||
class TeleQuebecLiveIE(TeleQuebecBaseIE):
|
||||
+ _WORKING = False
|
||||
_VALID_URL = r'https?://zonevideo\.telequebec\.tv/(?P<id>endirect)'
|
||||
- _TEST = {
|
||||
- 'url': 'http://zonevideo.telequebec.tv/endirect/',
|
||||
- 'info_dict': {
|
||||
- 'id': '6159095684001',
|
||||
- 'ext': 'mp4',
|
||||
- 'title': 're:^Télé-Québec [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
||||
- 'is_live': True,
|
||||
- 'description': 'Canal principal de Télé-Québec',
|
||||
- 'uploader_id': '6150020952001',
|
||||
- 'timestamp': 1590439901,
|
||||
- 'upload_date': '20200525',
|
||||
- },
|
||||
- 'params': {
|
||||
- 'skip_download': True,
|
||||
- },
|
||||
- }
|
||||
+ _TESTS = []
|
||||
|
||||
def _real_extract(self, url):
|
||||
return self._brightcove_result('6159095684001', 'skCsmi2Uw')
|
||||
@@ -198,15 +320,7 @@ class TeleQuebecVideoIE(TeleQuebecBaseIE):
|
||||
_VALID_URL = r'https?://video\.telequebec\.tv/player(?:-live)?/(?P<id>\d+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://video.telequebec.tv/player/31110/stream',
|
||||
- 'info_dict': {
|
||||
- 'id': '6202570652001',
|
||||
- 'ext': 'mp4',
|
||||
- 'title': 'Le coût du véhicule le plus vendu au Canada / Tous les frais liés à la procréation assistée',
|
||||
- 'description': 'md5:685a7e4c450ba777c60adb6e71e41526',
|
||||
- 'upload_date': '20201019',
|
||||
- 'timestamp': 1603115930,
|
||||
- 'uploader_id': '6101674910001',
|
||||
- },
|
||||
+ 'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://video.telequebec.tv/player-live/28527',
|
||||
'only_matching': True,
|
||||
@@ -56,6 +56,42 @@
|
||||
'';
|
||||
};
|
||||
|
||||
retries = mkOption {
|
||||
type = types.int;
|
||||
default = 3;
|
||||
description = "Attempts per push before giving up (handles transient Attic/network stalls).";
|
||||
};
|
||||
|
||||
background = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Run `attic push` in the background so the build finishes immediately.
|
||||
Failures/timeouts are logged; they do not fail the build.
|
||||
'';
|
||||
};
|
||||
|
||||
timeoutSec = mkOption {
|
||||
type = types.int;
|
||||
default = 600;
|
||||
description = "Kill `attic push` after this many seconds (background or foreground).";
|
||||
};
|
||||
|
||||
uploadJobs = mkOption {
|
||||
type = types.int;
|
||||
default = 3;
|
||||
description = "Parallel upload workers (`attic push -j`). Lower if the server stalls under load.";
|
||||
};
|
||||
|
||||
logFile = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Append push logs here. Empty → `$XDG_RUNTIME_DIR/nix-attic-push.log`
|
||||
(or `/tmp/nix-attic-push-$UID.log`).
|
||||
'';
|
||||
};
|
||||
|
||||
excludedPatterns = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
@@ -96,6 +132,7 @@
|
||||
enabled = cfg.enable && cfg.cacheName != "" && endpointBase != "" && cfg.publicKey != "";
|
||||
cacheUrl = "${endpointBase}/${cfg.cacheName}";
|
||||
pushTokenFile = if cfg.push.tokenFile != null then cfg.push.tokenFile else cfg.tokenFile;
|
||||
atticTomlServersSection = "[servers.ci]";
|
||||
hmAtticCliModule =
|
||||
{ lib, osConfig ? { }, ... }:
|
||||
let
|
||||
@@ -146,11 +183,16 @@
|
||||
set -eu
|
||||
set -f
|
||||
|
||||
export PATH="${lib.makeBinPath [
|
||||
pkgs.attic-client
|
||||
pkgs.nix
|
||||
pkgs.gnused
|
||||
pkgs.coreutils
|
||||
]}:$PATH"
|
||||
|
||||
echo "attic: hook start drv=''${DRV_PATH:-<unknown>}" >&2
|
||||
echo "attic: endpoint=${lib.escapeShellArg endpointBase} cache=${lib.escapeShellArg cfg.cacheName}" >&2
|
||||
|
||||
export PATH="${lib.makeBinPath [ pkgs.attic-client pkgs.nix pkgs.gnused ]}:$PATH"
|
||||
${lib.optionalString (pushTokenFile != null) ''
|
||||
token_path=${lib.escapeShellArg pushTokenFile}
|
||||
if [ ! -r "$token_path" ]; then
|
||||
echo "attic: skipping push (token not readable at $token_path)" >&2
|
||||
@@ -158,31 +200,17 @@
|
||||
fi
|
||||
|
||||
ATTIC_TOKEN="$(tr -d '\n' < "$token_path")"
|
||||
''}
|
||||
if [ -z "$ATTIC_TOKEN" ]; then
|
||||
echo "attic: skipping push (token is empty)" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ATTIC_CONFIG_HOME="$(mktemp -d /tmp/attic-hook-XXXXXX)"
|
||||
export XDG_CONFIG_HOME="$ATTIC_CONFIG_HOME"
|
||||
cleanup() {
|
||||
rm -rf "$ATTIC_CONFIG_HOME"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if ! attic login --set-default ci ${lib.escapeShellArg endpointBase} "$ATTIC_TOKEN" >/dev/null 2>&1; then
|
||||
echo "attic: login failed (build succeeded; check token/server URL)" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
push_paths=""
|
||||
skipped_roots=0
|
||||
pushed_roots=0
|
||||
seen_roots=0
|
||||
for path in $OUT_PATHS; do
|
||||
seen_roots=$((seen_roots + 1))
|
||||
echo "attic: evaluating OUT_PATH $path" >&2
|
||||
skip=0
|
||||
skip_reason=""
|
||||
|
||||
@@ -217,17 +245,59 @@
|
||||
|
||||
echo "attic: summary seen=$seen_roots selected=$pushed_roots skipped=$skipped_roots" >&2
|
||||
|
||||
if [ -n "$push_paths" ]; then
|
||||
echo "attic: pushing to ci:${cfg.cacheName}" >&2
|
||||
if ! attic push ${lib.escapeShellArg "ci:${cfg.cacheName}"} $push_paths; then
|
||||
echo "attic: push failed (build succeeded; check token/network)" >&2
|
||||
else
|
||||
echo "attic: push succeeded" >&2
|
||||
fi
|
||||
else
|
||||
if [ -z "$push_paths" ]; then
|
||||
echo "attic: nothing selected for push" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
runtime_dir="''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
||||
attic_config_home="$runtime_dir/nix-attic-hook"
|
||||
export XDG_CONFIG_HOME="$attic_config_home"
|
||||
mkdir -p "$attic_config_home/attic"
|
||||
{
|
||||
printf '%s\n' 'default-server = "ci"'
|
||||
printf '\n'
|
||||
printf '%s\n' ${builtins.toJSON atticTomlServersSection}
|
||||
printf 'endpoint = %s\n' ${builtins.toJSON endpointBase}
|
||||
printf 'token = "%s"\n' "$ATTIC_TOKEN"
|
||||
} > "$attic_config_home/attic/config.toml"
|
||||
|
||||
log_file=${lib.escapeShellArg cfg.push.logFile}
|
||||
if [ -z "$log_file" ]; then
|
||||
log_file="$runtime_dir/nix-attic-push.log"
|
||||
fi
|
||||
mkdir -p "$(dirname "$log_file")"
|
||||
|
||||
push_cmd() {
|
||||
attempt=1
|
||||
max_attempts=${toString cfg.push.retries}
|
||||
while [ "$attempt" -le "$max_attempts" ]; do
|
||||
echo "attic: push attempt $attempt/$max_attempts $(date -Is) paths:$push_paths" >&2
|
||||
if timeout ${toString cfg.push.timeoutSec} \
|
||||
attic push -j ${toString cfg.push.uploadJobs} \
|
||||
${lib.escapeShellArg "ci:${cfg.cacheName}"} \
|
||||
$push_paths; then
|
||||
echo "attic: push succeeded $(date -Is)" >&2
|
||||
return 0
|
||||
fi
|
||||
echo "attic: push failed or timed out (attempt $attempt/$max_attempts)" >&2
|
||||
attempt=$((attempt + 1))
|
||||
[ "$attempt" -le "$max_attempts" ] && sleep 5
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
${lib.optionalString cfg.push.background ''
|
||||
echo "attic: scheduling background push → $log_file" >&2
|
||||
(
|
||||
push_cmd
|
||||
) >> "$log_file" 2>&1 &
|
||||
exit 0
|
||||
''}
|
||||
${lib.optionalString (!cfg.push.background) ''
|
||||
push_cmd 2>&1 | tee -a "$log_file"
|
||||
exit 0
|
||||
''}
|
||||
'');
|
||||
|
||||
environment.systemPackages = lib.mkIf cfg.userCli.enable [ pkgs.attic-client ];
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# Pull everywhere; enable push only on the host that runs `nixos-rebuild --target-host` (needs sops `cachix/auth-token`).
|
||||
{ inputs, ... }: {
|
||||
flake.nixosModules.systemCachingCachix =
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.chiasson.system.caching.cachix;
|
||||
in
|
||||
{
|
||||
imports = [ inputs.sops-nix.nixosModules.sops ];
|
||||
|
||||
options.chiasson.system.caching.cachix = with lib; {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable Cachix cache integration for this host.";
|
||||
};
|
||||
|
||||
cacheName = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "chiasson-nixosnew";
|
||||
description = "Your Cachix cache name (from app.cachix.org). Leave empty to disable.";
|
||||
};
|
||||
publicKey = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "chiasson-nixosnew.cachix.org-1:xxxx=";
|
||||
description = "Public key for the cache (shown on the cache page).";
|
||||
};
|
||||
enablePush = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Push every build to Cachix (enable only on the machine that runs nixos-rebuild --target-host).";
|
||||
};
|
||||
pushExcludePatterns = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "wallpaper" "hm_wallpapers" ".iso" "-source" "large-assets" ];
|
||||
description = "Substring patterns: store paths containing any of these are not pushed to Cachix (saves cache space for heavy outputs).";
|
||||
};
|
||||
};
|
||||
|
||||
config = let
|
||||
enabled = cfg.enable && cfg.cacheName != "" && cfg.publicKey != "";
|
||||
cacheUrl = "https://${cfg.cacheName}.cachix.org";
|
||||
in
|
||||
lib.mkMerge [
|
||||
(lib.mkIf enabled {
|
||||
nix.settings = {
|
||||
substituters = lib.mkAfter [ cacheUrl ];
|
||||
trusted-public-keys = lib.mkAfter [ cfg.publicKey ];
|
||||
};
|
||||
})
|
||||
(lib.mkIf (enabled && cfg.enablePush) {
|
||||
nix.settings.post-build-hook = pkgs.writeShellScript "upload-to-cachix" ''
|
||||
set -eu
|
||||
set -f
|
||||
token_path="${config.sops.secrets."cachix/auth-token".path}"
|
||||
if [ ! -r "$token_path" ]; then
|
||||
echo "cachix: skipping push (token not readable at $token_path)" >&2
|
||||
exit 0
|
||||
fi
|
||||
export PATH="${lib.makeBinPath [ pkgs.cachix ]}:$PATH"
|
||||
export CACHIX_AUTH_TOKEN=$(cat "$token_path")
|
||||
|
||||
push_paths=""
|
||||
skipped_roots=0
|
||||
pushed_roots=0
|
||||
for path in $OUT_PATHS; do
|
||||
skip=0
|
||||
skip_reason=""
|
||||
|
||||
closure_paths="$(nix-store -qR "$path" 2>/dev/null || true)"
|
||||
for candidate in "$path" $closure_paths; do
|
||||
while IFS= read -r pat; do
|
||||
pat="$(printf '%s' "$pat" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
[ -z "$pat" ] && continue
|
||||
case "$candidate" in
|
||||
*"$pat"*)
|
||||
skip=1
|
||||
skip_reason="$pat ($candidate)"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done << 'EXCLUDE_PATTERNS'
|
||||
${lib.concatStringsSep "\n" cfg.pushExcludePatterns}
|
||||
EXCLUDE_PATTERNS
|
||||
if [ "$skip" -eq 1 ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$skip" -eq 0 ]; then
|
||||
push_paths="$push_paths $path"
|
||||
pushed_roots=$((pushed_roots + 1))
|
||||
else
|
||||
skipped_roots=$((skipped_roots + 1))
|
||||
echo "cachix: skipping root $path (matches exclude pattern via $skip_reason)" >&2
|
||||
fi
|
||||
done
|
||||
|
||||
echo "cachix: root paths selected for push: $pushed_roots, skipped: $skipped_roots" >&2
|
||||
|
||||
if [ -n "$push_paths" ]; then
|
||||
if ! cachix push ${lib.escapeShellArg cfg.cacheName} $push_paths; then
|
||||
echo "cachix: push failed (build succeeded; check token/network)" >&2
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
'';
|
||||
|
||||
sops.secrets."cachix/auth-token" = {
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# `system.caching.*` — Attic + Cachix; exported again as `nixosModules.systemCaching`.
|
||||
{ self, ... }: {
|
||||
flake.nixosModules.systemCaching = {
|
||||
imports = [
|
||||
self.nixosModules.systemCachingAttic
|
||||
self.nixosModules.systemCachingCachix
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
self.nixosModules.systemFonts
|
||||
self.nixosModules.systemNetworking
|
||||
self.nixosModules.systemLocalsend
|
||||
self.nixosModules.systemChromiumHevcVaapi
|
||||
self.nixosModules.systemMonitorInput
|
||||
self.nixosModules.systemSpotify
|
||||
self.nixosModules.systemPackagesDefaults
|
||||
@@ -13,6 +14,7 @@
|
||||
self.nixosModules.systemFlatpak
|
||||
self.nixosModules.systemAudio
|
||||
self.nixosModules.systemIdeapadMrubyOverlay
|
||||
self.nixosModules.systemYtDlpTelequebecPatch
|
||||
self.nixosModules.systemGaming
|
||||
self.nixosModules.systemUconsoleKernelBuilder
|
||||
self.nixosModules.systemLibrepods
|
||||
@@ -20,7 +22,6 @@
|
||||
self.nixosModules.systemCaching
|
||||
inputs.swiftshare.nixosModules.systemServiceSwiftshare
|
||||
self.nixosModules.systemServiceImmich
|
||||
self.nixosModules.systemVM
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,9 +75,9 @@
|
||||
|
||||
# Never remote-delete flathub here — interactive and breaks unattended rebuilds.
|
||||
${pkgs.flatpak}/bin/flatpak --system remote-add --if-not-exists flathub \
|
||||
https://flathub.org/repo/flathub.flatpakrepo || true
|
||||
https://dl.flathub.org/repo/ || true
|
||||
${pkgs.flatpak}/bin/flatpak --system remote-modify flathub \
|
||||
--url=https://flathub.org/repo/flathub.flatpakrepo 2>/dev/null || true
|
||||
--url=https://dl.flathub.org/repo/ 2>/dev/null || true
|
||||
|
||||
allowed=( ${lib.concatStringsSep " " (map lib.escapeShellArg allowedAppIds)} )
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
with pkgs;
|
||||
[
|
||||
lutris
|
||||
bottles
|
||||
wine
|
||||
winetricks
|
||||
gamemode
|
||||
mangohud
|
||||
goverlay
|
||||
]
|
||||
++ lib.optionals cfg.launchers.enableBottles [ bottles ]
|
||||
++ lib.optionals pkgs.stdenv.isx86_64 [ heroic ];
|
||||
|
||||
steamExtraPkgs =
|
||||
@@ -66,6 +66,19 @@
|
||||
description = "`programs.gamemode` (Feral GameMode).";
|
||||
};
|
||||
|
||||
gamescope = {
|
||||
enable = lib.mkEnableOption ''
|
||||
`programs.gamescope` — isolated compositor for Steam/Proton on Wayland
|
||||
(fixes games embedding inside the Steam window).
|
||||
'';
|
||||
|
||||
capSysNice = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Allow gamescope to renice itself for smoother frame pacing.";
|
||||
};
|
||||
};
|
||||
|
||||
jack.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
@@ -76,6 +89,11 @@
|
||||
};
|
||||
|
||||
launchers = {
|
||||
enableBottles = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Install native `pkgs.bottles` in the launcher bundle.";
|
||||
};
|
||||
forUsers = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.listOf lib.types.str);
|
||||
default = null;
|
||||
@@ -93,6 +111,17 @@
|
||||
|
||||
config = lib.mkIf cfg.enable (lib.mkMerge [
|
||||
{
|
||||
# openldap's upstream test suite is flaky in the Nix sandbox (test017, test001, …).
|
||||
# Disabling checks avoids cascading failures in lutris, apache, gnupg, nfs-utils, etc.
|
||||
# Upstream: https://github.com/NixOS/nixpkgs/issues/514113
|
||||
nixpkgs.overlays = [
|
||||
(_: prev: {
|
||||
openldap = prev.openldap.overrideAttrs (_: {
|
||||
doCheck = false;
|
||||
});
|
||||
})
|
||||
];
|
||||
|
||||
programs.steam = {
|
||||
enable = true;
|
||||
remotePlay.openFirewall = cfg.steam.remotePlay.openFirewall;
|
||||
@@ -106,6 +135,11 @@
|
||||
|
||||
programs.gamemode.enable = cfg.gamemode.enable;
|
||||
|
||||
programs.gamescope = lib.mkIf cfg.gamescope.enable {
|
||||
enable = true;
|
||||
inherit (cfg.gamescope) capSysNice;
|
||||
};
|
||||
|
||||
chiasson.system.audio.pipewire.jack.enable = lib.mkIf (cfg.jack.enable) (lib.mkDefault true);
|
||||
|
||||
assertions = [
|
||||
|
||||
@@ -11,11 +11,17 @@
|
||||
extraGroups = [
|
||||
"networkmanager"
|
||||
"wheel"
|
||||
"libvirtd"
|
||||
"docker"
|
||||
"fuse"
|
||||
"uinput"
|
||||
"kvm"
|
||||
# `video` is required for the brightnessctl/light udev rules to grant write access
|
||||
# to /sys/class/backlight/*/brightness without sudo. Harmless on hosts without a
|
||||
# backlight (servers, desktop towers): the group simply has no devices to own.
|
||||
"video"
|
||||
# DRI render nodes and input devices for gamescope / Steam on Wayland (no sudo).
|
||||
"render"
|
||||
"input"
|
||||
];
|
||||
|
||||
# Host must set `sops.secrets."users/olivier/hashedPassword".neededForUsers = true`.
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
}) names
|
||||
);
|
||||
inboundUsersAttr = usersLib.inboundHostsAttr selected;
|
||||
|
||||
# HM configures fish in ~/.config/fish but no longer sets /etc/passwd or /etc/shells.
|
||||
hmFishUsers =
|
||||
if !hmAvailable then { }
|
||||
else
|
||||
lib.filterAttrs (
|
||||
name: hmUser: (hmUser.programs.fish.enable or false) && builtins.elem name names
|
||||
) config.home-manager.users;
|
||||
in
|
||||
{
|
||||
config = lib.mkMerge [
|
||||
@@ -46,6 +54,18 @@
|
||||
chiasson.ssh.inbound.enable = true;
|
||||
chiasson.ssh.inbound.userAuthorizedHosts = inboundUsersAttr;
|
||||
})
|
||||
(lib.mkIf (hmFishUsers != { }) {
|
||||
environment.shells = lib.mkAfter (
|
||||
lib.mapAttrsToList (
|
||||
_: hmUser: lib.getExe hmUser.programs.fish.package
|
||||
) hmFishUsers
|
||||
);
|
||||
users.users = lib.mapAttrs (
|
||||
name: hmUser: {
|
||||
shell = lib.mkForce (lib.getExe hmUser.programs.fish.package);
|
||||
}
|
||||
) hmFishUsers;
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Tele-Québec extractor fixes until upstream yt-dlp carries them.
|
||||
#
|
||||
# Regenerate `modules/patches/yt-dlp-telequebec.patch` from yt-dlp checkout
|
||||
# (branch with fixes vs master), redirecting into this flake's modules/patches/:
|
||||
#
|
||||
# git diff master..ie/telequebec-update -- \
|
||||
# yt_dlp/extractor/telequebec.py yt_dlp/extractor/_extractors.py \
|
||||
# > path/to/NixOS-V2/modules/patches/yt-dlp-telequebec.patch
|
||||
#
|
||||
{ ... }: {
|
||||
flake.nixosModules.systemYtDlpTelequebecPatch =
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.chiasson.system.ytDlpTelequebecPatch;
|
||||
patchFile = ../patches/yt-dlp-telequebec.patch;
|
||||
in
|
||||
{
|
||||
options.chiasson.system.ytDlpTelequebecPatch = {
|
||||
enable = lib.mkEnableOption ''
|
||||
Patch yt-dlp with Tele-Québec extractor updates (telequebec.tv, Coucou, season playlists).
|
||||
Regenerate `modules/patches/yt-dlp-telequebec.patch` when nixpkgs bumps yt-dlp if the build fails.
|
||||
'';
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
nixpkgs.overlays = lib.mkOrder 1000 [
|
||||
(final: prev: {
|
||||
yt-dlp = prev.yt-dlp.overrideAttrs (old: {
|
||||
patches = (old.patches or [ ]) ++ [ patchFile ];
|
||||
});
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
{ ... }: {
|
||||
flake.nixosModules.systemChromiumHevcVaapi =
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.chiasson.system.chromiumHevc;
|
||||
in
|
||||
{
|
||||
options.chiasson.system.chromiumHevc.enable = lib.mkEnableOption ''
|
||||
VA-API packages for Chromium HEVC (Intel iHD + optional NVIDIA nvidia-vaapi-driver).
|
||||
Pair with `wisdomBrowsersChromiumHevc` on the user side.
|
||||
'';
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
hardware.graphics.enable = lib.mkDefault true;
|
||||
hardware.graphics.extraPackages = lib.mkAfter (
|
||||
with pkgs;
|
||||
[ nvidia-vaapi-driver ]
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
flake.homeManagerModules.wisdomBrowsersChromiumHevc =
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
root = config.chiasson.home;
|
||||
cfg = config.chiasson.home.browsers.chromiumHevc;
|
||||
|
||||
browserCatalog = {
|
||||
"google-chrome" = {
|
||||
package = pkgs.google-chrome;
|
||||
binary = "google-chrome-stable";
|
||||
launcher = "google-chrome-hevc";
|
||||
desktopName = "Google Chrome (HEVC)";
|
||||
icon = "google-chrome";
|
||||
};
|
||||
chromium = {
|
||||
package = pkgs.chromium;
|
||||
binary = "chromium";
|
||||
launcher = "chromium-hevc";
|
||||
desktopName = "Chromium (HEVC)";
|
||||
icon = "chromium";
|
||||
};
|
||||
"microsoft-edge" = {
|
||||
package = pkgs.microsoft-edge;
|
||||
binary = "microsoft-edge-stable";
|
||||
launcher = "microsoft-edge-hevc";
|
||||
desktopName = "Microsoft Edge (HEVC)";
|
||||
icon = "microsoft-edge";
|
||||
};
|
||||
};
|
||||
|
||||
gpuProfiles = {
|
||||
intel = {
|
||||
driver = "iHD";
|
||||
drmDevice = "/dev/dri/renderD128";
|
||||
nvdBackend = "direct";
|
||||
enableFeatures = [
|
||||
"VaapiVideoDecodeLinuxGL"
|
||||
"VaapiVideoDecoder"
|
||||
"VaapiIgnoreDriverChecks"
|
||||
"PlatformHEVCDecoderSupport"
|
||||
"UseMultiPlaneFormatForHardwareVideo"
|
||||
"AcceleratedVideoDecodeLinuxGL"
|
||||
];
|
||||
disableFeatures = [
|
||||
"AcceleratedVideoDecodeLinuxZeroCopyGL"
|
||||
];
|
||||
};
|
||||
nvidia = {
|
||||
driver = "nvidia";
|
||||
drmDevice = "/dev/dri/renderD129";
|
||||
nvdBackend = "direct";
|
||||
enableFeatures = [
|
||||
"VaapiVideoDecoder"
|
||||
"VaapiIgnoreDriverChecks"
|
||||
"PlatformHEVCDecoderSupport"
|
||||
"VaapiOnNvidiaGPUs"
|
||||
"AcceleratedVideoDecodeLinuxGL"
|
||||
];
|
||||
disableFeatures = [
|
||||
"AcceleratedVideoDecodeLinuxZeroCopyGL"
|
||||
"UseMultiPlaneFormatForHardwareVideo"
|
||||
"VaapiVideoDecodeLinuxGL"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
activeGpu = gpuProfiles.${cfg.vaapi.gpu};
|
||||
|
||||
mkChromiumHevc =
|
||||
packageName:
|
||||
let
|
||||
spec = browserCatalog.${packageName};
|
||||
browser = spec.package;
|
||||
launcherName = spec.launcher;
|
||||
enableFeatures = lib.concatStringsSep "," activeGpu.enableFeatures;
|
||||
disableFeatures = lib.concatStringsSep "," activeGpu.disableFeatures;
|
||||
desktopItem = pkgs.makeDesktopItem {
|
||||
name = launcherName;
|
||||
desktopName = spec.desktopName;
|
||||
genericName = "Web Browser";
|
||||
exec = "${launcherName} %U";
|
||||
icon = spec.icon;
|
||||
categories = [
|
||||
"Network"
|
||||
"WebBrowser"
|
||||
];
|
||||
mimeTypes = [
|
||||
"text/html"
|
||||
"text/xml"
|
||||
"application/xhtml+xml"
|
||||
"x-scheme-handler/http"
|
||||
"x-scheme-handler/https"
|
||||
];
|
||||
};
|
||||
in
|
||||
pkgs.runCommand launcherName
|
||||
{
|
||||
inherit (browser) version;
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
passthru = { inherit browser; };
|
||||
}
|
||||
''
|
||||
mkdir -p $out/bin $out/share/applications
|
||||
|
||||
makeWrapper ${browser}/bin/${spec.binary} $out/bin/${launcherName} \
|
||||
--set LIBVA_DRIVER_NAME ${lib.escapeShellArg activeGpu.driver} \
|
||||
--set LIBVA_DRM_DEVICE ${lib.escapeShellArg activeGpu.drmDevice} \
|
||||
--set NVD_BACKEND ${lib.escapeShellArg activeGpu.nvdBackend} \
|
||||
--add-flags "--enable-features=${enableFeatures}" \
|
||||
--add-flags "--disable-features=${disableFeatures}" \
|
||||
${lib.concatMapStringsSep " " (a: "--add-flags ${lib.escapeShellArg a}") cfg.extraCommandLineArgs}
|
||||
|
||||
cp ${desktopItem}/share/applications/${launcherName}.desktop \
|
||||
$out/share/applications/${launcherName}.desktop
|
||||
'';
|
||||
|
||||
selectedPackages = lib.filter (
|
||||
name:
|
||||
let
|
||||
spec = browserCatalog.${name};
|
||||
in
|
||||
lib.meta.availableOn pkgs.stdenv.hostPlatform spec.package
|
||||
) cfg.packages;
|
||||
|
||||
wrappers = map mkChromiumHevc selectedPackages;
|
||||
in
|
||||
{
|
||||
options.chiasson.home.browsers.chromiumHevc = {
|
||||
enable = lib.mkEnableOption ''
|
||||
`google-chrome-hevc`: Chromium with VA-API HEVC for Jellyfin / MSE playback.
|
||||
|
||||
Default GPU is **Intel** (`vaapi.gpu = "intel"`): Chromium + NVIDIA VA-API is
|
||||
unsupported upstream (`nvidia-vaapi-driver` README) and fails with
|
||||
`failed Initialize()ing the frame pool` in Jellyfin.
|
||||
|
||||
Requires `chiasson.system.chromiumHevc.enable` on NixOS.
|
||||
'';
|
||||
|
||||
packages = lib.mkOption {
|
||||
type = lib.types.listOf (
|
||||
lib.types.enum (lib.attrNames browserCatalog)
|
||||
);
|
||||
default = [ "google-chrome" ];
|
||||
description = "Chromium-based browsers to wrap.";
|
||||
};
|
||||
|
||||
vaapi.gpu = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"intel"
|
||||
"nvidia"
|
||||
];
|
||||
default = "intel";
|
||||
description = ''
|
||||
VA-API stack for `google-chrome-hevc`. Use **intel** for Jellyfin (Chromium +
|
||||
nvidia-vaapi-driver is unsupported and hits frame-pool init errors). **nvidia**
|
||||
keeps renderD129 + VaapiOnNvidiaGPUs for experiments only.
|
||||
'';
|
||||
};
|
||||
|
||||
extraCommandLineArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra Chromium flags appended after the HEVC profile flags.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (root.enable && cfg.enable) {
|
||||
home.packages = wrappers;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# HM side of the flake; option tree is `chiasson.home.*` (docs/module-conventions.md).
|
||||
{ self, inputs, ... }: {
|
||||
imports = [
|
||||
./apps/discord.nix
|
||||
./apps/localsend.nix
|
||||
./apps/pokeclicker
|
||||
./apps/spotify.nix
|
||||
./browsers/orion.nix
|
||||
./desktop/screenshot.nix
|
||||
./hardware/uconsole-gamepad.nix
|
||||
];
|
||||
|
||||
# Root module: chiasson.home.enable + bash. Everything else is separate `wisdom*` exports —
|
||||
# pull those into `home.users.<name>.extraModules` on each host as needed.
|
||||
flake.homeManagerModules.wisdom =
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.chiasson.home;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
self.homeManagerModules.wisdomShellBash
|
||||
];
|
||||
|
||||
options.chiasson.home = {
|
||||
enable = lib.mkEnableOption ''
|
||||
HM profile root for this flake (bash on by default). Wire other `wisdom*` modules in
|
||||
`home.users.<name>.extraModules` and flip their `chiasson.home.*.enable` options on the host.
|
||||
'' // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
extraPackages = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.package;
|
||||
default = [ ];
|
||||
description = ''
|
||||
Extra `home.packages` (e.g. `pkgs.parsec-bin`) when you do not want a separate wisdom module.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable { home.packages = cfg.extraPackages; };
|
||||
};
|
||||
}
|
||||
@@ -21,6 +21,23 @@
|
||||
pkgs.cursor-cli
|
||||
else
|
||||
null;
|
||||
nixIdeTools = [ pkgs.nixd pkgs.nixfmt ];
|
||||
cursorWithNixIde =
|
||||
if cursorPkg == null then
|
||||
null
|
||||
else
|
||||
pkgs.symlinkJoin {
|
||||
name = "cursor-with-nix-ide";
|
||||
paths = [ cursorPkg ];
|
||||
buildInputs = [ pkgs.makeWrapper ];
|
||||
postBuild = ''
|
||||
for prog in $out/bin/*; do
|
||||
if [ -x "$prog" ]; then
|
||||
wrapProgram "$prog" --prefix PATH : "${lib.makeBinPath nixIdeTools}"
|
||||
fi
|
||||
done
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
options.chiasson.home.editors.cursor = {
|
||||
@@ -45,12 +62,28 @@
|
||||
'';
|
||||
};
|
||||
};
|
||||
nixIde = {
|
||||
enable = lib.mkEnableOption ''
|
||||
Nix IDE extension tooling (`nixd` LSP, `nixfmt` formatter).
|
||||
|
||||
Installs `nixd` / `nixfmt` and wraps Cursor so they are on the editor `PATH`
|
||||
(the GUI does not inherit your shell profile).
|
||||
'' // {
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (root.enable && cfg.enable && cursorPkg != null) {
|
||||
home.packages =
|
||||
[ cursorPkg ]
|
||||
++ lib.optionals (cfg.agent.enable && cfg.agent.package != null) [ cfg.agent.package ];
|
||||
[
|
||||
(if cfg.nixIde.enable && cursorWithNixIde != null then
|
||||
cursorWithNixIde
|
||||
else
|
||||
cursorPkg)
|
||||
]
|
||||
++ lib.optionals (cfg.agent.enable && cfg.agent.package != null) [ cfg.agent.package ]
|
||||
++ lib.optionals cfg.nixIde.enable nixIdeTools;
|
||||
home.sessionVariables = lib.mkIf cfg.setAsDefaultEditor {
|
||||
EDITOR = "cursor --wait";
|
||||
VISUAL = "cursor --wait";
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{ ... }: {
|
||||
flake.homeManagerModules.wisdomEditorsKate =
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
root = config.chiasson.home;
|
||||
cfg = config.chiasson.home.editors.kate;
|
||||
in
|
||||
{
|
||||
options.chiasson.home.editors.kate.enable = lib.mkEnableOption "Kate.";
|
||||
|
||||
config = lib.mkIf (root.enable && cfg.enable) {
|
||||
home.packages = lib.optional (
|
||||
lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.kdePackages.kate
|
||||
) pkgs.kdePackages.kate;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{ ... }: {
|
||||
flake.homeManagerModules.wisdomEditorsObsidian =
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
root = config.chiasson.home;
|
||||
cfg = config.chiasson.home.editors.obsidian;
|
||||
in
|
||||
{
|
||||
options.chiasson.home.editors.obsidian.enable = lib.mkEnableOption ''
|
||||
Obsidian (unfree); skipped if unavailable here.
|
||||
'';
|
||||
|
||||
config = lib.mkIf (root.enable && cfg.enable) {
|
||||
home.packages = lib.optional (
|
||||
lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.obsidian
|
||||
) pkgs.obsidian;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{ ... }: {
|
||||
flake.homeManagerModules.wisdomShellBash =
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
root = config.chiasson.home;
|
||||
cfg = config.chiasson.home.shell.bash;
|
||||
in
|
||||
{
|
||||
options.chiasson.home.shell.bash.enable = lib.mkEnableOption "Bash + rbw SSH socket env in profile/init." // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
config = lib.mkIf (root.enable && cfg.enable) {
|
||||
programs.bash = {
|
||||
enable = true;
|
||||
profileExtra = ''
|
||||
if [ -z "''${SSH_AUTH_SOCK:-}" ]; then
|
||||
if [ -n "''${XDG_RUNTIME_DIR:-}" ]; then
|
||||
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/rbw/ssh-agent-socket"
|
||||
else
|
||||
export SSH_AUTH_SOCK="/run/user/$(id -u)/rbw/ssh-agent-socket"
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
initExtra = ''
|
||||
if [ -z "''${SSH_AUTH_SOCK:-}" ]; then
|
||||
if [ -n "''${XDG_RUNTIME_DIR:-}" ]; then
|
||||
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/rbw/ssh-agent-socket"
|
||||
else
|
||||
export SSH_AUTH_SOCK="/run/user/$(id -u)/rbw/ssh-agent-socket"
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{ ... }: {
|
||||
flake.homeManagerModules.wisdomShellFish =
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
root = config.chiasson.home;
|
||||
cfg = config.chiasson.home.shell.fish;
|
||||
in
|
||||
{
|
||||
options.chiasson.home.shell.fish = {
|
||||
enable = lib.mkEnableOption "Fish + grc, quiet greeting, rbw SSH socket defaults.";
|
||||
|
||||
nixYourShell = {
|
||||
enable = lib.mkEnableOption ''
|
||||
Wrap `nix` / `nix-shell` so ephemeral shells use fish (and keep oh-my-posh) instead of bash.
|
||||
'' // {
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (root.enable && cfg.enable) {
|
||||
# `fishPlugins.grc` only installs the plugin; the wrapper invokes the `grc` binary.
|
||||
home.packages = [ pkgs.grc ];
|
||||
|
||||
programs.fish = {
|
||||
enable = true;
|
||||
interactiveShellInit = ''
|
||||
set fish_greeting ""
|
||||
if test -z "$SSH_AUTH_SOCK"
|
||||
if test -n "$XDG_RUNTIME_DIR"
|
||||
set -gx SSH_AUTH_SOCK "$XDG_RUNTIME_DIR/rbw/ssh-agent-socket"
|
||||
else
|
||||
set -l _hm_uid (${pkgs.coreutils}/bin/id -u)
|
||||
set -gx SSH_AUTH_SOCK "/run/user/$_hm_uid/rbw/ssh-agent-socket"
|
||||
end
|
||||
end
|
||||
'';
|
||||
plugins = [
|
||||
{
|
||||
name = "grc";
|
||||
src = pkgs.fishPlugins.grc.src;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
programs.nix-your-shell = lib.mkIf cfg.nixYourShell.enable {
|
||||
enable = true;
|
||||
enableFishIntegration = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{ ... }: {
|
||||
flake.homeManagerModules.wisdomShellOhMyPosh =
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
root = config.chiasson.home;
|
||||
cfg = config.chiasson.home.shell.ohMyPosh;
|
||||
in
|
||||
{
|
||||
options.chiasson.home.shell.ohMyPosh = {
|
||||
enable = lib.mkEnableOption "[Oh My Posh](https://ohmyposh.dev/) prompt.";
|
||||
|
||||
builtinTheme = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "jandedobbeleer";
|
||||
description = ''
|
||||
Stock theme when DMS is not overriding `configFile` (DMS replaces this with matugen output).
|
||||
'';
|
||||
};
|
||||
|
||||
enableFishIntegration = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Run `oh-my-posh init fish` in Fish `shellInit` (Home Manager).";
|
||||
};
|
||||
|
||||
enableBashIntegration = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Run `oh-my-posh init bash` in bash `initExtra`.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (root.enable && cfg.enable) {
|
||||
programs.oh-my-posh = {
|
||||
enable = true;
|
||||
inherit (cfg) enableFishIntegration enableBashIntegration;
|
||||
useTheme = lib.mkDefault cfg.builtinTheme;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{ ... }: {
|
||||
# Optional fragment: import from `home.users.<name>.extraModules` only on hosts that need Yazi so this
|
||||
# module (and its `pkgs.yazi` wiring) is not evaluated when omitted.
|
||||
flake.homeManagerModules.wisdomShellYazi =
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
root = config.chiasson.home;
|
||||
cfg = config.chiasson.home.shell.yazi;
|
||||
in
|
||||
{
|
||||
options.chiasson.home.shell.yazi.enable = lib.mkEnableOption "Yazi as `y` + 7zz with rar.";
|
||||
|
||||
config = lib.mkIf (root.enable && cfg.enable) {
|
||||
programs.yazi = {
|
||||
enable = true;
|
||||
package = pkgs.yazi.override {
|
||||
_7zz = pkgs._7zz-rar;
|
||||
};
|
||||
shellWrapperName = "y";
|
||||
settings = {
|
||||
manager = {
|
||||
ratio = [
|
||||
1
|
||||
4
|
||||
3
|
||||
];
|
||||
sort_by = "natural";
|
||||
sort_sensitive = true;
|
||||
sort_reverse = false;
|
||||
sort_dir_first = true;
|
||||
linemode = "none";
|
||||
show_hidden = true;
|
||||
show_symlink = true;
|
||||
};
|
||||
preview = {
|
||||
image_filter = "lanczos3";
|
||||
image_quality = 90;
|
||||
tab_size = 1;
|
||||
max_width = 600;
|
||||
max_height = 900;
|
||||
cache_dir = "";
|
||||
ueberzug_scale = 1;
|
||||
ueberzug_offset = [
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
];
|
||||
};
|
||||
tasks = {
|
||||
micro_workers = 5;
|
||||
macro_workers = 10;
|
||||
bizarre_retry = 5;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{ ... }: {
|
||||
flake.homeManagerModules.wisdomTerminalsKitty =
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
root = config.chiasson.home;
|
||||
cfg = config.chiasson.home.terminals.kitty;
|
||||
in
|
||||
{
|
||||
options.chiasson.home.terminals.kitty.enable = lib.mkEnableOption ''
|
||||
Kitty + DMS includes; activation strips `*-theme.auto.conf` so matugen colors stick.
|
||||
'';
|
||||
|
||||
config = lib.mkIf (root.enable && cfg.enable) {
|
||||
programs.kitty = {
|
||||
enable = true;
|
||||
extraConfig = ''
|
||||
include dank-tabs.conf
|
||||
include dank-theme.conf
|
||||
allow_remote_control yes
|
||||
listen_on unix:@kitty
|
||||
'';
|
||||
};
|
||||
|
||||
home.activation.kittyRemoveAutoThemes = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
rm -f "$HOME/.config/kitty/dark-theme.auto.conf" \
|
||||
"$HOME/.config/kitty/light-theme.auto.conf" \
|
||||
"$HOME/.config/kitty/no-preference-theme.auto.conf"
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
+35
-34
File diff suppressed because one or more lines are too long
+31
-31
@@ -15,56 +15,56 @@ sops:
|
||||
- recipient: age1yyzgmazjxkvwtfcv9re3lqmt2ru5dcrfu3sauysm0wzfwzvyap8qkjkq32
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDT3hZams2RW9tYnpuclNU
|
||||
V1BSN2E3L1VicldXb2E4U2cwMEMvekw4MDJRCklyTlhwa3FhVTNibVNkcTFoNThC
|
||||
MzJKdFdhMlRqVlpvQjVmbmo0Z1JuZ3MKLS0tIHcwTk10WklKU3ZEYWg4dlVuRTFy
|
||||
T1NKaW1CdEppOU44c2t4Tm45MzhCRTAKeUTQB64sXl2NT3VlQqfDZVH1iqGI0HMN
|
||||
egV/S6pqKV0mC4aKrn2AKQUws4E6XF60z7ICM2ouBh+uth5xQ4U42g==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtc25Wd0dlZldBVHZKTDRW
|
||||
ZlVHamluT0hPbVArdWx0Qlk5czR6ZldIaEhRCnVkUVp6cHRwdnNsRDBhV285Q0tV
|
||||
RitNWHNrUy9pN0dpanFSVHJjK0VuM1kKLS0tIHVLRWFRNmZyR2JOR3FwYjZsME9v
|
||||
L2ZKbFIydkJ5L25GSWFBamhtbHZXV0UKAPIGMBbIdR7sCxsCYJa/9kajqmceOAJa
|
||||
/jjxxcaWDkv5cmq0u8qFZEDkTjY3PKoJofBcx5q0npuJmrIA/oLeJA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1elk6zwmcylwfk7gd4pjda7g29upftjvxys8py42s8d42jklnyv7s7dm9z2
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIN1Zkd0FFS2FkbEZVQWhM
|
||||
aWhka2lWY0tIbEtGK25maGtVcnRmL09Yc0NrCnF1RnYyakozM1RxTC91UUtlVHRN
|
||||
TTRxdTBFaHJ4RXRvQ3RFN3VXV1FDTzgKLS0tIEdYNkhEc21rdFF3eldOaFFTQWNR
|
||||
NUtkbTdVd0JNeEd4ZVBDb3YybGpUVUUKKhyOptpf1xD4z2nEYH2QKfEC0RPI9TSy
|
||||
XiQ66Wzh1fkdqTlOkNdRzBniSObKg03oRaiZVXBtPx3iIrZw0XwywA==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvcm5rZmtlUy9yQSs3Vnhh
|
||||
eDVYaUxvcCtMYXhEa0ViTmFYK3JONnFsTWw0Cmdad2ZKdGcxeGQzeGVnTXRUeGJZ
|
||||
R1kxQ1ZDK0FsQzc4S1EwMFN4VVZaTzAKLS0tIDJ5S3QycG5ZdThIVlZEVjBnMG9K
|
||||
V1RSem1LdWpKT2o2aHh5dC9wVGJRakUK+5/eE+9WtSXmwoJ2Nqk4ni01GS4c3gLQ
|
||||
p+wcpiOsxwnnisZTxag2yCn4hlv6FcOUWOcISq5H/sxwKgjBaeeuRQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age193gw802ytal7h5p5q37kpd9079k2vsflzmnvupcwfxh2kjdrwqtsk3g6rm
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxUlFTVmQwbGVCWjQxbjF4
|
||||
bUcvSTRZY3hMTFdiaUlwN3hCb2QrWEhETlJZCjlVN3JDR0xUbFI4ZjFwVXowUmVE
|
||||
WklhVEI1Vjh6Ukg3VzlSSXM0WWtHVkEKLS0tIDloaDB5VGxVYmhSU3hjbE4vZ3Zi
|
||||
bTFaVVcyRVJjQ1dFVm9ITnhYRzBKbGcKAIw03NgRWOsmd/Kc8r8j+8fRRA3syHCz
|
||||
fmMgs8w9lpuJj24XDmcpIkN48PnCTV5XzTuEg5nvIJdb3TG2t0KWvA==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6TjZVN3dFbmg5MGhNVFdD
|
||||
YTZ4WVdOUWRGTzkrUmRGVUpobmg0Y0ozaUFNClh6cXhKWVkxcTlRYmZ5NmNqeEJJ
|
||||
YzdYR1FyRExabXBDSnVSbXNxZy91am8KLS0tIHZLa0dXWHZhMDU5Q3BNR2U3NG00
|
||||
eFp4Vmxha0xOVTVwd09PVzgrdzFNL2sKDEofAS4W4i8+VBU0wl1yTWmOogNbGHhY
|
||||
azvb0QmxrYpurxjep3BYsc/5Co6U4mwowidoyzQLsiBJWDWy3wPdLQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1yr7vurfxc3w8ewfw9djfm54atw6ayze69qglamecuft5q0n9gu2sadsa2m
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxSkdOakRYSm5FOFEwRzdW
|
||||
UmZsREltNlhYdDhKWkE0blV0NVVwRWdvczBVCmVhSjB5Ymx4SEJ5S1NaU2RNSzV1
|
||||
RjRjQzRpaHlMeE9ZbVozQTBiVmVncUEKLS0tIDNTTE9JMG9jczE4cGI3TW1aVk9o
|
||||
anN5Wmgzd1NnWEFIY1lMU205REZjOHcKI3Jjobw0KmN6gK85QUsSW3m6IDtC960K
|
||||
eO6fk2WHT1jSPjWH87JuqNzbrkR7XJRB2CW+MXIDDb8h7euLWp7Png==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2d3dLVitDTVpyNllCTEJN
|
||||
R1RLc2NkRnBrandDakhxeTJWeEdMVkZyS0JRCnl5Uk9FSk1iZmxCZUI1ZWdmYlJa
|
||||
dEFlbjkxYjI4Y2FDZmswOGZqMUxGUXcKLS0tIGdqVWNaU1NWcGs4QXZyTkc5Z2VK
|
||||
MGpaWmtHdzdpRDJxQXNveTd5WFkzTTgKxKuoC36uLqy+QoSGVcQekv5wn69yF0qH
|
||||
2qZPAm3wnf0KzZ8Wo/B8nXkjkq4llfKHbwfePiMRL4RObKXAejYhLA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1m30m9xzszmcawte35m0yymz42gfx3x84w7d5l67mtdtajhgpfgssuc2plm
|
||||
- recipient: age1hya7pgpe8zal52w3pjf036tpapmehedatfm4r84h30t4wuh079ssedfd37
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvRmZ5ZFAweWFuWFJrSDJY
|
||||
SFJUR0FPYnZXdGVucVpBRUZRVEFhSktlMFZzCmg4T1VTY3l4cm1BK3ZGRC80ckRI
|
||||
RjFHWllwamdEanAwU3BjNjgyZGR2bmcKLS0tIEUxK2NDV0IvRW0ycjA5cFdWaVhj
|
||||
L0ZJODdNTjJiNDNqd3k2eGs4SktBN1kKhPba1fZ/fIMr6ys+sUc4bi71O/oE9Cns
|
||||
7tSKVUXUnP5aNSW217gMwPBoc/5vTl92cYJaGKFlQ2IpECCYJKCi7g==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4am5wWWRkL3J0dlZVaWJR
|
||||
eVdMNnhTdGJqeFlNdnppTHNLdjdjcVNYbUQ4CjcwdGM5Nmdvck1wb0RSRjMwL01D
|
||||
STlFdkdDallLLzNnbGY4WndiLzdRMzAKLS0tIHRENzRFTVA2MWE5djVHUy9peVdG
|
||||
dkZDTTJvWU1vZHN2ZUR1R1Z3UWpkSlEKjo621j7xjyyHcc/ij8/X0H5uNLD6SnZa
|
||||
o+apPj44+fTJFL5+VjP/XOsx0rCKTQhnV0gGfZU2SPtfH2BUiDjV0g==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1p05z980kdtngk9mw67hfev72h7xhslplpxfk9yskgmf0hl4lu3ls04zht9
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiMDh5MmVyOHlJaUZiRVc4
|
||||
RENYRDBORExRb1NETld4TnZkZ21DR0NIRm13ClVQeWVaNWYxc3FTcjFRcnRYZm1R
|
||||
a2loWlpoYlc4bzc4Z2V0dWd5V0d2c2cKLS0tIG0wUGxTQUxqb0Z1cGZYSHpjQWRW
|
||||
am1lYUp4VFpmKzJESjMrdUZwVFN0MWcK7wHDAEqHcMWcBcZyw+wL1dWH7R9xFq5H
|
||||
grGFxgoPv2sn+4eQNKagC7jACm+l2vUFX5UuH0qJRVTpatXHnYb6SA==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5YmF1K1ZtQmhoMGRVM0Fk
|
||||
bW1PL0RPQ1ZUZEg4VU5VUi9BdlNhM2hPZkRNCnVIaUJJR1dFeXA1SWk2UFBkQW9a
|
||||
cjc4bFRDb2p0eXJodG5IWk01ekdCdG8KLS0tIHFzU3l3WGcyTmZ0QjRyTmQwcDRZ
|
||||
Mnp5K1VjcncrWWt4SEUrUVlCTTc3OVkKtRqPoatEp8NvZW4Z73nfCUshdz90SCad
|
||||
VFgYF/2DYc7lSDP7otbsjBzGlauQQTWF1wfgEVOkw2EzOt2LCoflbg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2026-03-24T00:15:02Z"
|
||||
mac: ENC[AES256_GCM,data:dYTwO5DtkKinTKfBXGuvXRFxl8yavxXMKTw27M5/GcK/kkstHBG119IRk9B9KC6s6IHTY81U3MeUxE9XwdBiE7q4m15+ZO2vmdBVhN8wAh+82P9BP0HSaxLkjWLeKWBfULyLX/YXmQVsr09/NUEVSZcugJ6m40Ta+X9AQgO+cyA=,iv:FmsznsKTuIr61s3Zn0QZKSKvb/e2AljEB1ijKE52RKk=,tag:rHF2Xi4iP9VF33rxpBr5pg==,type:str]
|
||||
|
||||
Reference in New Issue
Block a user