Write Haskell scripts using Straylight.Script instead of bash. Use when writing scripts, shell automation, bash replacement, command-line tools, or when the user mentions bash, shell, scripts, or command-line operations.
Write Haskell scripts using Straylight.Script instead of bash.
ALL scripts in Haskell. ZERO bash logic.
If it has a branch, it's not bash. Rewrite in Haskell.
nix/scripts/
├── Straylight/
│ ├── Script.hs # Main prelude (1193 lines)
│ ├── Nix.hs # WASM plugin interface
│ ├── Config/ # Dhall type definitions
│ │ ├── Base.dhall # StorePath, MemMiB, CpuCount
│ │ ├── Firecracker.dhall # VM config schema
│ │ └── NvidiaSdk.dhall # NVIDIA extraction config
│ └── Script/
│ ├── Config.hs # FromDhall instances
│ ├── Clap.hs # Parser for clap-based CLI tools
│ ├── Getopt.hs # Parser for GNU getopt_long tools
│ ├── Oci.hs # OCI container operations
│ ├── Vm.hs # VM rootfs construction
│ ├── Vfio.hs # GPU passthrough (VFIO/IOMMU)
│ └── Tools/ # 24 tool wrappers
│ ├── Rg.hs, Bwrap.hs, Crane.hs, Jq.hs, ...
├── nvidia-extract.hs # NVIDIA SDK extraction
├── oci-gpu.hs # GPU container runner
├── fc-run.hs # Firecracker runner
└── vfio-bind.hs # GPU passthrough
-- nix/scripts/my-tool.hs
{-# LANGUAGE OverloadedStrings #-}
import Straylight.Script
import System.Environment (getArgs)
main :: IO ()
main = do
args <- getArgs
case args of
[arg1, arg2] -> script $ doThing (pack arg1) (pack arg2)
_ -> script $ do
echoErr "Usage: my-tool <arg1> <arg2>"
exit 1
doThing :: Text -> Text -> Sh ()
doThing x y = do
echoErr $ ":: Processing " <> x
output <- run "echo" [x, y]
echo output
compiled = {
my-tool = mkCompiledScript {
name = "my-tool";
deps = [ final.jq final.crane ];
};
};
# Compiled (production)
nix run .#straylight.script.compiled.my-tool -- arg1 arg2
# Interpreted (development)
nix run .#straylight.script.shell
runghc -i. my-tool.hs arg1 arg2
script :: Sh a -> IO a -- Silent, errors on failure
scriptV :: Sh a -> IO a -- Verbose (shows commands)
scriptDebug :: Sh a -> IO a -- Very verbose
run :: FilePath -> [Text] -> Sh Text -- Capture stdout
run_ :: FilePath -> [Text] -> Sh () -- Ignore stdout
bash :: Text -> Sh Text -- Run bash -c "..."
which :: FilePath -> Sh (Maybe FilePath)
echo :: Text -> Sh () -- Print to stdout
echoErr :: Text -> Sh () -- Print to stderr (USE THIS)
die :: Text -> Sh a -- Print error and exit
exit :: Int -> Sh ()
ls, cp, mv, rm, mkdir, mkdirP
pwd, cd, home, withTmpDir
test_f, test_d, test_e -- Existence tests
symlink, readlink, canonicalize
Text, pack, unpack
strip, lines, unlines, words, unwords
isPrefixOf, isSuffixOf, isInfixOf
replace, splitOn, breakOn
try, catch, bracket, finally
errExit :: Bool -> Sh a -> Sh a -- Control error-on-failure
Import qualified to avoid name conflicts:
import qualified Straylight.Script.Tools.Rg as Rg
import qualified Straylight.Script.Tools.Bwrap as Bwrap
import qualified Straylight.Script.Tools.Crane as Crane
import qualified Straylight.Script.Tools.Jq as Jq
| Category | Tools |
|---|---|
| Clap (Rust) | rg, fd, bat, delta, dust, tokei, hyperfine, deadnix, statix |
| GNU | ls, grep, sed, find, xargs, tar, gzip, wget, rsync |
| Hand-crafted | jq, crane, bwrap |
let sandbox = Bwrap.defaults
& Bwrap.roBind "/nix/store" "/nix/store"
& Bwrap.dev "/dev"
& Bwrap.proc "/proc"
& Bwrap.tmpfs "/tmp"
& Bwrap.unshareAll
& Bwrap.dieWithParent
Bwrap.exec sandbox ["./myprogram", "--flag"]
rootfs <- Oci.pullOrCache Oci.defaultConfig "alpine:latest"
let sandbox = Oci.baseSandbox rootfs & Oci.withGpuSupport env gpuBinds
gpus <- Vfio.listNvidiaGpus
devices <- Vfio.bindToVfio "0000:01:00.0"
Vm.buildExt4 "/tmp/rootfs-dir" "/tmp/rootfs.ext4"
Vm.runFirecracker config
For Nix -> Haskell value passing:
-- nix/scripts/Straylight/Config/MyTool.dhall
let Base = ./Base.dhall
let Config =
{ Type =
{ inputPath : Base.StorePath
, verbose : Bool
}
, default = { verbose = False }
}
in { Config }
-- Haskell side
data Config = Config
{ inputPath :: StorePath
, verbose :: Bool
} deriving (Generic)
instance FromDhall Config
echoErr for status messages (keeps stdout clean)errExit False when command failure is acceptablewithTmpDir for intermediate files&) for Bwrap sandboxes# Generate and print
nix run .#straylight.script.gen-wrapper -- rg
# Write to Tools/Fd.hs
nix run .#straylight.script.gen-wrapper -- fd --write
# Force GNU format
nix run .#straylight.script.gen-wrapper -- grep --gnu --write