1
0
Fork 0
devscripts/scripts/deb2apptainer.sh
Daniel Baumann b543f2e88d
Adding upstream version 2.25.15.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-21 11:04:07 +02:00

382 lines
11 KiB
Bash
Executable file

#! /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 " "