#!/bin/sh
# Exit on error and when referencing an undefined variable.
set -eu
if ! command -v guix >/dev/null; then
cat >&2 <<EOF
You need to have the GNU Guix package manager installed to use this script.
EOF
if command -v apt-get >/dev/null; then
cat >&2 <<EOF
You should be able to install GNU Guix on this machine using APT with, e.g., \
the command
sudo sh -c 'apt-get update && apt-get install -y guix'
EOF
elif uname | grep --ignore-case --quiet linux; then
cat >&2 <<EOF
Consider checking if GNU Guix is available in the package repositories of your \
operating system distribution. If it is not, you might want to instead follow \
the generic installation instructions at [1].
[1] https://guix.gnu.org/manual/en/html_node/Installation.html
EOF
else
cat >&2 <<EOF
Please be aware that, except for some experimental setups, GNU Guix requires \
an operating system based on the Linux kernel to run.
EOF
fi
exit 1
fi
GUEST_SCRIPT="$(awk '{if(on)print}/^#{80}/{on=1}' <"$(command -v "$0")")"
cd "$(dirname "$(command -v "$0")")"
TRY_TO_RUN_BROWSER=yes
if [ $# -ge 1 ] && [ "$1" = "--no-browser" ]; then
unset TRY_TO_RUN_BROWSER
fi
rm -rf notebook-file-open-fifo
mkfifo notebook-file-open-fifo
print_open_request() {
printf 'Request from the container to open: %s\n' "$1" >&2
}
if ! command -v xdg-open >/dev/null; then
cat >&2 <<EOF
The xdg-open program was not found. Web browser and requested files shall not \
be opened automatically. You can instead manually act on the URL and \
filenames that will be printed.
EOF
alias xdg-open=print_open_request
fi
open_files_for_guest() {
IFS= read TO_OPEN < notebook-file-open-fifo
if [ -n "$TRY_TO_RUN_BROWSER" ]; then
xdg-open "$(printf %s "$TO_OPEN" |
base64 -d |
tr --delete '\n' |
sed 's_.*/[.]\(local/.*\)[.]html_\1_;
s_.*_.container-data/\0.html_')" &
fi
while true; do
if ! IFS= read TO_OPEN < notebook-file-open-fifo; then
continue
fi
TO_OPEN="$(printf %s "$TO_OPEN" | base64 -d)"
if (printf %s "$TO_OPEN" |
tr --delete '\n' |
grep --quiet -E '^[^-].*[.](png|jpg)$'); then
xdg-open "$TO_OPEN" &
else
printf "Illegal filename. Not opening file %s\n" "$TO_OPEN" >&2
fi
done
}
open_files_for_guest &
trap "kill $! 2>/dev/null || true; wait $!" EXIT
for DIR in .container-data .container-data/var .container-data/local; do
test -e "$DIR" || mkdir "$DIR"
done
rm -rf .container-data/.my.cnf
touch .container-data/.my.cnf
GUIX_PACKAGES="\
coreutils
findutils
grep
gzip
jupyter
kawa # for qexo, the XQuery runtime
lighttpd # we run Wordpress under lighttpd
mariadb
nss-certs # for TLS
openssl # needed to have Guix set the SSL_CERT_FILE variable
php
postgresql
python
python-bcrypt
python-django
python-ipython-sql
python-mysqlclient
python-psycopg2
python-valkey
sed
tar
unzip
util-linux # for hexdump
valkey
wget
-fpackages/ferretdb.scm
-fpackages/pymongo.scm"
GUIX_PACKAGES="$(printf %s "$GUIX_PACKAGES" | grep -oE '^[^#]+')"
# Some distros still ship a Guix configured to use the Savannah git
# URL. Cloning from that one is terriby slow nowadays and the project
# now officially hosts code on Codeberg.
CODEBERG_URL=https://codeberg.org/guix/guix.git
# Some ordinary commit from 2025-12-07.
COMMIT_KNOWN_GOOD=3c81c4b8b8a4f6d28faf540d7fa5c36772be15fc
GUIX="guix time-machine --url=$CODEBERG_URL --commit=$COMMIT_KNOWN_GOOD --"
$GUIX shell -C --network --emulate-fhs \
--share=.container-data/var=/var \
--share=.container-data/local="$HOME"/.local \
--share=.="$(pwd)" \
$GUIX_PACKAGES \
-- sh -c "$GUEST_SCRIPT"
exit $?
################################################################################
# Container guest part of the script.
# Exit on error and when referencing an undefined variable.
set -eu
# These programs look up their files relative to argv[0].
INITDB=$(realpath $(command -v initdb))
POSTGRES=$(realpath $(command -v postgres))
mkdir -p /var/lib/postgresql
if ! [ -e /var/lib/postgresql/data ]; then
$INITDB --pgdata /var/lib/postgresql/data-tmp
mv /var/lib/postgresql/data-tmp /var/lib/postgresql/data
fi
mkdir -p /var/run/postgresql
mkdir -p /var/log
# What if there are some trolls in our LAN? We'd better only listen
# on localhost.
$POSTGRES -D /var/lib/postgresql/data -p 25432 -h 127.0.0.1 \
>/var/log/postgresql.log 2>&1 &
printf %s $! >/var/run/postgresql.pid
PSQL='psql --port 25432 --quiet'
# Wait for Postgres to start accepting connections.
for _ in $(seq 20); do
if $PSQL --command='' postgres 2>/dev/null; then
break
fi
sleep 1
done
# Let these fail if database or user already exists.
for COMMAND in \
"CREATE USER demo_user PASSWORD 'demo_pwd'" \
'CREATE DATABASE agh_it_northwind WITH OWNER demo_user' \
'CREATE DATABASE ferret'; do
$PSQL --command="$COMMAND" postgres 2>/dev/null || true
done
cat > ~/.my.cnf <<EOF
[mysqld]
socket=/var/run/mysql/mysql.sock
port=23306
bind-address=127.0.0.1
[client]
socket=/var/run/mysql/mysql.sock
port=23306
EOF
mkdir -p /var/run/mysql
mkdir -p /var/lib/mysql/data
if [ 2 = $(stat --format=%h /var/lib/mysql/data) ]; then
mariadb-install-db --datadir=/var/lib/mysql/data/
fi
mariadbd-safe --datadir=/var/lib/mysql/data/ &
# Wait for MariaDB to start accepting connections.
for _ in $(seq 20); do
if printf "CREATE DATABASE IF NOT EXISTS agh_it_northwind" | \
mariadb 2>/dev/null; then
break
fi
sleep 1
done
printf "SET PASSWORD = PASSWORD('demo_pwd')" | mariadb 2>/dev/null;
ferretdb --postgresql-url postgres://127.0.0.1:25432/ferret \
>/var/log/ferretdb.log 2>&1 &
printf %s $! >/var/run/ferretdb.pid
cat > /tmp/valkey.conf <<EOF
bind 127.0.0.1
port 20637
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize yes
logfile "/var/log/valkey.log"
EOF
valkey-server /tmp/valkey.conf
mkdir -p /var/www
ALLOWED_SRC="$(printf %s "\
blob:
http://localhost:127.0.0.1:28080
http://127.0.0.1:28080
'unsafe-eval'
'unsafe-inline'
" | tr '\n' ' ')"
cat > /tmp/lighttpd.conf <<EOF
server.document-root = "/var/www"
server.modules = ( "mod_indexfile", "mod_cgi", "mod_accesslog", "mod_setenv" )
server.port = 28080
server.bind = "127.0.0.1"
server.errorlog = "/var/log/lighttpd.log"
index-file.names = ( "index.php", "index.html" )
cgi.assign = ( ".php" => "$(command -v php-cgi)" )
accesslog.filename = "/var/log/lighttpd.log"
setenv.add-response-header = (
"Content-Security-Policy" => "connect-src 'self'; default-src $ALLOWED_SRC;"
)
EOF
# lighttpd shall daemonize itself by default.
lighttpd -f /tmp/lighttpd.conf
ln -sf "$(realpath notebook-file-open-fifo)" /tmp/notebook-file-open-fifo
mkdir -p /tmp/bin
# This is our dummy variant of xdg-open ;)
cat >/tmp/bin/xdg-open <<EOF
#!/bin/sh
printf '%s\n' "\$(realpath "\${1##file://}")" |
(base64 --wrap=0 && echo) >/tmp/notebook-file-open-fifo
EOF
chmod +x /tmp/bin/xdg-open
printf 'ca_certificate = %s\n' "$SSL_CERT_FILE" >~/.wgetrc
ln -s "$(realpath "$(pwd)")" /tmp/project-root
export PATH=/tmp/bin:"$PATH"
export BROWSER=/tmp/bin/xdg-open
export PYTHONPATH=/tmp/project-root"${PYTHONPATH+:PYTHONPATH}"
exec jupyter-notebook