snaggle

package module
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 19, 2025 License: MIT Imports: 11 Imported by: 0

README

Snaggle - snag the minimal files needed for a simple app container

Had enough of every container you pull having a full OS available inside? Create your own minimal app container easily by snaggling the binary and linked libraries.

ninjacoder@f52ce3a5f188:/workspaces/snaggle/snaggle$ ./snaggle --help
Snag a copy of a binary and all its dependencies to DESTINATION/bin & DESTINATION/lib64

Snaggle is designed to help create minimal runtime containers from pre-existing installations.
It may work for other use cases and I'd be interested to hear about them at:
https://github.com/MusicalNinjaDad/snaggle

Usage:
  snaggle [--in-place] FILE DESTINATION
  snaggle [--copy | --in-place] [--recursive] DIRECTORY DESTINATION

Flags:
      --copy        Copy entire directory contents to /DESTINATION/full/source/path
  -h, --help        help for snaggle
      --in-place    Snag in place: only snag dependencies & interpreter
  -r, --recursive   Recurse subdirectories & snag everything
  -v, --verbose     Output to stdout and process sequentially for readability
      --version     version for snaggle


In the form "snaggle FILE DESTINATION":
  FILE and all dependencies will be snagged to DESTINATION.
  An error will be returned if FILE is not a valid ELF binary.

In the form "snaggle DIRECTORY DESTINATION":
  All valid ELF binaries in DIRECTORY, and all their dependencies, will be snagged to DESTINATION.

Snaggle will hardlink (or copy, see notes):
- Executables              -> DESTINATION/bin
- Dynamic libraries (*.so) -> DESTINATION/lib64

