Skip to content

Instantly share code, notes, and snippets.

@igor-ramazanov
Last active September 2, 2025 13:41
Show Gist options
  • Select an option

  • Save igor-ramazanov/7bd29ac9be57e14b9672204dba0e3075 to your computer and use it in GitHub Desktop.

Select an option

Save igor-ramazanov/7bd29ac9be57e14b9672204dba0e3075 to your computer and use it in GitHub Desktop.
Self-contained Haskell scripts with Nix optionally compiled as a static binary (using nix bundlers)
#!/usr/bin/env -S nu --login --interactive
# Can be written in any language.
# The last positional argument will be automatically substituted to an absolute path to the script file.
def tasks [] {
{
options: {
case_sensitive: false
completion_algorithm: substring
sort: true
}
completions: [
"build",
"bundle",
"shell",
"run",
]
}
}
export def main [
--compiler: string
--deps: list<string>
--task: string@tasks
--debug
file:path
] {
def log [msg: string] { if $debug { print $msg } }
log $"compiler: ($compiler)"
log $"deps: ($deps)"
log $"task: ($task)"
let basename = $file | path basename
let name = $basename | str replace '.hs' ''
let expr = $"
let
pkgs = import <nixpkgs> {};
dependencies = hpkgs: builtins.map \(dep: hpkgs.${dep}\) ($deps | to json --raw | str replace ',' ' ' --all);
ghc = pkgs.haskell.packages.($compiler).ghcWithPackages dependencies;
source = pkgs.writeText \"($basename)\" ''
(open --raw Scotty.hs | lines | skip while {|line| ($line | str starts-with '#') or ($line | is-empty )} | str join "\n")
'';
name = \"($name)\";
in
pkgs.runCommandNoCCLocal \"($name)\" {pname = \"($name)\"; meta.mainProgram = \"($name)\"; nativeBuildInputs = [ghc];} ''
mkdir -p ./.tmp $out/bin
ghc \\
-outputdir ./.tmp/ \\
-hidir ./.tmp/ \\
-odir ./.tmp/ \\
-dumpdir ./.tmp/ \\
-tmpdir ./.tmp/ \\
-o ./.tmp/($name) \\
--make ${source}
mv ./.tmp/($name) $out/bin/
rm -r ./.tmp/
''
"
match $task {
"build" => {
let command = ["nix", "build", "--out-link", $name, "--impure", "--expr", $expr] | flatten
log ($command | str join ' ')
run-external ...($command)
},
"bundle" => {
let command = ["nix", "bundle", "--bundler", "github:DavHau/nix-portable/v012#zstd-max", "--out-link", ($name + "-bundled"), "--impure", "--expr", $expr] | flatten
log ($command | str join ' ')
run-external ...($command)
},
"shell" => {
# You can implement it the same manner as `build` and `bundle`.
error make {msg: "'shell': not implemented"}
},
"run" => {
# You can implement it the same manner as `build` and `bundle`.
error make {msg: "'run': not implemented"}
}
}
}
#!./haskell.nu --compiler ghc984 --deps [base scotty] --task bundle --debug
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Data.Monoid (mconcat)
main = scotty 3000 $ do
get "/:word" $ do
beam <- param "word"
html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]
@igor-ramazanov
Copy link
Author

❯ ls
#    name    type  size       modified
0 Scotty.hs  file  297 B 2025-08-01 22:48:59
1 haskell.nu file 2.0 kB 2025-08-01 22:43:30
❯ ./Scotty.hs
compiler: ghc984
deps: [base, scotty]
task: bundle
nix bundle --bundler github:DavHau/nix-portable/v012#zstd-max --out-link Scotty-bundled --impure --expr
let
  pkgs = import <nixpkgs> {};
  dependencies = hpkgs: builtins.map (dep: hpkgs.${dep}) ["base" "scotty"];
  ghc = pkgs.haskell.packages.ghc984.ghcWithPackages dependencies;
  source = pkgs.writeText "Scotty.hs" ''
    {-# LANGUAGE OverloadedStrings #-}

import Web.Scotty
import Data.Monoid (mconcat)

main = scotty 3000 $ do
    get "/:word" $ do
        beam <- param "word"
        html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]
  '';
  name = "Scotty";
in
  pkgs.runCommandNoCCLocal "Scotty" {pname = "Scotty"; meta.mainProgram = "Scotty"; nativeBuildInputs = [ghc];} ''
    mkdir -p ./.tmp $out/bin
    ghc \
      -outputdir ./.tmp/ \
      -hidir ./.tmp/ \
      -odir ./.tmp/ \
      -dumpdir ./.tmp/ \
      -tmpdir ./.tmp/ \
      -o ./.tmp/Scotty \
      --make ${source}
    mv ./.tmp/Scotty $out/bin/
    rm -r ./.tmp/
  ''
❯ ls
#      name       type    size       modified
0 Scotty-bundled symlink   50 B 2025-08-01 22:49:31
1 Scotty.hs      file     297 B 2025-08-01 22:48:59
2 haskell.nu     file    2.0 kB 2025-08-01 22:43:30
❯ with-env {NP_GIT: /run/current-system/sw/bin/git} {./Scotty-bundled/bin/Scotty}
Setting phasers to stun... (port 3000) (ctrl-c to quit)

@igor-ramazanov
Copy link
Author

igor-ramazanov commented Aug 1, 2025

Would be cool to rewrite the haskell.nu to use https://github.com/nh2/static-haskell-nix for building static binaries instead of https://github.com/DavHau/nix-portable/tree/main

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment