A shepherd for your Appimages.
0

Configure Feed

Select the types of activity you want to include in your feed.

don't hardcode XDG dirs

Aly Raffauf (Jun 18, 2026, 2:03 PM EDT) 537f2a56 7aa52a44

+28 -14
+1
go.mod
··· 5 5 require ( 6 6 github.com/CalebQ42/squashfs v1.4.1 7 7 github.com/ProtonMail/go-crypto v1.4.1 8 + github.com/adrg/xdg v0.5.3 8 9 github.com/alyraffauf/goxdgdesktop v0.1.0 9 10 github.com/spf13/cobra v1.10.2 10 11 )
+10
go.sum
··· 2 2 github.com/CalebQ42/squashfs v1.4.1/go.mod h1:/As5wg6ScFFaab9SaNFNHyCOsd73Q5IFPOFJCVnwWzQ= 3 3 github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= 4 4 github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= 5 + github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= 6 + github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= 5 7 github.com/alyraffauf/goxdgdesktop v0.1.0 h1:n5+3AjF2ntvON6W0nN4O5bHDmcjYuFtKVCwBr6ekqj0= 6 8 github.com/alyraffauf/goxdgdesktop v0.1.0/go.mod h1:K91gqx5usBl0hjhNUxHcBSRaOaU8gaggPxfKpCnTXzQ= 7 9 github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= 8 10 github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= 9 11 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 12 + github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 + github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 14 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 11 15 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 12 16 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= ··· 17 21 github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= 18 22 github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk= 19 23 github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= 24 + github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 25 + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 26 github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs= 21 27 github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M= 22 28 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= ··· 25 31 github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 26 32 github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= 27 33 github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 34 + github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 35 + github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 28 36 github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= 29 37 github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 30 38 go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= ··· 33 41 golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= 34 42 golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 35 43 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 44 + gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 45 + gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+10 -3
internal/appherder/app.go
··· 4 4 "fmt" 5 5 "os" 6 6 "path/filepath" 7 + 8 + "github.com/adrg/xdg" 7 9 ) 8 10 9 11 // App is the core engine for managing AppImages. It holds no CLI or I/O ··· 12 14 appimagesDir string 13 15 applicationsDir string 14 16 iconsDir string 17 + binDir string 15 18 } 16 19 17 - // NewApp returns an App wired to the current user's home directory. 20 + // NewApp returns an App wired to the current user's home directory. The 21 + // applications directory honors XDG_DATA_HOME; the user bin directory 22 + // (~/.local/bin) is not covered by the XDG spec and is derived from home. 18 23 func NewApp() (App, error) { 19 24 home, err := os.UserHomeDir() 20 25 if err != nil { ··· 22 27 } 23 28 return NewAppWithDirs( 24 29 filepath.Join(home, "AppImages"), 25 - filepath.Join(home, ".local", "share", "applications"), 30 + filepath.Join(xdg.DataHome, "applications"), 26 31 filepath.Join(home, "AppImages", ".icons"), 32 + filepath.Join(home, ".local", "bin"), 27 33 ), nil 28 34 } 29 35 30 36 // NewAppWithDirs returns an App that uses the given directories directly, 31 37 // for tests or non-standard layouts. 32 - func NewAppWithDirs(appimagesDir, applicationsDir, iconsDir string) App { 38 + func NewAppWithDirs(appimagesDir, applicationsDir, iconsDir, binDir string) App { 33 39 return App{ 34 40 appimagesDir: appimagesDir, 35 41 applicationsDir: applicationsDir, 36 42 iconsDir: iconsDir, 43 + binDir: binDir, 37 44 } 38 45 }
+1
internal/appherder/helpers_test.go
··· 14 14 filepath.Join(home, "AppImages"), 15 15 filepath.Join(home, ".local", "share", "applications"), 16 16 filepath.Join(home, "AppImages", ".icons"), 17 + filepath.Join(home, ".local", "bin"), 17 18 ), home 18 19 } 19 20
+2 -2
internal/appherder/http.go
··· 50 50 51 51 func (t *idleTimeoutReader) Read(buf []byte) (int, error) { 52 52 t.timer.Reset(t.timeout) 53 - n, err := t.reader.Read(buf) 53 + bytesRead, err := t.reader.Read(buf) 54 54 t.timer.Stop() 55 - return n, err 55 + return bytesRead, err 56 56 } 57 57 58 58 // httpGetOK sends a GET request and returns the response when the server
+3 -8
internal/appherder/link.go
··· 6 6 "path/filepath" 7 7 ) 8 8 9 - func (a App) localBinDir() string { 10 - return filepath.Join(filepath.Dir(filepath.Dir(a.applicationsDir)), "bin") 11 - } 12 - 13 9 func (a App) linkPath(appName string) string { 14 - return filepath.Join(a.localBinDir(), appName) 10 + return filepath.Join(a.binDir, appName) 15 11 } 16 12 17 13 func (a App) appImagePath(appName string) string { ··· 24 20 if _, err := os.Stat(src); err != nil { 25 21 return fmt.Errorf("%s is not installed", appName) 26 22 } 27 - binDir := a.localBinDir() 28 - if err := os.MkdirAll(binDir, 0o755); err != nil { 29 - return fmt.Errorf("create %s: %w", binDir, err) 23 + if err := os.MkdirAll(a.binDir, 0o755); err != nil { 24 + return fmt.Errorf("create %s: %w", a.binDir, err) 30 25 } 31 26 dst := a.linkPath(appName) 32 27 if err := os.Remove(dst); err != nil && !os.IsNotExist(err) {
+1 -1
package.nix
··· 10 10 pname = "appherder"; 11 11 inherit version; 12 12 src = ./.; 13 - vendorHash = "sha256-DW+OYl2Lr7j4ZGOD/Cml2/2yuauX4EudLRaYH15YtAA="; 13 + vendorHash = "sha256-YoNtqb5dflJNCZBstAQxP458ktpUighC8uYuqFWjTyo="; 14 14 subPackages = ["cmd/appherder"]; 15 15 ldflags = ["-X main.version=${version}"]; 16 16 nativeBuildInputs = [makeWrapper];