authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-05-15 01:48:42-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-06-09 22:27:46-07:00
logb1b433e6c16b2ca52b33bbe8aea31734aa23212c
tree31a702ff97d856c39357a91c61fea712f7733f4b
parentf60c39943834cc26bc64c715c9b94d541e98526a
signature Signed by SSH key SHA256:xbd+BjjhyBfwk7GVoURf9Yx0gzDerHbvYv7SddNWmAs

feat: random terrifying stuff + a backup systsem


12 files changed, 289 insertions(+), 103 deletions(-)

modules/home/default.nix+6
...@@ -59,6 +59,12 @@ in...@@ -59,6 +59,12 @@ in
59 home.shell = {59 home.shell = {
60 enableShellIntegration = true;60 enableShellIntegration = true;
61 };61 };
62 home.sessionPath = [
63 "$HOME/.local/bin"
64 "$HOME/.opencode/bin"
65 "$HOME/.vite-plus/bin"
66 "$HOME/bin"
67 ];
62 programs = {68 programs = {
63 home-manager.enable = true;69 home-manager.enable = true;
64 nix-index.enable = true;70 nix-index.enable = true;
packages/autofmt/default.nix+9-7
...@@ -15,11 +15,13 @@ pkgs.writeTextFile {...@@ -15,11 +15,13 @@ pkgs.writeTextFile {
15 name = "autofmt";15 name = "autofmt";
16 destination = "/bin/autofmt";16 destination = "/bin/autofmt";
17 executable = true;17 executable = true;
18 text = builtins.replaceStrings18 text =
19 [ "#!/usr/bin/env node" ]19 builtins.replaceStrings
20 [20 [ "#!/usr/bin/env node" ]
21 ''#!${pkgs.nodejs}/bin/node21 [
22 process.env.PATH = "${pkgs.lib.makeBinPath runtimeInputs}:" + (process.env.PATH ?? "");''22 ''
23 ]23 #!${pkgs.nodejs}/bin/node
24 (builtins.readFile ./autofmt.js);24 process.env.PATH = "${pkgs.lib.makeBinPath runtimeInputs}:" + (process.env.PATH ?? "");''
25 ]
26 (builtins.readFile ./autofmt.js);
25}27}
packages/jj-import-merge/jj-import-merge.js+42-13
...@@ -86,10 +86,14 @@ function usage(code) {...@@ -86,10 +86,14 @@ function usage(code) {
86 const stream = code === 0 ? process.stdout : process.stderr;86 const stream = code === 0 ? process.stdout : process.stderr;
87 stream.write(`usage: ${exe} [--all-jj] [--] <file>...\n`);87 stream.write(`usage: ${exe} [--all-jj] [--] <file>...\n`);
88 stream.write(`\n`);88 stream.write(`\n`);
89 stream.write(`merge top-of-file JS/TS imports from 2-sided Jujutsu conflicts\n`);89 stream.write(
90 `merge top-of-file JS/TS imports from 2-sided Jujutsu conflicts\n`,
91 );
90 stream.write(`\n`);92 stream.write(`\n`);
91 stream.write(`options:\n`);93 stream.write(`options:\n`);
92 stream.write(` --all-jj resolve all conflicted JS/TS files from 'jj resolve --list'\n`);94 stream.write(
95 ` --all-jj resolve all conflicted JS/TS files from 'jj resolve --list'\n`,
96 );
93 stream.write(` -h show help\n`);97 stream.write(` -h show help\n`);
94 process.exit(code);98 process.exit(code);
95}99}
...@@ -158,7 +162,11 @@ function processFile(file) {...@@ -158,7 +162,11 @@ function processFile(file) {
158 leftImports.start,162 leftImports.start,
159 rightImports.start,163 rightImports.start,
160 );164 );
161 const end = projectBoundaryToOriginal(parsed.parts, leftImports.end, rightImports.end);165 const end = projectBoundaryToOriginal(
166 parsed.parts,
167 leftImports.end,
168 rightImports.end,
169 );
162 if (start == null || end == null || end < start) {170 if (start == null || end == null || end < start) {
163 return { kind: "noop" };171 return { kind: "noop" };
164 }172 }
...@@ -168,7 +176,8 @@ function processFile(file) {...@@ -168,7 +176,8 @@ function processFile(file) {
168 rightImports.statements,176 rightImports.statements,
169 detectNewline(original),177 detectNewline(original),
170 );178 );
171 const updated = original.slice(0, start) + mergedImports + original.slice(end);179 const updated = original.slice(0, start) + mergedImports +
180 original.slice(end);
172181
173 if (updated === original) {182 if (updated === original) {
174 return { kind: "noop" };183 return { kind: "noop" };
...@@ -193,7 +202,9 @@ function processFile(file) {...@@ -193,7 +202,9 @@ function processFile(file) {
193 if (formatResult.error || formatResult.status !== 0) {202 if (formatResult.error || formatResult.status !== 0) {
194 fs.writeFileSync(file, original, "utf8");203 fs.writeFileSync(file, original, "utf8");
195 if (formatResult.error) {204 if (formatResult.error) {
196 throw new Error(`autofmt failed for ${file}: ${formatResult.error.message}`);205 throw new Error(
206 `autofmt failed for ${file}: ${formatResult.error.message}`,
207 );
197 }208 }
198 throw new Error(`autofmt failed for ${file}`);209 throw new Error(`autofmt failed for ${file}`);
199 }210 }
...@@ -259,7 +270,9 @@ function parseConflictBlock(lines, startIndex) {...@@ -259,7 +270,9 @@ function parseConflictBlock(lines, startIndex) {
259 continue;270 continue;
260 }271 }
261 if (line.startsWith("%%%%%%% diff from:")) {272 if (line.startsWith("%%%%%%% diff from:")) {
262 if (index + 1 >= lines.length || !/^\\\\+\s*to:/.test(lines[index + 1].text)) {273 if (
274 index + 1 >= lines.length || !/^\\\\+\s*to:/.test(lines[index + 1].text)
275 ) {
263 return { supported: false, nextIndex: startIndex + 1 };276 return { supported: false, nextIndex: startIndex + 1 };
264 }277 }
265 index += 2;278 index += 2;
...@@ -512,7 +525,7 @@ function scanLine(line, state) {...@@ -512,7 +525,7 @@ function scanLine(line, state) {
512 index += 1;525 index += 1;
513 continue;526 continue;
514 }527 }
515 if (char === "'" || char === "\"" || char === "`") {528 if (char === "'" || char === '"' || char === "`") {
516 state.stringQuote = char;529 state.stringQuote = char;
517 continue;530 continue;
518 }531 }
...@@ -716,12 +729,18 @@ function canMergeImports(left, right) {...@@ -716,12 +729,18 @@ function canMergeImports(left, right) {
716 }729 }
717730
718 const defaultBinding = mergeField(left.defaultBinding, right.defaultBinding);731 const defaultBinding = mergeField(left.defaultBinding, right.defaultBinding);
719 const namespaceImport = mergeField(left.namespaceImport, right.namespaceImport);732 const namespaceImport = mergeField(
733 left.namespaceImport,
734 right.namespaceImport,
735 );
720 if (defaultBinding === undefined || namespaceImport === undefined) {736 if (defaultBinding === undefined || namespaceImport === undefined) {
721 return false;737 return false;
722 }738 }
723739
724 const namedSpecifiers = unique([...left.namedSpecifiers, ...right.namedSpecifiers]);740 const namedSpecifiers = unique([
741 ...left.namedSpecifiers,
742 ...right.namedSpecifiers,
743 ]);
725 if (namespaceImport && namedSpecifiers.length > 0) {744 if (namespaceImport && namedSpecifiers.length > 0) {
726 return false;745 return false;
727 }746 }
...@@ -731,8 +750,14 @@ function canMergeImports(left, right) {...@@ -731,8 +750,14 @@ function canMergeImports(left, right) {
731750
732function mergeImports(left, right) {751function mergeImports(left, right) {
733 const defaultBinding = mergeField(left.defaultBinding, right.defaultBinding);752 const defaultBinding = mergeField(left.defaultBinding, right.defaultBinding);
734 const namespaceImport = mergeField(left.namespaceImport, right.namespaceImport);753 const namespaceImport = mergeField(
735 const namedSpecifiers = unique([...left.namedSpecifiers, ...right.namedSpecifiers]);754 left.namespaceImport,
755 right.namespaceImport,
756 );
757 const namedSpecifiers = unique([
758 ...left.namedSpecifiers,
759 ...right.namedSpecifiers,
760 ]);
736761
737 return {762 return {
738 kind: "binding",763 kind: "binding",
...@@ -782,13 +807,17 @@ function renderImport(analysis, newline) {...@@ -782,13 +807,17 @@ function renderImport(analysis, newline) {
782 pieces.push(`{ ${analysis.namedSpecifiers[0]} }`);807 pieces.push(`{ ${analysis.namedSpecifiers[0]} }`);
783 } else {808 } else {
784 pieces.push(809 pieces.push(
785 `{${newline}${analysis.namedSpecifiers.map((spec) => ` ${spec},`).join(newline)}${newline}}`,810 `{${newline}${
811 analysis.namedSpecifiers.map((spec) => ` ${spec},`).join(newline)
812 }${newline}}`,
786 );813 );
787 }814 }
788 }815 }
789816
790 return ensureTrailingNewline(817 return ensureTrailingNewline(
791 `import ${analysis.isTypeImport ? "type " : ""}${pieces.join(", ")} from ${analysis.quote}${analysis.source}${analysis.quote}${analysis.attributes};`,818 `import ${analysis.isTypeImport ? "type " : ""}${
819 pieces.join(", ")
820 } from ${analysis.quote}${analysis.source}${analysis.quote}${analysis.attributes};`,
792 newline,821 newline,
793 );822 );
794}823}
switch+6-3
...@@ -12,13 +12,16 @@ if [ -z "$hostname" ]; then...@@ -12,13 +12,16 @@ if [ -z "$hostname" ]; then
12 hostname="$(hostname)"12 hostname="$(hostname)"
13fi13fi
14printf "%s" "$hostname" >.hostname14printf "%s" "$hostname" >.hostname
15if [ "$(uname -o)" = "Darwin" ]; then15if [ "$(uname -s)" = "Darwin" ]; then
16 nh_subcommand="darwin"16 nh_subcommand="darwin"
17 fallback_command="nix run .#darwin-rebuild"17 fallback_command="nix run .#darwin-rebuild"
18 hostname="darwinConfigurations.$hostname"
19fi18fi
20if command -v nh >/dev/null; then19if command -v nh >/dev/null; then
21 nh $nh_subcommand switch '.#'"$hostname"20 if [ "$nh_subcommand" = "darwin" ]; then
21 nh darwin switch . --hostname "$hostname"
22 else
23 nh os switch '.#'"$hostname"
24 fi
22else25else
23 $fallback_command -- switch --flake '.#'"$hostname"26 $fallback_command -- switch --flake '.#'"$hostname"
24fi27fi
users/clover/AGENTS.md+4
...@@ -13,6 +13,10 @@ You are working with Clover, an intelligent web/systems engineer. Please avoid e...@@ -13,6 +13,10 @@ You are working with Clover, an intelligent web/systems engineer. Please avoid e
13- Never include a section on checks saying "pnpm test passed" or "checks passed" or whatever. Just make sure your validation is strong in the first place; You're expected to have that done.13- Never include a section on checks saying "pnpm test passed" or "checks passed" or whatever. Just make sure your validation is strong in the first place; You're expected to have that done.
14- Feedback should be useful and actionable. A couple words is OK if there is genuinely nothing to add.14- Feedback should be useful and actionable. A couple words is OK if there is genuinely nothing to add.
1515
16## Verification
17
18- Use the `code-reviewer` agent for all verification after meaningful code, config, test, or doc changes. The top-level agent should pass only a concise list of what it did, including changed files and intended behavior; the reviewer independently inspects the diff, runs relevant checks, and reports findings. The `code-reviewer` agent is told it is the reviewer agent, so it must not recurse into itself.
19
16## Comments20## Comments
1721
18Comments must always be placed and edited with intent to serialize codebase theory for future versions of ourselves and coworkers. They are professionally written, containing relevant context to the current iteration. Clover's comments are always timeless and rarely written from the first person (no `I`, `we`) -- refer directly to the code. Rewording comments this way helps solidify your own understanding, and can even reveal logic bugs in the designed systems. Prefer documentation comments over line comments.22Comments must always be placed and edited with intent to serialize codebase theory for future versions of ourselves and coworkers. They are professionally written, containing relevant context to the current iteration. Clover's comments are always timeless and rarely written from the first person (no `I`, `we`) -- refer directly to the code. Rewording comments this way helps solidify your own understanding, and can even reveal logic bugs in the designed systems. Prefer documentation comments over line comments.
users/clover/home.nix-4
...@@ -10,10 +10,6 @@ in...@@ -10,10 +10,6 @@ in
10{10{
11 home = {11 home = {
12 stateVersion = "23.05"; # Please read the comment before changing.12 stateVersion = "23.05"; # Please read the comment before changing.
13 sessionPath = [
14 "$HOME/.vite-plus/bin"
15 "$HOME/.local/bin"
16 ];
17 packages =13 packages =
18 with pkgs;14 with pkgs;
19 let15 let
users/clover/paperback/configuration.nix+5-1
...@@ -1,2 +1,6 @@...@@ -1,2 +1,6 @@
1_: {1{ lib, ... }:
2{
3 nix.enable = false;
4 nix.optimise.automatic = lib.mkForce false;
5 nix.gc.automatic = lib.mkForce false;
2}6}
users/clover/pnpm-lock.yaml+62-44
...@@ -1,99 +1,117 @@...@@ -1,99 +1,117 @@
1lockfileVersion: '9.0'1lockfileVersion: "9.0"
22
3settings:3settings:
4 autoInstallPeers: true4 autoInstallPeers: true
5 excludeLinksFromLockfile: false5 excludeLinksFromLockfile: false
66
7importers:7importers:
8
9 .:8 .:
10 dependencies:9 dependencies:
11 '@clo/lib':10 "@clo/lib":
12 specifier: jsr:*11 specifier: jsr:*
13 version: '@jsr/clo__lib@4.0.0'12 version: "@jsr/clo__lib@4.0.0"
14 '@lydell/node-pty':13 "@lydell/node-pty":
15 specifier: 1.2.0-beta.1214 specifier: 1.2.0-beta.12
16 version: 1.2.0-beta.1215 version: 1.2.0-beta.12
17 '@types/node':16 "@types/node":
18 specifier: ^24.12.217 specifier: ^24.12.2
19 version: 24.12.218 version: 24.12.2
2019
21packages:20packages:
2221 "@jsr/clo__lib@4.0.0":
23 '@jsr/clo__lib@4.0.0':22 resolution: {
24 resolution: {integrity: sha512-uOzRjwbFn9HizCpJeqPXzuiri3hqqcePNLA7ESi1Vgwon9dOmhMSzHfmb9kz599VsKF1RLH7i3NK302K60Oe/A==, tarball: https://npm.jsr.io/~/11/@jsr/clo__lib/4.0.0.tgz}23 integrity: sha512-uOzRjwbFn9HizCpJeqPXzuiri3hqqcePNLA7ESi1Vgwon9dOmhMSzHfmb9kz599VsKF1RLH7i3NK302K60Oe/A==,
2524 tarball: https://npm.jsr.io/~/11/@jsr/clo__lib/4.0.0.tgz,
26 '@lydell/node-pty-darwin-arm64@1.2.0-beta.12':25 }
27 resolution: {integrity: sha512-tqaifcY9Cr41SblO1+FLzh8oxxtkNhuW9Dhl22lKme9BreYvKvxEZcdPIXTuqkJc5tagOEC4QHShKmJjLyLXLQ==}26
27 "@lydell/node-pty-darwin-arm64@1.2.0-beta.12":
28 resolution: {
29 integrity: sha512-tqaifcY9Cr41SblO1+FLzh8oxxtkNhuW9Dhl22lKme9BreYvKvxEZcdPIXTuqkJc5tagOEC4QHShKmJjLyLXLQ==,
30 }
28 cpu: [arm64]31 cpu: [arm64]
29 os: [darwin]32 os: [darwin]
3033
31 '@lydell/node-pty-darwin-x64@1.2.0-beta.12':34 "@lydell/node-pty-darwin-x64@1.2.0-beta.12":
32 resolution: {integrity: sha512-4LrS5pCJwqHKDVf1zS2gyNV0m4hKAXch+XZNhbZ6LY8uwVL8BhchzQBO40Os5anuRxRCWzHpw4Sp64Ie8q7E4Q==}35 resolution: {
36 integrity: sha512-4LrS5pCJwqHKDVf1zS2gyNV0m4hKAXch+XZNhbZ6LY8uwVL8BhchzQBO40Os5anuRxRCWzHpw4Sp64Ie8q7E4Q==,
37 }
33 cpu: [x64]38 cpu: [x64]
34 os: [darwin]39 os: [darwin]
3540
36 '@lydell/node-pty-linux-arm64@1.2.0-beta.12':41 "@lydell/node-pty-linux-arm64@1.2.0-beta.12":
37 resolution: {integrity: sha512-Sx+A71x5BDGHt9ansfrtGxwq2VFVDWvJUAdlUL0Hv0qeiJUfts+hgopx+CgT4PSwahKjdEgtu0+FAfY9rICKRw==}42 resolution: {
43 integrity: sha512-Sx+A71x5BDGHt9ansfrtGxwq2VFVDWvJUAdlUL0Hv0qeiJUfts+hgopx+CgT4PSwahKjdEgtu0+FAfY9rICKRw==,
44 }
38 cpu: [arm64]45 cpu: [arm64]
39 os: [linux]46 os: [linux]
4047
41 '@lydell/node-pty-linux-x64@1.2.0-beta.12':48 "@lydell/node-pty-linux-x64@1.2.0-beta.12":
42 resolution: {integrity: sha512-bJzs94njofYhGg/UDqW1nj0dtvvu+2OvxMY+RlLS1T17VgcktKoIR6PuenTwE5HJ/D6StCPADmXcT0nNsCKmIQ==}49 resolution: {
50 integrity: sha512-bJzs94njofYhGg/UDqW1nj0dtvvu+2OvxMY+RlLS1T17VgcktKoIR6PuenTwE5HJ/D6StCPADmXcT0nNsCKmIQ==,
51 }
43 cpu: [x64]52 cpu: [x64]
44 os: [linux]53 os: [linux]
4554
46 '@lydell/node-pty-win32-arm64@1.2.0-beta.12':55 "@lydell/node-pty-win32-arm64@1.2.0-beta.12":
47 resolution: {integrity: sha512-p7POgjVEiFaBC3/y+AKuV1FzePCsJ6HmZDv2XK+jBZSfwP8+uBAw181ZiKYN1YuRa/XpmBGaWezcI8hZkbW++g==}56 resolution: {
57 integrity: sha512-p7POgjVEiFaBC3/y+AKuV1FzePCsJ6HmZDv2XK+jBZSfwP8+uBAw181ZiKYN1YuRa/XpmBGaWezcI8hZkbW++g==,
58 }
48 cpu: [arm64]59 cpu: [arm64]
49 os: [win32]60 os: [win32]
5061
51 '@lydell/node-pty-win32-x64@1.2.0-beta.12':62 "@lydell/node-pty-win32-x64@1.2.0-beta.12":
52 resolution: {integrity: sha512-IDFa00g7qUDGUYgByrUBJtC+mOjYVt/8KYyWivCg5JjGOHbBUACUQZLl0jTWmnr+tld/UyTpX90a2PY6oTVtRw==}63 resolution: {
64 integrity: sha512-IDFa00g7qUDGUYgByrUBJtC+mOjYVt/8KYyWivCg5JjGOHbBUACUQZLl0jTWmnr+tld/UyTpX90a2PY6oTVtRw==,
65 }
53 cpu: [x64]66 cpu: [x64]
54 os: [win32]67 os: [win32]
5568
56 '@lydell/node-pty@1.2.0-beta.12':69 "@lydell/node-pty@1.2.0-beta.12":
57 resolution: {integrity: sha512-qIK890UwPupoj07osVvgOIa++1mxeHbcGry4PKRHhNVNs81V2SCG34eJr46GybiOmBtc8Sj5PB1/GGM5PL549g==}70 resolution: {
71 integrity: sha512-qIK890UwPupoj07osVvgOIa++1mxeHbcGry4PKRHhNVNs81V2SCG34eJr46GybiOmBtc8Sj5PB1/GGM5PL549g==,
72 }
5873
59 '@types/node@24.12.2':74 "@types/node@24.12.2":
60 resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==}75 resolution: {
76 integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==,
77 }
6178
62 undici-types@7.16.0:79 undici-types@7.16.0:
63 resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}80 resolution: {
81 integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==,
82 }
6483
65snapshots:84snapshots:
85 "@jsr/clo__lib@4.0.0": {}
6686
67 '@jsr/clo__lib@4.0.0': {}87 "@lydell/node-pty-darwin-arm64@1.2.0-beta.12":
68
69 '@lydell/node-pty-darwin-arm64@1.2.0-beta.12':
70 optional: true88 optional: true
7189
72 '@lydell/node-pty-darwin-x64@1.2.0-beta.12':90 "@lydell/node-pty-darwin-x64@1.2.0-beta.12":
73 optional: true91 optional: true
7492
75 '@lydell/node-pty-linux-arm64@1.2.0-beta.12':93 "@lydell/node-pty-linux-arm64@1.2.0-beta.12":
76 optional: true94 optional: true
7795
78 '@lydell/node-pty-linux-x64@1.2.0-beta.12':96 "@lydell/node-pty-linux-x64@1.2.0-beta.12":
79 optional: true97 optional: true
8098
81 '@lydell/node-pty-win32-arm64@1.2.0-beta.12':99 "@lydell/node-pty-win32-arm64@1.2.0-beta.12":
82 optional: true100 optional: true
83101
84 '@lydell/node-pty-win32-x64@1.2.0-beta.12':102 "@lydell/node-pty-win32-x64@1.2.0-beta.12":
85 optional: true103 optional: true
86104
87 '@lydell/node-pty@1.2.0-beta.12':105 "@lydell/node-pty@1.2.0-beta.12":
88 optionalDependencies:106 optionalDependencies:
89 '@lydell/node-pty-darwin-arm64': 1.2.0-beta.12107 "@lydell/node-pty-darwin-arm64": 1.2.0-beta.12
90 '@lydell/node-pty-darwin-x64': 1.2.0-beta.12108 "@lydell/node-pty-darwin-x64": 1.2.0-beta.12
91 '@lydell/node-pty-linux-arm64': 1.2.0-beta.12109 "@lydell/node-pty-linux-arm64": 1.2.0-beta.12
92 '@lydell/node-pty-linux-x64': 1.2.0-beta.12110 "@lydell/node-pty-linux-x64": 1.2.0-beta.12
93 '@lydell/node-pty-win32-arm64': 1.2.0-beta.12111 "@lydell/node-pty-win32-arm64": 1.2.0-beta.12
94 '@lydell/node-pty-win32-x64': 1.2.0-beta.12112 "@lydell/node-pty-win32-x64": 1.2.0-beta.12
95113
96 '@types/node@24.12.2':114 "@types/node@24.12.2":
97 dependencies:115 dependencies:
98 undici-types: 7.16.0116 undici-types: 7.16.0
99117
users/clover/sandwich/home.nix+77-2
...@@ -1,7 +1,82 @@...@@ -1,7 +1,82 @@
1{ pkgs, ... }:1{ config, pkgs, ... }:
2{2let
3 # Hourly backup: rsync the local working drives up to the NAS, then (only on a
4 # fully clean push) trigger a ZFS snapshot + tiered prune on the NAS. The backup
5 # on the NAS is plain browsable files; history lives in ZFS snapshots.
6 sandwich-backup = pkgs.writeShellApplication {
7 name = "sandwich-backup";
8 runtimeInputs = with pkgs; [
9 rsync
10 openssh
11 coreutils
12 ];
13 text = ''
14 NAS=clo@git.paperclover.net
15 DEST=$NAS:/mnt/storage1/backup/sandwich
16 LOCK=/tmp/sandwich-backup.lock
17
18 # Dedicated passphraseless key so the launchd job runs unattended; the
19 # interactive key keeps its passphrase. IdentitiesOnly stops ssh from
20 # offering the passphrased key first.
21 SSH=(ssh -i "$HOME/.ssh/id_sandwich_backup" -o IdentitiesOnly=yes -o BatchMode=yes)
22
23 # Single-instance guard: the initial sync takes hours, far longer than the
24 # hourly interval. mkdir is atomic; recover if a previous run died uncleanly.
25 if ! mkdir "$LOCK" 2>/dev/null; then
26 if [ -f "$LOCK/pid" ] && kill -0 "$(cat "$LOCK/pid")" 2>/dev/null; then
27 echo "already running (pid $(cat "$LOCK/pid"))"
28 exit 0
29 fi
30 echo "clearing stale lock"
31 rm -rf "$LOCK"
32 mkdir "$LOCK"
33 fi
34 echo $$ >"$LOCK/pid"
35 trap 'rm -rf "$LOCK"' EXIT
36
37 # rsync exit 24 (source files vanished mid-transfer) is benign on a live
38 # machine; anything else is a real failure and aborts before the snapshot.
39 rsync_one() {
40 local rc=0
41 rsync -a --delete --partial --human-readable --stats \
42 -e "''${SSH[*]}" \
43 --exclude '.DS_Store' \
44 --exclude '.Spotlight-V100' \
45 --exclude '.Trashes' \
46 --exclude '.fseventsd' \
47 --exclude '.TemporaryItems' \
48 --exclude '.DocumentRevisions-V100' \
49 "$@" || rc=$?
50 if [ "$rc" -ne 0 ] && [ "$rc" -ne 24 ]; then
51 return "$rc"
52 fi
53 }
354
55 rsync_one /Volumes/Asset/ "$DEST/Asset/"
56 rsync_one /Volumes/Project/ "$DEST/Project/"
57 rsync_one --exclude 'LMStudio/' /Volumes/Documents/ "$DEST/Documents/"
58 rsync_one "$HOME/Desktop/" "$DEST/Desktop/"
59
60 # Snapshot only reached if every push above succeeded.
61 "''${SSH[@]}" "$NAS" /mnt/storage1/apps/home-infra/backup/snapshot-prune.sh storage1/backup/sandwich
62 '';
63 };
64in
65{
4 home.packages = with pkgs; [66 home.packages = with pkgs; [
5 nodejs_2467 nodejs_24
68 rsync
69 sandwich-backup
6 ];70 ];
71
72 launchd.agents.sandwich-backup = {
73 enable = true;
74 config = {
75 ProgramArguments = [ "${sandwich-backup}/bin/sandwich-backup" ];
76 StartInterval = 3600;
77 RunAtLoad = true;
78 StandardOutPath = "${config.home.homeDirectory}/Library/Logs/sandwich-backup.log";
79 StandardErrorPath = "${config.home.homeDirectory}/Library/Logs/sandwich-backup.log";
80 };
81 };
7}82}
users/clover/scripts/jj-ci.ts+76-24
...@@ -153,8 +153,9 @@ if (!fs.existsSync(ciScript)) {...@@ -153,8 +153,9 @@ if (!fs.existsSync(ciScript)) {
153if (invalid) process.exit(1);153if (invalid) process.exit(1);
154154
155if (parallelize) {155if (parallelize) {
156 if (dryRun) log.info("would run", "jj parallelize " + selectedRevisions.join(" "));156 if (dryRun) {
157 else jj(["parallelize", ...selectedRevisions]);157 log.info("would run", "jj parallelize " + selectedRevisions.join(" "));
158 } else jj(["parallelize", ...selectedRevisions]);
158}159}
159160
160const changeIds = [...new Set(selectedRevisions.flatMap(resolveChangeIds))];161const changeIds = [...new Set(selectedRevisions.flatMap(resolveChangeIds))];
...@@ -185,14 +186,14 @@ const plans: Plan[] = changeIds.map((changeId) => ({...@@ -185,14 +186,14 @@ const plans: Plan[] = changeIds.map((changeId) => ({
185 .filter((candidate) => candidate !== "" && candidate !== changeId),186 .filter((candidate) => candidate !== "" && candidate !== changeId),
186 ),187 ),
187 ],188 ],
188 label: changeId.slice(0, 6) + ': ' + jj(189 label: changeId.slice(0, 6) + ": " + jj(
189 [190 [
190 "log",191 "log",
191 "--no-graph",192 "--no-graph",
192 "-r",193 "-r",
193 `${changeId}`,194 `${changeId}`,
194 "-T",195 "-T",
195 'self.description().first_line()',196 "self.description().first_line()",
196 ],197 ],
197 { ignoreWorkingCopy: true },198 { ignoreWorkingCopy: true },
198 ),199 ),
...@@ -272,7 +273,9 @@ try {...@@ -272,7 +273,9 @@ try {
272 if (state === "ok") passed += 1;273 if (state === "ok") passed += 1;
273 else if (state === "failed" || state === "skipped") failed += 1;274 else if (state === "failed" || state === "skipped") failed += 1;
274 }275 }
275 return `${ansi.fgGreen}${passed} pass${ansi.reset}, ${failed > 0 ? ansi.fgRed : ""}${failed} fail${ansi.reset}`;276 return `${ansi.fgGreen}${passed} pass${ansi.reset}, ${
277 failed > 0 ? ansi.fgRed : ""
278 }${failed} fail${ansi.reset}`;
276 },279 },
277 });280 });
278281
...@@ -313,7 +316,14 @@ try {...@@ -313,7 +316,14 @@ try {
313316
314 try {317 try {
315 plainLog.push("--- prep ---\n");318 plainLog.push("--- prep ---\n");
316 ensureWorkspace(slot, plan.changeId, plan.label, trackedWorkspaces, node, plainLog);319 ensureWorkspace(
320 slot,
321 plan.changeId,
322 plan.label,
323 trackedWorkspaces,
324 node,
325 plainLog,
326 );
317 plainLog.push("\n");327 plainLog.push("\n");
318328
319 phase = "ci";329 phase = "ci";
...@@ -324,7 +334,9 @@ try {...@@ -324,7 +334,9 @@ try {
324 node,334 node,
325 });335 });
326 plainLog.push(336 plainLog.push(
327 `--- ci ---\nexit: ${ci.exitCode ?? "signal"}\nduration_ms: ${ci.durationMs}\n\n`,337 `--- ci ---\nexit: ${
338 ci.exitCode ?? "signal"
339 }\nduration_ms: ${ci.durationMs}\n\n`,
328 );340 );
329 plainLog.push(ci.plainText);341 plainLog.push(ci.plainText);
330 if (ci.plainText !== "" && !ci.plainText.endsWith("\n")) {342 if (ci.plainText !== "" && !ci.plainText.endsWith("\n")) {
...@@ -369,7 +381,9 @@ try {...@@ -369,7 +381,9 @@ try {
369 ? (error.stack ?? error.message)381 ? (error.stack ?? error.message)
370 : String(error);382 : String(error);
371 node.log.error(error);383 node.log.error(error);
372 plainLog.push(`--- ${phase} error ---\n${normalizePlain(message)}\n`);384 plainLog.push(
385 `--- ${phase} error ---\n${normalizePlain(message)}\n`,
386 );
373 writeFailureLog(plan.logPath, plainLog);387 writeFailureLog(plan.logPath, plainLog);
374 return {388 return {
375 changeId: plan.changeId,389 changeId: plan.changeId,
...@@ -393,10 +407,13 @@ try {...@@ -393,10 +407,13 @@ try {
393 for (const plan of plans) {407 for (const plan of plans) {
394 if (stateByChangeId.get(plan.changeId) !== "pending") continue;408 if (stateByChangeId.get(plan.changeId) !== "pending") continue;
395409
396 const blockedBy = plan.dependencyChangeIds.filter((dependencyChangeId) => {410 const blockedBy = plan.dependencyChangeIds.filter(
397 const dependencyState = stateByChangeId.get(dependencyChangeId);411 (dependencyChangeId) => {
398 return dependencyState === "failed" || dependencyState === "skipped";412 const dependencyState = stateByChangeId.get(dependencyChangeId);
399 });413 return dependencyState === "failed" ||
414 dependencyState === "skipped";
415 },
416 );
400417
401 if (blockedBy.length === 0) continue;418 if (blockedBy.length === 0) continue;
402419
...@@ -456,12 +473,17 @@ try {...@@ -456,12 +473,17 @@ try {
456473
457 if (!result.ok) continue;474 if (!result.ok) continue;
458475
459 for (const childChangeId of childrenByChangeId.get(result.changeId) ?? []) {476 for (
477 const childChangeId of childrenByChangeId.get(result.changeId) ?? []
478 ) {
460 if (stateByChangeId.get(childChangeId) !== "pending") continue;479 if (stateByChangeId.get(childChangeId) !== "pending") continue;
461480
462 const remainingDependencies =481 const remainingDependencies =
463 (remainingDependenciesByChangeId.get(childChangeId) ?? 1) - 1;482 (remainingDependenciesByChangeId.get(childChangeId) ?? 1) - 1;
464 remainingDependenciesByChangeId.set(childChangeId, remainingDependencies);483 remainingDependenciesByChangeId.set(
484 childChangeId,
485 remainingDependencies,
486 );
465487
466 if (remainingDependencies === 0) {488 if (remainingDependencies === 0) {
467 const childPlan = planByChangeId.get(childChangeId);489 const childPlan = planByChangeId.get(childChangeId);
...@@ -509,7 +531,14 @@ try {...@@ -509,7 +531,14 @@ try {
509 summary.push(531 summary.push(
510 `${ansi.style(failStyle, "x")} ${result.label} (${result.phase})`,532 `${ansi.style(failStyle, "x")} ${result.label} (${result.phase})`,
511 result.blockedBy?.length533 result.blockedBy?.length
512 ? ` ${ansi.style(dimStyle, `[waiting on ${result.blockedBy.map((id) => id.slice(0, 12)).join(", ")}]`)}`534 ? ` ${
535 ansi.style(
536 dimStyle,
537 `[waiting on ${
538 result.blockedBy.map((id) => id.slice(0, 12)).join(", ")
539 }]`,
540 )
541 }`
513 : "",542 : "",
514 result.logPath543 result.logPath
515 ? ` ${ansi.style(dimStyle, `[log ${result.logPath}]`)}`544 ? ` ${ansi.style(dimStyle, `[log ${result.logPath}]`)}`
...@@ -525,7 +554,14 @@ try {...@@ -525,7 +554,14 @@ try {
525 summary.push(554 summary.push(
526 `${ansi.style(skipStyle, "-")} ${result.label}`,555 `${ansi.style(skipStyle, "-")} ${result.label}`,
527 result.blockedBy?.length556 result.blockedBy?.length
528 ? ` ${ansi.style(dimStyle, `[blocked by ${result.blockedBy.map((id) => id.slice(0, 12)).join(", ")}]`)}`557 ? ` ${
558 ansi.style(
559 dimStyle,
560 `[blocked by ${
561 result.blockedBy.map((id) => id.slice(0, 12)).join(", ")
562 }]`,
563 )
564 }`
529 : "",565 : "",
530 "\n",566 "\n",
531 );567 );
...@@ -543,7 +579,9 @@ try {...@@ -543,7 +579,9 @@ try {
543 if (successfulChangeIds.length > 0) {579 if (successfulChangeIds.length > 0) {
544 summary.push(580 summary.push(
545 "\n",581 "\n",
546 `${ansi.style(infoStyle, "push successful commits:")} jj git push -c "${successfulChangeIds.join("|")}"`,582 `${ansi.style(infoStyle, "push successful commits:")} jj git push -c "${
583 successfulChangeIds.join("|")
584 }"`,
547 "\n",585 "\n",
548 );586 );
549 }587 }
...@@ -616,7 +654,6 @@ function resolveGitDir(cwd: string): string | null {...@@ -616,7 +654,6 @@ function resolveGitDir(cwd: string): string | null {
616 return value;654 return value;
617}655}
618656
619
620function jj(args: string[], options: CommandOptions = {}): string {657function jj(args: string[], options: CommandOptions = {}): string {
621 const result = child_process.spawnSync(658 const result = child_process.spawnSync(
622 "jj",659 "jj",
...@@ -637,7 +674,9 @@ function jj(args: string[], options: CommandOptions = {}): string {...@@ -637,7 +674,9 @@ function jj(args: string[], options: CommandOptions = {}): string {
637 const text = stdout + stderr;674 const text = stdout + stderr;
638675
639 if (options.node && text !== "") options.node.log.write(text);676 if (options.node && text !== "") options.node.log.write(text);
640 if (options.plainLog && text !== "") options.plainLog.push(normalizePlain(text));677 if (options.plainLog && text !== "") {
678 options.plainLog.push(normalizePlain(text));
679 }
641680
642 if (result.error) throw result.error;681 if (result.error) throw result.error;
643 if (result.status !== 0) {682 if (result.status !== 0) {
...@@ -891,7 +930,10 @@ function forgetPoolWorkspaces(...@@ -891,7 +930,10 @@ function forgetPoolWorkspaces(
891 }930 }
892931
893 if (errors.length > 0) {932 if (errors.length > 0) {
894 throw new AggregateError(errors, "failed to forget one or more jj-ci workspaces");933 throw new AggregateError(
934 errors,
935 "failed to forget one or more jj-ci workspaces",
936 );
895 }937 }
896}938}
897939
...@@ -964,7 +1006,12 @@ function runScript(...@@ -964,7 +1006,12 @@ function runScript(
964 ? preferredShell1006 ? preferredShell
965 : "bash";1007 : "bash";
9661008
967 const term = pty.spawn(shell, ["-c", 'set -o pipefail; set -eux; source "$1"', "jj-ci", scriptPath], {1009 const term = pty.spawn(shell, [
1010 "-c",
1011 'set -o pipefail; set -eux; source "$1"',
1012 "jj-ci",
1013 scriptPath,
1014 ], {
968 cols: process.stdout.columns ?? 120,1015 cols: process.stdout.columns ?? 120,
969 cwd: options.cwd,1016 cwd: options.cwd,
970 encoding: "utf8",1017 encoding: "utf8",
...@@ -1055,11 +1102,16 @@ function tryMatchDescendant(...@@ -1055,11 +1102,16 @@ function tryMatchDescendant(
1055 matchedDependencyByDescendant: Map<string, string>,1102 matchedDependencyByDescendant: Map<string, string>,
1056 seenDescendants: Set<string>,1103 seenDescendants: Set<string>,
1057): boolean {1104): boolean {
1058 for (const descendantChangeId of descendantsByChangeId.get(dependencyChangeId) ?? []) {1105 for (
1106 const descendantChangeId of descendantsByChangeId.get(dependencyChangeId) ??
1107 []
1108 ) {
1059 if (seenDescendants.has(descendantChangeId)) continue;1109 if (seenDescendants.has(descendantChangeId)) continue;
1060 seenDescendants.add(descendantChangeId);1110 seenDescendants.add(descendantChangeId);
10611111
1062 const matchedDependency = matchedDependencyByDescendant.get(descendantChangeId);1112 const matchedDependency = matchedDependencyByDescendant.get(
1113 descendantChangeId,
1114 );
1063 if (1115 if (
1064 matchedDependency == null ||1116 matchedDependency == null ||
1065 tryMatchDescendant(1117 tryMatchDescendant(
...@@ -1092,7 +1144,7 @@ function progressAttachToScreen(...@@ -1092,7 +1144,7 @@ function progressAttachToScreen(
1092 widget ??= startWidget({1144 widget ??= startWidget({
1093 format: ({ now }) => progress.formatAnsi(now, root.active),1145 format: ({ now }) => progress.formatAnsi(now, root.active),
1094 }) ?? null;1146 }) ?? null;
1095 if (!widget)return;1147 if (!widget) return;
1096 widget.fps = items.some((x) => x.showTotal !== false && x.total > 0)1148 widget.fps = items.some((x) => x.showTotal !== false && x.total > 0)
1097 ? 151149 ? 15
1098 : null;1150 : null;
users/clover/work/home.nix-3
...@@ -25,7 +25,4 @@...@@ -25,7 +25,4 @@
25 typescript25 typescript
26 zig26 zig
27 ];27 ];
28 home.sessionPath = [
29 "$HOME/.opencode/bin"
30 ];
31}28}
users/natalie/laptop/configuration.nix+2-2
...@@ -7,7 +7,7 @@...@@ -7,7 +7,7 @@
7 # Custom configuration modules in "modules" are shared between users,7 # Custom configuration modules in "modules" are shared between users,
8 # and can be configured in this "shared" namespace8 # and can be configured in this "shared" namespace
9 shared.darwin = {9 shared.darwin = {
10# macAppStoreApps = [ "wireguard" ];10 # macAppStoreApps = [ "wireguard" ];
11 };11 };
1212
13 system.defaults.NSGlobalDomain."com.apple.trackpad.scaling" = 1.0;13 system.defaults.NSGlobalDomain."com.apple.trackpad.scaling" = 1.0;
...@@ -40,7 +40,7 @@...@@ -40,7 +40,7 @@
40 casks = [40 casks = [
41 "battle-net"41 "battle-net"
42 "stremio"42 "stremio"
43# "alt-tab"43 # "alt-tab"
44 "legcord"44 "legcord"
45 "zulip"45 "zulip"
46 "zen"46 "zen"