File: deb2apptainer.sh

package info (click to toggle)
devscripts 2.25.15
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 8,528 kB
  • sloc: perl: 26,530; sh: 11,698; python: 4,428; makefile: 363
file content (382 lines) | stat: -rwxr-xr-x 10,917 bytes parent folder | download | duplicates (3)
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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
#! /bin/bash

# Purpose: install Debian packages in a Singularity/Apptainer image
#
# Usage: deb2apptainer [options] packages
#   -B        do NOT build the image (default is to build)
#   -c CMD    command to run in the container (default to '/bin/bash')
#   -f FROM   indicate which distribution is to be used (default to debian:stable)
#   -h        show this help
#   -n NAME   name of the image (default to package list)
#   -o DIR    use given directory for the build (default in /tmp)
#   -p PRE    execute the given script during the container build (before packages install)
#   -s POST   execute the given script during the container build (after packages install)
#   -v        show package version
# The package list can be any Debian package, as well as local .deb
#
# Example: 'deb2apptainer -o /tmp/xeyes x11-apps' then '/tmp/xeyes/start xeyes'

# (c) E. Farhi - Synchrotron SOLEIL - GPL3

# requires:
#   bash 
#   apptainer.deb from https://apptainer.org/docs/admin/main/installation.html#install-debian-packages

# info:   apptainer inspect    image.sif
# header: apptainer sif header image.sif
# data:   apptainer sif list   image.sif

set -e

# default settings -------------------------------------------------------------
FROM=debian:stable
BUILD=1
NAME=
SETNAME=0
CMD="/bin/bash"
WORK_DIR=
SCRIPT=
PRE=
VERSION=1.0.8

# handle input arguments -------------------------------------------------------
while getopts vhBf:n:c:o:d:p:s: flag
do
    case "${flag}" in
        h) # display help
        echo "Usage: $0 [-hB][-c CMD][-f FROM][-n NAME][-o DIR][-s SCRIPT] packages..."
        echo "  Build a Singularity/Apptainer image with given Debian packages."
        echo "  Options:"
        echo "  -B        do NOT build the image (default is to build)"
        echo "  -c EXEC   command to run in the container (default to $CMD)"
        echo "  -f FROM   indicate which distribution is to be used (default to $FROM)"
        echo "  -h        show this help"
        echo "  -n NAME   name of the image (default to package list)"
        echo "  -o DIR    use given directory for the build (default is in /tmp)"
        echo "  -p PRE    execute script PRE before packages install"
        echo "  -s POST   execute script POST after packages install"
        echo "  -v        show package version"
        echo "  The package list can be any Debian package, as well as local .deb"
        echo " "
        echo "Example: '$0 -o /tmp/xeyes x11-apps' then '/tmp/xeyes/start xeyes'"
        exit 0; 
        ;;
        B) # do not build the image
        BUILD=0
        ;;
        f) # set FROM image
        FROM=${OPTARG}
        ;;
        n) # set image name
        NAME=${OPTARG}
        ;;
        c) # command to execute
        CMD=${OPTARG}
        ;;
        o|d) # output directory
        WORK_DIR=${OPTARG}
        ;;
        p) # PRE SCRIPT
        PRE=${OPTARG}
        ;;
        s) # SCRIPT (POST)
        SCRIPT=${OPTARG}
        ;;
        v) # VERSION
        echo "$0 version $VERSION"
        exit 0;
        ;;
        *)
        echo "ERROR: Invalid option. Use -h for help."
        exit 1;
        ;;
    esac
done
shift $((OPTIND-1))

# check that apptainer is installed
if ! command -v apptainer > /dev/null
then
    echo "ERROR: apptainer could not be found. Install it from"
    echo "       https://apptainer.org/docs/admin/main/installation.html#install-debian-packages"
    exit 1
fi

# set name when not set
if [ "x$NAME" = "x" ]; then
  SETNAME=1
fi

# create a temporary directory to work in --------------------------------------
if [ "x$WORK_DIR" = "x" ]; then
  N=`basename $0`
  WORK_DIR=`mktemp -p /tmp -d $N-XXXXXXXX`
else
  mkdir -p $WORK_DIR || echo "ERROR: Invalid directory $WORK_DIR"
fi
PW=$PWD


# search for executable commands and launchers in the packages -----------------
DEB=
# get a local copy of packages and find bin and desktop files
mkdir -p $WORK_DIR/apt      || exit # hold deb pkg copies for analysis

