15 | The following documents explain how an AppBundle behaves, how, and why.
16 | They also explain the reasoning behind these design choices.
17 |
18 | These documents can be a starting point for reimplementing the existing AppBundle tooling.
19 |
15 | The following documents explain how an AppBundle behaves, how, and why.
16 | They also explain the reasoning behind these design choices.
17 |
18 | These documents can be a starting point for reimplementing the existing AppBundle tooling.
19 |
20 |
--------------------------------------------------------------------------------
/assets/AppRun.multiBinary:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ -n "$SELF" ]; then # Check if ARGV0 is set, which should contain the original command name
4 | SELF=$(basename "$SELF")
5 | else
6 | SELF=$(basename "$0") # Fallback to $0 if ARGV0 is not set, but this shouldn't happen with proper symlink setup
7 | fi
8 |
9 | SELF_TEMPDIR="$(dirname "$0")"
10 |
11 | # Check if the binary exists in the specified directories and execute it
12 | if [ -f "$SELF_TEMPDIR/bin/$SELF" ]; then
13 | exec "$SELF_TEMPDIR/bin/$SELF" "$@"
14 | elif [ -f "$SELF_TEMPDIR/usr/bin/$SELF" ]; then
15 | exec "$SELF_TEMPDIR/usr/bin/$SELF" "$@"
16 | fi
17 |
18 | if [ "$#" -lt 1 ]; then
19 | echo "No arguments were passed or the command does not match any binaries in bin/ or usr/bin/"
20 | else
21 | exec "$@"
22 | fi
23 |
--------------------------------------------------------------------------------
/assets/AppRun.sharun:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # shellcheck disable=SC2086
4 | [ -n "$DEBUG" ] && set -$DEBUG
5 |
6 | SELF="$(readlink -f "$0")" && export SELF
7 | APPDIR="${SELF%/*}" && export APPDIR
8 |
9 | _sh_cat() {
10 | while IFS= read -r line; do
11 | echo "$line"
12 | done < "$1"
13 | }
14 |
15 | FALLBACK="$(_sh_cat "$APPDIR/entrypoint")"
16 | FALLBACK="${FALLBACK##*/}"
17 | [ -z "$ARGV0" ] && {
18 | ARGV0="${0##*/}"
19 | }
20 |
21 | CMD="$1"
22 |
23 | oPATH="$PATH"
24 | PATH="${APPDIR}/bin"
25 |
26 | # What command shall we exec?
27 | if _cmd="$(command -v "${ARGV0#./}")" >/dev/null 2>&1; then
28 | PATH="$PATH:$oPATH"
29 | elif _cmd="$(command -v "$CMD")" >/dev/null 2>&1; then
30 | shift
31 | PATH="$PATH:$oPATH"
32 | elif _cmd="$(command -v "$FALLBACK")" >/dev/null 2>&1; then
33 | PATH="$PATH:$oPATH"
34 | else
35 | echo "Error: Neither ARGV0 ('${ARGV0%.*}') nor ARGS ('$CMD') are available in \$PATH"
36 | exit 1
37 | fi
38 |
39 | exec "$_cmd" "$@"
40 |
--------------------------------------------------------------------------------
/assets/LAUNCH-multicall.rootfs.entrypoint:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | [ "$DEBUG" = "1" ] && set -x
4 |
5 | # Strip the first character from ARGS and ARGV0 (assuming it's '!')
6 | ARGS="$(echo "$ARGS" | cut -c2-)"
7 | ARGV0="$(echo "$ARGV0" | cut -c2-)"
8 |
9 | _cat() {
10 | files="$*"
11 | for file in $files; do
12 | while IFS= read -r line || [ -n "$line" ]; do
13 | printf '%s\n' "$line"
14 | done <"$file"
15 | done
16 | } ; FALLBACK=$(_cat /usr/local/bin/default)
17 |
18 | # Split ARGS into command and its arguments
19 | set -- $ARGS
20 | CMD="$1"
21 | shift # Remove the command from the list, leaving the arguments in $@
22 |
23 | # Check if ARGV0 is available as a command. We remove the "./" that might prepend ARGV0
24 | if command -v "${ARGV0%.*}" >/dev/null 2>&1; then
25 | # If ARGV0 is available, execute ARGV0 with its arguments
26 | exec "${ARGV0%.*}" "$ARGS" # Because ARGS' first element was not in fact the CMD
27 | elif command -v "$CMD" >/dev/null 2>&1; then
28 | # If CMD (the first part of ARGS) is available, execute it with remaining arguments
29 | exec "$CMD" "$@"
30 | elif command -v "$FALLBACK" >/dev/null 2>&1; then
31 | exec "$FALLBACK" "$@"
32 | else
33 | echo "Error: Neither ARGV0 ('${ARGV0%.*}') nor ARGS ('$CMD') are available in \$PATH"
34 | exit 1
35 | fi
36 |
--------------------------------------------------------------------------------
/cmd/pfusermount/fusermount.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "embed"
6 | "fmt"
7 | "os"
8 | "os/exec"
9 | "path/filepath"
10 | )
11 |
12 | //go:embed fusermount
13 | var fusermount embed.FS
14 |
15 | func main() {
16 | // Extract the fusermount binary to a temporary location
17 | tempDir := os.TempDir()
18 | fusermountPath := filepath.Join(tempDir, "fusermount")
19 | fusermountData, err := fusermount.ReadFile("fusermount")
20 | if err != nil {
21 | fmt.Fprintf(os.Stderr, "Error reading embedded fusermount: %v\n", err)
22 | os.Exit(1)
23 | }
24 | err = os.WriteFile(fusermountPath, fusermountData, 0755)
25 | if err != nil {
26 | fmt.Fprintf(os.Stderr, "Error writing fusermount to temp directory: %v\n", err)
27 | os.Exit(1)
28 | }
29 |
30 | // Check if unshare is available
31 | unshareCmd := exec.Command("unshare")
32 | var out bytes.Buffer
33 | unshareCmd.Stdout = &out
34 | err = unshareCmd.Run()
35 |
36 | var cmd *exec.Cmd
37 | if err == nil {
38 | // unshare is available, use unshare
39 | args := []string{"--mount", "--user", "-r", fusermountPath}
40 | args = append(args, os.Args[1:]...)
41 | cmd = exec.Command("unshare", args...)
42 | } else {
43 | // unshare is not available, run fusermount directly
44 | cmd = exec.Command(fusermountPath, os.Args[1:]...)
45 | }
46 |
47 | // Set stdout and stderr for the command
48 | cmd.Stdout = os.Stdout
49 | cmd.Stderr = os.Stderr
50 |
51 | // Run the command
52 | _ = cmd.Run()
53 | }
54 |
--------------------------------------------------------------------------------
/cmd/pfusermount/fusermount3.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "embed"
6 | "fmt"
7 | "os"
8 | "os/exec"
9 | "path/filepath"
10 | )
11 |
12 | //go:embed fusermount3
13 | var fusermount embed.FS
14 |
15 | func main() {
16 | // Extract the fusermount3 binary to a temporary location
17 | tempDir := os.TempDir()
18 | fusermountPath := filepath.Join(tempDir, "fusermount3")
19 | fusermountData, err := fusermount.ReadFile("fusermount3")
20 | if err != nil {
21 | fmt.Fprintf(os.Stderr, "Error reading embedded fusermount3: %v\n", err)
22 | os.Exit(1)
23 | }
24 | err = os.WriteFile(fusermountPath, fusermountData, 0755)
25 | if err != nil {
26 | fmt.Fprintf(os.Stderr, "Error writing fusermount3 to temp directory: %v\n", err)
27 | os.Exit(1)
28 | }
29 |
30 | // Check if unshare is available
31 | unshareCmd := exec.Command("unshare")
32 | var out bytes.Buffer
33 | unshareCmd.Stdout = &out
34 | err = unshareCmd.Run()
35 |
36 | var cmd *exec.Cmd
37 | if err == nil {
38 | // unshare is available, use unshare
39 | args := []string{"--mount", "--user", "-r", fusermountPath}
40 | args = append(args, os.Args[1:]...)
41 | cmd = exec.Command("unshare", args...)
42 | } else {
43 | // unshare is not available, run fusermount directly
44 | cmd = exec.Command(fusermountPath, os.Args[1:]...)
45 | }
46 |
47 | // Set stdout and stderr for the command
48 | cmd.Stdout = os.Stdout
49 | cmd.Stderr = os.Stderr
50 |
51 | // Run the command
52 | _ = cmd.Run()
53 | }
54 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2024, xplshn
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/appbundle-runtime/embed_squashfs.go:
--------------------------------------------------------------------------------
1 | //go:build !noEmbed && squashfs
2 |
3 | package main
4 |
5 | import (
6 | _ "embed"
7 | "fmt"
8 | "strings"
9 | )
10 |
11 | //go:embed binaryDependencies/squashfuse
12 | var squashfuseBinary []byte
13 |
14 | //go:embed binaryDependencies/unsquashfs
15 | var unsquashfsBinary []byte
16 |
17 | var Filesystems = []*Filesystem{
18 | {
19 | Type: "squashfs",
20 | Commands: []string{"squashfuse", "unsquashfs"},
21 | MountCmd: func(cfg *RuntimeConfig) CommandRunner {
22 | args := []string{
23 | "-o", "ro,nodev",
24 | "-o", "uid=0,gid=0",
25 | "-o", fmt.Sprintf("offset=%d", cfg.archiveOffset),
26 | cfg.selfPath,
27 | cfg.mountDir,
28 | }
29 | if getEnv(globalEnv, "ENABLE_FUSE_DEBUG") != "" {
30 | logWarning("squashfuse's debug mode implies foreground. The AppRun won't be called.")
31 | args = append(args, "-o", "debug")
32 | }
33 | memitCmd, err := newMemitCmd(cfg, squashfuseBinary, "squashfuse", args...)
34 | if err != nil {
35 | logError("Failed to create memit command", err, cfg)
36 | }
37 | return memitCmd
38 | },
39 | ExtractCmd: func(cfg *RuntimeConfig, query string) CommandRunner {
40 | args := []string{"-d", cfg.mountDir, "-o", fmt.Sprintf("%d", cfg.archiveOffset), cfg.selfPath}
41 | if query != "" {
42 | for _, file := range strings.Split(query, " ") {
43 | args = append(args, "-e", file)
44 | }
45 | }
46 | memitCmd, err := newMemitCmd(cfg, unsquashfsBinary, "unsquashfs", args...)
47 | if err != nil {
48 | logError("Failed to create memit command", err, cfg)
49 | }
50 | return memitCmd
51 | },
52 | },
53 | }
54 |
--------------------------------------------------------------------------------
/cmd/misc/thumbgen:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Function to calculate the MD5 hash of a URI
4 | calculate_md5() {
5 | if ! printf "%s" "$1" | md5sum | cut -d ' ' -f1; then
6 | echo "There was an error calculating the MD5 hash. Quitting..."
7 | exit 1
8 | fi
9 | # Return the hash value
10 | echo "$hash"
11 | }
12 |
13 | # Function to create a thumbnail for a file
14 | create_thumbnail() {
15 | input_file="$1"
16 | thumbnail_file="$2"
17 |
18 | # Ensure input file and thumbnail file are specified
19 | if [ -z "$input_file" ]; then
20 | echo "Usage: $0 [file_to_thumbnail] <128x128thumbnail.png>"
21 | exit 1
22 | fi
23 |
24 | # Check if the thumbnail file exists
25 | if [ -n "$thumbnail_file" ] && [ ! -f "$thumbnail_file" ]; then
26 | echo "The thumbnail file does not exist."
27 | exit 1
28 | fi
29 |
30 | # Determine the canonical URI of the input file
31 | abs_path=$(readlink -f "$input_file")
32 | uri="file://$abs_path"
33 |
34 | # Calculate the MD5 hash of the URI
35 | hash=$(calculate_md5 "$uri")
36 |
37 | # Determine the target directory and filename for the thumbnail
38 | thumbnail_dir="${XDG_CACHE_HOME:-$HOME/.cache}/thumbnails/normal"
39 | mkdir -p "$thumbnail_dir"
40 | thumbnail_path="$thumbnail_dir/$hash.png"
41 |
42 | # Copy the provided thumbnail to the target path
43 | if [ -n "$thumbnail_file" ]; then
44 | cp "$thumbnail_file" "$thumbnail_path"
45 | echo "Thumbnail saved to: $thumbnail_path"
46 | exit 0
47 | fi
48 |
49 | echo "$thumbnail_path"
50 | }
51 |
52 | # Call the function with arguments
53 | create_thumbnail "$1" "$2"
54 |
--------------------------------------------------------------------------------
/cmd/pelfd/appbundle_support.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 | "os"
7 | "os/exec"
8 | "strings"
9 | "path/filepath"
10 | )
11 |
12 | func integrateAppBundle(path, appPath string, entry *BundleEntry) {
13 | baseName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
14 | entry.Png = executeAppBundle(path, "--pbundle_pngIcon", filepath.Join(appPath, baseName+".png"))
15 | entry.Svg = executeAppBundle(path, "--pbundle_svgIcon", filepath.Join(appPath, baseName+".svg"))
16 | entry.Desktop = executeAppBundle(path, "--pbundle_desktop", filepath.Join(appPath, baseName+".desktop"))
17 | }
18 |
19 | func executeAppBundle(bundle, param, outputFile string) string {
20 | logMessage("INF", fmt.Sprintf("Retrieving metadata from %s with parameter: %s", bundle, param))
21 | // Prepend `sh -c` to the bundle execution
22 | cmd := exec.Command("sh", "-c", bundle+" "+param)
23 | output, err := cmd.Output()
24 | if err != nil {
25 | logMessage("WRN", fmt.Sprintf("Bundle %s with parameter %s didn't return a metadata file", bundle, param))
26 | return ""
27 | }
28 |
29 | outputStr := string(output)
30 |
31 | // Remove the escape sequence "^[[1F^[[2K"
32 | // Remove the escape sequence from the output
33 | outputStr = strings.ReplaceAll(outputStr, "\x1b[1F\x1b[2K", "")
34 |
35 | data, err := base64.StdEncoding.DecodeString(outputStr)
36 | if err != nil {
37 | logMessage("ERR", fmt.Sprintf("Failed to decode base64 output for %s %s: %v", bundle, param, err))
38 | return ""
39 | }
40 |
41 | if err := os.WriteFile(outputFile, data, 0644); err != nil {
42 | logMessage("ERR", fmt.Sprintf("Failed to write file %s: %v", outputFile, err))
43 | return ""
44 | }
45 |
46 | logMessage("INF", fmt.Sprintf("Successfully wrote file: %s", outputFile))
47 | return outputFile
48 | }
49 |
--------------------------------------------------------------------------------
/cmd/misc/rootfs2sharun:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Function to display help information
4 | show_help() {
5 | printf "Usage: %s -d output_dir -a app_dir file1 [file2 ...]\n" "$0"
6 | printf "\nOptions:\n"
7 | printf " -d output_dir Specify the new output directory\n"
8 | printf " -a app_dir Specify the rootfs-based AppDir\n"
9 | printf " -h Show this help message\n"
10 | }
11 |
12 | # Initialize variables
13 | OUTPUT_DIR=""
14 | APP_DIR=""
15 |
16 | # Parse command-line options
17 | while [ $# -gt 0 ]; do
18 | case "$1" in
19 | -h)
20 | show_help
21 | exit 0
22 | ;;
23 | -d)
24 | shift
25 | OUTPUT_DIR="$1"
26 | shift
27 | ;;
28 | -a)
29 | shift
30 | APP_DIR="$1"
31 | shift
32 | ;;
33 | -*)
34 | printf "Invalid option: %s\n" "$1" >&2
35 | show_help
36 | exit 1
37 | ;;
38 | *)
39 | break
40 | ;;
41 | esac
42 | done
43 |
44 | # Check if both OUTPUT_DIR and APP_DIR are specified
45 | if [ -z "$OUTPUT_DIR" ] || [ -z "$APP_DIR" ]; then
46 | printf "Error: Both output directory and AppDir must be specified.\n" >&2
47 | show_help
48 | exit 1
49 | fi
50 |
51 | # Check if at least one filename is specified
52 | if [ "$#" -eq 0 ]; then
53 | printf "Error: No files specified. Please provide at least one file.\n" >&2
54 | show_help
55 | exit 1
56 | fi
57 |
58 | mkdir -p "$OUTPUT_DIR"
59 | # Loop through each file passed as argument
60 | for FILE in "$@"; do
61 | if [ -e "$APP_DIR/rootfs/usr/bin/$(basename "$FILE")" ]; then
62 | lib4bin --dst-dir "$OUTPUT_DIR" "$FILE"
63 | else
64 | printf "%s does not exist in %s/rootfs/usr/bin/\n" "$(basename "$FILE")" "$APP_DIR"
65 | fi
66 | done
67 |
--------------------------------------------------------------------------------
/.github/workflows/static.yml:
--------------------------------------------------------------------------------
1 | name: Deploy static content to Pages
2 |
3 | on:
4 | push:
5 | branches: ["master"]
6 | workflow_dispatch:
7 |
8 |
9 | permissions:
10 | contents: write
11 | pages: write
12 | id-token: write
13 |
14 | concurrency:
15 | group: "pages"
16 | cancel-in-progress: false
17 |
18 | jobs:
19 | deploy:
20 | environment:
21 | name: github-pages
22 | url: ${{ steps.deployment.outputs.page_url }}
23 | runs-on: ubuntu-latest
24 |
25 | container:
26 | image: alpine:latest
27 |
28 | steps:
29 | - name: Install Hugo, Git, and the worst version of Tar
30 | run: |
31 | apk update
32 | apk add hugo git tar
33 |
34 | - name: Checkout GitHub repo
35 | uses: actions/checkout@v4
36 | with:
37 | ref: 'master'
38 | submodules: 'true'
39 | fetch-depth: 0
40 |
41 | - name: Configure Git safe.directory
42 | run: |
43 | git config --global --add safe.directory "${GITHUB_WORKSPACE}"
44 |
45 | - name: Generate the site
46 | run: |
47 | cd "${GITHUB_WORKSPACE}/www"
48 | tree
49 | ./gen.sh
50 | cd "${GITHUB_WORKSPACE}"
51 |
52 | # Prepare git to commit changes
53 | git config user.name '[CI]'
54 | git config user.email 'action@github.com'
55 |
56 | ## Commit changes dynamically
57 | #git add "${GITHUB_WORKSPACE}/www"
58 | #git commit -m "[www]" || echo "Nothing to commit"
59 | #git push origin HEAD || echo "Push failed; check repository settings"
60 |
61 | - name: Setup Pages
62 | uses: actions/configure-pages@v5
63 |
64 | - name: Upload artifact
65 | uses: actions/upload-pages-artifact@v3
66 | with:
67 | path: "./www/pub"
68 |
69 | - name: Deploy to GitHub Pages
70 | id: deployment
71 | uses: actions/deploy-pages@v4
72 |
--------------------------------------------------------------------------------
/appbundle-runtime/embed.go:
--------------------------------------------------------------------------------
1 | //go:build !noEmbed
2 |
3 | package main
4 |
5 | import (
6 | "bytes"
7 | "fmt"
8 | "io"
9 | "os"
10 | "os/exec"
11 | "path/filepath"
12 | // "syscall"
13 |
14 | "github.com/liamg/memit"
15 | )
16 |
17 | type memitCmd struct {
18 | *exec.Cmd
19 | file *os.File
20 | }
21 |
22 | func (c *memitCmd) SetStdout(w io.Writer) {
23 | c.Cmd.Stdout = w
24 | }
25 | func (c *memitCmd) SetStderr(w io.Writer) {
26 | c.Cmd.Stderr = w
27 | }
28 | func (c *memitCmd) SetStdin(r io.Reader) {
29 | c.Cmd.Stdin = r
30 | }
31 | func (c *memitCmd) CombinedOutput() ([]byte, error) {
32 | return c.Cmd.CombinedOutput()
33 | }
34 | func (c *memitCmd) Run() error {
35 | defer c.file.Close()
36 | return c.Cmd.Run()
37 | }
38 |
39 | func newMemitCmd(cfg *RuntimeConfig, binary []byte, name string, args ...string) (*memitCmd, error) {
40 | if getEnv(globalEnv, "NO_MEMFDEXEC") == "1" {
41 | tempDir := filepath.Join(cfg.workDir, ".static")
42 | if err := os.MkdirAll(tempDir, 0755); err != nil {
43 | return nil, fmt.Errorf("failed to create temporary directory: %v", err)
44 | }
45 | tempFile := filepath.Join(tempDir, name)
46 | if err := os.WriteFile(tempFile, binary, 0755); err != nil {
47 | return nil, fmt.Errorf("failed to write temporary file: %v", err)
48 | }
49 | cmd := exec.Command(tempFile, args...)
50 | cmd.Env = globalEnv
51 | //// Detach FUSE binary so it survives runtime death
52 | //cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
53 | return &memitCmd{Cmd: cmd}, nil
54 | }
55 | cmd, file, err := memit.Command(bytes.NewReader(binary), args...)
56 | if err != nil {
57 | return nil, err
58 | }
59 | cmd.Args[0] = name
60 | cmd.Env = globalEnv
61 | //// Detach FUSE binary so it survives runtime death
62 | //cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
63 | return &memitCmd{Cmd: cmd, file: file}, nil
64 | }
65 |
66 | func checkDeps(cfg *RuntimeConfig, fh *fileHandler) (*Filesystem, error) {
67 | fs, ok := getFilesystem(cfg.appBundleFS)
68 | if !ok {
69 | return nil, fmt.Errorf("unsupported filesystem: %s", cfg.appBundleFS)
70 | }
71 | return fs, nil
72 | }
73 |
--------------------------------------------------------------------------------
/assets/AppRun.rootfs-based.stable:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # shellcheck disable=SC2086
4 | [ -n "$DEBUG" ] && set -$DEBUG
5 |
6 | # Determine the path to the script itself
7 | SELF=$(readlink -f "$0")
8 | SELF_TEMPDIR=${SELF%/*}
9 |
10 | [ -d "$SELF_TEMPDIR/rootfs" ] || exit 1
11 | BWROOTFS="$SELF_TEMPDIR/rootfs"
12 |
13 | if [ "$1" = "--Xbwrap" ]; then
14 | shift
15 | SELF_ARGS="$*"
16 | else
17 | SELF_ARGS="-- env -u LD_PRELOAD -u LD_LIBRARY_PATH $(cat "$BWROOTFS/entrypoint") $*"
18 | fi
19 |
20 | if [ -f "$SELF_TEMPDIR/usr/bin/bwrap" ]; then
21 | BWRAP_BIN="$SELF_TEMPDIR/usr/bin/bwrap"
22 | else
23 | BWRAP_BIN="bwrap"
24 | fi
25 |
26 | if [ -z "$ARGV0" ]; then
27 | ARGV0="${0##*/}"
28 | fi
29 |
30 | if [ "$WITHIN_BWRAP" = 1 ] && [ -f "/entrypoint" ]; then
31 | "/entrypoint"
32 | fi
33 | $BWRAP_BIN --bind "$BWROOTFS" / \
34 | --share-net \
35 | --proc /proc \
36 | --dev-bind /dev /dev \
37 | --bind /run /run \
38 | --bind-try /sys /sys \
39 | --bind /tmp /tmp \
40 | --bind-try /media /media \
41 | --bind-try /mnt /mnt \
42 | --bind /home /home \
43 | --bind-try /opt /opt \
44 | --bind-try /usr/share/fontconfig /usr/share/fontconfig \
45 | --ro-bind-try /usr/share/fonts /usr/share/fonts \
46 | --ro-bind-try /usr/share/themes /usr/share/themes \
47 | --ro-bind-try /sys /sys \
48 | --ro-bind-try /etc/resolv.conf /etc/resolv.conf \
49 | --ro-bind-try /etc/hosts /etc/hosts \
50 | --ro-bind-try /etc/nsswitch.conf /etc/nsswitch.conf \
51 | --ro-bind-try /etc/passwd /etc/passwd \
52 | --ro-bind-try /etc/group /etc/group \
53 | --ro-bind-try /etc/machine-id /etc/machine-id \
54 | --ro-bind-try /etc/asound.conf /etc/asound.conf \
55 | --ro-bind-try /etc/localtime /etc/localtime \
56 | --ro-bind-try /etc/hostname /etc/hostname \
57 | --setenv SELF "$SELF" \
58 | --setenv SELF_TEMPDIR "$SELF_TEMPDIR" \
59 | --setenv BWROOTFS "$BWROOTFS" \
60 | --setenv ARGV0 "$ARGV0" \
61 | --setenv ARGS "!$*" \
62 | --setenv WITHIN_BWRAP "1" \
63 | $SELF_ARGS
64 |
65 | # --bind-try /usr/lib/locale /usr/lib/locale \
66 | # --perms 0700 \
67 | # --uid "0" --gid "0" \
68 |
--------------------------------------------------------------------------------
/assets/AppRun.generic:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Taken from https://github.com/Samueru-sama/deploy-linux.sh/blob/d608f71a55bb4368b4fab3eb203b96dff6a71c43/deploy-linux.sh#L42C2-L95C4
3 | # Autogenerated AppRun
4 | # Simplified version of the AppRun that go-appimage makes
5 |
6 | # shellcheck disable=SC2086
7 | [ -n "$DEBUG" ] && set -$DEBUG
8 |
9 | HERE="$(dirname "$(readlink -f "${0}")")"
10 | BIN="$ARGV0"
11 | unset ARGVO
12 | BIN_DIR="$HERE/usr/bin"
13 | LIB_DIR="$HERE/usr/lib"
14 | SHARE_DIR="$HERE/usr/share"
15 | SCHEMA_HERE="$SHARE_DIR/glib-2.0/runtime-schemas:$SHARE_DIR/glib-2.0/schemas"
16 | LD_LINUX="$(find "$HERE" -name 'ld-*.so.*' -print -quit)"
17 | PY_HERE="$(find "$LIB_DIR" -type d -name 'python*' -print -quit)"
18 | QT_HERE="$HERE/usr/plugins"
19 | GTK_HERE="$(find "$LIB_DIR" -name 'gtk-*' -type d -print -quit)"
20 | GDK_HERE="$(find "$HERE" -type d -regex '.*gdk.*loaders' -print -quit)"
21 | GDK_LOADER="$(find "$HERE" -type f -regex '.*gdk.*loaders.cache' -print -quit)"
22 |
23 | if [ ! -e "$BIN_DIR/$BIN" ]; then
24 | BIN="$(awk -F"=| " '/Exec=/{print $2; exit}' "$HERE"/*.desktop)"
25 | fi
26 | export PATH="$BIN_DIR:$PATH"
27 | export XDG_DATA_DIRS="$SHARE_DIR:$XDG_DATA_DIRS"
28 | if [ -n "$PY_HERE" ]; then
29 | export PYTHONHOME="$PY_HERE"
30 | fi
31 | if [ -d "$SHARE_DIR"/perl5 ] || [ -d "$LIB_DIR"/perl5 ]; then
32 | export PERLLIB="$SHARE_DIR/perl5:$LIB_DIR/perl5:$PERLLIB"
33 | fi
34 | if [ -d "$QT_HERE" ]; then
35 | export QT_PLUGIN_PATH="$QT_HERE"
36 | fi
37 | if [ -d "$GTK_HERE" ]; then
38 | export GTK_PATH="$GTK_HERE" \
39 | GTK_EXE_PREFIX="$HERE/usr" \
40 | GTK_DATA_PREFIX="$HERE/usr"
41 | fi
42 |
43 | TARGET="$BIN_DIR/$BIN"
44 | # deploy everything mode
45 | if [ -n "$LD_LINUX" ] ; then
46 | export GTK_THEME=Default \
47 | GCONV_PATH="$LIB_DIR"/gconv \
48 | GDK_PIXBUF_MODULEDIR="$GDK_HERE" \
49 | GDK_PIXBUF_MODULE_FILE="$GDK_LOADER" \
50 | FONTCONFIG_FILE="/etc/fonts/fonts.conf" \
51 | GSETTINGS_SCHEMA_DIR="$SCHEMA_HERE:$GSETTINGS_SCHEMA_DIR"
52 | if echo "$LD_LINUX" | grep -qi musl; then
53 | exec "$LD_LINUX" "$TARGET" "$@"
54 | else
55 | exec "$LD_LINUX" --inhibit-cache "$TARGET" "$@"
56 | fi
57 | else
58 | exec "$TARGET" "$@"
59 | fi
60 |
--------------------------------------------------------------------------------
/appbundle-runtime/embed_dwarfs.go:
--------------------------------------------------------------------------------
1 | //go:build !noEmbed && !squashfs
2 |
3 | package main
4 |
5 | import (
6 | _ "embed"
7 | "fmt"
8 | "strings"
9 | )
10 |
11 | //go:embed binaryDependencies/dwarfs
12 | var dwarfsBinary []byte
13 |
14 | var Filesystems = []*Filesystem{
15 | {
16 | Type: "dwarfs",
17 | Commands: []string{"dwarfs", "dwarfsextract"},
18 | MountCmd: func(cfg *RuntimeConfig) CommandRunner {
19 | cacheSize := getDwarfsCacheSize()
20 | args := []string{
21 | "-o", "ro,nodev",
22 | "-o", "cache_files,no_cache_image,clone_fd",
23 | "-o", "block_allocator=" + getEnvWithDefault(globalEnv, "DWARFS_BLOCK_ALLOCATOR", DWARFS_BLOCK_ALLOCATOR),
24 | "-o", getEnvWithDefault(globalEnv, "DWARFS_TIDY_STRATEGY", DWARFS_TIDY_STRATEGY),
25 | "-o", "debuglevel=" + T(getEnv(globalEnv, "ENABLE_FUSE_DEBUG") != "", "debug", "error"),
26 | "-o", "readahead=" + getEnvWithDefault(globalEnv, "DWARFS_READAHEAD", DWARFS_READAHEAD),
27 | "-o", "blocksize=" + getEnvWithDefault(globalEnv, "DWARFS_BLOCKSIZE", DWARFS_BLOCKSIZE),
28 | "-o", "cachesize=" + cacheSize,
29 | "-o", "workers=" + getDwarfsWorkers(&cacheSize),
30 | "-o", fmt.Sprintf("offset=%d", cfg.archiveOffset),
31 | cfg.selfPath,
32 | cfg.mountDir,
33 | }
34 | if e := getEnv(globalEnv, "DWARFS_ANALYSIS_FILE"); e != "" {
35 | args = append(args, "-o", "analysis_file="+e)
36 | }
37 | if e := getEnv(globalEnv, "DWARFS_PRELOAD_ALL"); e != "" {
38 | args = append(args, "-o", "preload_all")
39 | } else {
40 | args = append(args, "-o", "preload_category=hotness")
41 | }
42 | memitCmd, err := newMemitCmd(cfg, dwarfsBinary, "dwarfs", args...)
43 | if err != nil {
44 | logError("Failed to create memit command", err, cfg)
45 | }
46 | return memitCmd
47 | },
48 | ExtractCmd: func(cfg *RuntimeConfig, query string) CommandRunner {
49 | args := []string{
50 | "--input", cfg.selfPath,
51 | "--image-offset", fmt.Sprintf("%d", cfg.archiveOffset),
52 | "--output", cfg.mountDir,
53 | }
54 | if query != "" {
55 | for _, pattern := range strings.Split(query, " ") {
56 | args = append(args, "--pattern", pattern)
57 | }
58 | }
59 | memitCmd, err := newMemitCmd(cfg, dwarfsBinary, "dwarfsextract", args...)
60 | if err != nil {
61 | logError("Failed to create memit command", err, cfg)
62 | }
63 | return memitCmd
64 | },
65 | },
66 | }
67 |
--------------------------------------------------------------------------------
/pelf_linker:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Initialize variables
4 | PELF_BINDIRS=""
5 | PELF_LIBDIRS=""
6 |
7 | # Function to concatenate existing directories from *_binDir environment variables into PELF_BINDIRS
8 | concatenate_bindirs() {
9 | # Find all environment variables ending with _binDir
10 | vars="$(env | grep ".*_binDir=" | cut -f 1 -d '=')"
11 | for v in $vars; do
12 | # Get the value of the variable
13 | eval "vval=\$$v"
14 |
15 | # Save the current IFS and change it to handle colon-separated paths
16 | old_ifs="$IFS"
17 | IFS=":"
18 |
19 | # Loop through each path in the variable
20 | for dir in $vval; do
21 | # Check if the directory exists
22 | if [ -d "$dir" ]; then
23 | # Append to PELF_BINDIRS if the directory exists
24 | if [ -z "$PELF_BINDIRS" ]; then
25 | PELF_BINDIRS="$dir"
26 | else
27 | PELF_BINDIRS="$PELF_BINDIRS:$dir"
28 | fi
29 | fi
30 | done
31 |
32 | # Restore the original IFS
33 | IFS="$old_ifs"
34 | done
35 |
36 | # Print the concatenated PELF_BINDIRS
37 | if [ -z "$1" ]; then
38 | echo "PELF_BINDIRS=\"$PELF_BINDIRS\""
39 | fi
40 | }
41 |
42 | # Function to concatenate existing directories from *_libDir environment variables into PELF_LIBDIRS
43 | concatenate_libdirs() {
44 | # Find all environment variables ending with _libDir
45 | vars="$(env | grep ".*_libDir=" | cut -f 1 -d '=')"
46 | for v in $vars; do
47 | # Get the value of the variable
48 | eval "vval=\$$v"
49 |
50 | # Save the current IFS and change it to handle colon-separated paths
51 | old_ifs="$IFS"
52 | IFS=":"
53 |
54 | # Loop through each path in the variable
55 | for dir in $vval; do
56 | # Check if the directory exists
57 | if [ -d "$dir" ]; then
58 | # Append to PELF_LIBDIRS if the directory exists
59 | if [ -z "$PELF_LIBDIRS" ]; then
60 | PELF_LIBDIRS="$dir"
61 | else
62 | PELF_LIBDIRS="$PELF_LIBDIRS:$dir"
63 | fi
64 | fi
65 | done
66 |
67 | # Restore the original IFS
68 | IFS="$old_ifs"
69 | done
70 |
71 | # Print the concatenated PELF_LIBDIRS
72 | if [ -z "$1" ]; then
73 | echo "PELF_LIBDIRS=\"$PELF_LIBDIRS\""
74 | fi
75 | }
76 |
77 | # Call the functions
78 | concatenate_bindirs "$1"
79 | concatenate_libdirs "$1"
80 |
81 | if [ "$1" = "--export" ]; then
82 | export PELF_LIBDIRS="$PELF_LIBDIRS"
83 | export PELF_BINDIRS="$PELF_BINDIRS"
84 | else
85 | LD_LIBRARY_PATH="$PELF_LIBDIRS" PATH="$PATH:$PELF_BINDIRS" "$@"
86 | fi
87 |
--------------------------------------------------------------------------------
/assets/AppRun.gccToolchain:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ -n "$SELF" ]; then # Check if ARGV0 is set, which should contain the original command name
4 | SELF=$(basename "$SELF")
5 | else
6 | SELF=$(basename "$0") # Fallback to $0 if ARGV0 is not set, but this shouldn't happen with proper symlink setup
7 | fi
8 |
9 | SELF_TEMPDIR="$(dirname "$0")"
10 |
11 | # Set PATH to include local directories
12 | export PATH="$SELF_TEMPDIR/usr/bin:$SELF_TEMPDIR/bin:$PATH"
13 |
14 | # Set C_INCLUDE_PATH if it exists, or just initialize it
15 | if [ -n "$C_INCLUDE_PATH" ]; then
16 | export C_INCLUDE_PATH="$SELF_TEMPDIR/usr/include:$SELF_TEMPDIR/include:$C_INCLUDE_PATH"
17 | else
18 | export C_INCLUDE_PATH="$SELF_TEMPDIR/usr/include:$SELF_TEMPDIR/include"
19 | fi
20 |
21 | # Set CPLUS_INCLUDE_PATH if it exists, or just initialize it
22 | if [ -n "$CPLUS_INCLUDE_PATH" ]; then
23 | export CPLUS_INCLUDE_PATH="$SELF_TEMPDIR/usr/include:$SELF_TEMPDIR/include:$CPLUS_INCLUDE_PATH"
24 | else
25 | export CPLUS_INCLUDE_PATH="$SELF_TEMPDIR/usr/include:$SELF_TEMPDIR/include"
26 | fi
27 |
28 | # Set LIBRARY_PATH if it exists, or just initialize it
29 | if [ -n "$LIBRARY_PATH" ]; then
30 | export LIBRARY_PATH="$SELF_TEMPDIR/usr/lib:$SELF_TEMPDIR/lib:$LIBRARY_PATH"
31 | else
32 | export LIBRARY_PATH="$SELF_TEMPDIR/usr/lib:$SELF_TEMPDIR/lib"
33 | fi
34 |
35 | # Set PKG_CONFIG_PATH if it exists, or just initialize it
36 | if [ -n "$PKG_CONFIG_PATH" ]; then
37 | export PKG_CONFIG_PATH="$SELF_TEMPDIR/usr/lib/pkgconfig:$SELF_TEMPDIR/lib/pkgconfig:$PKG_CONFIG_PATH"
38 | else
39 | export PKG_CONFIG_PATH="$SELF_TEMPDIR/usr/lib/pkgconfig:$SELF_TEMPDIR/lib/pkgconfig"
40 | fi
41 |
42 | # Set CFLAGS if it exists, or just initialize it
43 | if [ -n "$CFLAGS" ]; then
44 | export CFLAGS="-I$SELF_TEMPDIR/usr/include -I$SELF_TEMPDIR/include $CFLAGS"
45 | else
46 | export CFLAGS="-I$SELF_TEMPDIR/usr/include -I$SELF_TEMPDIR/include"
47 | fi
48 |
49 | # Set LDFLAGS if it exists, or just initialize it
50 | if [ -n "$LDFLAGS" ]; then
51 | export LDFLAGS="-L$SELF_TEMPDIR/usr/lib -L$SELF_TEMPDIR/lib $LDFLAGS"
52 | else
53 | export LDFLAGS="-L$SELF_TEMPDIR/usr/lib -L$SELF_TEMPDIR/lib"
54 | fi
55 |
56 | # Check if the binary exists in the specified directories and execute it
57 | if [ -f "$SELF_TEMPDIR/bin/$SELF" ]; then
58 | exec "$SELF_TEMPDIR/bin/$SELF" "$@"
59 | elif [ -f "$SELF_TEMPDIR/usr/bin/$SELF" ]; then
60 | exec "$SELF_TEMPDIR/usr/bin/$SELF" "$@"
61 | fi
62 |
63 | if [ "$#" -lt 1 ]; then
64 | echo "No arguments were passed or the command does not match any binaries in bin/ or usr/bin/"
65 | else
66 | exec "$@"
67 | fi
68 |
--------------------------------------------------------------------------------
/docs/appBundleID.md:
--------------------------------------------------------------------------------
1 | # AppBundleID Format Specification
2 |
3 | An `AppBundleID` is required for every AppBundle to ensure proper functionality, such as generating the `mountDir` and enabling tools like `appstream-helper` to figure out info about the AppBundle. It can be non-compliant (i.e., not follow Type I, II, or III) if distribution via AppBundleHUB or `dbin` is not intended.
4 |
5 | ## Supported Formats
6 |
7 | ### Type I: Legacy Format
8 | - **Structure**: `name-versionString-maintainer` or `name-date-maintainer`
9 | - **Examples**: `steam-v128.0.3-xplshn`, `steam-20250101-xplshn`
10 | - **Description**: Consists of the application name, either a version or date, and the maintainer/repository identifier. Suitable for filenames on systems with restrictive character support (e.g., no `#`, `:`).
11 | - **Use Case**: Legacy distribution; preferred only for filenames
12 |
13 | ### Type II: Modern Format without Date
14 | - **Structure**: `name#repo[:version]`
15 | - **Examples**: `steam#xplshn:v128.0.3`, `steam#xplshn`
16 | - **Description**: Includes the application name, repository/maintainer, and an optional version. Uses `#` to separate name and repository, with `:` for version.
17 | - **Use Case**: Most preferred format, in its short-form version
18 |
19 | ### Type III: Modern Format with Optional Date
20 | - **Structure**: `name#repo[:version][@date]`
21 | - **Examples**: `steam#xplshn:v128.0.3@20250101`, `steam#xplshn@20250101`
22 | - **Description**: The most flexible format, including application name, repository/maintainer, optional version, and optional date. Uses `#`, `:`, and `@` as separators.
23 | - **Use Case**: Most preferred format, as it contains the most complete data for `appstream-helper` to parse
24 |
25 | ## Requirements and Usage
26 |
27 | - **AppBundleHUB Distribution**: For inclusion in AppBundleHUB or `dbin` repositories, the `AppBundleID` must adhere to Type I, II, or III, as these formats allow `appstream-helper` to parse metadata (name/appstreamID, version, maintainer, date) for automated repository indexing.
28 | - **Recommendation**: Type III is encouraged for the AppBundleID, as it is the most complete while Type I is recommended for AppBundle filenames on systems that may not support special characters like `#`, `:`, or `@` used in Type II and Type III.
29 |
30 | # NOTEs
31 |
32 | 1. The program's AppStreamID should be used as the Name in the AppBundleID if the AppBundle does not ship with an .xml appstream metainfo file in the top-level of its AppDir. This way `appstream-helper` can use scrapped Flathub data to automatically populate a .description, .icon, .screenshots, .rank & .version entry for the `dbin` repository index file
33 | 2. Type I, II & III are all parsable by `appstream-helper`
34 |
35 |
--------------------------------------------------------------------------------
/www/gen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | OPWD="$PWD"
4 | BASE="$(dirname "$(realpath "$0")")"
5 | TEMP_DIR="/tmp/pelf_build_$(date +%s)"
6 | # Change to BASE directory if not already there
7 | if [ "$OPWD" != "$BASE" ]; then
8 | echo "Changing to $BASE"
9 | cd "$BASE" || exit 1
10 | fi
11 | trap 'cd "$OPWD"; [ -d "$TEMP_DIR" ] && rm -rf "$TEMP_DIR"' EXIT
12 |
13 | # Check if we're really in WWW
14 | if [ ! "$(basename "$BASE")" = "www" ] || [ ! -f "$PWD/config.toml" ]; then
15 | echo "\"\$(basename \"\$BASE\")\" != \"www\" || \"\$PWD/config.toml\" does not exist"
16 | exit 1
17 | fi
18 |
19 | process_markdown_files() {
20 | mkdir -p "$2"
21 | for FILE in "$1"/*.md; do
22 | if [ ! -f "$FILE" ]; then continue; fi
23 | if [ "$(basename "$FILE")" = "_index.md" ]; then
24 | echo "Skipping \"$FILE\""
25 | cp "$FILE" "./content/docs"
26 | _GENERATE_EMPTY_INDEX=0
27 | continue
28 | fi
29 | if [ "$(basename "$FILE")" = "index.md" ]; then
30 | echo "Skipping \"$FILE\""
31 | continue
32 | fi
33 |
34 | FILENAME="$(basename "$FILE")"
35 | DATE="$(git log -1 --format="%ai" -- "$FILE" | awk '{print $1 "T" $2}')"
36 | TITLE=$(head -n 1 "$FILE" | grep '^#' | sed 's/^# //')
37 | [ -z "$TITLE" ] && {
38 | TITLE="$FILENAME"
39 | FILENAME_AS_TITLE=1
40 | }
41 | AUTHOR_NAME="$(git log --follow --format="%an" -- "$FILE" | tail -n 1)"
42 | AUTHOR_EMAIL="$(git log --follow --format="%ae" -- "$FILE" | tail -n 1)"
43 |
44 | {
45 | echo "+++"
46 | echo "date = '$DATE'"
47 | echo "draft = false"
48 | echo "title = '$TITLE'"
49 | echo "[params.author]"
50 | echo " name = '$AUTHOR_NAME'"
51 | echo " email = '$AUTHOR_EMAIL'"
52 | echo "+++"
53 | [ -z "$FILENAME_AS_TITLE" ] && sed '1{/^#/d}' "$FILE"
54 | } >"$2/$FILENAME"
55 | done
56 |
57 | if [ "$_GENERATE_EMPTY_INDEX" != "0" ]; then
58 | echo "Automatically generated an empty \"_index.md\""
59 | if [ "$(find "$2" -maxdepth 1 -type f | wc -l)" -gt 0 ]; then
60 | {
61 | echo "---"
62 | echo "title: '$3'"
63 | echo "---"
64 | } >"$2/_index.md"
65 | fi
66 | fi
67 | }
68 |
69 | # Start actual processing
70 | rm -rf -- ./content/docs/*
71 | rm -rf -- ./static/assets/*
72 | process_markdown_files "../docs" "./content/docs" "Documentation"
73 | find ../assets/ -type f ! -name '*AppRun*' ! -name '*LAUNCH*' -exec cp {} ./static/assets/ \;
74 | {
75 | echo "---"
76 | echo "title: 'Home'"
77 | echo "---"
78 | } >./content/_index.md
79 | sed 's|src="files/|src="assets/|g' ../README.md >>./content/_index.md
80 |
81 | # Build with Hugo
82 | hugo
83 |
--------------------------------------------------------------------------------
/www/config.toml:
--------------------------------------------------------------------------------
1 | title = "AppBundle Documentation & Implementation Details"
2 | baseURL = "https://pelf.xplshn.com.ar/"
3 | languageCode = "en-us"
4 | theme = "werx"
5 | publishDir = "pub"
6 | enableRobotsTXT = true
7 |
8 | ignoreFiles = ["\\.Rmd$", "_files$", "_cache$"]
9 | preserveTaxonomyNames = true
10 | enableEmoji = true
11 | footnotereturnlinkcontents = "↩"
12 |
13 | [module]
14 | [[module.mounts]]
15 | source = 'assets'
16 | target = 'assets'
17 | [[module.mounts]]
18 | source = 'static'
19 | target = 'assets'
20 |
21 | [permalinks]
22 | post = "/post/:year/:month/:day/:slug/"
23 |
24 | [[menu.main]]
25 | name = "Home"
26 | url = "/"
27 | weight = 1
28 | #[[menu.main]]
29 | # name = "Categories"
30 | # url = "/categories/"
31 | # weight = 2
32 | #[[menu.main]]
33 | # name = "Tags"
34 | # url = "/tags/"
35 | # weight = 3
36 | [[menu.feed]]
37 | name = "Subscribe"
38 | url = "/index.xml"
39 | weight = 100
40 | [[menu.feed]]
41 | name = "git.xplshn.com.ar"
42 | url = "https://git.xplshn.com.ar"
43 | weight = 90
44 | [[menu.feed]]
45 | name = "harmful.cat-v.org"
46 | url = "https://harmful.cat-v.org"
47 | weight = 80
48 | [[menu.feed]]
49 | name = "nosystemd.org"
50 | url = "https://nosystemd.org"
51 | weight = 70
52 | [[menu.feed]]
53 | name = "suckless.org"
54 | url = "https://suckless.org"
55 | weight = 60
56 | [[menu.feed]]
57 | name = "copacabana.pindorama.net.br"
58 | url = "https://copacabana.pindorama.net.br"
59 | weight = 50
60 | [[menu.feed]]
61 | name = "shithub.us"
62 | url = "https://shithub.us"
63 | weight = 40
64 | [[menu.feed]]
65 | name = "managainstthestate.blogspot.com"
66 | url = "https://web.archive.org/web/20231123031907/https://managainstthestate.blogspot.com/2011/08/anarcho-capitalist-resources-by-wesker.html"
67 | weight = 20
68 | [[menu.feed]]
69 | name = "musl.libc.org"
70 | url = "https://musl.libc.org"
71 | weight = 10
72 |
73 | [taxonomies]
74 | category = "categories"
75 | series = "series"
76 | tag = "tags"
77 |
78 | [params]
79 | subtitle = "Labs"
80 | brandIconFile = "assets/images/icon.svg"
81 | abbrDateFmt = "Jan 2"
82 | dateFmt = "01.02.2006 15:04"
83 | themeVariant = "theme_blue.css"
84 | printSidebar = false
85 |
86 | [[social]]
87 | name = "Github"
88 | url = "https://github.com/xplshn"
89 |
90 | [markup.goldmark.renderer]
91 | hardWraps = false
92 | unsafe = true
93 |
94 | [markup.goldmark.extensions]
95 | [markup.goldmark.extensions.passthrough]
96 | enable = true
97 | [markup.goldmark.extensions.passthrough.delimiters]
98 | block = [['\[', '\]'], ['$$', '$$']]
99 | inline = [['\(', '\)']]
100 |
101 | [markup.goldmark.renderHooks.image]
102 | enableDefault = true
103 |
104 |
--------------------------------------------------------------------------------
/cmd/pfusermount/cbuild.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | OPWD="$PWD"
4 | BASE="$(dirname "$(realpath "$0")")"
5 | if [ "$OPWD" != "$BASE" ]; then
6 | echo "... $BASE is not the same as $PWD ..."
7 | echo "Going into $BASE and coming back here in a bit"
8 | cd "$BASE" || exit 1
9 | fi
10 | trap 'cd "$OPWD"' EXIT
11 |
12 | # Function to log to stdout with green color
13 | log() {
14 | _reset="\033[m"
15 | _blue="\033[34m"
16 | printf "${_blue}->${_reset} %s\n" "$*"
17 | }
18 |
19 | # Function to log_warning to stdout with yellow color
20 | log_warning() {
21 | _reset="\033[m"
22 | _yellow="\033[33m"
23 | printf "${_yellow}->${_reset} %s\n" "$*"
24 | }
25 |
26 | # Function to log_error to stdout with red color
27 | log_error() {
28 | _reset="\033[m"
29 | _red="\033[31m"
30 | printf "${_red}->${_reset} %s\n" "$*"
31 | exit 1
32 | }
33 |
34 | unnappear() {
35 | "$@" >/dev/null 2>&1
36 | }
37 |
38 | # Check if a dependency is available.
39 | available() {
40 | unnappear which "$1" || return 1
41 | }
42 |
43 | # Exit if a dependency is not available
44 | require() {
45 | available "$1" || log_error "[$1] is not installed. Please ensure the command is available [$1] and try again."
46 | }
47 |
48 | download() {
49 | log "Downloading $1"
50 | if ! wget -U "dbin" -O "./$(basename "$1")" "https://bin.pkgforge.dev/$(uname -m)_$(uname)/$1"; then
51 | log_error "Unable to download [$1]"
52 | fi
53 | chmod +x "./$1"
54 | }
55 |
56 | build_project() {
57 | # FUSERMOUNT
58 | log 'Compiling "pfusermount"'
59 | go build -o ./fusermount ./fusermount.go
60 | # FUSERMOUNT3
61 | log 'Compiling "pfusermount3"'
62 | go build -o ./fusermount3 ./fusermount3.go
63 | }
64 |
65 | clean_project() {
66 | log "Starting clean process"
67 | echo "rm ./*fusermount"
68 | unnappear rm ./*fusermount
69 | echo "rm ./*fusermount3"
70 | unnappear rm ./*fusermount3
71 | log "Clean process completed"
72 | }
73 |
74 | retrieve_executable() {
75 | readlink -f ./pfusermount
76 | readlink -f ./pfusermount3
77 | }
78 |
79 | # Main case statement for actions
80 | case "$1" in
81 | "" | "build")
82 | require go
83 | #log "Checking if embeddable assets are available"
84 | #if [ ! -f "./fusermount" ] || [ ! -f "./fusermount3" ]; then
85 | log "Procuring embeddable goodies"
86 | download "Baseutils/fuse/fusermount"
87 | download "Baseutils/fuse3/fusermount3"
88 | #fi
89 | log "Starting build process"
90 | build_project
91 | ;;
92 | "clean")
93 | clean_project
94 | ;;
95 | "retrieve")
96 | retrieve_executable
97 | ;;
98 | *)
99 | log_warning "Usage: $0 {build|clean|retrieve}"
100 | exit 1
101 | ;;
102 | esac
103 |
--------------------------------------------------------------------------------
/cmd/dynexec/dynexec.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | "regexp"
8 | "syscall"
9 | )
10 |
11 | const dynexeName = "dynexe"
12 |
13 | // Matches any linker name that follows the pattern "ld-linux" or "ld-musl" for x86_64 or aarch64.
14 | var linkerRegexp = regexp.MustCompile(`ld-(linux|musl)-(x86_64|aarch64).so.[0-9]+`)
15 |
16 | func matchLinkerName(sharedLib string) string {
17 | // Check files in the sharedLib directory and match against the linker pattern
18 | files, err := os.ReadDir(sharedLib)
19 | if err != nil {
20 | panic(fmt.Sprintf("failed to read shared library directory: %v", err))
21 | }
22 |
23 | for _, file := range files {
24 | if !file.IsDir() && linkerRegexp.MatchString(file.Name()) {
25 | return file.Name()
26 | }
27 | }
28 | return ""
29 | }
30 |
31 | func realpath(path string) string {
32 | absPath, err := filepath.EvalSymlinks(path)
33 | if err != nil {
34 | panic(err)
35 | }
36 | return absPath
37 | }
38 |
39 | func basename(path string) string {
40 | return filepath.Base(path)
41 | }
42 |
43 | func isFile(path string) bool {
44 | info, err := os.Stat(path)
45 | if err != nil {
46 | return false
47 | }
48 | return !info.IsDir()
49 | }
50 |
51 | func main() {
52 | // Get the executable path
53 | dynexe, err := os.Executable()
54 | if err != nil {
55 | panic(err)
56 | }
57 | dynexeDir := filepath.Dir(dynexe)
58 | lowerDir := filepath.Join(dynexeDir, "../") // TODO, what is this?
59 |
60 | // Check if the parent directory contains the dynexe binary
61 | if basename(dynexeDir) == "bin" && isFile(filepath.Join(lowerDir, dynexeName)) {
62 | dynexeDir = realpath(lowerDir)
63 | }
64 |
65 | // Collect command-line arguments
66 | execArgs := os.Args
67 | arg0 := execArgs[0]
68 | execArgs = execArgs[1:]
69 |
70 | sharedBin := filepath.Join(dynexeDir, "shared/bin")
71 | sharedLib := filepath.Join(dynexeDir, "shared/lib")
72 |
73 | // Determine the binary name to run
74 | binName := basename(arg0)
75 | if binName == dynexeName {
76 | binName = execArgs[0]
77 | execArgs = execArgs[1:]
78 | }
79 | bin := filepath.Join(sharedBin, binName)
80 |
81 | // Get the linker path by matching against shared lib files using regular expressions
82 | linkerName := matchLinkerName(sharedLib)
83 | if linkerName == "" {
84 | panic(fmt.Sprintf("no valid linker found in %s", sharedLib))
85 | }
86 | linker := filepath.Join(sharedLib, linkerName)
87 |
88 | // Prepare arguments for execve
89 | args := []string{linker, "--library-path", sharedLib, bin}
90 | args = append(args, execArgs...)
91 |
92 | // Prepare environment variables
93 | envs := os.Environ()
94 |
95 | // Execute the binary using syscall.Exec (equivalent to userland execve)
96 | err = syscall.Exec(linker, args, envs)
97 | if err != nil {
98 | panic(fmt.Sprintf("failed to execute %s: %v", linker, err))
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/cmd/pelfd/appimage_support.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "path/filepath"
5 | "strings"
6 | "fmt"
7 | "os"
8 | "os/exec"
9 | )
10 |
11 | func integrateAppImage(path, appPath string, entry *BundleEntry) {
12 | baseName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
13 | entry.Png = extractAppImageMetadata("icon", path, filepath.Join(appPath, baseName+".png"))
14 | entry.Desktop = extractAppImageMetadata("desktop", path, filepath.Join(appPath, baseName+".desktop"))
15 | }
16 |
17 | func extractAppImageMetadata(metadataType, appImagePath, outputFile string) string {
18 | logMessage("INF", fmt.Sprintf("Extracting %s from AppImage: %s", metadataType, appImagePath))
19 |
20 | // Create a temporary directory for extraction
21 | tempDir, err := os.MkdirTemp("", "appimage-extract-")
22 | if err != nil {
23 | logMessage("ERR", fmt.Sprintf("Failed to create temporary directory: %v", err))
24 | return ""
25 | }
26 | // Defer the removal of the tempDir to ensure it is deleted at the end of the function
27 | defer func() {
28 | if err := os.RemoveAll(tempDir); err != nil {
29 | logMessage("ERR", fmt.Sprintf("Failed to remove temporary directory: %v", err))
30 | }
31 | }()
32 |
33 | if err := os.Chdir(tempDir); err != nil {
34 | logMessage("ERR", fmt.Sprintf("Failed to change directory to %s: %v", tempDir, err))
35 | return ""
36 | }
37 |
38 | var metadataPath string
39 |
40 | switch metadataType {
41 | case "icon":
42 | cmd := exec.Command("sh", "-c", fmt.Sprintf("%s --appimage-extract .DirIcon", appImagePath))
43 | if err := cmd.Run(); err != nil {
44 | logMessage("WRN", fmt.Sprintf("Failed to extract .DirIcon from AppImage: %s", appImagePath))
45 | return ""
46 | }
47 | metadataPath = filepath.Join(tempDir, "squashfs-root", ".DirIcon")
48 | case "desktop":
49 | cmd := exec.Command("sh", "-c", fmt.Sprintf("%s --appimage-extract *.desktop", appImagePath))
50 | if err := cmd.Run(); err != nil {
51 | logMessage("WRN", fmt.Sprintf("Failed to extract .desktop from AppImage: %s", appImagePath))
52 | return ""
53 | }
54 | // Find the first .desktop file in the directory
55 | files, err := filepath.Glob(filepath.Join(tempDir, "squashfs-root", "*.desktop"))
56 | if err != nil || len(files) == 0 {
57 | logMessage("WRN", fmt.Sprintf(".desktop file not found in AppImage: %s", appImagePath))
58 | return ""
59 | }
60 | metadataPath = files[0]
61 | default:
62 | logMessage("ERR", fmt.Sprintf("Unknown metadata type: %s", metadataType))
63 | return ""
64 | }
65 |
66 | if !fileExists(metadataPath) {
67 | logMessage("WRN", fmt.Sprintf("%s not found in AppImage: %s", strings.Title(metadataType), appImagePath))
68 | return ""
69 | }
70 |
71 | if err := copyFile(metadataPath, outputFile); err != nil {
72 | logMessage("ERR", fmt.Sprintf("Failed to copy %s file: %v", metadataType, err))
73 | return ""
74 | }
75 |
76 | logMessage("INF", fmt.Sprintf("Successfully extracted %s to: %s", metadataType, outputFile))
77 | return outputFile
78 | }
79 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/xplshn/pelf //module pelf
2 |
3 | go 1.24.0
4 |
5 | require (
6 | fyne.io/fyne/v2 v2.5.5
7 | github.com/emmansun/base64 v0.7.0
8 | github.com/go-ini/ini v1.67.0
9 | github.com/goccy/go-json v0.10.5
10 | github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
11 | github.com/joho/godotenv v1.5.1
12 | github.com/klauspost/compress v1.18.1
13 | github.com/liamg/memit v0.0.3
14 | github.com/liamg/tml v0.7.0
15 | github.com/mholt/archives v0.1.2
16 | github.com/minio/md5-simd v1.1.2
17 | github.com/pkg/xattr v0.4.12
18 | github.com/shamaton/msgpack/v2 v2.4.0
19 | github.com/shirou/gopsutil/v4 v4.25.4
20 | github.com/u-root/u-root v0.14.0
21 | github.com/urfave/cli/v3 v3.6.1
22 | github.com/zeebo/blake3 v0.2.4
23 | golang.org/x/sys v0.38.0
24 | pgregory.net/rand v1.0.2
25 | )
26 |
27 | require (
28 | fyne.io/systray v1.11.0 // indirect
29 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
30 | github.com/STARRY-S/zip v0.2.3 // indirect
31 | github.com/andybalholm/brotli v1.2.0 // indirect
32 | github.com/bodgit/plumbing v1.3.0 // indirect
33 | github.com/bodgit/sevenzip v1.6.1 // indirect
34 | github.com/bodgit/windows v1.0.1 // indirect
35 | github.com/davecgh/go-spew v1.1.1 // indirect
36 | github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
37 | github.com/ebitengine/purego v0.8.4 // indirect
38 | github.com/fredbi/uri v1.1.0 // indirect
39 | github.com/fsnotify/fsnotify v1.7.0 // indirect
40 | github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
41 | github.com/fyne-io/glfw-js v0.0.0-20241126112943-313d8a0fe1d0 // indirect
42 | github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
43 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
44 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
45 | github.com/go-ole/go-ole v1.3.0 // indirect
46 | github.com/go-text/render v0.2.0 // indirect
47 | github.com/go-text/typesetting v0.2.0 // indirect
48 | github.com/godbus/dbus/v5 v5.1.0 // indirect
49 | github.com/gopherjs/gopherjs v1.17.2 // indirect
50 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
51 | github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 // indirect
52 | github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
53 | github.com/klauspost/cpuid/v2 v2.3.0 // indirect
54 | github.com/klauspost/pgzip v1.2.6 // indirect
55 | github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
56 | github.com/mattn/go-runewidth v0.0.16 // indirect
57 | github.com/minio/minlz v1.0.1 // indirect
58 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
59 | github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect
60 | github.com/nwaples/rardecode/v2 v2.1.0 // indirect
61 | github.com/olekukonko/tablewriter v0.0.5 // indirect
62 | github.com/pierrec/lz4/v4 v4.1.22 // indirect
63 | github.com/pmezard/go-difflib v1.0.0 // indirect
64 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
65 | github.com/rivo/uniseg v0.4.7 // indirect
66 | github.com/rymdport/portal v0.3.0 // indirect
67 | github.com/sorairolake/lzip-go v0.3.7 // indirect
68 | github.com/spf13/afero v1.14.0 // indirect
69 | github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
70 | github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
71 | github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
72 | github.com/stretchr/testify v1.11.1 // indirect
73 | github.com/therootcompany/xz v1.0.1 // indirect
74 | github.com/tklauser/go-sysconf v0.3.15 // indirect
75 | github.com/tklauser/numcpus v0.10.0 // indirect
76 | github.com/ulikunitz/xz v0.5.14 // indirect
77 | github.com/yuin/goldmark v1.7.1 // indirect
78 | github.com/yusufpapurcu/wmi v1.2.4 // indirect
79 | go4.org v0.0.0-20230225012048-214862532bf5 // indirect
80 | golang.org/x/image v0.25.0 // indirect
81 | golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
82 | golang.org/x/net v0.40.0 // indirect
83 | golang.org/x/text v0.26.0 // indirect
84 | gopkg.in/yaml.v3 v3.0.1 // indirect
85 | )
86 |
--------------------------------------------------------------------------------
/cmd/dynexec/C/dynexec.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #define DYNEXE_NAME "dynexe"
12 | extern char **environ;
13 |
14 | char* match_linker_name(const char* shared_lib) {
15 | struct dirent *entry;
16 | DIR *dp;
17 | regex_t regex;
18 | int reti;
19 | static char linker_name[256];
20 |
21 | // Compile regex to match "ld-*-*.so.*"
22 | reti = regcomp(®ex, "^ld-.*-.*\\.so\\..*", 0);
23 | if (reti) {
24 | fprintf(stderr, "Could not compile regex\n");
25 | exit(EXIT_FAILURE);
26 | }
27 |
28 | dp = opendir(shared_lib);
29 | if (dp == NULL) {
30 | perror("opendir");
31 | exit(EXIT_FAILURE);
32 | }
33 |
34 | while ((entry = readdir(dp))) {
35 | struct stat st;
36 | char path[1024];
37 | snprintf(path, sizeof(path), "%s/%s", shared_lib, entry->d_name);
38 | if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) {
39 | reti = regexec(®ex, entry->d_name, 0, NULL, 0);
40 | if (!reti) {
41 | snprintf(linker_name, sizeof(linker_name), "%s", entry->d_name);
42 | closedir(dp);
43 | regfree(®ex);
44 | return linker_name;
45 | }
46 | }
47 | }
48 |
49 | closedir(dp);
50 | regfree(®ex);
51 | return NULL;
52 | }
53 |
54 | char* realpath_alloc(const char* path) {
55 | char* resolved_path = realpath(path, NULL);
56 | if (!resolved_path) {
57 | perror("realpath");
58 | exit(EXIT_FAILURE);
59 | }
60 | return resolved_path;
61 | }
62 |
63 | char* custom_basename(const char* path) {
64 | char* base = strrchr(path, '/');
65 | return base ? base + 1 : (char*)path;
66 | }
67 |
68 | int is_file(const char* path) {
69 | struct stat path_stat;
70 | if (stat(path, &path_stat) != 0) {
71 | return 0;
72 | }
73 | return S_ISREG(path_stat.st_mode);
74 | }
75 |
76 | int main(int argc, char* argv[]) {
77 | char* dynexe = realpath_alloc("/proc/self/exe");
78 | char* dynexe_dir = strdup(dynexe);
79 | dynexe_dir = dirname(dynexe_dir);
80 | char lower_dir[512];
81 |
82 | snprintf(lower_dir, sizeof(lower_dir), "%s/../", dynexe_dir);
83 |
84 | // Check if we are in the "bin" directory
85 | if (strcmp(custom_basename(dynexe_dir), "bin") == 0 && is_file(lower_dir)) {
86 | free(dynexe_dir);
87 | dynexe_dir = realpath_alloc(lower_dir);
88 | }
89 |
90 | char* shared_bin = malloc(strlen(dynexe_dir) + 12);
91 | snprintf(shared_bin, strlen(dynexe_dir) + 12, "%s/shared/bin", dynexe_dir);
92 |
93 | char* shared_lib = malloc(strlen(dynexe_dir) + 12);
94 | snprintf(shared_lib, strlen(dynexe_dir) + 12, "%s/shared/lib", dynexe_dir);
95 |
96 | // Determine the binary to run
97 | char* bin_name = custom_basename(argv[0]);
98 | if (strcmp(bin_name, DYNEXE_NAME) == 0 && argc > 1) {
99 | bin_name = argv[1];
100 | argv++;
101 | argc--;
102 | }
103 |
104 | char* bin = malloc(strlen(shared_bin) + strlen(bin_name) + 2);
105 | snprintf(bin, strlen(shared_bin) + strlen(bin_name) + 2, "%s/%s", shared_bin, bin_name);
106 |
107 | char* linker_name = match_linker_name(shared_lib);
108 | if (!linker_name) {
109 | fprintf(stderr, "No valid linker found in %s\n", shared_lib);
110 | exit(EXIT_FAILURE);
111 | }
112 |
113 | char* linker = malloc(strlen(shared_lib) + strlen(linker_name) + 2);
114 | snprintf(linker, strlen(shared_lib) + strlen(linker_name) + 2, "%s/%s", shared_lib, linker_name);
115 |
116 | // Prepare arguments for execve
117 | char* exec_args[argc + 4];
118 | exec_args[0] = linker;
119 | exec_args[1] = "--library-path";
120 | exec_args[2] = shared_lib;
121 | exec_args[3] = bin;
122 | for (int i = 1; i < argc; i++) {
123 | exec_args[3 + i] = argv[i];
124 | }
125 | exec_args[argc + 3] = NULL;
126 |
127 | // Execute the binary using execve
128 | if (execve(linker, exec_args, environ) == -1) {
129 | fprintf(stderr, "Failed to execute %s: %s\n", linker, strerror(errno));
130 | exit(EXIT_FAILURE);
131 | }
132 |
133 | // Clean up
134 | free(dynexe);
135 | free(dynexe_dir);
136 | free(shared_bin);
137 | free(shared_lib);
138 | free(bin);
139 | free(linker);
140 |
141 | return 0;
142 | }
143 |
--------------------------------------------------------------------------------
/cmd/misc/BS2AppBundle:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ -n "$OVERRIDE_ENTRYPOINT" ]; then
4 | echo "OVERRIDE_ENTRYPOINT is set to: $OVERRIDE_ENTRYPOINT. It will be used as the entrypoint."
5 | fi
6 | if [ -n "$OVERRIDE_DESKTOP" ]; then
7 | echo "OVERRIDE_DESKTOP will be passed to a sed command"
8 | fi
9 |
10 | # Check if an AppImage was provided
11 | if [ -z "$1" ]; then
12 | echo "Usage: $0 "
13 | exit 1
14 | fi
15 |
16 | # Ensure full path to avoid confusion with relative paths
17 | APPIMAGE="$(realpath "$1")"
18 |
19 | # Check if the file actually exists
20 | if [ ! -f "$APPIMAGE" ]; then
21 | echo "Error: $APPIMAGE not found!"
22 | exit 1
23 | fi
24 |
25 | DIROFTHEAPP="$(dirname "$APPIMAGE")"
26 | APPNAME="$(basename "$APPIMAGE" .AppImage)"
27 | WORKDIR="$(mktemp -d)"
28 |
29 | if [ -z "$COMPRESSION_OPTS" ]; then
30 | if [ "$APPBUNDLE_FS" = "dwfs" ]; then
31 | COMPRESSION_OPTS="-l7 -C zstd:level=22 --metadata-compression null -S 21 -B 8 --order nilsimsa -W 12 -w 4"
32 | elif [ "$APPBUNDLE_FS" = "sqfs" ]; then
33 | COMPRESSION_OPTS="-comp zstd -Xcompression-level 15"
34 | fi
35 | fi
36 |
37 | # Clean up on exit
38 | cleanup() {
39 | fusermount3 -uz "$WORKDIR/rootfs-based.AppDir/rootfs" >/dev/null 2>&1
40 | fusermount -uz "$WORKDIR/rootfs-based.AppDir/rootfs" >/dev/null 2>&1
41 | rm -rf "$WORKDIR"
42 | }
43 | trap cleanup EXIT
44 |
45 | cd "$WORKDIR" || exit 1
46 |
47 | # Ensure the AppImage is executable
48 | chmod +x "$APPIMAGE"
49 |
50 | # Extract the AppImage contents
51 | "$APPIMAGE" --appimage-extract
52 |
53 | # Check if squashfs-root was created
54 | if [ ! -d "squashfs-root" ]; then
55 | echo "Failed to extract AppImage. squashfs-root not found."
56 | exit 1
57 | fi
58 |
59 | # Check if conty.sh exists in squashfs-root
60 | if [ -f squashfs-root/conty.sh ]; then
61 | echo "Found conty.sh. Extracting with dwarfsextract..."
62 |
63 | # Create the rootfs-based.AppDir
64 | mkdir -p rootfs-based.AppDir/rootfs rootfs-based.AppDir/usr/bin
65 |
66 | # Extract the conty.sh to the rootfs
67 | cp ./squashfs-root/*.desktop ./squashfs-root/.DirIcon ./rootfs-based.AppDir
68 | dwarfs -o offset="auto",ro,auto_unmount "./squashfs-root/conty.sh" "rootfs-based.AppDir/rootfs" && {
69 | echo "Removing decompressed squashfs-root to free up RAM"
70 | rm -rf ./squashfs-root
71 | }
72 |
73 | # Download AppRun for rootfs-based AppDir
74 | if ! wget -qO "rootfs-based.AppDir/AppRun" https://raw.githubusercontent.com/xplshn/pelf/refs/heads/dev/assets/AppRun.rootfs-based; then
75 | echo "Failed to download AppRun.rootfs-based"
76 | exit 1
77 | fi
78 | chmod +x "rootfs-based.AppDir/AppRun"
79 |
80 | # Download and install bwrap
81 | if ! wget -qO "rootfs-based.AppDir/usr/bin/bwrap" "https://bin.ajam.dev/$(uname -m)/bwrap-patched"; then
82 | echo "Unable to install bwrap to rootfs-based.AppDir/usr/bin/bwrap"
83 | exit 1
84 | fi
85 | chmod +x "rootfs-based.AppDir/usr/bin/bwrap"
86 | echo "Packaging as a rootfs-based AppBundle..."
87 |
88 | # Pack the new rootfs-based.AppDir as an AppBundle
89 | pelf-$APPBUNDLE_FS --add-appdir ./rootfs-based.AppDir \
90 | --appbundle-id "$APPNAME" \
91 | --output-to "$DIROFTHEAPP/$APPNAME.$APPBUNDLE_FS.AppBundle" \
92 | --embed-static-tools \
93 | --compression "$COMPRESSION_OPTS"
94 | else
95 | echo "Packaging as a standard AppBundle..."
96 |
97 | # No conty.sh, package the squashfs-root directly as an AppBundle
98 | pelf-$APPBUNDLE_FS --add-appdir ./squashfs-root \
99 | --appbundle-id "$APPNAME" \
100 | --output-to "$DIROFTHEAPP/$APPNAME.$APPBUNDLE_FS.AppBundle" \
101 | --embed-static-tools \
102 | --compression "$COMPRESSION_OPTS"
103 | fi
104 |
105 | # Find the .desktop file and extract the Exec= line
106 | DESKTOP_FILE=$(find ./rootfs-based.AppDir -type f -name "*.desktop" | head -n 1)
107 | if [ -f "$DESKTOP_FILE" ]; then
108 | if [ -n "$OVERRIDE_DESKTOP" ]; then
109 | # Apply the custom SED expression if provided
110 | sed -i "$OVERRIDE_DESKTOP" "$DESKTOP_FILE"
111 | echo "Applied OVERRIDE_DESKTOP SED expression on $DESKTOP_FILE"
112 | fi
113 |
114 | # Extract the Exec= line after possible modifications
115 | EXEC_LINE=$(awk -F"=| " '/Exec=/ {print $2; exit}' "$DESKTOP_FILE")
116 | if [ -n "$OVERRIDE_ENTRYPOINT" ]; then
117 | # Use the provided override if set
118 | echo "$OVERRIDE_ENTRYPOINT" > "$WORKDIR/entrypoint"
119 | echo "Set entrypoint to OVERRIDE_ENTRYPOINT: $OVERRIDE_ENTRYPOINT"
120 | elif [ -n "$EXEC_LINE" ]; then
121 | echo "Exec line found: $EXEC_LINE"
122 | echo "$EXEC_LINE" > "$WORKDIR/entrypoint"
123 | else
124 | echo "Exec line not found in $DESKTOP_FILE"
125 | fi
126 | else
127 | echo "No .desktop file found in rootfs-based.AppDir"
128 | fi
129 |
130 | echo "AppBundle created successfully in $DIROFTHEAPP."
131 |
--------------------------------------------------------------------------------
/.github/workflows/buildTooling.yml:
--------------------------------------------------------------------------------
1 | name: Build and Release PELF tooling as a single-file executable
2 | concurrency:
3 | group: build-${{ github.ref }}
4 | cancel-in-progress: true
5 | on:
6 | workflow_dispatch:
7 | #schedule:
8 | # - cron: "0 14 * * 0"
9 | jobs:
10 | build:
11 | name: "${{ matrix.name }} (${{ matrix.arch }})"
12 | runs-on: ${{ matrix.runs-on }}
13 | strategy:
14 | matrix:
15 | include:
16 | - runs-on: ubuntu-latest
17 | name: "cbuild.sh (amd64)"
18 | arch: x86_64
19 | - runs-on: ubuntu-24.04-arm
20 | name: "cbuild.sh (arm64)"
21 | arch: aarch64
22 | container:
23 | image: "alpine:edge"
24 | volumes:
25 | - /:/host # Jailbreak!
26 | - /tmp/node20:/__e/node20
27 | steps:
28 | - name: Patch native Alpine NodeJS into Runner environment
29 | if: matrix.arch == 'aarch64'
30 | run: |
31 | apk add nodejs gcompat openssl
32 | sed -i "s:ID=alpine:ID=NotpineForGHA:" /etc/os-release
33 | # --- old workaround ---
34 | #ls /host/home/runner/*/*
35 | #cd /host/home/runner/runners/*/externals/
36 | #rm -rf node20/*
37 | #mkdir node20/bin
38 | #ln -sfT /usr/bin/node node20/bin/node
39 | # --- second workaround ---
40 | mkdir -p /__e/node20/bin
41 | ln -sfT /usr/bin/node /__e/node20/bin/node
42 | ln -sfT /usr/bin/npm /__e/node20/bin/npm
43 |
44 | - name: Checkout repository
45 | uses: actions/checkout@v4
46 | with:
47 | fetch-depth: 0
48 | - name: Set up GOBIN and install lib4bin
49 | run: |
50 | set -x
51 | apk add zstd git bash file binutils patchelf findutils grep sed strace go fuse3 fuse curl yq-go b3sum
52 | export GOBIN="$GITHUB_WORKSPACE/.local/bin" CGO_ENABLED=0 GO_LDFLAGS='-buildmode=static-pie' GOFLAGS='-ldflags=-static-pie -ldflags=-s -ldflags=-w'
53 | export DBIN_INSTALL_DIR="$GOBIN" DBIN_NOCONFIG=1 PATH="$GOBIN:$PATH"
54 | mkdir -p "$GOBIN"
55 | wget -qO- "https://raw.githubusercontent.com/xplshn/dbin/master/stubdl" | sh -s -- --install "$DBIN_INSTALL_DIR/dbin" -v
56 | "$DBIN_INSTALL_DIR/dbin" --silent add yq upx
57 | echo "PATH=$PATH" >> $GITHUB_ENV
58 | echo "DBIN_INSTALL_DIR=$GOBIN" >> $GITHUB_ENV
59 | echo "WITH_SHARUN=1" >> $GITHUB_ENV
60 | echo "GEN_LIB_PATH=1" >> $GITHUB_ENV
61 | echo "ANY_EXECUTABLE=1" >> $GITHUB_ENV
62 | mkdir "$GITHUB_WORKSPACE/dist"
63 | ROOTFS_URL="$(curl -qsL https://dl-cdn.alpinelinux.org/alpine/edge/releases/${{ matrix.arch }}/latest-releases.yaml | yq '.[0].file')"
64 | echo "https://dl-cdn.alpinelinux.org/alpine/edge/releases/${{ matrix.arch }}/${ROOTFS_URL}" >"$GITHUB_WORKSPACE/dist/alpineLinuxEdge.${{ matrix.arch }}.rootfsURL"
65 | ROOTFS_URL="https://dl-cdn.alpinelinux.org/alpine/edge/releases/${{ matrix.arch }}/${ROOTFS_URL}"
66 | export ROOTFS_URL
67 | echo "ROOTFS_URL=$ROOTFS_URL" >> "$GITHUB_ENV"
68 | apk add coreutils
69 | - name: Build AppBundle tooling
70 | run: |
71 | cd "$GITHUB_WORKSPACE"
72 | export CGO_ENABLED=0 GOFLAGS="-ldflags=-static-pie -ldflags=-s -ldflags=-w" GO_LDFLAGS="-buildmode=static-pie -s -w"
73 | export _RELEASE="1"
74 | ./cbuild.sh && ./cbuild.sh pelfCreator_extensions
75 | B3SUM_CHECKSUM="$(b3sum ./pelf | awk '{print $1}')"
76 | mv ./pelf "$GITHUB_WORKSPACE/dist/pelf_${{ matrix.arch }}"
77 | mv ./cmd/pelfCreator/pelfCreator "$GITHUB_WORKSPACE/dist/pelfCreator_${{ matrix.arch }}"
78 | mv ./cmd/pelfCreator/pelfCreatorExtension_archLinux.tar.zst "$GITHUB_WORKSPACE/dist/pelfCreatorExtension_archLinux_${{ matrix.arch }}".tar.zst
79 | mv ./cmd/misc/appstream-helper/appstream-helper "$GITHUB_WORKSPACE/dist/appstream-helper_${{ matrix.arch }}"
80 | echo "RELEASE_TAG=$(date +%d%m%Y)-$B3SUM_CHECKSUM" >> $GITHUB_ENV
81 | - name: Upload artifact
82 | uses: actions/upload-artifact@v4.6.1
83 | with:
84 | name: AppBundle-${{ matrix.arch }}
85 | path: ${{ github.workspace }}/dist/*
86 | - name: Set build output
87 | id: build_output
88 | run: |
89 | echo "release_tag=$(date +%d%m%Y)-$(b3sum ./pelf | awk '{print $1}')" >> $GITHUB_OUTPUT
90 |
91 | release:
92 | name: Create Release
93 | needs: build
94 | runs-on: ubuntu-latest
95 | steps:
96 | - name: Download all artifacts
97 | uses: actions/download-artifact@v4
98 | with:
99 | path: artifacts
100 | merge-multiple: true
101 |
102 | - name: List files
103 | run: find artifacts -type f | sort
104 |
105 | - name: Create Release
106 | uses: softprops/action-gh-release@v2.2.1
107 | with:
108 | name: "Build ${{ needs.build.outputs.release_tag || github.run_number }}"
109 | tag_name: "${{ needs.build.outputs.release_tag || github.run_number }}"
110 | prerelease: false
111 | draft: false
112 | generate_release_notes: false
113 | make_latest: true
114 | files: |
115 | artifacts/*
116 |
--------------------------------------------------------------------------------
/www/content/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Home'
3 | ---
4 | ### PELF - The AppBundle format and the AppBundle Creation Tool
5 | ###### PELF used to stand for Pack an Elf, but we slowly evolved into a much simpler yet more featureful alternative to .AppImages
6 | ###### PELF now refers to the tool used to create .AppBundles
7 |
8 | ---
9 |
10 | > .AppBundles are an executable *packaging format* designed to pack applications, toolchains, window managers, and multiple programs into a *single portable file*.
11 |
12 | AppBundles can serve as a drop-in replacement for AppImages. Both AppBundles and AppImages utilize the AppDir specification, making it easy to unpack an AppImage and re-package it as an AppBundle, gaining many features, such as faster start-up times, better compression and file de-duplication, and faster build-time. A completely customizable and flexible format.
13 |
14 | #### Advantages
15 | - **Support for multiple filesystem formats**: Support for multiple mountable filesystem formats, we currently support `squashfs` and `dwarfs`. With ongoing efforts to add a third alternative that isn't copylefted/propietary
16 | - **Simplicity**: PELF is a minimalistic Go program that makes creating portable POSIX executables a trivial task.
17 | - **Flexibility of AppBundles**: AppBundles do not force compliance with the AppDir standard. For example, you can bundle window managers and basic GUI utilities into a single file (as done with `Sway.AppBundle`). You can even package toolchains as single-file executables.
18 | - **Endless Possibilities**: With a custom AppRun script, you can create versatile `.AppBundles`. For instance, packaging a Rick Roll video with a video player that works on both glibc and musl systems is straightforward. You can even generate AppBundles that overlay on top of each other.
19 | - **Complete tooling**: The `pelfd` daemon (and its GUI version) are available for use as system integrators, they're in charge of adding the AppBundles that you put under ~/Applications in your "start menu". This is one of the many programs that are part of the tooling, another great tool is pelfCreator, which lets you create programs via simple one-liners (by default it uses an Alpine rootfs + bwrap, but you can get smaller binaries via using -x to only keep the binaries you want), a one-liner to pack Chromium into a single-file executable looks like this: `pelfCreator --maintainer "xplshn" --name "org.chromium.Chromium" --pkg-add "chromium" --entrypoint "chromium.desktop"`
20 | - **Predictable mount directories**: Our mount directories contain the AppBundle's ID, making it clear to which AppBundle the mount directory belongs
21 | - **Reliable unmount**: The AppBundle starts a background task to unmount the filesystem, and it retries 5 times, then it forces the unmount if all 5 tries failed
22 | - **Leverages many handy env variables**: Thus making .AppBundles very flexible and scriptable
23 | - **AppImage compatibility**: The --appimage-* flags are supported by our runtime, making us an actual drop-in replacement
24 |
25 | ### Usage
26 | ```
27 | ./pelf --add-appdir "nano-14_02_2025.AppDir" --appbundle-id "nano-14_02_2025-xplshn" --output-to "nano-14_02_2025.dwfs.AppBundle"
28 | ```
29 | OR
30 | ```
31 | ./pelf --add-appdir "nano-14_02_2025.AppDir" --appbundle-id "nano-14_02_2025-xplshn" --output-to "nano-14_02_2025.sqfs.AppBundle"
32 | ```
33 |
34 | ### Build ./pelf
35 | 1. Get yourself an up-to-date `go` toolchain and install `dbin` into your system or put it anywhere in your `$PATH`
36 | 2. execute `./cbuild.sh`
37 | 3. Put the resulting `./pelf` binary in your `$PATH`
38 | 4. Spread the joy of AppBundles! :)
39 |
40 | ### Usage of the Resulting `.AppBundle`
41 | > By using the `--pbundle_link` option, you can access files contained within the `./bin` or `./usr/bin` directories of an `.AppBundle`, inheriting environment variables like `PATH`. This allows multiple AppBundles to stack on top of each other, sharing libraries and binaries across "parent" bundles.
42 |
43 | #### Explanation
44 | You specify an `AppDir` to be packed and an ID for the app. This ID will be used when mounting the `.AppBundle` and should include the packing date, the project or program name, and the maintainer's information. While you can choose an arbitrary name, it’s not recommended.
45 |
46 | Additionally, we embed the tools used for mounting and unmounting the `.AppBundle`, such as `dwarfs` when using `pelf`.
47 |
48 |
49 |
50 |
51 |
52 | #### Known working distros/OSes:
53 | - Ubuntu (10.04 onwards) & derivatives, Ubuntu Touch
54 | - Alpine Linux 2.+ onwards
55 | - Void Linux Musl/Glibc
56 | - Debian/Devuan, and derivatives
57 | - Fedora
58 | - *SUSE
59 | - Maemo leste
60 | - AliceLinux
61 | - FreeBSD's Linuxlator
62 | - FreeBSD native
63 | - Chimera Linux
64 | - LFS (Linux from Scratch)
65 | - Most if not all Musl linux distributions
66 | - etc (please contribute to this list if you're a user of AppBundles)
67 |
68 | #### Resources:
69 | - [AppBundle format documentation & specifications](https://xplshn.github.io/pelf/docs)
70 | - The [AppBundleHUB](https://github.com/xplshn/AppBundleHUB) a repo which builds a ton of portable AppBundles in an automated fashion, using GH actions. (we have a [webStore](https://xplshn.github.io/AppBundleHUB) too, tho that is WIP)
71 | - [dbin](https://github.com/xplshn/dbin) a self-contained, portable, statically linked, package manager, +4000 binaries (portable, self-contained/static) are available in its repos at the time of writting. Among these, are the AppBundles from the AppBundleHUB and from pkgforge
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### PELF - The AppBundle format and the AppBundle Creation Tool
2 | ###### PELF used to stand for Pack an Elf, but we slowly evolved into a much simpler yet more featureful alternative to .AppImages
3 | ###### PELF now refers to the tool used to create .AppBundles
4 |
5 | ---
6 |
7 | > .AppBundles are an executable *packaging format* designed to pack applications, toolchains, window managers, and multiple programs into a *single portable file*.
8 |
9 | AppBundles can serve as a drop-in replacement for AppImages. Both AppBundles and AppImages utilize the AppDir specification, making it easy to unpack an AppImage and re-package it as an AppBundle, gaining many features, such as faster start-up times, better compression and file de-duplication, and faster build-time. A completely customizable and flexible format.
10 |
11 | #### Advantages
12 | - **Support for multiple filesystem formats**: Support for multiple mountable filesystem formats, we currently support `squashfs` and `dwarfs`. With ongoing efforts to add a third alternative that isn't copylefted/propietary
13 | - **Simplicity**: PELF is a minimalistic Go program that makes creating portable POSIX executables a trivial task.
14 | - **Flexibility of AppBundles**: AppBundles do not force compliance with the AppDir standard. For example, you can bundle window managers and basic GUI utilities into a single file (as done with `Sway.AppBundle`). You can even package toolchains as single-file executables.
15 | - **Endless Possibilities**: With a custom AppRun script, you can create versatile `.AppBundles`. For instance, packaging a Rick Roll video with a video player that works on both glibc and musl systems is straightforward. You can even generate AppBundles that overlay on top of each other.
16 | - **Complete tooling**: The `pelfd` daemon (and its GUI version) are available for use as system integrators, they're in charge of adding the AppBundles that you put under ~/Applications in your "start menu". This is one of the many programs that are part of the tooling, another great tool is pelfCreator, which lets you create programs via simple one-liners (by default it uses an Alpine rootfs + bwrap, but you can get smaller binaries via using -x to only keep the binaries you want), a one-liner to pack Chromium into a single-file executable looks like this: `pelfCreator --maintainer "xplshn" --name "org.chromium.Chromium" --pkg-add "chromium" --entrypoint "chromium.desktop"`
17 | - **Predictable mount directories**: Our mount directories contain the AppBundle's ID, making it clear to which AppBundle the mount directory belongs
18 | - **Reliable unmount**: The AppBundle starts a background task to unmount the filesystem, and it retries 5 times, then it forces the unmount if all 5 tries failed
19 | - **Leverages many handy env variables**: Thus making .AppBundles very flexible and scriptable
20 | - **AppImage compatibility**: The --appimage-* flags are supported by our runtime, making us an actual drop-in replacement
21 |
22 | ### Usage
23 | ```
24 | ./pelf --add-appdir "nano-14_02_2025.AppDir" --appbundle-id "nano-14_02_2025-xplshn" --output-to "nano-14_02_2025.dwfs.AppBundle"
25 | ```
26 | OR
27 | ```
28 | ./pelf --add-appdir "nano-14_02_2025.AppDir" --appbundle-id "nano-14_02_2025-xplshn" --output-to "nano-14_02_2025.sqfs.AppBundle"
29 | ```
30 |
31 | ### Build ./pelf
32 | 1. Get yourself an up-to-date `go` toolchain and install `dbin` into your system or put it anywhere in your `$PATH`
33 | 2. execute `./cbuild.sh`
34 | 3. Put the resulting `./pelf` binary in your `$PATH`
35 | 4. Spread the joy of AppBundles! :)
36 |
37 | ### Usage of the Resulting `.AppBundle`
38 | > By using the `--pbundle_link` option, you can access files contained within the `./bin` or `./usr/bin` directories of an `.AppBundle`, inheriting environment variables like `PATH`. This allows multiple AppBundles to stack on top of each other, sharing libraries and binaries across "parent" bundles.
39 |
40 | #### Explanation
41 | You specify an `AppDir` to be packed and an ID for the app. This ID will be used when mounting the `.AppBundle` and should include the packing date, the project or program name, and the maintainer's information. While you can choose an arbitrary name, it’s not recommended.
42 |
43 | Additionally, we embed the tools used for mounting and unmounting the `.AppBundle`, such as `dwarfs` when using `pelf`.
44 |
45 |
46 |
47 |
48 |
49 | #### Known working distros/OSes:
50 | - Ubuntu (10.04 onwards) & derivatives, Ubuntu Touch
51 | - Alpine Linux 2.+ onwards
52 | - Void Linux Musl/Glibc
53 | - Debian/Devuan, and derivatives
54 | - Fedora
55 | - *SUSE
56 | - Maemo leste
57 | - AliceLinux
58 | - FreeBSD's Linuxlator
59 | - FreeBSD native
60 | - Chimera Linux
61 | - LFS (Linux from Scratch)
62 | - Most if not all Musl linux distributions
63 | - etc (please contribute to this list if you're a user of AppBundles)
64 |
65 | #### Resources:
66 | - [AppBundle format documentation & specifications](https://xplshn.github.io/pelf/docs)
67 | - The [AppBundleHUB](https://github.com/xplshn/AppBundleHUB) a repo which builds a ton of portable AppBundles in an automated fashion, using GH actions. (we have a [webStore](https://xplshn.github.io/AppBundleHUB) too, tho that is WIP)
68 | - [dbin](https://github.com/xplshn/dbin) a self-contained, portable, statically linked, package manager, +4000 binaries (portable, self-contained/static) are available in its repos at the time of writting. Among these, are the AppBundles from the AppBundleHUB and from pkgforge
69 |
70 | ---
71 |
72 |