From d2ac6d757ef618e0120fbcb3494c52d7800e77db Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Mon, 12 Jan 2026 23:18:15 -0600 Subject: [PATCH 1/4] bert: Update caddy-natural-sort.patch For next time this patch breaks, if I don't feel like fixing it, a good approximation for this use case is sorting by modified time, e.g.: handle /downloads/Newsletters/* { file_server { browse ${./caddy-browse-template.html} { sort time desc } } } --- bert/caddy-natural-sort.patch | 153 +++++++++++++++++++++++++++++----- 1 file changed, 130 insertions(+), 23 deletions(-) diff --git a/bert/caddy-natural-sort.patch b/bert/caddy-natural-sort.patch index f86505a..1c39a5d 100644 --- a/bert/caddy-natural-sort.patch +++ b/bert/caddy-natural-sort.patch @@ -1,17 +1,50 @@ -commit 668a0efbf6591ed7a90298bd8f6c63ad1a783587 +commit cf4751f200cbdb3823d02647a985555e43706f0f Author: Chandler Swift Date: Sat Aug 30 22:52:35 2025 -0500 file_server: Implement natural sort for browse templates + + For files with regions of numbers and other characters, it's intuitive + to have the numbers sorted by increasing numeric value rather than by + ASCII code. (Jeff Atwood has a nice article here: + https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/) + + For example, the listing for a directory containing `foo1`, `foo2`, and + `foo10` would sort in that order, rather than putting `foo10` between + `foo1` and `foo2`. + + Closes #7226 diff --git a/modules/caddyhttp/fileserver/browsetplcontext.go b/modules/caddyhttp/fileserver/browsetplcontext.go -index b9489c6a..339eea21 100644 +index b9489c6a..2d7eae1e 100644 --- a/modules/caddyhttp/fileserver/browsetplcontext.go +++ b/modules/caddyhttp/fileserver/browsetplcontext.go -@@ -325,11 +325,72 @@ type ( - byTime browseTemplateContext - ) +@@ -329,7 +329,7 @@ func (l byName) Len() int { return len(l.Items) } + func (l byName) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] } + func (l byName) Less(i, j int) bool { +- return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name) ++ return naturalLess(strings.ToLower(l.Items[i].Name), strings.ToLower(l.Items[j].Name)) + } + + func (l byNameDirFirst) Len() int { return len(l.Items) } +@@ -338,7 +338,7 @@ func (l byNameDirFirst) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l. + func (l byNameDirFirst) Less(i, j int) bool { + // sort by name if both are dir or file + if l.Items[i].IsDir == l.Items[j].IsDir { +- return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name) ++ return naturalLess(strings.ToLower(l.Items[i].Name), strings.ToLower(l.Items[j].Name)) + } + // sort dir ahead of file + return l.Items[i].IsDir +diff --git a/modules/caddyhttp/fileserver/natsort.go b/modules/caddyhttp/fileserver/natsort.go +new file mode 100644 +index 00000000..65f6a161 +--- /dev/null ++++ b/modules/caddyhttp/fileserver/natsort.go +@@ -0,0 +1,65 @@ ++package fileserver ++ +func isDigit(b byte) bool { return '0' <= b && b <= '9' } + +// naturalLess compares two strings using natural ordering. This means that e.g. @@ -22,6 +55,9 @@ index b9489c6a..339eea21 100644 +// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02") +// +// Limitation: only ASCII digits (0-9) are considered. ++// ++// This implementation is copied from https://github.com/fvbommel/sortorder, ++// which is MIT licensed. +func naturalLess(str1, str2 string) bool { + idx1, idx2 := 0, 0 + for idx1 < len(str1) && idx2 < len(str2) { @@ -72,22 +108,93 @@ index b9489c6a..339eea21 100644 + // it sorts last. + return len(str1) < len(str2) +} +diff --git a/modules/caddyhttp/fileserver/natsort_test.go b/modules/caddyhttp/fileserver/natsort_test.go +new file mode 100644 +index 00000000..321f1197 +--- /dev/null ++++ b/modules/caddyhttp/fileserver/natsort_test.go +@@ -0,0 +1,63 @@ ++// Copyright 2015 Matthew Holt and The Caddy Authors ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. + - func (l byName) Len() int { return len(l.Items) } - func (l byName) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] } - - func (l byName) Less(i, j int) bool { -- return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name) -+ return naturalLess(strings.ToLower(l.Items[i].Name), strings.ToLower(l.Items[j].Name)) - } - - func (l byNameDirFirst) Len() int { return len(l.Items) } -@@ -338,7 +399,7 @@ func (l byNameDirFirst) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l. - func (l byNameDirFirst) Less(i, j int) bool { - // sort by name if both are dir or file - if l.Items[i].IsDir == l.Items[j].IsDir { -- return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name) -+ return naturalLess(strings.ToLower(l.Items[i].Name), strings.ToLower(l.Items[j].Name)) - } - // sort dir ahead of file - return l.Items[i].IsDir ++package fileserver ++ ++import ( ++ "context" ++ "io/fs" ++ "net/http/httptest" ++ "os" ++ "testing" ++ ++ "github.com/caddyserver/caddy/v2/modules/caddyhttp" ++) ++ ++// TestNatSort confirms that, although an ASCIIbetical sort would order foo2.txt ++// after foo10.txt, Caddy will return them in a "natural" human-intuitive order. ++func TestNatSort(t *testing.T) { ++ fsrv := &FileServer{Browse: &Browse{}} ++ ++ base := "./testdata" ++ dirName := "natsort" ++ ++ fsys := os.DirFS(base) ++ f, err := fsys.Open(dirName) ++ if err != nil { ++ t.Fatalf("opening testdata dir: %v", err) ++ } ++ defer f.Close() ++ ++ repl := caddyhttp.NewTestReplacer(httptest.NewRequest("GET", "/", nil)) ++ ++ listing, err := fsrv.loadDirectoryContents(context.Background(), fsys, f.(fs.ReadDirFile), base, "/natsort/", repl) ++ if err != nil { ++ t.Fatalf("loadDirectoryContents returned error: %v", err) ++ } ++ ++ if len(listing.Items) != 3 { ++ t.Fatalf("expected 3 items in listing, got %d", len(listing.Items)) ++ } ++ ++ listing.applySortAndLimit(sortByNameDirFirst, sortOrderAsc, "", "") ++ ++ got := []string{listing.Items[0].Name, listing.Items[1].Name, listing.Items[2].Name} ++ want := []string{"foo1.txt", "foo2.txt", "foo10.txt"} ++ ++ for i := range want { ++ if got[i] != want[i] { ++ t.Fatalf("unexpected item at index %v: got %v, want %v", i, got, want) ++ } ++ } ++} +diff --git a/modules/caddyhttp/fileserver/testdata/natsort/foo1.txt b/modules/caddyhttp/fileserver/testdata/natsort/foo1.txt +new file mode 100644 +index 00000000..b0a92ba4 +--- /dev/null ++++ b/modules/caddyhttp/fileserver/testdata/natsort/foo1.txt +@@ -0,0 +1 @@ ++foo1.txt +diff --git a/modules/caddyhttp/fileserver/testdata/natsort/foo10.txt b/modules/caddyhttp/fileserver/testdata/natsort/foo10.txt +new file mode 100644 +index 00000000..ade2a424 +--- /dev/null ++++ b/modules/caddyhttp/fileserver/testdata/natsort/foo10.txt +@@ -0,0 +1 @@ ++foo10.txt +diff --git a/modules/caddyhttp/fileserver/testdata/natsort/foo2.txt b/modules/caddyhttp/fileserver/testdata/natsort/foo2.txt +new file mode 100644 +index 00000000..6aab57b1 +--- /dev/null ++++ b/modules/caddyhttp/fileserver/testdata/natsort/foo2.txt +@@ -0,0 +1 @@ ++foo2.txt From a77643be95f3f3844c6e4b1c84ecd52f5435cd7a Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Sun, 8 Feb 2026 19:24:26 -0600 Subject: [PATCH 2/4] oscar: Use `android-tools` package not `adb` option `programs.adb.enable` is no longer supported: https://github.com/NixOS/nixpkgs/commit/d037468346dfcd4057d5aaf5ecfac9c10a253fa8 --- oscar/configuration.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oscar/configuration.nix b/oscar/configuration.nix index 76dcc84..824c4d5 100644 --- a/oscar/configuration.nix +++ b/oscar/configuration.nix @@ -148,6 +148,7 @@ # command line utilities alsa-utils + android-tools dig file ffmpeg @@ -353,7 +354,6 @@ power-profiles-daemon ]; - programs.adb.enable = true; # Some programs need SUID wrappers, can be configured further or are # started in user sessions. From 927470ea9a81e66fb3ee54e0ebb74e0e8a1db310 Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Wed, 18 Feb 2026 18:25:33 -0600 Subject: [PATCH 3/4] oscar, sam: Install zed-editor --- oscar/configuration.nix | 1 + sam/configuration.nix | 1 + 2 files changed, 2 insertions(+) diff --git a/oscar/configuration.nix b/oscar/configuration.nix index 824c4d5..1d02fe5 100644 --- a/oscar/configuration.nix +++ b/oscar/configuration.nix @@ -135,6 +135,7 @@ solvespace supersonic tenacity + zed-editor # games bolt-launcher diff --git a/sam/configuration.nix b/sam/configuration.nix index 45b1bbe..20333f6 100644 --- a/sam/configuration.nix +++ b/sam/configuration.nix @@ -88,6 +88,7 @@ inkscape kdePackages.kate libreoffice-qt + zed-editor # command line applications (azure-cli.withExtensions [ From 516b9dd0acd3627787866472da4ea779ea65e3e7 Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Wed, 18 Feb 2026 18:32:38 -0600 Subject: [PATCH 4/4] bert: Upgrade Navidrome to 0.60.0 --- bert/services/navidrome.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bert/services/navidrome.nix b/bert/services/navidrome.nix index 58a629c..bf6bc3e 100644 --- a/bert/services/navidrome.nix +++ b/bert/services/navidrome.nix @@ -1,8 +1,8 @@ { services.navidrome = { enable = true; - # 0.59.0 - package = (import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/bba6fa435f90b2e28812197e71155953a1694b58.tar.gz") {}).navidrome; + # 0.60.0 + package = (import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/1e37efce361b837b7c58889dd15ad5cd283b34ca.tar.gz") {}).navidrome; settings = { MusicFolder = "/mnt/bigbird_public/media/music"; ScanSchedule = "@every 12h";