Bass

guide

This guide glosses over language semantics in favor of being a quick reference for common tasks. If you'd like to learn the language, see bassics.

getting started

Bass is shipped as a single bass binary which needs to be installed somewhere in your $PATH.

To run Bass you'll need either Docker Engine (Linux), Docker Desktop (OS X, Windows), or Buildkit running.

With everything installed, try one of the demos:

bass demos/git-lib.bass

If you see bass: command not found, it's not in your $PATH.

If you see some other kind of error you're welcome to ask for help in GitHub or Discord.

running thunks

𝄢

Bass is built around thunks. Thunks are cacheable commands that produce files and/or a stream of values.

(from (linux/alpine)
  ($ echo "Hello, world!"))
.echo
{
:image
{
:repository "alpine"
:platform
{
:os "linux"
}
:tag "latest"
:digest "sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4"
}
:cmd .echo
:args
(
  1. "Hello, world!"
)
}
𝄢

Throughout this documentation, thunks will be rendered as space invaders to make them easier to identify.

𝄢

To run a thunk's command and raise an error if it fails, call run:

(run (from (linux/alpine)
       ($ echo "Hello, world!")))
stderr: 8 lines
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.02s]
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.01s]
=> echo "Hello, world!" [0.23s]
Hello, world!
null
𝄢

To run a thunk and get true or false instead of erroring, call succeeds?:

(succeeds? (from (linux/alpine)
             ($ sh -c "exit 1")))
stderr: 8 lines
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.02s]
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.01s]
=> sh -c "exit 1" ERROR [0.23s]
!!! sh -c "exit 1"
false
𝄢

Thunks are cached forever. They can be cleared with bass --prune, but this should only be necessary for regaining disk space.

𝄢

To influence caching, use with-label to stamp thunks with arbitrary data. Two thunks that differ only in labels will be cached independently.

(run (with-label
       (from (linux/alpine)
         ($ echo "Hello, world!"))
       :foo "bar"))
stderr: 8 lines
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.01s]
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.01s]
=> echo "Hello, world!" [0.24s]
Hello, world!
null
𝄢

Tip: to avoid deep nesting like above, consider the alternative -> form.

(-> ($ echo "Hello, world!")
    (with-image (linux/alpine))
    (with-label :foo "bar")
    run)
stderr: 8 lines
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.01s]
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.01s]
=> echo "Hello, world!" [0.24s]
Hello, world!
null

reading output

𝄢

To parse a stream of JSON values from a thunk's stdout, call read with the :json protocol:

(def cat-thunk
  (from (linux/alpine)
   ; note: stdin is also JSON
    (.cat "hello" "goodbye")))

(let [stream (read cat-thunk :json)]
  [(next stream :end)
   (next stream :end)
   (next stream :end)])
stderr: 9 lines
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.01s]
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.01s]
=> cat [0.26s]
=> exporting to client directory [0.00s]
-> copying files 44B [0.00s]
(
  1. "hello"
  2. "goodbye"
  3. end
)
𝄢

To read output line-by-line, set the protocol to :lines:

(-> ($ ls -r /usr/bin)
    (with-image (linux/alpine))
    (read :lines)
    next)
stderr: 9 lines
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.01s]
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.01s]
=> ls -r /usr/bin [0.24s]
=> exporting to client directory [0.00s]
-> copying files 924B [0.00s]
"yes"
𝄢

To parse UNIX style tabular output, set the protocol to :unix-table:

(-> ($ ls -r /usr/bin)
    (with-image (linux/alpine))
    (read :unix-table)
    next)
stderr: 9 lines
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.01s]
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.01s]
=> ls -r /usr/bin [0.24s]
=> exporting to client directory [0.00s]
-> copying files 924B [0.00s]
(
  1. "yes"
)
𝄢

To collect all output into one big string, set the protocol to :raw:

(-> ($ echo "Hello, world!")
    (with-image (linux/alpine))
    (read :raw)
    next)
stderr: 9 lines
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.01s]
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.00s]
=> echo "Hello, world!" [0.26s]
=> exporting to client directory [0.00s]
-> copying files 40B [0.00s]
"Hello, world!\n"

providing secrets

𝄢

To shroud a string in secrecy, pass it to mask and give it a name.

(mask "hunter2" :nickserv)
<secret: nickserv (7 bytes)>
𝄢

Secrets can be passed to thunks as regular strings. When serialized, a secret's value is omitted.

($ echo (mask "secret" :password))
.echo
{
:cmd .echo
:args
(
  1. <secret: password (6 bytes)>
)
}
𝄢

Bass does not mask the secret from the command's output. This may come in the future.

𝄢

Sensitive values can end up in all sorts of sneaky places. Bass does its best to prevent that from happening.

  • A thunk's command runs in an isolated environment, so an evil thunk can't* spy on your secrets.

  • A thunk's command (i.e. stdin, env, argv) isn't captured into image layer metadata, so exporting a thunk as an OCI image will not leak secrets passed to it.

  • Secret values are never serialized, so publishing a thunk path will not leak any secrets used to build it.

  • All env vars passed to bass are only provided to the entrypoint script (as *env*). They are also removed from the bass process so that they can't be sneakily accessed at runtime.

With the above precautions, passing secrets to thunks as env vars may often be most ergonomic approach. If you have more ideas, please suggest them!

𝄢

To pass a secret to a command using a secret mount, use with-mount:

(-> ($ cat /secret)
    (with-mount (mask "hello" :shh) /secret)
    (with-image (linux/alpine))
    run)
stderr: 8 lines
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.02s]
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.00s]
=> cat /secret [0.24s]
hello
null

* This is all obviously to the best of my ability - I can't promise it's perfect. If you find other ways to make Bass safer, please share them!

caching directories

𝄢

Cache paths may be created using cache-dir and passed to thunks like any other path. Any data written to a cache path persists until cleared by bass --prune.

(def my-cache (cache-dir "my cache"))

(defn counter [tag]
  (from (linux/alpine)
    (-> ($ sh -c "echo x >> /var/cache/file; cat /var/cache/file | wc -l")
        (with-label :tag tag)
        (with-mount my-cache /var/cache/))))

(defn count [tag]
  (next (read (counter tag) :json)))

[(count "once")
 (count "twice")
 (count "thrice")]