echo "$0: creating image $NAME in $WORK_DIR"
echo "Getting Debian packages..."
for i in $@; do
  echo "  $i"
  if [ -f "$i" ]; then
    cp $i $WORK_DIR/apt/
    n=`basename $i`
    DEB="$DEB /opt/install/apt/$n"
  else
    DEB="$DEB $i"
    cd $WORK_DIR/apt
    apt download $i
    cd $PW
  fi
done

echo " "                                  >> $WORK_DIR/README
echo "Created with $0"                    >> $WORK_DIR/README
echo "$ARGS"                              >> $WORK_DIR/README
echo " "                                  >> $WORK_DIR/file_list.txt
for i in $WORK_DIR/apt/*.deb; do
  echo "Analyzing $i"
  
  N=$(dpkg-deb -f $i Package)  || continue
  # set the container name if needed
  if [ "x$SETNAME" = "x2" ]; then
    NAME="$NAME-$N"
  fi
  if [ "x$SETNAME" = "x1" ]; then
    SETNAME=2
    NAME=$N
  fi
  echo "Package $N ------------------------------------------" >> $WORK_DIR/README
  dpkg-deb -I $i                                               >> $WORK_DIR/README
  
  F=`dpkg -c $i` 
  echo "$F" >> $WORK_DIR/file_list.txt
  echo " "  >> $WORK_DIR/README
  
done

# prepare the Singularity definition file --------------------------------------
FILE="$WORK_DIR/$NAME.def"
DATE=`date`

# get a random UUID for /etc/machine-id
# see:
# - https://github.com/denisbrodbeck/machineid/issues/5
# - https://github.com/apptainer/singularity/issues/3609
if command -v uuidgen > /dev/null
then
  UUID=`uuidgen | sed "s/-//g"`
  UUID="echo $UUID > /etc/machine-id"
else
  if [ -f "/etc/machine-id" ]; then
    UUID=`cat /etc/machine-id`
    UUID="echo $UUID > /etc/machine-id"
  else
    UUID=0de1bbc1982243198b320e756d12224b
  fi
fi

# the base command to start the containers from image
cmd="apptainer run $NAME.sif "
if [ "x$CMD" = "x/bin/sh" -o "x$CMD" = "x/bin/bash" ]; then
  cmd_arg="-c"
else
  cmd_arg=""
fi

if [ -f "$PRE" ]; then
  cp $PRE $WORK_DIR/
  N=`basename $PRE`
  PRE="chmod a+x /opt/install/$N && sh -c \"/opt/install/$N\""
  PRE_FILE="$N /opt/install/"
else
  PRE_FILE=
fi

if [ -f "$SCRIPT" ]; then
  cp $SCRIPT $WORK_DIR/
  N=`basename $SCRIPT`
  SCRIPT="chmod a+x /opt/install/$N && sh -c \"/opt/install/$N\""
  SCRIPT_FILE="$N /opt/install/"
else
  SCRIPT_FILE=
fi

echo "Creating Singularity definition file $NAME into $FILE"
dd status=none of=${FILE} << EOF
# created by $0 on $DATE
#
# Singularity/Apptainer image $NAME
#
# build: apptainer build $NAME.sif
# run:   apptainer run   $NAME.sif

Bootstrap: docker
From: $FROM

%files
    apt/  /opt/install/
    README /opt/install/
    file_list.txt /opt/install/
    $NAME.def /opt/install/
    $PRE_FILE
    $SCRIPT_FILE

%post
    $PRE
    apt-get update -y
    apt-get install -y --no-install-recommends bash $DEB
    $UUID
    $SCRIPT
    rm -rf /opt/install/apt
    useradd -ms /bin/bash user
    
%environment
    export LC_ALL=C
    export PATH=/usr/games:$PATH

%runscript
    echo "Starting container $NAME, built on $DATE from $FROM"
    cat /opt/install/README || echo " "
    if [ \$# -ge 1 ]; then $CMD $cmd_arg \$@;  else $CMD; fi
    
%help
    This is a $NAME Singularity/Apptainer with packages $@.
    The default start-up command is $CMD.
    Installation files and README are in /opt/install
    
%labels
    Name $NAME
    System $FROM
    Date $DATE
    Creator $0
    Command $CMD
EOF

# build image ------------------------------------------------------------------
FILE=$WORK_DIR/build
dd status=none of=${FILE} << EOF
#!/bin/bash
# created by $0 on $DATE
# $ARGS
#
# build image $NAME with
#
# Usage: build

apptainer build $NAME.sif $NAME.def

# handle of Desktop launchers
mkdir -p launchers/
mkdir -p icons/

# get .desktop files ----------------------------------------------------------
D=\$(grep '\.desktop' file_list.txt) || echo "WARNING: No desktop file found."
# get the desktop files
D=\$(echo "\$D" | awk '{ print \$6 }')

# we need to copy them back, as well as their icons, and change the Exec lines
for i in \$D; do
  if [ \${i:0:1} == "." ] ; then
    i=\$(echo "\$i" | cut -c 2-)
  fi
  n=\`basename \$i\`
  apptainer exec $NAME.sif cat \$i >> launchers/\$n || echo "WARNING: Failed to get desktop launcher \$i"
done

# get icon files --------------------------------------------------------------
D=\$(grep 'icon' file_list.txt) || echo "WARNING: No icon file found."
# get the icon files
D=\$(echo "\$D" | awk '{ print \$6 }')

# we need to copy the icon files back
for i in \$D; do
  if [ \${i:0:1} == "." ] ; then
    i=\$(echo "\$i" | cut -c 2-)
  fi
  n=\`basename \$i\`
  apptainer exec $NAME.sif cp \$i /tmp/$n  &> /dev/null || n= 
  if [ -f "/tmp/\$n" ]; then
    mv /tmp/\$n icons/\$n
  fi
done

# adapt the Desktop launchers to insert 'run', set Icons=
for i in launchers/*; do
  if [ ! -f "\$i" ]; then continue; fi
  I=\$(grep 'Icon=' \$i | cut -d = -f 2) || I=
  if [ ! -z "\$I" ]; then
    n=\`basename \$I\`
    if [ ! -f "icons/\$n" ]; then
      # get closest file that match Icon name when initial name is not present as a file
      n1=( $icons/\$n* ) || n1=
      if [ ! -z "\$n1" ]; then
        n=\`basename \${n1[0]}\`
      fi
    fi
    sed -i "s|Icon=.*|Icon=icons/\$n|g" \$i            || echo " "
  fi
  sed -i 's|Exec=|&$cmd $cmd_arg |g' \$i        || echo " "
  sed -i 's|Terminal=false|Terminal=true|g' \$i || echo " "
  chmod a+x \$i                                 || echo " "
done

# create a Terminal launcher
echo "[Desktop Entry]"       > launchers/$NAME-terminal.desktop
echo "Type=Application"     >> launchers/$NAME-terminal.desktop
echo "Name=$NAME Terminal"  >> launchers/$NAME-terminal.desktop
echo "Terminal=true"        >> launchers/$NAME-terminal.desktop
echo "Exec=$cmd"            >> launchers/$NAME-terminal.desktop
chmod a+x                      launchers/$NAME-terminal.desktop

EOF

chmod a+x $WORK_DIR/build

if [ "x$BUILD" = "x1" ]; then
  # build the image
  (cd $WORK_DIR && ./build)
  chmod a+x $WORK_DIR/$NAME.sif || exit
else
  echo "INFO: To build this image, use: cd $WORK_DIR; ./build"
  echo " "
  cat $WORK_DIR/build
fi

# ------------------------------------------------------------------------------
# get executables and Desktop launchers (from the container)
echo "------------------------------------------------------" >> $WORK_DIR/README
B=$(grep '\.desktop' $WORK_DIR/file_list.txt) || echo " "
echo "$B"                                                     >> $WORK_DIR/README

FILE=$WORK_DIR/start
dd status=none of=${FILE} << EOF
#!/bin/bash
# created by $0 on $DATE
# $ARGS
#
# start a container from image $NAME
#
# Usage: start [CMD]
#   default CMD is $CMD

$cmd \$@
EOF
chmod a+x $WORK_DIR/start

# display final message
echo "--------------------------------------------"
echo "The image $NAME has been prepared in $WORK_DIR"
echo "  Desktop launchers are available in $WORK_DIR/launchers"
echo "To start $NAME, use any of: "
echo "  cd $WORK_DIR; ./$NAME.sif [cmd]"
echo "  cd $WORK_DIR; ./start     [cmd]"
echo " "