Last active
April 8, 2021 19:22
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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