Notes:
- Follows symlinks
- Hardlinks will be created if possible.
- A copy will be performed if hardlinking fails for one of the following reasons:
    FILE/DIRECTORY & DESTINATION are on different filesystems or
    the user does not have permission to hardlink (e.g.
      https://docs.kernel.org/admin-guide/sysctl/fs.html#protected-hardlinks)
- Copies will retain the original filemode
- Copies will attempt to retain the original ownership, although this will likely fail if running as non-root
- Running with --verbose will be slower, not only due to processing stdout, but also as each file will be processed
  sequentially to provide readable output. Running silently will process all files and dependencies in parallel.

Exit Codes:
  0: Success
  1: Error
  2: Invalid command
  3: Panic

Installing snaggle

In a container (easiest)
COPY --from=ghcr.io/musicalninjadad/snaggle /snaggle /bin/
Download

Grab the latest release binary (& SHA) from GitHub MusicalNinjaDad/snaggle/releases

Go install

Install with go install https://github.com/MusicalNinjaDad/snaggle@latest

Example usage

# For example to get a locked-down nginx webserver running in 4 simple steps...

# You can use any base distro you like. I find fedora generally does well staying up to date with latest versions.
FROM fedora:latest AS installer

# 1. Install nginx and tini (for pid1)
RUN dnf install -y \
  nginx \
  tini

# 2. Install snaggle
COPY --from=ghcr.io/musicalninjadad/snaggle:latest /snaggle /bin/

# 3. Build the runtime root filesystem
WORKDIR /runtime

    # snaggle tini & nginx
    RUN snaggle /bin/tini . \
     && snaggle /bin/nginx .

    # add our config and data
    COPY nginx.conf ./etc/nginx/
    COPY index.html ./data/www/

    # Create the few standard locations nginx needs
    RUN \
        # for temp files
        mkdir --parents ./var/lib/nginx/tmp && chmod 1777 ./var/lib/nginx/tmp \
        # for logs & link logs to stdout & stderr
     && mkdir --parents ./var/log/nginx && chmod 0777 ./var/log/nginx \
     && ln -sf /dev/stdout ./var/log/nginx/access.log \
     && ln -sf /dev/stderr ./var/log/nginx/error.log \
        # for the pid file
     && mkdir ./run && chmod 1777 ./run

# 4. copy the minimal root filesystem to a new empty layer
FROM scratch AS runtime

COPY --from=installer /runtime /

USER 1000
EXPOSE 8000
ENTRYPOINT [ "tini", "--", "nginx" ]

Known limitations

  • only handles dynamic binaries with /lib64/ld_linux...so as an interpreter, no interpreter and static binaries.
  • does not handle binaries compiled with dependencies in a custom RUNPATH or RPATH (#13)

Planned improvements

Future versions will:

  • provide standard profiles for apps with non-linked dependencies such as SSL certs, locales, gconv etc.
  • further improve performance by bailing early on an error (#37)

Why Go?

Historically this started as a python script, but I had to learn Go at some point - and this seemed like a good one. Plus it's much easier to ADD and use a single statically linked binary than a script and supporting interpreter.

That old python script is here in the git repo as v0.0.1 in case you're interested.

Documentation

Overview

Snag a copy of a ELF binary and all its dependencies to another/path/bin & another/path//lib64.

This is the main implementation of the command-line application `snaggle`, for use as a library in other code and scripts.

Snaggle is designed to help create minimal runtime containers from pre-existing installations. It may work for other use cases and I'd be interested to hear about them at: https://github.com/MusicalNinjaDad/snaggle

WARNING: This is not designed work on non-linux systems - don't try it unless you want to have fun with unexpected and unhandled os error types.

Index

Constants

View Source
const Version = "1.2.1"

Variables

View Source
var (
	ErrCopyInplace = errors.New("cannot copy in-place")
)

Functions

func Snaggle

func Snaggle(path string, root string, opts ...Option) error

Snaggle parses the file(s) given by path and build minimal /bin & /lib64 under root.

If path refers to a directory, all valid ELF binaries directly under path will be snagged. Provide the Option [Recursive()] to recurse subdirectories.

Snaggle will hardlink (or copy, see notes):

  • path -> root/bin (executables) or path/lib64 (libraries), unless the Option [InPlace()] is provided
  • All dynamically linked dependencies -> root/lib64

For example:

_ = Snaggle("/bin/which", "/runtime") // you probably want to handle any error, not ignore it
// Results in:
//  /runtime/bin/which
//  /runtime/lib64/libc.so.6
//  /runtime/lib64/libpcre2-8.so.0
//  /runtime/lib64/libselinux.so.1

Notes:

  • Hardlinks will be created if possible.
  • A copy will be performed if hardlinking fails for one of the following reasons:
  • path & root are on different filesystems
  • the user does not have permission to hardlink (e.g. https://docs.kernel.org/admin-guide/sysctl/fs.html#protected-hardlinks)
  • Copies will retain the original filemode
  • Copies will attempt to retain the original ownership, although this will likely fail if running as non-root

Types

type InvocationError added in v0.3.0

type InvocationError struct {
	Path   string
	Target string
	// contains filtered or unexported fields
}

Snaggle was invoked with semantically invalid inputs. err will be formatted to read well when directly output to stderr during CLI invokation E.g: invoking Snaggle(path/to/FILE, root, Recursive()) will wrap a [&fs.PathError]{Op: "--recursive", Path: "path/to/FILE", Err: syscall.ENOTDIR}

func (*InvocationError) Error added in v0.3.0

func (e *InvocationError) Error() string

func (*InvocationError) Unwrap added in v0.3.0

func (e *InvocationError) Unwrap() error

type Option added in v0.2.0

type Option func(*options)

Option setting functions

func Copy added in v1.1.0

func Copy() Option

Copy entire directory contents to /destinationroot/full/source/path

func InPlace added in v0.2.0

func InPlace() Option

Snag in place: only snag dependencies & interpreter

func Recursive added in v0.2.0

func Recursive() Option

Snag recursively: only works when snaggling a directory

func Verbose added in v1.0.0

func Verbose() Option

Output to stdout and process sequentially for readability

type SnaggleError added in v0.3.0

type SnaggleError struct {
	Src string // Source path
	Dst string // Destination path
	// contains filtered or unexported fields
}

An error occurred during snaglling

func (*SnaggleError) Error added in v0.3.0

func (e *SnaggleError) Error() string

func (*SnaggleError) Unwrap added in v0.3.0

func (e *SnaggleError) Unwrap() error

Directories

Path Synopsis
cmd
snaggle command
Snag a copy of a binary and all its dependencies to DESTINATION/bin & DESTINATION/lib64
Snag a copy of a binary and all its dependencies to DESTINATION/bin & DESTINATION/lib64
Parses an ELF binary providing additional details vs.
Parses an ELF binary providing additional details vs.
Helpers and values which can be used in any package
Helpers and values which can be used in any package
testing
Helpers related to testdata assets.
Helpers related to testdata assets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL