Initial commit

This commit is contained in:
Collin J. Doering 2023-07-15 16:37:59 -04:00
commit 75361f9a8a
Signed by: rekahsoft
GPG Key ID: 7B4DEB93212B3022
9 changed files with 228 additions and 0 deletions

10
.envrc Normal file
View File

@ -0,0 +1,10 @@
use_guix-shell() {
CHANNEL_FILE=channels.scm
if [ -f $CHANNEL_FILE ]; then
eval "$(guix time-machine -C $CHANNEL_FILE -- shell "$@" --search-paths)"
else
eval "$(guix shell "$@" --search-paths)"
fi
}
use guix-shell -m manifest-dev.scm

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.tar.gz

7
Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM scratch
ADD guix-tarball-pack.tar.gz /guix-builder
ENV PATH=/guix-builder/bin
VOLUME /var/guix/daemon-socket/socket /gnu/store /etc/ssl
ENTRYPOINT ["/guix-builder/bin/bash"]

33
Makefile Normal file
View File

@ -0,0 +1,33 @@
.DEFAULT_GOAL := build
GUIX_MANIFEST := manifest.scm
GUIX_MANIFEST_DEV := manifest-dev.scm
TARBALL ?= guix-tarball-pack.tar.gz
IMAGE_TAG ?= guix-builder
$(TARBALL):
@cp $$(guix pack -R -S /bin=bin -S /etc/ssl=etc/ssl -m $(GUIX_MANIFEST)) $@
@chmod +w $@
.PHONY: build
build: $(TARBALL)
@docker build -t $(IMAGE_TAG) .
.PHONY: run
run: build
@docker run --rm -it \
-v /var/guix/daemon-socket/socket:/var/guix/daemon-socket/socket \
-v /gnu/store:/gnu/store:ro \
-v /etc/ssl:/etc/ssl:ro \
-e HOME=/tmp \
-w /tmp \
$(IMAGE_TAG):latest
.PHONY: shell
shell:
@./shell.sh $(GUIX_MANIFEST) $(GUIX_MANIFEST_DEV)
.PHONY:
clean:
rm $(TARBALL)

142
README.org Normal file
View File

