A shepherd for your Appimages.
0

Configure Feed

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

dwarfs: don't fallback to executable bc dwarfsextract is a hard dep now

Aly Raffauf (Jun 20, 2026, 1:00 AM EDT) 55d6ac65 390c83fe

+21 -28
+1 -1
README.md
··· 137 137 138 138 ## Under the hood 139 139 140 - AppHerder reads the AppImage's filesystem to grab its icon and desktop entry. SquashFS images are parsed in-process. DwarFS images use the system dwarfsextract tool, falling back to the AppImage's own runtime when the tool isn't available. Everything it writes is tagged, so uninstall and sync only touch its own files. 140 + AppHerder reads the AppImage's filesystem to grab its icon and desktop entry. SquashFS images are parsed in-process. DwarFS images require `dwarfsextract`; the AppHerder CLI AppImage bundles it, while native and source installs need `dwarfsextract` available on `PATH`. AppHerder does not automatically fall back to `--appimage-extract`, because that executes the AppImage runtime. Everything it writes is tagged, so uninstall and sync only touch its own files. 141 141 142 142 ## License 143 143
+20 -27
internal/appherder/dwarfs.go
··· 6 6 "io/fs" 7 7 "os" 8 8 "os/exec" 9 - "path/filepath" 10 9 "time" 11 10 ) 12 11 13 - // openDwarFS extracts the DwarFS payload from an AppImage. Tries the system 14 - // dwarfsextract tool first; falls back to the AppImage's own --appimage-extract 15 - // as a last resort. Returns the extracted filesystem and a cleanup function. 12 + // openDwarFS extracts the DwarFS payload from an AppImage using dwarfsextract. 13 + // It intentionally does not fall back to --appimage-extract, because that 14 + // executes the AppImage runtime. 16 15 func openDwarFS(ctx context.Context, appimagePath string) (fs.FS, func(), error) { 17 16 dir, err := os.MkdirTemp("", "appherder-dwarfs") 18 17 if err != nil { ··· 23 22 ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) 24 23 defer cancel() 25 24 26 - cmd := extractCommand(ctx, appimagePath, dir) 25 + cmd, err := extractCommand(ctx, appimagePath, dir) 26 + if err != nil { 27 + cleanup() 28 + return nil, nil, err 29 + } 27 30 if out, err := cmd.CombinedOutput(); err != nil { 28 31 cleanup() 29 32 return nil, nil, fmt.Errorf("extract DwarFS AppImage: %w\n%s", err, out) 30 33 } 31 34 32 - // dwarfsextract -o extracts directly; --appimage-extract nests under squashfs-root. 33 - root := dir 34 - if _, err := os.Stat(filepath.Join(dir, "squashfs-root")); err == nil { 35 - root = filepath.Join(dir, "squashfs-root") 36 - } 37 - return os.DirFS(root), cleanup, nil 35 + return os.DirFS(dir), cleanup, nil 38 36 } 39 37 40 - func extractCommand(ctx context.Context, appimagePath, destDir string) *exec.Cmd { 38 + func extractCommand(ctx context.Context, appimagePath, destDir string) (*exec.Cmd, error) { 41 39 extract, err := exec.LookPath("dwarfsextract") 42 - if err == nil { 43 - return exec.CommandContext(ctx, extract, 44 - "--input="+appimagePath, 45 - "--output="+destDir, 46 - "--pattern=**.desktop", 47 - "--pattern=**.png", 48 - "--pattern=**.svg", 49 - "--pattern=.DirIcon", 50 - ) 40 + if err != nil { 41 + return nil, fmt.Errorf("find dwarfsextract: %w", err) 51 42 } 52 - // Fall back to the AppImage's own --appimage-extract. The file must be 53 - // executable for this to work. 54 - os.Chmod(appimagePath, 0o755) 55 - cmd := exec.CommandContext(ctx, appimagePath, "--appimage-extract") 56 - cmd.Dir = destDir 57 - return cmd 43 + return exec.CommandContext(ctx, extract, 44 + "--input="+appimagePath, 45 + "--output="+destDir, 46 + "--pattern=**.desktop", 47 + "--pattern=**.png", 48 + "--pattern=**.svg", 49 + "--pattern=.DirIcon", 50 + ), nil 58 51 } 59 52 60 53 // isDwarFS reports whether the payload at offset starts with the DwarFS magic.