stderr: 15 lines
=> sh -c "echo x >> /var/cache/file; cat /var/cache/file | wc -l" [0.21s]
=> exporting to client directory [0.00s]
-> copying files 28B [0.00s]
=> sh -c "echo x >> /var/cache/file; cat /var/cache/file | wc -l" [0.23s]
=> exporting to client directory [0.00s]
-> copying files 28B [0.00s]
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.01s]
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.00s]
=> sh -c "echo x >> /var/cache/file; cat /var/cache/file | wc -l" [0.22s]
=> exporting to client directory [0.00s]
-> copying files 28B [0.00s]
(
  1. 1
  2. 1
  3. 1
)
𝄢

Currently only one thunk can access a cache path at a time. This may become configurable in the future.

building stuff

passing bits around

𝄢

Thunks run in an initial working directory controlled by Bass. Files created within this directory can be passed to other thunks by using thunk paths.

𝄢

Thunk paths are created by using a thunk with path notation:

(def meowed
  (from (linux/alpine)
    (-> ($ sh -c "cat > ./file")
        (with-stdin ["hello" "goodbye"]))))

meowed/file
.sh
{
:image
{
:repository "alpine"
:platform
{
:os "linux"
}
:tag "latest"
:digest "sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4"
}
:cmd .sh
:args
(
  1. "-c"
  2. "cat > ./file"
)
:stdin
(
  1. "hello"
  2. "goodbye"
)
}
./file
𝄢

If the thunk isn't bound to a symbol first, you can use subpath:

(-> ($ sh -c "cat > ./file")
    (with-image (linux/alpine))
    (with-stdin ["hello" "goodbye"])
    (subpath ./file))
.sh
{
:image
{
:repository "alpine"
:platform
{
:os "linux"
}
:tag "latest"
:digest "sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4"
}
:cmd .sh
:args
(
  1. "-c"
  2. "cat > ./file"
)
:stdin
(
  1. "hello"
  2. "goodbye"
)
}
./file
𝄢

Just like thunks, a thunk path is just an object. Its underlying thunk won't run until the path is needed by something.

𝄢

When you read a thunk path, Bass runs its thunk and reads the content of the path using the same protocols for reading output:

(next (read meowed/file :json))
stderr: 9 lines
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.02s]
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.01s]
=> sh -c "cat > ./file" [0.22s]
=> exporting to client tarball [0.00s]
-> sending tarball [0.00s]
"hello"
𝄢

When you pass a thunk path to an outer thunk, Bass runs the path's thunk and mounts the path into the outer thunk's working directory under a hashed directory name:

(-> ($ ls -al meowed/file)
    (with-image (linux/alpine))
    run)
stderr: 9 lines
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.01s]
=> sh -c "cat > ./file" [0.22s]
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.01s]
=> ls -al <thunk 834ETBC7VQFFM: (.sh)>/file [0.24s]
-rw-r--r-- 1 root root 18 Oct 26 1985 ./834ETBC7VQFFM/file
null
𝄢

If the outer thunk sets a thunk path as its working directory (viw cd or with-dir), you can use ../ to refer back to the original working directory.

(defn go-build [src pkg]
  (-> (from (linux/golang)
        (cd src
          ($ go build -o ./out/ $pkg)))
      (subpath ./out/)))

(def cloned
  (from (linux/alpine/git)
    ($ git clone "https://github.com/vito/bass" ./repo/)))

(go-build cloned/repo/ "./cmd/...")
.go
{
:image
{
:repository "golang"
:platform
{
:os "linux"
}
:tag "latest"
:digest "sha256:dc76ef03e54c34a00dcdca81e55c242d24b34d231637776c4bb5c1a8e8514253"
}
:cmd .go
:args
(
  1. "build"
  2. "-o"
  3. ./out/
  4. "./cmd/..."
)
:mounts
(
  1. {
    :source
    .git
    {
    :image
    {
    :repository "alpine/git"
    :platform
    {
    :os "linux"
    }
    :tag "latest"
    :digest "sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2"
    }
    :cmd .git
    :args
    (
    1. "clone"
    2. "https://github.com/vito/bass"
    3. ./repo/
    )
    }
    ./repo/
    :target ./
    }
)
}
./out/
𝄢

Note that any modifications made to an input thunk path will not propagate to subsequent thunks.

𝄢

Astute observers will note that cloned above is not a hermetic, because it doesn't specify a version.

𝄢

The .git module provides basic tools for cloning Git repositories in a hermetic manner.

(use (.git (linux/alpine/git)))

(let [uri "https://github.com/vito/bass"]
  (git:checkout uri (git:ls-remote uri "HEAD")))
stderr: 9 lines
=> resolve image config for docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b3
71a6cbe2630a4b37d23275658bd3f2 [0.02s]
=> docker-image://docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe263
0a4b37d23275658bd3f2 CACHED [0.00s]
-> resolve docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d
23275658bd3f2 [0.01s]
=> git ls-remote https://github.com/vito/bass HEAD [0.54s]
=> exporting to client directory [0.00s]
-> copying files 72B [0.00s]
.git
{
:image
.git
{
:image
.git
{
:image
.git
{
:image
{
:repository "alpine/git"
:platform
{
:os "linux"
}
:tag "latest"
:digest "sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2"
}
:cmd .git
:args
(
  1. "clone"
  2. "https://github.com/vito/bass"
  3. ./
)
}
:cmd .git
:args
(
  1. "fetch"
  2. "origin"
  3. "836efe1a4b59be0c148ae4cbc25f7eff581d9dde"
)
}
:cmd .git
:args
(
  1. "checkout"
  2. "836efe1a4b59be0c148ae4cbc25f7eff581d9dde"
)
}
:cmd .git
:args
(
  1. "submodule"
  2. "update"
  3. "--init"
  4. "--recursive"
)
}
./
𝄢

The .git module also provides github, a path root for repositories hosted at GitHub.

git:github/vito/bass/ref/HEAD/
stderr: 9 lines
=> resolve image config for docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b3
71a6cbe2630a4b37d23275658bd3f2 [0.02s]
=> docker-image://docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe263
0a4b37d23275658bd3f2 CACHED [0.00s]
-> resolve docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d
23275658bd3f2 [0.00s]
=> git ls-remote https://github.com/vito/bass HEAD [0.44s]
=> exporting to client directory [0.00s]
-> copying files 72B [0.00s]
.git
{
:image
.git
{
:image
.git
{
:image
.git
{
:image
{
:repository "alpine/git"
:platform
{
:os "linux"
}
:tag "latest"
:digest "sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2"
}
:cmd .git
:args
(
  1. "clone"
  2. "https://github.com/vito/bass"
  3. ./
)
}
:cmd .git
:args
(
  1. "fetch"
  2. "origin"
  3. "836efe1a4b59be0c148ae4cbc25f7eff581d9dde"
)
}
:cmd .git
:args
(
  1. "checkout"
  2. "836efe1a4b59be0c148ae4cbc25f7eff581d9dde"
)
}
:cmd .git
:args
(
  1. "submodule"
  2. "update"
  3. "--init"
  4. "--recursive"
)
}
./