@ -0,0 +1,142 @@
#+TITLE: Guix Builder
#+AUTHOR: Collin J. Doering
#+begin_abstract
This repository defines a minimal OCI container containing guix and a few other tools useful
for CI/CD jobs. The container is expected to be run from a system that already has the
~guix-daemon~ installed and running, and is most useful in the context of existing CI/CD
tools that already use an existing OCI container runtime (eg. docker, podman, etc..).
#+end_abstract
* Prerequisites
- ~guix-daemon~ installed and running on the host
- ~dockerd~ to build and run the produced image
- ~direnv~ (optional)
* Repository Structure
- ~channels.scm~ :: [[*Guix Channel File][Guix Channel File]].
- ~Dockerfile~ :: From scratch image that uses guix pack tar.gz output.
- ~Makefile~ :: Various make targets to build and run the guix-builder OCI image.
- ~manifest-dev.scm~ :: Packages required for development.
- ~manifest.scm~ :: Packages to be installed in the OCI image.
- ~.gitignore~ :: Files ignored by git.
- ~README.org~ :: Org-mode[fn:1] documentation.
- ~shell.sh~ :: Launch a containerized development shell.
** Guix Channel File
Guix channels[fn:2] allow for Guix to be customized and extended. They are also critical for
replicating a Guix system[fn:3]. To ensure reproducibility, a ~channels.scm~ file is provided
in this repository that is expected to be used during deployment. It pins external guix
channels to specific versions.
* Setup development environment
~direnv~ is the preferred and optimal way to enter a development environment. Use ~direnv allow~.
Alternatively, ~./shell.sh~ can be invoked directly
* Building the image
#+begin_src shell
make [build]
#+end_src
~build~ is optional, as its the default target.
* Using the image
Now that the OCI image has been produced, some care must be taken when running it. Namely,
the following volumes are expected to be mounted.
- ~/var/guix/daemon-socket/socket~ :: the ~guix-daemon~ socket
- ~/gnu/store~ :: The hosts guix store
- ~/etc/ssl~ :: The hosts ssl certificates (required for ~guix time-machine ...~ commands)
Additionally, for ~guix~ to function appropriately, the ~HOME~ environment variable must be
set. This allows guix to store its cache (mandatory for many operations, eg ~guix pull~).
Depending on use-case, it is sometimes useful to retain the guix cache between container
invocations. To do so, mount a volume to ~$HOME/.cache/guix~ (replacing ~$HOME~ with what it
will be set to within the container).
A complete example is given below, where the aforementioned volumes are mounted, ~HOME~ is
set to ~/tmp~, and a volume for retaining the guix cache between invocations is put in place.
The working directory within the container is also set with the ~-w~ option, however this is
not required.
#+begin_src shell
docker run --rm -it \
-v /var/guix/daemon-socket/socket:/var/guix/daemon-socket/socket \
-v /gnu/store:/gnu/store:ro \
-v /etc/ssl:/etc/ssl:ro \
-v /var/lib/ci/guix-cache:/tmp/.cache/guix \
-e HOME=/tmp \
-w /tmp \
guix-builder:latest
#+end_src
** Using the image with DroneCI / WoodpeckerCI
The primary use-case for the image produced by this repository is for ci/cd jobs (mandatorily
on a host that is running guix-daemon itself). This allows for the hosts guix store to be
reused, allowing for a node-local caching layer, and enabling optimal ci/cd build/deploy
times.
Any ci/cd tool that can run OCI containers should be compatible with this image, though the
optimal setup assumes it provides a mechanism to mount volumes and set environment variables
(by an administrator). Both DroneCI and WoodpeckerCI allow for this, using
~DRONE_RUNNER_VOLUMES~ and ~WOODPECKER_BACKEND_DOCKER_VOLUMES~ respectively.
Note, in WoodpeckerCI, this functionality has not yet made it into a release (see [[https://github.com/woodpecker-ci/woodpecker/pull/1203][PR]]).
Below is an example of running ~drone-runner-docker~, setup to spawn ~guix~ images produced
by this repository:
#+begin_src shell
docker run -d \
-v /var/run/docker.sock:/var/run/docker.sock \
-e DRONE_RPC_PROTO="https" \
-e DRONE_RPC_HOST="<REDACTED>" \
-e DRONE_RPC_SECRET="<REDACTED>" \
-e DRONE_RUNNER_CAPACITY=4 \
-e DRONE_RUNNER_NAME="<REDACTED>" \
-e DRONE_RUNNER_LABELS=guix:on \
-e DRONE_RUNNER_VOLUMES=/var/guix/daemon-socket/socket:/var/guix/daemon-socket/socket,/gnu/store:/gnu/store,/var/lib/ci/guix-cache:/.cache/guix \
-p 3001:3000 \
--restart unless-stopped \
--name runner-guix \
drone/drone-runner-docker:1.6.3
#+end_src
* FAQ
** Why not produce this image with ~guix pack -f docker ...~ directly?
One may ask, why not simply produce a OCI container using ~guix pack~ directly? Eg.
#+begin_src shell
guix pack -f docker -S /bin=bin --entry-point=bin/guix -m manifest.scm
#+end_src
Well, I'm glad you asked! This produces a container that has the guix store in its default
location ~/gnu/store~. Using the same method as described in [[*Using the image with DroneCI / WoodpeckerCI][Using the image with DroneCI /
WoodpeckerCI]], this container image works, however it has an implicit constraint: all store
items used within the pack must also exist in the hosts guix store. This is because the guix
store is volume mounted into the container at runtime, shadowing the already existing
~/gnu/store~ directory put in place via ~guix pack~. There are ways one could work around
this constraint, however the best solution would be to have a guix container that is
independent of the host (as described in this repository).
** What about running a ~guix-daemon~ within a container (so that builds can be completely isolated)?
This currently is not possible ... TBD (more detail)
* Footnotes
[fn:1] https://orgmode.org/
[fn:2] https://guix.gnu.org/manual/en/html_node/Channels.html
[fn:3] https://guix.gnu.org/manual/en/html_node/Replicating-Guix.html

11
channels.scm Normal file
View File

@ -0,0 +1,11 @@
(list (channel
(name 'guix)
(url "https://git.savannah.gnu.org/git/guix.git")
(branch "master")
(commit
"1b07f397dc17e31ad55b80a4efd34fdcb5b3c690")
(introduction
(make-channel-introduction
"9edb3f66fd807b096b48283debdcddccfea34bad"
(openpgp-fingerprint
"BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA")))))

3
manifest-dev.scm Normal file
View File

@ -0,0 +1,3 @@
(specifications->manifest
(list "make"
"docker-cli"))

8
manifest.scm Normal file
View File

@ -0,0 +1,8 @@
(specifications->manifest
(list "guix"
"coreutils"
"bash"
"nss-certs"
"grep"
"gawk"
"sed"))

13
shell.sh Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Usage: ./shell.sh [manifest] [dev-manifest]
GUIX_MANIFEST="${1:-manifest.scm}"
GUIX_MANIFEST_DEV="${2:-manifest-dev.scm}"
export PS1="\W [env]\$ "
exec guix time-machine -C channels.scm -- shell -m $GUIX_MANIFEST -m $GUIX_MANIFEST_DEV -E '^PS1$' -C \
--expose=/var/run/docker.sock=/var/run/docker.sock \
--expose=/var/guix/daemon-socket/socket=/var/guix/daemon-socket/socket \
--expose=/gnu/store=/gnu/store