blob: 9c98d3e170e8f7e3374cdcdc70b87454862c2462 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
  | 
#!/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
# Note: we pick openssl to have Guix set the SSL_CERT_FILE variable.
GUIX_PACKAGES="\
coreutils
grep
gzip
jupyter
lighttpd
mariadb
nss-certs
openssl
php
postgresql
python
python-bcrypt
python-ipython-sql
python-mysqlclient
python-psycopg2
sed
tar
unzip
wget"
# 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-10-16.
COMMIT_KNOWN_GOOD=daea83629d7f6ccbc1c9ef6c334f5ba90d8f4bab
GUIX=guix
if ! guix show $GUIX_PACKAGES >/dev/null 2>&1; then
    # We're running an older Guix that misses one of the required
    # packages (probably the python-ipython-sql needed for the %%sql
    # magic in notebook cells).  Let's have the older Guix compile its
    # newer friend.
    GUIX="guix time-machine --url=$CODEBERG_URL --commit=$COMMIT_KNOWN_GOOD --"
fi
$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 &
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'; 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
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
  |