troubleshooting

When something goes wrong, Bass tries to provide an ergonomic error message. Backtraces show annotated source code complete with syntax highlighting. When a thunk fails its output is included in the error message at the bottom of the screen so you don't have to skim the whole output.

(defn echo-sleep-exit [msg seconds exit-code]
  (subpath
    (from (linux/alpine)
      (with-label ($ sleep (str seconds)) :at (now 0))
      ($ sh -c (str "echo \"$0\"; exit " exit-code) $msg))
    ./))

(defn ls paths
  (run (from (linux/alpine)
         ($ ls & $paths))))

(defn main []
  (ls (echo-sleep-exit "hello\nanother line" 1 1)
      (echo-sleep-exit "oh no" 3 42)))
stderr: 46 lines
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697
984fba772b3976835194c6d4 CACHED [0.00s]
-> resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba7
72b3976835194c6d4 [0.01s]
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e48
0fef81e697984fba772b3976835194c6d4 [0.01s]
=> sleep 1 [1.26s]
=> sh -c "echo \"$0\"; exit 1" "hello\nanother line" ERROR [0.22s]
hello
another line
=> sleep 3 CANCELED [1.56s]
!!! sh -c "echo \"$0\"; exit 1" "hello\nanother line"
13: [0.17s] hello
13: [0.17s] another line
error! call trace (oldest first):
┆ <fs>/multi-fail.bass:13:2..14:37
12 │ (defn main []
13 │ (ls (echo-sleep-exit "hello\nanother line" 1 1)
14 │ (echo-sleep-exit "oh no" 3 42)))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
┆ <fs>/multi-fail.bass:9:2..10:26
8 │ (defn ls paths
9 │ (run (from (linux/alpine)
10 │ ($ ls & $paths))))
^^^^^^^^^^^^^^^^^^^^^^^^^
build failed: exit code: 1
run summary:
=> docker-image://docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e6
97984fba772b3976835194c6d4
=> resolve image config for docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e
480fef81e697984fba772b3976835194c6d4 [0.01s]
=> sleep 1 [1.26s]
=> sleep 3 [canceled] [1.56s]
=> sh -c "echo \"$0\"; exit 1" "hello\nanother line" [0.22s]
hello
another line
ERROR: exit code: 1
=> sh -c "echo \"$0\"; exit 42" "oh no"
=> ls <thunk BFDSD5NAFOB7U: (.sh)>/ <thunk 57S4637F93F4E: (.sh)>/
for more information, refer to the full output above

That being said, there's a good chance you'll run into a cryptic error message now and then while I work towards making them friendly. If you find one, please open an issue.

exporting files

𝄢

Thunk paths can be saved in JSON format for archival, auditing, efficient distribution, or just for funsies.

(use (.git (linux/alpine/git)))

(-> ($ go build -o ../out/ "./cmd/...")
    (with-dir git:github/vito/bass/ref/HEAD/)
    (with-image (linux/golang))
    (subpath ./out/)
    (emit *stdout*))
{
  "thunk": {
    "image": {
      "ref": {
        "repository": "golang",
        "platform": {
          "os": "linux"
        },
        "tag": "latest",
        "digest": "sha256:dc76ef03e54c34a00dcdca81e55c242d24b34d231637776c4bb5c1a8e8514253"
      }
    },
    "cmd": {
      "command": {
        "name": "go"
      }
    },
    "args": [
      {
        "string": {
          "value": "build"
        }
      },
      {
        "string": {
          "value": "-o"
        }
      },
      {
        "dirPath": {
          "path": "../out"
        }
      },
      {
        "string": {
          "value": "./cmd/..."
        }
      }
    ],
    "dir": {
      "thunk": {
        "thunk": {
          "image": {
            "thunk": {
              "image": {
                "thunk": {
                  "image": {
                    "thunk": {
                      "image": {
                        "ref": {
                          "repository": "alpine/git",
                          "platform": {
                            "os": "linux"
                          },
                          "tag": "latest",
                          "digest": "sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2"
                        }
                      },
                      "cmd": {
                        "command": {
                          "name": "git"
                        }
                      },
                      "args": [
                        {
                          "string": {
                            "value": "clone"
                          }
                        },
                        {
                          "string": {
                            "value": "https://github.com/vito/bass"
                          }
                        },
                        {
                          "dirPath": {
                            "path": "."
                          }
                        }
                      ]
                    }
                  },
                  "cmd": {
                    "command": {
                      "name": "git"
                    }
                  },
                  "args": [
                    {
                      "string": {
                        "value": "fetch"
                      }
                    },
                    {
                      "string": {
                        "value": "origin"
                      }
                    },
                    {
                      "string": {
                        "value": "836efe1a4b59be0c148ae4cbc25f7eff581d9dde"
                      }
                    }
                  ]
                }
              },
              "cmd": {
                "command": {
                  "name": "git"
                }
              },
              "args": [
                {
                  "string": {
                    "value": "checkout"
                  }
                },
                {
                  "string": {
                    "value": "836efe1a4b59be0c148ae4cbc25f7eff581d9dde"
                  }
                }
              ]
            }
          },
          "cmd": {
            "command": {
              "name": "git"
            }
          },
          "args": [
            {
              "string": {
                "value": "submodule"
              }
            },
            {
              "string": {
                "value": "update"
              }
            },
            {
              "string": {
                "value": "--init"
              }
            },
            {
              "string": {
                "value": "--recursive"
              }
            }
          ]
        },
        "path": {
          "dir": {
            "path": "."
          }
        }
      }
    }
  },
  "path": {
    "dir": {
      "path": "out"
    }
  }
}
null
𝄢

Feeding thunk path JSON to bass --export will print a tar stream containing the file tree.

exporting images

𝄢

Feeding thunk JSON to bass --export will print an OCI image tar stream, which can be piped to docker load for troubleshooting with docker run. This will be made easier in the future.

