Skip to content

Instantly share code, notes, and snippets.

@bjoernsauer
Last active April 8, 2021 19:22
Show Gist options
  • Save bjoernsauer/2dfa200fd709ca18b04eca890911d85e to your computer and use it in GitHub Desktop.
Save bjoernsauer/2dfa200fd709ca18b04eca890911d85e to your computer and use it in GitHub Desktop.
Script to install GNU Guix Functional package management for GNU on PLCnext based PLC from Phoenix Contact. Minimal needed firmware is 2019.0
#!/bin/sh
# Script to install GNU Guix --- Functional package management for GNU
# on PLCnext based PLC from Phoenix Contact. (Minimal needed firmware 2019.0)
# The script is derived from the guix-install.sh script of GNU Guix
# which can be found at
# <https://git.savannah.gnu.org/cgit/guix.git/plain/etc/guix-install.sh>
#
# Copyright 2019 PHOENIX CONTACT GmbH & Co KG
# This script is licensed under the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or (at
# your option) any later version.
#
# You can find a the GNU General Public License at <http://www.gnu.org/licenses/>.
# We require Bash but for portability we'd rather not use /bin/bash or
# /usr/bin/env in the shebang, hence this hack.
if [ "x$BASH_VERSION" = "x" ]
then
exec bash "$0" "$@"
fi
set -e
[ "$UID" -eq 0 ] || { echo "This script must be run as root."; exit 1; }
REQUIRE=(
"dirname"
"readlink"
"curl"
"wget"
"grep"
"which"
"sed"
"sort"
"mktemp"
"rm"
"chmod"
"uname"
"groupadd"
"tail"
"tr"
"tar"
"dpkg"
)
PAS=$'[ \033[32;1mPASS\033[0m ] '
ERR=$'[ \033[31;1mFAIL\033[0m ] '
INF="[ INFO ] "
DEBUG=0
GNU_URL="https://ftp.gnu.org/gnu/guix/"
OPENPGP_SIGNING_KEY_ID="3CE464558A84FDC69DB40CFB090B11993D9AEBB5"
# This script needs to know where root's home directory is. However, we
# cannot simply use the HOME environment variable, since there is no guarantee
# that it points to root's home directory.
ROOT_HOME="$(echo ~root)"
# ------------------------------------------------------------------------------
#+UTILITIES
_err()
{ # All errors go to stderr.
printf "[%s]: %s\n" "$(date +%s.%3N)" "$1"
}
_msg()
{ # Default message to stdout.
printf "[%s]: %s\n" "$(date +%s.%3N)" "$1"
}
_debug()
{
if [ "${DEBUG}" = '1' ]; then
printf "[%s]: %s\n" "$(date +%s.%3N)" "$1"
fi
}
chk_require()
{ # Check that every required command is available.
declare -a cmds
declare -a warn
GPG_AVAIL=1
cmds=(${1})
_debug "--- [ $FUNCNAME ] ---"
for c in ${cmds[@]}; do
command -v "$c" &>/dev/null || warn+=("$c")
done
# Check if pgp is available
command -v "gpg" &>/dev/null || GPG_AVAIL=0
[ "${#warn}" -ne 0 ] &&
{ _err "${ERR}Missing commands: ${warn[*]}.";
return 1; }
_msg "${PAS}verification of required commands completed"
if [ $GPG_AVAIL -eq 1 ]; then
gpg --list-keys ${OPENPGP_SIGNING_KEY_ID} >/dev/null 2>&1 || (
_err "${ERR}Missing OpenPGP public key. Fetch it with this command:"
echo " gpg --keyserver pool.sks-keyservers.net --recv-keys ${OPENPGP_SIGNING_KEY_ID}"
exit 1
)
else
_msg "${INF}gpg is not availabel. Skipping OpenPGP verification.";
fi
}
chk_term()
{ # Check for ANSI terminal for color printing.
local ansi_term
if [ -t 2 ]; then
if [ "${TERM+set}" = 'set' ]; then
case "$TERM" in
xterm*|rxvt*|urxvt*|linux*|vt*|eterm*|screen*)
ansi_term=true
;;
*)
ansi_term=false
ERR="[ FAIL ] "
PAS="[ PASS ] "
;;
esac
fi
fi
}
chk_init_sys()
{ # Return init system type name.
local systemd_init=1
command -v "systemctl" &>/dev/null || systemd_init=0
if [[ $(/sbin/init --version 2>/dev/null) =~ upstart ]]; then
_msg "${INF}init system is: upstart"
INIT_SYS="upstart"
return 0
elif [ $systemd_init -eq 1 ]; then
_msg "${INF}init system is: systemd"
INIT_SYS="systemd"
return 0
elif [[ -f /etc/init.d/crond && ! -h /etc/init.d/crond ]]; then
_msg "${INF}init system is: sysv-init"
INIT_SYS="sysv-init"
return 0
else
INIT_SYS="NA"
_err "${ERR}Init system could not be detected."
fi
}
chk_sys_arch()
{ # Check for operating system and architecture type.
local os
local arch
os="$(uname -s)"
arch="$(uname -m)"
case "$arch" in
i386 | i486 | i686 | i786 | x86)
local arch=i686
;;
x86_64 | x86-64 | x64 | amd64)
local arch=x86_64
;;
aarch64)
local arch=aarch64
;;
armv7l)
local arch=armhf
;;
*)
_err "${ERR}Unsupported CPU type: ${arch}"
exit 1
esac
case "$os" in
Linux | linux)
local os=linux
;;
*)
_err "${ERR}Your operation system (${os}) is not supported."
exit 1
esac
ARCH="${arch}"
ARCH_OS="${arch}-${os}"
}
# ------------------------------------------------------------------------------
#+MAIN
guix_get_bin_list()
{ # Scan GNU archive and save list of binaries
local gnu_url="$1"
local -a bin_ver_ls
local latest_ver="1.0.0"
local default_ver
_debug "--- [ $FUNCNAME ] ---"
# Filter only version and architecture
bin_ver_ls=("$(curl -Ls "$gnu_url" \
| sed -n -e 's/.*guix-binary-\([0-9.]*\)\..*.tar.xz.*/\1/p' \
| sort -Vu)")
#latest_ver="$(echo "$bin_ver_ls" \
# | grep -oP "([0-9]{1,2}\.){2}[0-9]{1,2}" \
# | tail -n1)"
default_ver="guix-binary-${latest_ver}.${ARCH_OS}"
if [[ "${#bin_ver_ls}" -ne "0" ]]; then
_msg "${PAS}Release for your system: ${default_ver}"
else
_err "${ERR}Could not obtain list of Guix releases."
exit 1
fi
# Use default to download according to the list and local ARCH_OS.
BIN_VER="$default_ver"
}
guix_get_bin()
{ # Download and verify binary package.
local url="$1"
local bin_ver="$2"
local dl_path="$3"
_debug "--- [ $FUNCNAME ] ---"
_msg "${INF}Downloading Guix release archive"
curl -L -o "${dl_path}/${bin_ver}.tar.xz" "${url}/${bin_ver}.tar.xz"
if [ $GPG_AVAIL -eq 1 ]; then
curl -L -o "${$dl_path}/${bin_ver}.tar.xz.sig" "${url}/${bin_ver}.tar.xz.sig"
fi
if [[ "$?" -eq 0 ]]; then
_msg "${PAS}download completed."
else
_err "${ERR}could not download ${url}/${bin_ver}.tar.xz."
exit 1
fi
if [ $GPG_AVAIL -eq 1 ]; then
pushd $dl_path >/dev/null
gpg --verify "${bin_ver}.tar.xz.sig" >/dev/null 2>&1
if [[ "$?" -eq 0 ]]; then
_msg "${PAS}Signature is valid."
popd >/dev/null
else
_err "${ERR}could not verify the signature."
exit 1
fi
fi
}
sys_create_store()
{ # Unpack and install /gnu/store and /var/guix
local pkg="$1"
local tmp_path="$2"
local extract_path="$tmp_path"
_debug "--- [ $FUNCNAME ] ---"
# Extract it directly to the upper directory of the overlay filesystem,
# otherwise we could get some kind of the following error message:
# tar: ./gnu/store/bjdpc29ilwjix0snnprim9g6xrgw257h-profile/bin: Directory renamed before its status could be extracted
# tar: Exiting with failure status due to previous error
if [ -e "/media/rfs/rw/upperdir${tmp_path}" ]; then
extract_path="/media/rfs/rw/upperdir${tmp_path}"
fi
_msg "${INF}Extracting Guix release archive"
cd "$tmp_path"
tar --warning=no-timestamp \
--extract \
-C "$extract_path" \
--file "$pkg" &&
_msg "${PAS}unpacked archive"
if [[ -e "/var/guix" || -e "/gnu" ]]; then
_err "${ERR}A previous Guix installation was found. Refusing to overwrite."
exit 1
else
_msg "${INF}Installing /var/guix and /gnu..."
mv "${tmp_path}/var/guix" /var/
mv "${tmp_path}/gnu" /
fi
_msg "${INF}Linking the root user's profile"
mkdir -p "${ROOT_HOME}/.config/guix"
ln -sf /var/guix/profiles/per-user/root/current-guix \
"${ROOT_HOME}/.config/guix/current"
GUIX_PROFILE="${ROOT_HOME}/.config/guix/current"
source "${GUIX_PROFILE}/etc/profile"
_msg "${PAS}activated root profile at ${ROOT_HOME}/.config/guix/current"
}
sys_create_build_user()
{ # Create the group and user accounts for build users.
local group="guixbuild"
_debug "--- [ $FUNCNAME ] ---"
if grep -q "^${group}:" "/etc/group" ; then
_msg "${INF}group <${group}> exists"
else
groupadd --system $group
_msg "${PAS}group <${group}> created"
fi
for i in $(seq -w 1 10); do
if id "guixbuilder${i}" &>/dev/null; then
_msg "${INF}user <guixbuilder${i}> is already in the system, reset"
usermod -g guixbuild -G $group \
-d /var/empty -s "$(which nologin)" \
-c "Guix build user $i" \
"guixbuilder${i}";
else
useradd -g guixbuild -G $group \
-d /var/empty -s "$(which nologin)" \
-c "Guix build user $i" --system \
"guixbuilder${i}";
_msg "${PAS}user added <guixbuilder${i}>"
fi
done
}
sys_enable_guix_daemon()
{ # Run the daemon, and set it to automatically start on boot.
local info_path
local local_bin
local var_guix
local sysv_name
local sysv_path
_debug "--- [ $FUNCNAME ] ---"
info_path="/usr/local/share/info"
local_bin="/usr/local/bin"
var_guix="/var/guix/profiles/per-user/root/current-guix"
sysv_name="guix-daemon"
sysv_path="/etc/init.d/${sysv_name}"
case "$INIT_SYS" in
upstart)
{ initctl reload-configuration;
cp "${ROOT_HOME}/.config/guix/current/lib/upstart/system/guix-daemon.conf" \
/etc/init/ &&
start guix-daemon; } &&
_msg "${PAS}enabled Guix daemon via upstart"
;;
systemd)
{ cp "${ROOT_HOME}/.config/guix/current/lib/systemd/system/guix-daemon.service" \
/etc/systemd/system/;
chmod 664 /etc/systemd/system/guix-daemon.service;
systemctl daemon-reload &&
systemctl start guix-daemon &&
systemctl enable guix-daemon; } &&
_msg "${PAS}enabled Guix daemon via systemd"
;;
sysv-init)
if [ ! -e "$sysv_path" ]; then
write_sysv_init_script "$sysv_path"
update-rc.d $sysv_name defaults 99
"${sysv_path}" start
fi
_msg "${PAS}enabled Guix daemon via sysv-init"
;;
NA|*)
_msg "${ERR}unsupported init system; run the daemon manually:"
echo " ${ROOT_HOME}/.config/guix/current/bin/guix-daemon --build-users-group=guixbuild"
;;
esac
_msg "${INF}making the guix command available to other users"
[ -e "$local_bin" ] || mkdir -p "$local_bin"
ln -sf "${var_guix}/bin/guix" "$local_bin"
[ -e "$info_path" ] || mkdir -p "$info_path"
for i in ${var_guix}/share/info/*; do
ln -sf "$i" "$info_path"
done
}
sys_authorize_build_farms()
{ # authorize the public keys of the two build farms
while true; do
read -p "Permit downloading pre-built package binaries from the project's build farms? (yes/no) " yn
case $yn in
[Yy]*) guix archive --authorize < "${ROOT_HOME}/.config/guix/current/share/guix/hydra.gnu.org.pub" &&
_msg "${PAS}Authorized public key for hydra.gnu.org";
guix archive --authorize < "${ROOT_HOME}/.config/guix/current/share/guix/ci.guix.gnu.org.pub" &&
_msg "${PAS}Authorized public key for ci.guix.gnu.org";
break;;
[Nn]*) _msg "${INF}Skipped authorizing build farm public keys"
break;;
*) _msg "Please answer yes or no.";
esac
done
}
write_sysv_init_script()
{ # Write the sysv-init script
local script_path="$1"
[ -e "$script_path" ] && { return; }
touch $script_path
chmod 755 $script_path
cat <<"EOT" >> $script_path
#! /bin/sh
### BEGIN INIT INFO
# Provides: guix-daemon
# Required-Start: $local_fs $remote_fs $network
# Required-Stop: $local_fs $remote_fs $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 6
# Short-Description: Build daemon for GNU Guix
### END INIT INFO
USER=root
PATH=/var/guix/profiles/per-user/root/current-guix/bin:/usr/sbin:/sbin:/usr/bin:/bin
NAME=guix-daemon
DAEMON=/var/guix/profiles/per-user/root/current-guix/bin/guix-daemon
GUIX_LOCPATH=/var/guix/profiles/per-user/root/guix-profile/lib/locale
OPTIONS="--build-users-group=guixbuild --cores=2 --max-jobs=2"
LOG=/var/log/guix-daemon.log
PIDFILE=/var/run/guix-daemon.pid
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
. /etc/init.d/functions
start_daemon () {
start-stop-daemon --start --background --no-close \
--chuid $USER --name $NAME \
$START_STOP_OPTIONS \
--make-pidfile --pidfile $PIDFILE \
--exec "$DAEMON" -- $OPTIONS > $LOG 2>&1
echo "Logging to "$LOG
}
stop_daemon () {
start-stop-daemon --stop \
--user $USER \
--remove-pidfile --pidfile $PIDFILE \
--retry 5 --oknodo
}
case "$1" in
start)
echo "Starting daemon" "$NAME"
start_daemon
;;
stop)
echo "Stopping daemon" "$NAME"
stop_daemon
;;
restart)
$0 stop
sleep 5
$0 start
;;
status)
status "$DAEMON" "$NAME"
exit $?
;;
*)
echo "Usage: $0 {start|stop|status|restart}"
exit 1
;;
esac
exit 0
EOT
}
write_profile_script()
{ # Write profile script
local script_path="$1"
[ -e "$script_path" ] && { return; }
touch $script_path
chmod 755 $script_path
cat <<"EOT" >> $script_path
#!/bin/sh
if [ -e "$HOME/.config/guix/current" ]; then
export PATH="$HOME/.config/guix/current/bin:$PATH"
export INFOPATH="$HOME/.config/guix/current/share/info:$INFOPATH"
fi
if [ -e "$HOME/.guix-profile" ]; then
export GUIX_PROFILE="$HOME/.guix-profile"
. "$GUIX_PROFILE/etc/profile"
export GUIX_LOCPATH="$GUIX_PROFILE/lib/locale"
fi
EOT
}
get_xz_utils()
{ # Install the xz-utils
local dl_path="$1"
local xz_avail=1
local package_name="xz-utils_5.2.2-1.2+b1_${ARCH}.deb"
local url="http://ftp.de.debian.org/debian/pool/main/x/xz-utils/${package_name}"
_debug "--- [ $FUNCNAME ] ---"
# Check if xz is available
command -v "xz" &>/dev/null || xz_avail=0
[ $xz_avail -eq 1 ] && { return; }
_msg "${INF}Downloading ${package_name} debian package"
curl -L -o "${dl_path}/${package_name}" "${url}"
if [[ "$?" -eq 0 ]]; then
_msg "${PAS}download completed."
else
_err "${ERR}could not download ${url}."
exit 1
fi
_msg "${INF}installing xz-utils"
dpkg -i --force-depends --force-architecture "${dl_path}/${package_name}"
if [[ "$?" -eq 0 ]]; then
_msg "${PAS}installation completed."
else
_err "${ERR}could not intall xz-utils."
exit 1
fi
}
welcome()
{
cat<<"EOF"
░░░ ░░░
░░▒▒░░░░░░░░░ ░░░░░░░░░▒▒░░
░░▒▒▒▒▒░░░░░░░ ░░░░░░░▒▒▒▒▒░
░▒▒▒░░▒▒▒▒▒ ░░░░░░░▒▒░
░▒▒▒▒░ ░░░░░░
▒▒▒▒▒ ░░░░░░
▒▒▒▒▒ ░░░░░
░▒▒▒▒▒ ░░░░░
▒▒▒▒▒ ░░░░░
▒▒▒▒▒ ░░░░░
░▒▒▒▒▒░░░░░
▒▒▒▒▒▒░░░
▒▒▒▒▒▒░
_____ _ _ _ _ _____ _
/ ____| \ | | | | | / ____| (_)
| | __| \| | | | | | | __ _ _ ___ __
| | |_ | . ' | | | | | | |_ | | | | \ \/ /
| |__| | |\ | |__| | | |__| | |_| | |> <
\_____|_| \_|\____/ \_____|\__,_|_/_/\_\
This script installs GNU Guix on your system
https://www.gnu.org/software/guix/
EOF
echo -n "Press return to continue..."
read -r ANSWER
}
main()
{
local tmp_path
welcome
_msg "Starting installation ($(date))"
chk_term
chk_require "${REQUIRE[*]}"
chk_init_sys
chk_sys_arch
_msg "${INF}system is ${ARCH_OS}"
tmp_path="$(mktemp -t -p "${ROOT_HOME}" -d guix.XXX)"
get_xz_utils "${tmp_path}"
guix_get_bin_list "${GNU_URL}"
guix_get_bin "${GNU_URL}" "${BIN_VER}" "$tmp_path"
sys_create_store "${BIN_VER}.tar.xz" "${tmp_path}"
sys_create_build_user
sys_enable_guix_daemon
sys_authorize_build_farms
[ -e "/etc/profile.d" ] || mkdir -p "/etc/profile.d"
write_profile_script "/etc/profile.d/guix.sh"
_msg "${INF}cleaning up ${tmp_path}"
rm -r "${tmp_path}"
_msg "${PAS}Guix has successfully been installed!"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment