Browse Source

refactoring for more flexible config

master
Ralph Rönnquist 6 months ago
parent
commit
7f766756c6
  1. 166
      functions
  2. 77
      overlay-boot
  3. 189
      overlay-boot.8.adoc
  4. 34
      overlay-init
  5. 13
      overlay-postmount
  6. 35
      overlay-premount
  7. 42
      overlay-stop

166
functions

@ -1,18 +1,69 @@
# This file implements common functions for all boot methods
# This file implements common functions for all boot scripts
# Rerun with sudo if needed
[ $(id -u) = 0 ] || exec sudo $0 $@
# Function to write a message and exit with error code
die() {
echo "$*" >&2
exit 1
}
# Function to setup subhost name and log file
subhost_name() {
CONFIG="$1"
[ -r "$CONFIG" ] || die "Cannot use $CONFIG"
config NAME "$(basename $CONFIG .conf)"
config LOG /tmp/oly-$NAME.log
}
# Function to set up all subhost configuration
subhost_config() {
config BASE
BASE="$(cd $(dirname $CONFIG); realpath $BASE)"
[ -z "$BASE" ] && die "BASE is unset; bogus $CONFIG ?"
[ -d "$BASE" ] || die "$BASE is not a directory; bogus $CONFIG ?"
cd "$BASE" || die "$BASE is inaccessible"
config CABLES ""
config LIVE "$BASE/live"
config UPPER "$BASE/root"
config WORK "$BASE/work"
config LOWER "/"
config START "!networking ssh"
config PREMOUNT "$PROGRAMDIR/overlay-premount"
config POSTMOUNT "$PROGRAMDIR/overlay-postmount"
config INIT "$PROGRAMDIR/overlay-init"
config RAM_SIZE 50M
}
# function to reverse the $* words
reverse() {
local OUT=""
for w in $* ; do OUT="$w $OUT" ; done
echo "${OUT% }"
}
# grab and set a configuration variable
# $1 = variable, [ $2 = default .. error otherwise ]
config() {
eval $1="'$(sed "/^$1=.*/{s|^$1=||;b};d" $CONFIG)'"
[ -z "$(eval echo "\$$1")" ] || return 0
[ $# -lt 2 ] && die "Missing $1=... in $CONFIG"
eval $1="'$2'"
eval echo "$1=\$$1"
local V W
read V <<EOF
$(sed "/^$1=.*/{s|^$1=||;s|^\\s*||;s|\\s*\$||;b};d" $CONFIG)
EOF
if [ -z "$V" ] ; then
[ $# -lt 2 ] && die "Missing $1=... in $CONFIG"
V="$2" # use the given default
elif [ -z "${V##!*}" ] ; then
read W <<EOF
$(${V#!})
EOF
[ -z "$W" ] && die "bad $1 config: $V"
V="$W"
fi
eval $1="'$V'"
eval echo "$1=$V" >&2
}
# Install a default $1/etc/network/interfaces on the subhost root $1
@ -57,67 +108,33 @@ setup_veth_cables() {
done
}
# (name live system root work)
# Set up an overlay fmr $name on $live, with a new tmpfs on its /run,
# Set up an overlay for $name on $live, with a new tmpfs on its /run,
# and "install" a "reaper" as the upcoming pid 1
setup_overlay() {
local LIVE="$2" LOWER="$3" UPPER="$4" ROOT
local NAME="$1" LIVE="$2" LOWER="$3" UPPER="$4" WORK="$5"
if grep -q "$1 $2" /proc/mounts ; then
die "$1 is already mounted"
fi
echo setup_overlay "$NAME" "$LIVE" "$LOWER" "$UPPER" "$WORK"
if [ -f "${UPPER%% *}" ] ; then
if [ -x "${UPPER%% *}" ] ; then
echo "${UPPER%% *} appears to be executable" >&2
# Giving a program/script as UPPER= asks for running this
# first, to make a root filesystem available. The script takes
# ACTION "setup" and "teardown", and on "setup" it must tell
# where the ROOT is set up.
ROOT="$(env ACTION=setup $UPPER)"
if [ ! -d "$ROOT" ] ; then
# setup failed
die "root setup failed: $UPPER"
fi
UPPER="$ROOT"
## Now falling down to "normal overlay" setup
else
die "${UPPER%% *} (root setup program/script) is not executable"
fi
if grep -qE "^[^ ]+ $LIVE " /proc/mounts ; then
die "$LIVE already has a mount"
fi
# LIVE is the same as LOWER then skip the overlay; just assume
# a proper chroot system exists at LIVE.
if [ "$LIVE" != "$LOWER" ] ; then
[ -d "$UPPER" ] || die "UPPER=$UPPER is not a directory"
[ -d "$LOWER" ] || die "LOWER=LOWPER is not a directory"
[ -d "$LIVE" ] || die "LOWER=LOWPER is not a directory"
[ -x "${PREMOUNT%% *}" ] || die "PREMOUNT=${PREMOUNT%% *} not executable"
[ -f "${PREMOUNT%% *}" ] || die "PREMOUNT='$PREMOUNT' is not a command"
[ -x "${POSTMOUNT%% *}" ] || \
die "POSTMOUNT=${POSTMOUNT%% *} not executable"
[ -f "${POSTMOUNT%% *}" ] || \
die "POSTMOUNT='$POSTMOUNT' is not a command"
# UPPER is the same as LOWER then skip the overlay mount
if [ "$UPPER" != "$LOWER" ] ; then
# sanity check
[ -d "$WORK" ] || die "WORK=$WORK is not a directory"
[ -d "$UPPER" ] || die "UPPER=$UPPER is not a directory"
[ -d "$LOWER" ] || die "LOWER=LOWPER is not a directory"
[ -d "$LIVE" ] || die "LOWER=LOWPER is not a directory"
# setup $UPPER/dev
mkdir -p "$UPPER/dev"
mount -t tmpfs -osize=50M tmpfs "$UPPER/dev"
mknod -m 622 "$UPPER/dev/console" c 5 1
mknod -m 666 "$UPPER/dev/null" c 1 3
mknod -m 666 "$UPPER/dev/zero" c 1 5
mknod -m 666 "$UPPER/dev/ptmx" c 5 2
mknod -m 666 "$UPPER/dev/tty" c 5 0
mknod -m 444 "$UPPER/dev/random" c 1 8
mknod -m 444 "$UPPER/dev/urandom" c 1 9
chown root:tty "$UPPER/dev/console"
chown root:tty "$UPPER/dev/ptmx"
chown root:tty "$UPPER/dev/tty"
ln -sTf /proc/self/fd "$UPPER/dev/fd"
ln -sTf /proc/self/fd/0 "$UPPER/dev/stdin"
ln -sTf /proc/self/fd/1 "$UPPER/dev/stdout"
ln -sTf /proc/self/fd/2 "$UPPER/dev/stderr"
ln -sTf /proc/kcore "$UPPER/dev/core"
mkdir "$UPPER/dev/shm"
mkdir "$UPPER/dev/pts"
chmod 1777 "$UPPER/dev/shm"
# all good so far ; now avoid using the host's networking setup
setup_networking "$UPPER"
env CONFIG="$CONFIG" $PREMOUNT "$UPPER"
OLY="-olowerdir=$3,upperdir=$UPPER,workdir=$5"
if ! mount -t overlay "$OLY" $1 $2 ; then
@ -125,36 +142,25 @@ setup_overlay() {
umount "$UPPER/run"
die "Cannot set up the overlay mount $2"
fi
elif [ "$LIVE" != "$UPPER" ] ; then
# With UPPER = LOWER we rather make a bind mount to LIVE
env CONFIG="$CONFIG" $PREMOUNT "$UPPER"
mount --bind $UPPER $LOWER
fi
echo "Installing $OVERLAYDIR/reaper to $LIVE/.reaper"
cp -p $OVERLAYDIR/reaper $LIVE/.reaper
}
start_services() {
for S in "$@" ; do
service $S start
done
env CONFIG="$CONFIG" $POSTMOUNT "LIVE" "$UPPER"
}
# find the upperdir option for an overlay mount line
getupper() {
sed 's/.*upperdir=\([^,]*\).*/\1/'
}
# Check if $1 is "live" and echo the
# unshare and reaper process pids
# Find the "unshare" process for $1 and echo the its pid and the pids
# of its child processes.
is_live() {
local NAME=$1
local USPID="$(pgrep -f "unshare.* $NAME ")"
[ -z "$USPID" ] && return 1
echo $USPID $(pgrep -f ".reaper $NAME")
echo "$USPID $(ps -hopid --ppid=$USPID)"
}
# Find all overlay-boot processes and list their config files
list_running() {
for C in $(pgrep -a overlay-boot | awk '{print $4}') ; do
eval NAME="$(sed "/^NAME=.*/{s|^NAME=||;b};d" $C)"
[ -z "$NAME" ] && NAME=$(basename $C .conf)
echo $NAME
done
pgrep -a overlay-boot | awk '{print $4}'
}

77
overlay-boot

@ -1,88 +1,35 @@
#!/bin/sh
#
# This boot method runs a service subhost with a root filesystem that
# is an overlay of the subhost's root and an OS root. The service
# subhost is defined by a configuration file named on teh command line
# This boot scripts runs a service subhost as defined by the
# configuration file named on the command line.
# See "man overlay-boot" for details.
OVERLAYDIR="$(dirname $(realpath $0))"
PROGRAMDIR="$(dirname $(realpath $0))"
. $PROGRAMDIR/functions
[ $(id -u) = 0 ] || exec sudo $0 $@
. $OVERLAYDIR/functions $*
CONFIG="$1"
[ -r "$CONFIG" ] || die "Missing configuration $CONFIG"
config NAME $(basename $1 .${1##*.})
config LOG /tmp/oly-$NAME.log
subhost_name $1
if [ -z "$UNSHARED" ] ; then
# Pre-unsharing:
#
# Create the network namespace for the subhost, then trigger
# detached re-run with unshared mount namespace
[ -r /run/netns/$NAME ] || {
if [ ! -r /run/netns/$NAME ] ; then
ip netns add $NAME
ip netns exec $NAME ip link set lo up
}
ip netns exec $NAME ip link set lo up || exit 1
fi
exec env UNSHARED=yes unshare -m $0 $@ > $LOG 2>&1 &
echo "Logging to $LOG" >&2
exit 0
fi
config BASE
BASE="$(cd $(dirname $CONFIG); realpath $BASE)"
[ -z "$BASE" ] && die "BASE is unset; bogus $CONFIG ?"
[ -d "$BASE" ] || die "$BASE is not a directory; bogus $CONFIG ?"
cd "$BASE" || die "$BASE is inaccessible"
config LIVE "$BASE/live"
config UPPER "$BASE/root"
config WORK "$BASE/work"
config LOWER "/"
config CABLES ""
config START "networking ssh"
config SUBSHELL /bin/sh
# Setup virtual cabling
subhost_config
setup_veth_cables $NAME $CABLES
# Set up the mount for this subhost, including a new tmpfs on its /run
# and a default $UPPER/etc/network/interfaces if needed
echo setup_overlay "$NAME" "$LIVE" "$LOWER" "$UPPER" "$WORK"
setup_overlay "$NAME" "$LIVE" "$LOWER" "$UPPER" "$WORK"
exithandler() {
ip netns del $NAME
[ "$LOWER" != "$LIVE" ] && umount -R "$LIVE"
[ -f "${UPPER%% *}" ] && [ -x "${UPPER%% *}" ] && \
env ACTION=teardown $UPPER
[ "$UPPER" != "$LIVE" ] && umount -R "$LIVE"
}
trap "exithandler" 0
CMD="unshare -fp --mount-proc -i -u ip netns exec $NAME chroot $LIVE /bin/sh"
echo "$CMD"
config RAM_SIZE 50M
cat <<EOF | $CMD
set -x
mount -t proc proc /proc
mount -t devpts devpts /dev/pts
mount -t sysfs sysfs /sys
if [ "$RAM_SIZE" != "none" ] && ! grep -q '/run tmpfs' /proc/mounts ; then
mount -t tmpfs -osize=$RAM_SIZE,mode=755 tmpfs /run
fi
for srv in $START ; do service \$srv start ; done
dummy_service() {
[ \$# -gt 3 ] && return 0
echo "Starting dummy service" >&2
set +x
[ -p /run/dummy_service ] || mkfifo /run/dummy_service
( printf dummy_service > /proc/self/comm ; read X < /run/dummy_service ) &
set -x
}
dummy_service /proc/*/comm
exec /.reaper $NAME
EOF
env CONFIG="$CONFIG" $INIT | $CMD
echo "EXITED $CMD"

189
overlay-boot.8.adoc

@ -0,0 +1,189 @@
overlay-boot(8)
===============
:doctype: manpage
:revdate: {sys:date "+%Y-%m-%d %H:%M:%S"}
:COLON: :
:EQUALS: =
NAME
----
overlay-boot - Start a subhost with overlay root filesystem.
SYNOPSIS
--------
*overlay-boot* _conf_
DESCRIPTION
-----------
*overlay-boot* is the main script on a small collection of
administration scripts for containerizing services with minimal ado.
The script starts a "subhost" with a dedicated network namespace, and
the mount and pid namespaces separated from the main host by means of
+unshare+. A subhost root file system may in particular be set up as
an overlay of the main host filesystem to keep the specifics of a
service distinctly separate from the main host while sharing files
wherever sensible.
A subhost is started by identifyinf its configuration file on the
command line for *overlay-boot*. The configuration file is a plain
text file with a small collection of "variables" that tell how the
subhost is set up. When all is good, *overlay-boot* spawns a
subprocess that invokes a command shell within an chroot into
"unshared" subhost root filesystem, all similar to the bootup of any
odd computer.
The subhost execution environment may be "entered" to perform
adminstrative tasks with *overlay-go*, and it is later stopped with
*overlay-stop*.
OPTIONS
-------
An overlay-boot subhost is defined in the configuration file, which is
a plain text file with a number of variable assignments. Each
assignment is written with the varable name flush left and immediately
followed by an equal sign, The rest of that line (ignoring leading and
trailing spaces) is its value, or if that value startes with an
exclamation mark, then the line is a command to run so as to generate
the value. See examples below.
*NAME*::
This variable declares a short name for the subhost, and should be no
more than 12 printable ascii characters. The base name of the
configuration file is used by default. I.e., a configuration file
named +foo.conf+ by default names its subhost +foo+ unless there is a
+NAME+ variable says differently.
*BASE*::
This variable declares a pathname for a directory that is considered
to be a "base" for the subhost setup. This is the only required
variable.
*CABLES*::
This variable declares the subhost networking in terms of its virtual
cables. The value is a space separated list of "virtual cable
specifiers", each consisting of an equal sign optionally with a bridge
name to the left and optinally a MAC address to the right. See the
section on Networking below for more details.
INIT::
This variable is a command line to run, with envirnment variable
CONFIG set, for producing the initial commands to the running subhost,
similar to the initrd phase of a computer bootup. The default value
for this variable is +overlay-init+.
*LIVE*::
This variable nominates the mount point for the running subhost's root
file system. It defaults to +$BASE/live+ The nominated directory must
exist, and depending on the directory pathnames in the +UPPER+ and
+LOWER+ variables, the subhost root filesystem is either of a
pre-mounted directory, bind mounted or overlay mounted directory. See
the details of this with the UPPER variable below.
*LOG*::
This variable nominates the logfile to use by +overlay-boot+ when
running the subhost. The default is +/tmp/overlay-$NAME.log+.
*LOWER*::
This variable nominates the "lower" filesystem of an overlay mount.
This will be accessed read-only, an it is intended to be the operating
system root file system. The default is +/+, i.e. the main host root
filesystem. When overlay is not desired, then LOWER should be the smae
as UPPER.
*POSTMOUNT*::
This variable is a command line to run, with envirnment variable
CONFIG set, just after the setup of the subhost root filesystem and
before the services are started. The default for this variable is
+overlay-postmount+
*PREMOUNT*::
This variable is a command line to run, with envirnment variable
CONFIG set, just before the setup of the subhost root filesystem and
before the services are started. The default for this variable is
+overlay-premount+
*UPPER*::
This variable nominates the "upper" filesystem for an overlay mount.
This will be accessed read-write and it constitutes the "private"
files of the subhost.
If UPPER is different from LOWER, then the root file system is set up
as an overlay mount. In that case WORK (below) must be defined as a
directory outside of but on the same mount as UPPER.
If UPPER is the same as LOWER, then the subhost root filesystem is not
setup as an overla. Rather it is set up as a bind mount, if UPPER is
different from LIVE, or just as a pre-mounted LIVE filesystem.
*WORK*::
This variable nominates the "work" directory for an overlay mount. It
has to be a writable directory on the same mount device as the UPPER
directory.
*START*::
This variable tells which services should be started on the
overlay-boot startup. The default value is +networking ssh+.
Note that if no services are started, then +overlay-init+ starts a
+dummy_service+ so as to keep +/.reaper+ from running out of child
processes, as it otherwise will exit and thereby terminate
+overlay-boot+.
*RAM_SIZE*::
This variable may be used to configure the +/run+ directory which by
defult gets mounted on an overlay mount as a +tmpfs+ of 50M. Use
+none+ to avoid that mount, and otherwise the desired +tmpfs+ size.
Networking
----------
*overlay-boot* sets up a nework namespace named by the subhost NAME,
and uses the CABLES variable to set up +veth+ virtual cables. The host
end of such cables are named by NAME followed by a number from 0 and
up while the subhost end are named by +eth+ followed by the same
number.
As mentioned above, CABLES consists of a space separated list of cable
specifiers, each consisting of a bridge interface name and a with an
equal sign ("=") between them. The equal sign is required while either
or both of the bridge and MAC address may be left empty.
The bridge interface name, if given, will be given control of the host
end cable interface. A cable specification with empty bridge name part
results in that the host end is brought up at link level and then
+ifup $IF+ is attempted.
The MAC address, if given, is used for the subhost end cable
interface. A cable specification with empty MAC address part results
in that the interface get its MAC address from the kernel, possibly a
different one upon each start.
EXAMPLES
--------
./opt/subhost/mta/mta.conf
****
BASE=.
START= rsyslog newtorking ssh postfix
****
The above example assumes a directory +/opt/subhost/mta+ that contains
the configuration file +mta.conf+ and directories +root+, +work+ and
+live+. *overlay-boot* will set up an overlay mount on +live+ with
+root+ as UPPER, +work+ as WORK and +/+ as LOWER, i.e. an overlay of
the main host filesystem.

34
overlay-init

@ -0,0 +1,34 @@
#!/bin/sh
#
# This script performs default actions. It is invoked with CONFIG set
# for the subhost.
OVERLAYDIR="$(dirname $(realpath $0))"
. $OVERLAYDIR/functions
subhost_name "$CONFIG"
subhost_config
# Print the default init script
cat <<EOF
set -x
mount -t proc proc /proc
mount -t devpts devpts /dev/pts
mount -t sysfs sysfs /sys
if [ "$RAM_SIZE" != "none" ] && ! grep -q '/run tmpfs' /proc/mounts ; then
mount -t tmpfs -osize=$RAM_SIZE,mode=755 tmpfs /run
fi
for srv in $START ; do service \$srv start ; done
dummy_service() {
[ \$# -gt 3 ] && return 0
echo "Starting dummy service" >&2
set +x
[ -p /run/dummy_service ] || mkfifo /run/dummy_service
( printf dummy_service > /proc/self/comm ; read X < /run/dummy_service ) &
set -x
}
dummy_service /proc/*/comm
exec /.reaper $NAME
EOF

13
overlay-postmount

@ -0,0 +1,13 @@
#!/bin/sh
#
# This script performs default actions. It is invoked with CONFIG set
# for the subhost.
OVERLAYDIR="$(dirname $(realpath $0))"
. $OVERLAYDIR/functions
subhost_name "$CONFIG"
subhost_config
echo "Installing $OVERLAYDIR/reaper to $LIVE/.reaper"
cp -p $OVERLAYDIR/reaper $LIVE/.reaper

35
overlay-premount

@ -0,0 +1,35 @@
#!/bin/sh
#
# This script performs default actions. It is invoked with CONFIG set
# for the subhost.
OVERLAYDIR="$(dirname $(realpath $0))"
. $OVERLAYDIR/functions
subhost_name "$CONFIG"
subhost_config
# setup $UPPER/dev
mkdir -p "$UPPER/dev"
mount -t tmpfs -osize=50M tmpfs "$UPPER/dev"
mknod -m 622 "$UPPER/dev/console" c 5 1
mknod -m 666 "$UPPER/dev/null" c 1 3
mknod -m 666 "$UPPER/dev/zero" c 1 5
mknod -m 666 "$UPPER/dev/ptmx" c 5 2
mknod -m 666 "$UPPER/dev/tty" c 5 0
mknod -m 444 "$UPPER/dev/random" c 1 8
mknod -m 444 "$UPPER/dev/urandom" c 1 9
chown root:tty "$UPPER/dev/console"
chown root:tty "$UPPER/dev/ptmx"
chown root:tty "$UPPER/dev/tty"
ln -sTf /proc/self/fd "$UPPER/dev/fd"
ln -sTf /proc/self/fd/0 "$UPPER/dev/stdin"
ln -sTf /proc/self/fd/1 "$UPPER/dev/stdout"
ln -sTf /proc/self/fd/2 "$UPPER/dev/stderr"
ln -sTf /proc/kcore "$UPPER/dev/core"
mkdir "$UPPER/dev/shm"
mkdir "$UPPER/dev/pts"
chmod 1777 "$UPPER/dev/shm"
# all good so far ; now avoid using the host's networking setup
setup_networking "$UPPER"

42
overlay-stop

@ -3,22 +3,20 @@
# Script to stop the nominated overlay subhost
OVERLAYDIR="$(dirname $(realpath $0))"
[ $(id -u) = 0 ] || exec sudo $0 $@
. $OVERLAYDIR/functions $*
CONFIG="$1"
[ -r "$CONFIG" ] || die "Missing configuration $CONFIG"
config NAME $(basename $1 .${1##*.})
config START "ssh networking"
config LIVE
subhost_name "$1"
subhost_config
read USPID RSPID <<EOF
$(is_live $NAME)
EOF
[ -z "$USPID" ] && echo "$NAME is snot running" >&2 && exit 1
if [ -z "$USPID" ] ; then
[ -r /run/netns/$NAME ] && ip netns del $NAME
echo "$NAME is not running" >&2
exit 1
fi
if [ -z "$RSPID" ] ; then
cat <<EOF >&2
@ -28,16 +26,18 @@ EOF
exit 1
fi
# function to reverse the $* words
reverse() {
local OUT=""
for w in $* ; do OUT="$w $OUT" ; done
echo "${OUT% }"
}
START="$(reverse "$START")"
if nsenter -t $RSPID -p -m -i -u ip netns exec $NAME chroot $LIVE /bin/sh \
-c "for srv in $START ; do service \$srv stop ; done" ; then
CHILDPIDS="$(nsenter -t $RSPID -p -m ps -hopid --ppid 1)"
nsenter -t $RSPID -p -m kill $CHILDPIDS
fi
nsenter -t $RSPID -p -m -i -u \
ip netns exec $NAME chroot $(realpath $LIVE) \
/bin/sh -c "for srv in $START ; do service \$srv stop ; done"
for p in $RSPID $USPID ; do
for S in 15 1 2 9 ; do
ps -hocmd $p || break
kill -$S $p
done
done
[ -r /run/netns/$NAME ] && ip netns del $NAME
true

Loading…
Cancel
Save