(emit
  (from (linux/ubuntu)
    ($ apt-get update)
    ($ apt-get -y install git))
  *stdout*)
stderr: 1 lines
=> resolve image config for docker.io/library/ubuntu:latest [0.57s]
{
  "image": {
    "thunk": {
      "image": {
        "ref": {
          "repository": "ubuntu",
          "platform": {
            "os": "linux"
          },
          "tag": "latest",
          "digest": "sha256:4b1d0c4a2d2aaf63b37111f34eb9fa89fa1bf53dd6e4ca954d47caebca4005c2"
        }
      },
      "cmd": {
        "command": {
          "name": "apt-get"
        }
      },
      "args": [
        {
          "string": {
            "value": "update"
          }
        }
      ]
    }
  },
  "cmd": {
    "command": {
      "name": "apt-get"
    }
  },
  "args": [
    {
      "string": {
        "value": "-y"
      }
    },
    {
      "string": {
        "value": "install"
      }
    },
    {
      "string": {
        "value": "git"
      }
    }
  ]
}
null

special tactics

pinning in bass.lock

𝄢

Bass comes with baby's first dependency pinning solution: memo. It works by storing results of functions loaded from Bass modules into a file typically called bass.lock and committed to your repository.

𝄢

memo takes a bass.lock path, a module thunk, and a symbol, and returns a memoized function.

(def memo-ls-remote
  (memo *dir*/bass.lock (.git (linux/alpine/git)) :ls-remote))
memo-ls-remote
𝄢

Calling the function passes through to the specified function from the loaded module.

(memo-ls-remote "https://github.com/moby/buildkit" "HEAD")
stderr: 9 lines
=> resolve image config for docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b3
71a6cbe2630a4b37d23275658bd3f2 [0.02s]
=> docker-image://docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe263
0a4b37d23275658bd3f2 CACHED [0.00s]
-> resolve docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d
23275658bd3f2 [0.01s]
=> git ls-remote https://github.com/moby/buildkit HEAD [0.60s]
=> exporting to client directory [0.00s]
-> copying files 72B [0.00s]
"7804e2c7fc09b4a536934f5607698bc3255cf0ed"
𝄢

When the function is called again with the same arguments, the cached response value is returned instead of making the call again:

(memo-ls-remote "https://github.com/moby/buildkit" "HEAD")
"7804e2c7fc09b4a536934f5607698bc3255cf0ed"
𝄢

Use bass --bump to refresh every dependency in a bass.lock file:

bass --bump bass.lock

The bass --bump command re-loads all embedded module thunks and calls each function with each of its its associated arguments, updating the file in-place.

𝄢

Memoization is mostly leveraged for caching dependency version resolution. For this, your module must define the bass.lock path as a special binding: *memos*.

(def *memos* *dir*/bass.lock)
*memos*
𝄢

The linux and github path roots use this binding to automatically discover the memos location.

(use (.git (linux/alpine/git)))
git:github/vito/bass/ref/main/
stderr: 10 lines
=> resolve image config for docker.io/alpine/git:latest [0.13s]
=> resolve image config for docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b3
71a6cbe2630a4b37d23275658bd3f2 [0.02s]
=> docker-image://docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe263
0a4b37d23275658bd3f2 CACHED [0.00s]
-> resolve docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d
23275658bd3f2 [0.00s]
=> git ls-remote https://github.com/vito/bass main [0.48s]
=> exporting to client directory [0.00s]
-> copying files 83B [0.00s]
.git
{
:image
.git
{
:image
.git
{
:image
.git
{
:image
{
:repository "alpine/git"
:platform
{
:os "linux"
}
:tag "latest"
:digest "sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2"
}
:cmd .git
:args
(
  1. "clone"
  2. "https://github.com/vito/bass"
  3. ./
)
}
:cmd .git
:args
(
  1. "fetch"
  2. "origin"
  3. "836efe1a4b59be0c148ae4cbc25f7eff581d9dde"
)
}
:cmd .git
:args
(
  1. "checkout"
  2. "836efe1a4b59be0c148ae4cbc25f7eff581d9dde"
)
}
:cmd .git
:args
(
  1. "submodule"
  2. "update"
  3. "--init"
  4. "--recursive"
)
}
./
𝄢

Third-party modules may respect this binding too. Here's how linux is defined, for reference:

(defop linux args scope
  (let [path-root (path {:os "linux"} (:*memos* scope null))]
    (eval [path-root & args] scope)))
linux

sharing bass code

Using bass.lock files lets you share and reuse Bass code in git repos:

; image and git path resolution will be cached here.
;
; images store digests, git paths store shas.
(def *memos* *dir*/bass.lock)

(use (.git (linux/alpine/git))
     (git:github/vito/tabs/ref/main/wget.bass))

(defn main []
  (emit (wget:wget "https://example.com" ./index.html) *stdout*))
stderr: 346 lines
=> git ls-remote https://github.com/vito/tabs main [0.52s]
=> exporting to client directory [0.00s]
-> copying files 83B [0.00s]
=> resolve image config for docker.io/alpine/git@sha256:760aaf0d59c93f87572ec40dee1efd10a7
ea13a78dff1f59a904e908449329ae [0.02s]
=> docker-image://docker.io/alpine/git@sha256:760aaf0d59c93f87572ec40dee1efd10a7ea13a78dff
1f59a904e908449329ae CACHED [0.00s]
-> resolve docker.io/alpine/git@sha256:760aaf0d59c93f87572ec40dee1efd10a7ea13a78dff1f59a90
4e908449329ae [0.00s]
=> git clone https://github.com/vito/tabs ./ [0.64s]
Cloning into '.'...
=> git fetch origin c97bdc3bc41acb5c1bebec6fba9994ee2fb992a5 [0.49s]
From https://github.com/vito/tabs
* branch c97bdc3bc41acb5c1bebec6fba9994ee2fb992a5 -> FETCH_HEAD
=> git checkout c97bdc3bc41acb5c1bebec6fba9994ee2fb992a5 [0.23s]
Note: switching to 'c97bdc3bc41acb5c1bebec6fba9994ee2fb992a5'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at c97bdc3 use tls for nixery via caddy
=> git submodule update --init --recursive [0.25s]
=> resolve image config for docker.io/basslang/nixery@sha256:aaaed1efa8f4dd10c2021bfb92ad8
b510224d2474460426d0263bc955674b717 [0.02s]
=> docker-image://docker.io/basslang/nixery@sha256:aaaed1efa8f4dd10c2021bfb92ad8b510224d24
74460426d0263bc955674b717 CACHED [0.00s]
-> resolve docker.io/basslang/nixery@sha256:aaaed1efa8f4dd10c2021bfb92ad8b510224d247446042
6d0263bc955674b717 [0.00s]
=> resolve image config for docker.io/library/caddy@sha256:740c1c9e461ea1f8a54d7512b35cd31
927c814c86455f71e7e8e0c2b6ee423a2 [0.01s]
=> mkfile /Caddyfile CACHED [0.00s]
=> docker-image://docker.io/library/caddy@sha256:740c1c9e461ea1f8a54d7512b35cd31927c814c86
455f71e7e8e0c2b6ee423a2 CACHED [0.00s]
-> resolve docker.io/library/caddy@sha256:740c1c9e461ea1f8a54d7512b35cd31927c814c86455f71e
7e8e0c2b6ee423a2 [0.01s]
=> resolve image config for HTG3JI3RGOCOG:443/wget:latest [13.3s]
=> caddy run CANCELED [14.0s]
{"level":"info","ts":1669598880.1480286,"msg":"using adjacent Caddyfile"}
{"level":"warn","ts":1669598880.1485791,"msg":"Caddyfile input is not formatted; run t
he 'caddy fmt' command to fix inconsistencies","adapter":"caddyfile","file":"Caddyfile
","line":4}
{"level":"info","ts":1669598880.14898,"logger":"admin","msg":"admin endpoint started",
"address":"tcp/localhost:2019","enforce_origin":false,"origins":["//localhost:2019","/
/[::1]:2019","//127.0.0.1:2019"]}
{"level":"info","ts":1669598880.1491582,"logger":"tls.cache.maintenance","msg":"starte
d background certificate maintenance","cache":"0xc0003b81c0"}
{"level":"warn","ts":1669598880.149221,"logger":"tls","msg":"stapling OCSP","error":"n
o OCSP stapling for [htg3ji3rgocog]: no OCSP server specified in certificate"}
{"level":"info","ts":1669598880.1492364,"logger":"http","msg":"enabling automatic HTTP
->HTTPS redirects","server_name":"srv0"}
{"level":"info","ts":1669598880.1493967,"logger":"tls","msg":"cleaning storage unit","
description":"FileStorage:/data/caddy"}
{"level":"info","ts":1669598880.1494143,"logger":"tls","msg":"finished cleaning storag
e units"}
{"level":"info","ts":1669598880.1495576,"msg":"autosaved config (load with --resume fl
ag)","file":"/config/caddy/autosave.json"}
{"level":"info","ts":1669598880.1495667,"msg":"serving initial configuration"}
=> nixery CANCELED [14.4s]
{"channel":"a3fddd46a7f3418d7e3940ded94701aba569161d","eventTime":"2022-11-28T01:27:59
.832576418Z","message":"using Nix package set from Nix channel or commit","serviceCont
ext":{"service":"nixery","version":"depot"},"severity":"INFO"}
{"backend":"Filesystem (/var/lib/nixery)","eventTime":"2022-11-28T01:27:59.832691762Z"
,"message":"initialised storage backend","serviceContext":{"service":"nixery","version
":"depot"},"severity":"INFO"}
{"eventTime":"2022-11-28T01:27:59.83272159Z","message":"starting Nixery","port":"9110"
,"serviceContext":{"service":"nixery","version":"depot"},"severity":"INFO","version":"
depot"}
{"eventTime":"2022-11-28T01:28:00.24783193Z","image":"wget","message":"requesting imag
e manifest","serviceContext":{"service":"nixery","version":"depot"},"severity":"INFO",
"tag":"latest"}
{"backend":"Filesystem (/var/lib/nixery)","context":{"filePath":"github.com/google/nix
ery/builder/cache.go","lineNumber":117,"functionName":"github.com/google/nixery/builde
r.manifestFromCache"},"error":"open /var/lib/nixery/manifests/5f39c438969f8c9883a0dcf7
3be1f8b035edae98: no such file or directory","eventTime":"2022-11-28T01:28:00.24792942
1Z","manifest":"5f39c438969f8c9883a0dcf73be1f8b035edae98","message":"failed to fetch m
anifest from cache","serviceContext":{"service":"nixery","version":"depot"},"severity"
:"ERROR"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:00.248275548Z","image":"wg
et","message":"invoked Nix build","serviceContext":{"service":"nixery","version":"depo
t"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:05.437925965Z","image":"wg
et","message":"[nix] unpacking 'https://github.com/NixOS/nixpkgs/archive/a3fddd46a7f34
18d7e3940ded94701aba569161d.tar.gz'...","serviceContext":{"service":"nixery","version"
:"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:08.979702135Z","image":"wg
et","message":"[nix] copying path '/nix/store/9vjb94s3ka7154jsgbqywibpypd42wif-iana-et
c-20220520' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","v
ersion":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.013288526Z","image":"wg
et","message":"[nix] copying path '/nix/store/gfmim8czl10xfmcarcfrzs4s3wf5jpi6-libunis
tring-1.0' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","ve
rsion":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.067989772Z","image":"wg
et","message":"[nix] copying path '/nix/store/hha4czak7w679zqymac98cxf2fwjn4dw-nss-cac
ert-3.80' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","ver
sion":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.091312215Z","image":"wg
et","message":"[nix] copying path '/nix/store/a9jxaiiw8xlprxalc5s2pgdj1rqbdn6y-libidn2
-2.3.2' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","versi
on":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.115609563Z","image":"wg
et","message":"[nix] copying path '/nix/store/v483gzp7hv2hf5iz5sbbmv9qfhvninis-glibc-2
.34-210' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","vers
ion":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.593036527Z","image":"wg
et","message":"[nix] copying path '/nix/store/wq0idmq0qbjah8vxysvz9dma39g5mdrl-attr-2.
5.1' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version"
:"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.60776948Z","image":"wge
t","message":"[nix] copying path '/nix/store/lj2bdg618093ny9505d0nzzjdq0fwp8a-bash-5.1
-p16' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version
":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.651373098Z","image":"wg
et","message":"[nix] copying path '/nix/store/cd0rqgs4kpqxpmjp05kfxkwm2j8m8pva-acl-2.3
.1' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version":
"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.667420783Z","image":"wg
et","message":"[nix] copying path '/nix/store/g5qxf0dlnggwlhzy2b9gy0qxvimz98dm-bzip2-1
.0.8' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version
":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.682530585Z","image":"wg
et","message":"[nix] copying path '/nix/store/2snaj6gblfrxsbjw0cxcj53za6bw0pjq-ed-1.18
' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version":"d
epot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.699613653Z","image":"wg
et","message":"[nix] copying path '/nix/store/ph4zzcfry5ca23b4y3i6ii3269qln61v-bzip2-1
.0.8-bin' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","ver
sion":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.713928277Z","image":"wg
et","message":"[nix] copying path '/nix/store/jnijggavwqn6g64h4dp9qzmrracv2bbb-gawk-5.
1.1' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version"
:"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.769115718Z","image":"wg
et","message":"[nix] copying path '/nix/store/7ddznpa25zl4klm2vn52b1cixx3iw6l3-gcc-11.
3.0-lib' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","vers
ion":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.879137347Z","image":"wg
et","message":"[nix] copying path '/nix/store/k88h9p2vvyivn95xc5hkvsya27jxn8b6-gnumake
-4.3' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version
":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.924247655Z","image":"wg
et","message":"[nix] copying path '/nix/store/zs08dmq3ns8z1xkzv5mmwfzkpmcbqq3v-gmp-wit
h-cxx-stage4-6.2.1' from 'https://cache.nixos.org'...","serviceContext":{"service":"ni
xery","version":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.964802801Z","image":"wg
et","message":"[nix] copying path '/nix/store/aq35ngg834iw836wi718f8qh12jzcr1h-gnused-
4.8' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version"
:"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:09.992437541Z","image":"wg
et","message":"[nix] copying path '/nix/store/2j2lmqsq9pgadlbzzzx70k2iydilq99p-coreuti
ls-9.1' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","versi
on":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.042315524Z","image":"wg
et","message":"[nix] copying path '/nix/store/wryagqmi3mhiap2dsg19ycd9lr64j0bk-gnutar-
1.34' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version
":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.098355943Z","image":"wg
et","message":"[nix] copying path '/nix/store/q6v4gpzr9qj8jb8kzp97a5w1n7khxpwq-diffuti
ls-3.8' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","versi
on":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.135127986Z","image":"wg
et","message":"[nix] copying path '/nix/store/vb34kj44sziw0zslkdfbkdmrx21j7chn-finduti
ls-4.9.0' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","ver
sion":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.192992481Z","image":"wg
et","message":"[nix] copying path '/nix/store/2s38y41w0n2mgb3knbg95kry6ymmcpfv-gzip-1.
12' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version":
"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.212274221Z","image":"wg
et","message":"[nix] copying path '/nix/store/2k8xdnp75lya99khkvm29w6b91a26jq9-openssl
-1.1.1q' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","vers
ion":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.311672118Z","image":"wg
et","message":"[nix] copying path '/nix/store/64k7364by64kr0bn2j2g9czsg3kwvxmw-patch-2
.7.6' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version
":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.334503115Z","image":"wg
et","message":"[nix] copying path '/nix/store/z15np9n0i42abw4ga1r8qgyshqr2cnzg-patchel
f-0.14.5' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","ver
sion":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.353471424Z","image":"wg
et","message":"[nix] copying path '/nix/store/06j8l51s3b1j149g5xnh17dki6fsk6v3-pcre-8.
45' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version":
"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.386535498Z","image":"wg
et","message":"[nix] copying path '/nix/store/fsgpvixxm1vmyz3hbibiyb08imnh8sgm-util-li
nux-minimal-2.38-lib' from 'https://cache.nixos.org'...","serviceContext":{"service":"
nixery","version":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.429216666Z","image":"wg
et","message":"[nix] copying path '/nix/store/hq7m6n9wgdf8ds8zd210881wpznm9kjc-gnugrep
-3.7' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version
":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.490190505Z","image":"wg
et","message":"[nix] copying path '/nix/store/gq22vvr7ms80jwdxhag8ib00icb8ykhl-xz-5.2.
5' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version":"
depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.515479639Z","image":"wg
et","message":"[nix] copying path '/nix/store/w4fi658bk3pddr84cb4cp6bc4s927rqp-zlib-1.
2.12' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version
":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.547987053Z","image":"wg
et","message":"[nix] copying path '/nix/store/9i6fm47liyysmpk5xfgdwnk62wzrl8ia-xz-5.2.
5-bin' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","versio
n":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.566375096Z","image":"wg
et","message":"[nix] copying path '/nix/store/rsyl8frv3h8lkcpfahcyap6j0h36a40w-file-5.
42' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version":
"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.621706715Z","image":"wg
et","message":"[nix] copying path '/nix/store/7a5va08wgq1528l0kkns4ywdbq19n3ac-wget-1.
21.3' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version
":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.713338574Z","image":"wg
et","message":"[nix] copying path '/nix/store/nibvx25ygk8mmxfjzwlh9rkr10clcpmq-stdenv-
linux' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","versio
n":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.758906878Z","image":"wg
et","message":"[nix] building '/nix/store/f5fhy1fkhgzwgawaan96rn839wnw2pvr-runtime-gra
ph.json.drv'...","serviceContext":{"service":"nixery","version":"depot"},"severity":"I
NFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.914692466Z","image":"wg
et","message":"[nix] copying path '/nix/store/dzlr6002351j40vsily79a2ma9ykqbvx-onig-6.
9.8' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version"
:"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.940701866Z","image":"wg
et","message":"[nix] copying path '/nix/store/pkga8bjam87h9ma5pm2qlq593bswnv6l-lndir-1
.0.3' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version
":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.953680655Z","image":"wg
et","message":"[nix] copying path '/nix/store/v1v3wk60pwdxbbsr5iz1rzqvs3sv18sc-jq-1.6-
lib' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version"
:"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:10.976378468Z","image":"wg
et","message":"[nix] copying path '/nix/store/4bcp8llm1zxpg7r318am55s93g7w2ak3-perl-5.
34.1' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version
":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:11.661170998Z","image":"wg
et","message":"[nix] copying path '/nix/store/kmm63rfd6pjxj7g1zgy7642laq8jjrlq-jq-1.6-
bin' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version"
:"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:11.675825207Z","image":"wg
et","message":"[nix] copying path '/nix/store/s1v7r9pqgg12vzn3kim0s1n2m7lk2qhn-openssl
-1.1.1q-bin' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","
version":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:11.70810048Z","image":"wge
t","message":"[nix] copying path '/nix/store/vwq7wycz4q3hm4gqjp4cqblcwicm4725-jq-1.6-d
ev' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","version":
"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:11.721271793Z","image":"wg
et","message":"[nix] copying path '/nix/store/0cs5zm2d56cx32f2xk38hx2ga792j8ya-openssl
-1.1.1q-dev' from 'https://cache.nixos.org'...","serviceContext":{"service":"nixery","
version":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:11.763913235Z","image":"wg
et","message":"[nix] building '/nix/store/r554kd9gwd670yak6375m9g7kb9vrrhz-bulk-layers
.drv'...","serviceContext":{"service":"nixery","version":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:11.790244144Z","image":"wg
et","message":"[nix] /nix/store/9vjb94s3ka7154jsgbqywibpypd42wif-iana-etc-20220520/nix
-support:","serviceContext":{"service":"nixery","version":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:11.790300088Z","image":"wg
et","message":"[nix] setup-hook: /nix/store/hha4czak7w679zqymac98cxf2fwjn4dw-nss-cacer
t-3.80/nix-support/setup-hook","serviceContext":{"service":"nixery","version":"depot"}
,"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:11.832966816Z","image":"wg
et","message":"[nix] building '/nix/store/22aw234nhd3y9dypqb47nj2y69rdslqi-symlink-lay
er.tar.drv'...","serviceContext":{"service":"nixery","version":"depot"},"severity":"IN
FO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:11.876095455Z","image":"wg
et","message":"[nix] building '/nix/store/56byrcxkacxvwm1mnw3b55psdddp2fvw-symlink-lay
er-meta.json.drv'...","serviceContext":{"service":"nixery","version":"depot"},"severit
y":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:11.936324352Z","image":"wg
et","message":"[nix] these derivations will be built:","serviceContext":{"service":"ni
xery","version":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:11.93639137Z","image":"wge
t","message":"[nix] /nix/store/13bphx4pc3nmck8iggdc58aingvds1an-build-output.json.dr
v","serviceContext":{"service":"nixery","version":"depot"},"severity":"INFO"}
{"cmd":"nixery-prepare-image","eventTime":"2022-11-28T01:28:11.945509714Z","image":"wg
et","message":"[nix] building '/nix/store/13bphx4pc3nmck8iggdc58aingvds1an-build-outpu
t.json.drv'...","serviceContext":{"service":"nixery","version":"depot"},"severity":"IN
FO"}
{"eventTime":"2022-11-28T01:28:12.039990518Z","image":"wget","message":"finished image
preparation via Nix","serviceContext":{"service":"nixery","version":"depot"},"severit
y":"INFO","tag":"latest"}
{"eventTime":"2022-11-28T01:28:13.47967874Z","layer":"33ea4ecbb2dad735eda7c3892e7796fb
f3b0f1e6","message":"created and persisted layer","serviceContext":{"service":"nixery"
,"version":"depot"},"severity":"INFO","sha256":"6252b58975c011458939fbc660d0e82d0f9cc6
83c7a59933ea633ddd808c628b","size":15684453}
{"eventTime":"2022-11-28T01:28:13.479733339Z","layer":"33ea4ecbb2dad735eda7c3892e7796f
bf3b0f1e6","message":"created image layer","packages":["pcre-8.45","openssl-1.1.1q","w
get-1.21.3","libidn2-2.3.2","util-linux-minimal-2.38-lib","libunistring-1.0","glibc-2.
34-210","zlib-1.2.12"],"serviceContext":{"service":"nixery","version":"depot"},"severi
ty":"INFO","tarhash":"sha256:840cf58308c01c7064661b880668e05464fe925223292a132db16d8b3
327af3b"}
{"eventTime":"2022-11-28T01:28:13.487711649Z","layer":"e0b1ee34b889f5f4a8ca25d14e27766
305d877a4","message":"created and persisted layer","serviceContext":{"service":"nixery
","version":"depot"},"severity":"INFO","sha256":"6588fae47e53e7213af11534b8a58e2b72460
34e0688b8ba6eb34d2ac774846d","size":151594}
{"eventTime":"2022-11-28T01:28:13.487757074Z","layer":"e0b1ee34b889f5f4a8ca25d14e27766
305d877a4","message":"created image layer","packages":["nss-cacert-3.80"],"serviceCont
ext":{"service":"nixery","version":"depot"},"severity":"INFO","tarhash":"sha256:22fce9
30bfe8f90badcb323cc94611eb28f9948069b84ce848c54b781396194d"}
{"eventTime":"2022-11-28T01:28:13.511929521Z","layer":"f3ca9f52b335cf623e1c447cdbc61e2
c25617228","message":"created and persisted layer","serviceContext":{"service":"nixery
","version":"depot"},"severity":"INFO","sha256":"4fbe3d6fa49b831b6f67660e62214259ea0cc
43ca87799ff3795bbee36c3ed04","size":140175}
{"eventTime":"2022-11-28T01:28:13.511984085Z","layer":"f3ca9f52b335cf623e1c447cdbc61e2
c25617228","message":"created image layer","packages":["iana-etc-20220520"],"serviceCo
ntext":{"service":"nixery","version":"depot"},"severity":"INFO","tarhash":"sha256:b110
3956d151df3e85e9da560d13f786860a5de6c2e3864fde64b391e983228c"}
{"eventTime":"2022-11-28T01:28:13.512936976Z","layer":"9d94fc9b4f04194dc0567c0a8dbf2d8
55b2b0c700bfbf08783ccd18dd45d65b7","message":"created and persisted layer","serviceCon
text":{"service":"nixery","version":"depot"},"severity":"INFO","sha256":"fcae1804c5637
dbd711aca73458cf14510698571008a025330bc2bb209ab6877","size":2592}
{"eventTime":"2022-11-28T01:28:13.513068108Z","layer":"78fefb5a2a899e92745281e830649b9
7a7f5bbed58670c33af448df9f202080e","message":"created and persisted layer","serviceCon
text":{"service":"nixery","version":"depot"},"severity":"INFO","sha256":"78fefb5a2a899
e92745281e830649b97a7f5bbed58670c33af448df9f202080e","size":437}
{"backend":"Filesystem (/var/lib/nixery)","eventTime":"2022-11-28T01:28:13.513291874Z"
,"manifest":"5f39c438969f8c9883a0dcf73be1f8b035edae98","message":"cached manifest to s
torage backend","serviceContext":{"service":"nixery","version":"depot"},"severity":"IN
FO","size":915}
{"eventTime":"2022-11-28T01:28:13.513972294Z","image":"wget","message":"requesting ima
ge manifest","serviceContext":{"service":"nixery","version":"depot"},"severity":"INFO"
,"tag":"latest"}
{"digest":"9f344460fd5122dd8ab6a4e25ae928abe8ba38ae3d38f523c936be4b1f1e13c0","eventTim
e":"2022-11-28T01:28:13.531105691Z","message":"serving blob from filesystem","path":"/
var/lib/nixery/layers/9f344460fd5122dd8ab6a4e25ae928abe8ba38ae3d38f523c936be4b1f1e13c0
","serviceContext":{"service":"nixery","version":"depot"},"severity":"INFO"}
{"digest":"78fefb5a2a899e92745281e830649b97a7f5bbed58670c33af448df9f202080e","eventTim
e":"2022-11-28T01:28:13.536317718Z","message":"serving blob from filesystem","path":"/
var/lib/nixery/layers/78fefb5a2a899e92745281e830649b97a7f5bbed58670c33af448df9f202080e
","serviceContext":{"service":"nixery","version":"depot"},"severity":"INFO"}
=> exporting to client tarball [14.6s]
-> sending tarball [14.6s]
{
  "thunk": {
    "image": {
      "ref": {
        "addr": {
          "thunk": {
            "image": {
              "ref": {
                "repository": "caddy",
                "platform": {
                  "os": "linux"
                },
                "tag": "latest",
                "digest": "sha256:740c1c9e461ea1f8a54d7512b35cd31927c814c86455f71e7e8e0c2b6ee423a2"
              }
            },
            "cmd": {
              "command": {
                "name": "caddy"
              }
            },
            "args": [
              {
                "string": {
                  "value": "run"
                }
              }
            ],
            "env": [
              {
                "symbol": "PROXY_ADDR",
                "value": {
                  "thunkAddr": {
                    "thunk": {
                      "image": {
                        "ref": {
                          "repository": "basslang/nixery",
                          "platform": {
                            "os": "linux"
                          },
                          "tag": "latest",
                          "digest": "sha256:aaaed1efa8f4dd10c2021bfb92ad8b510224d2474460426d0263bc955674b717"
                        }
                      },
                      "cmd": {
                        "command": {
                          "name": "nixery"
                        }
                      },
                      "env": [
                        {
                          "symbol": "NIXERY_STORAGE_BACKEND",
                          "value": {
                            "string": {
                              "value": "filesystem"
                            }
                          }
                        },
                        {
                          "symbol": "STORAGE_PATH",
                          "value": {
                            "string": {
                              "value": "/var/lib/nixery"
                            }
                          }
                        },
                        {
                          "symbol": "PORT",
                          "value": {
                            "string": {
                              "value": "9110"
                            }
                          }
                        },
                        {
                          "symbol": "WEB_DIR",
                          "value": {
                            "string": {
                              "value": "/srv/www"
                            }
                          }
                        },
                        {
                          "symbol": "NIXERY_CHANNEL",
                          "value": {
                            "string": {
                              "value": "a3fddd46a7f3418d7e3940ded94701aba569161d"
                            }
                          }
                        }
                      ],
                      "mounts": [
                        {
                          "source": {
                            "cache": {
                              "id": "nixery-storage",
                              "path": {
                                "dir": {
                                  "path": "."
                                }
                              }
                            }
                          },
                          "target": {
                            "dir": {
                              "path": "/var/lib/nixery"
                            }
                          }
                        }
                      ],
                      "ports": [
                        {
                          "name": "registry",
                          "port": 9110
                        }
                      ]
                    },
                    "port": "registry",
                    "format": "$host:$port"
                  }
                }
              }
            ],
            "mounts": [
              {
                "source": {
                  "logical": {
                    "file": {
                      "name": "Caddyfile",
                      "content": "OjQ0Mwp0bHMgL2NhZGR5LmNydCAvY2FkZHkua2V5CnJldmVyc2VfcHJveHkgewogIHRvIHskUFJPWFlfQUREUn0KfQo="
                    }
                  }
                },
                "target": {
                  "file": {
                    "path": "Caddyfile"
                  }
                }
              }
            ],
            "ports": [
              {
                "name": "registry",
                "port": 443
              }
            ],
            "tls": {
              "cert": {
                "path": "/caddy.crt"
              },
              "key": {
                "path": "/caddy.key"
              }
            }
          },
          "port": "registry",
          "format": "$host:$port/wget"
        },
        "platform": {
          "os": "linux"
        },
        "tag": "latest",
        "digest": "sha256:9f344460fd5122dd8ab6a4e25ae928abe8ba38ae3d38f523c936be4b1f1e13c0"
      }
    },
    "cmd": {
      "command": {
        "name": "wget"
      }
    },
    "args": [
      {
        "string": {
          "value": "https://example.com"
        }
      },
      {
        "string": {
          "value": "-O"
        }
      },
      {
        "filePath": {
          "path": "index.html"
        }
      }
    ]
  },
  "path": {
    "file": {
      "path": "index.html"
    }
  }
}

server mode

I'm not sure if this is the right design for this yet, but it seems nifty and it works. Expect this to change at any moment. Suggestions welcome!

To serve Bass scripts in ./srv/ over HTTP on port 6455 ("bass"), run:

bass --serve 6455 ./srv/

This is particularly handy for cobbling together endpoints for receiving webhooks (e.g. a GitHub App for CI/CD).

HTTP requests sent to http://localhost:6455/foo will run the ./srv/foo Bass script.

The HTTP request sent on *stdin* as a structure like the following:

{:headers {:Accept "application/json"}
 :body "{\"foo\":1}"}

Values emitted to *stdout* will be sent as the response. If the script fails a 500 status code will be returned.

The UX here is very spartan at the moment. Notably there is no way to view progress over HTTP; it's only rendered server-side in the console.

I'd like the server-side to self-update somehow, but haven't figured that out yet.

webhooks based CI/CD

The Bass project uses Bass Loop to receive GitHub webhooks and run its own builds. Docs coming soon - see the announcement for now.

pipeline based CI/CD

Trigging builds on push is just one form of CI/CD. What if you have external dependencies you'd like to trigger builds from? What if you want to write sophisticated pipelines with fan-in and fan-out semantics?

Dunno yet! I think we're a few steps away from this, but we need to figure out the best steps.

Ideas for the future: