#!/bin/sh -e # # SatNOGS station setup script # # Copyright (C) 2024-2026 Libre Space Foundation # # This program is free software: you can redistribute it and/or modify # it under the terms of 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. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . { DEFAULT_SATNOGS_SETUP_SATNOGS_ANSIBLE_CHECKOUT_REF="stable" DEFAULT_SATNOGS_SETUP_SATNOGS_ANSIBLE_PLAYBOOK_URL="https://gitlab.com/librespacefoundation/satnogs/satnogs-ansible.git" DEFAULT_SATNOGS_SETUP_DOCKER_ANSIBLE_IMAGE="librespace/ansible:9.13.0" DOCKER_BINDMOUNTS_DIR="/var/lib/docker-bindmounts" DOCKER_ANSIBLE_NAME="ansible" DOCKER_ANSIBLE_CONTAINER_NAME="ansible_ansible" DOCKER_SATNOGS_CONFIG_CONTAINER_NAME="ansible_satnogs-config" DOCKER_ANSIBLE_CONFIG_DIR="/etc/ansible" DOCKER_ANSIBLE_PULL_DIR="/root/.ansible/pull" DOCKER_SATNOGS_CONFIG_UID="500" INSTALL_PACKAGES=" docker-ce:Docker git:Git " DOCKER_CE_APT_BASE_URL="https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")" DOCKER_CE_APT_KEY_URL="$DOCKER_CE_APT_BASE_URL/gpg" DOCKER_CE_APT_KEYRING_FILE="/etc/apt/keyrings/docker.asc" DOCKER_CE_APT_REPO_URL="deb [arch=$(dpkg --print-architecture) signed-by=$DOCKER_CE_APT_KEYRING_FILE] $DOCKER_CE_APT_BASE_URL $(. /etc/os-release; echo "$VERSION_CODENAME") stable" DOCKER_CE_APT_REPO_FILE="/etc/apt/sources.list.d/docker.list" DEFAULT_CONF_FILE="/etc/satnogs-setup.conf" REQUIREMENTS=" apt-get awk grep sudo gpg curl " requirements() { for req in $REQUIREMENTS; do if ! which "$req" >/dev/null; then if [ -z "$has_missing" ]; then echo "satnogs-setup: Missing script requirements!" 1>&2 echo "Please install:" 1>&2 has_missing=1 fi echo " - '$req'" 1>&2 fi done if [ -n "$has_missing" ]; then exit 1 fi } usage() { cat 1>&2 </dev/null } configure_docker_ce() { cat </dev/null { "log-driver": "journald" } EOF root_or_sudo systemctl reload docker } install_packages() { unset packages installed_packages="$(dpkg --get-selections | awk '/[ \t]install$/ { print $1 }')" while read -r install_package; do if [ -z "$install_package" ]; then continue fi package_name="${install_package%%:*}";install_package="${install_package#*:}" package_desc="$install_package" package_desc="${package_desc:-$package_name}" if ! echo "$installed_packages" | grep -q "^$package_name$"; then package_names="${package_names}${package_names:+ }${package_name}" package_descs="${package_descs}${package_descs:+, }${package_desc}" fi done </dev/null all: hosts: ${DOCKER_ANSIBLE_NAME}: ansible_connection: 'community.docker.nsenter' satnogses: hosts: ${DOCKER_ANSIBLE_NAME}: EOF root_or_sudo tee "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_CONTAINER_NAME}${DOCKER_ANSIBLE_CONFIG_DIR}/ansible.cfg" </dev/null [defaults] interpreter_python = auto_silent EOF if root_or_sudo /bin/sh -c '[ -f "'"${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_NAME}${DOCKER_ANSIBLE_CONFIG_DIR}/host_vars/${DOCKER_ANSIBLE_NAME}"'" ]'; then echo "Migrating configuration path..." root_or_sudo mv "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_NAME}${DOCKER_ANSIBLE_CONFIG_DIR}/host_vars/${DOCKER_ANSIBLE_NAME}" "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_CONTAINER_NAME}${DOCKER_ANSIBLE_CONFIG_DIR}/host_vars/${DOCKER_ANSIBLE_NAME}/config.yml" root_or_sudo chown ${DOCKER_SATNOGS_CONFIG_UID}:${DOCKER_SATNOGS_CONFIG_UID} "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_CONTAINER_NAME}${DOCKER_ANSIBLE_CONFIG_DIR}/host_vars/${DOCKER_ANSIBLE_NAME}/config.yml" root_or_sudo rm -rf "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_NAME}" fi echo } needs_update() { applied_ref="$1" satnogs_ansible_playbook_url="$2" satnogs_ansible_checkout_ref="$3" remote_ref="$(git ls-remote -q -h "$satnogs_ansible_playbook_url" "$satnogs_ansible_checkout_ref" 2>/dev/null | awk '{ print $1 }' || true)" if [ -z "$remote_ref" ] || [ "$applied_ref" = "$remote_ref" ]; then return 1 fi return 0 } provision_station() { docker_ansible_image="$1" satnogs_ansible_playbook_url="$2" satnogs_ansible_checkout_ref="$3" offline="$4" if [ -z "$offline" ]; then root_or_sudo mkdir -p "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_CONTAINER_NAME}${DOCKER_ANSIBLE_PULL_DIR}" root_or_sudo rm -rf "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_CONTAINER_NAME}${DOCKER_ANSIBLE_PULL_DIR}/ansible" root_or_sudo git clone \ -b "${satnogs_ansible_checkout_ref}" \ --depth 1 \ --recurse-submodules \ --shallow-submodules \ -o origin \ "${satnogs_ansible_playbook_url}" \ "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_CONTAINER_NAME}${DOCKER_ANSIBLE_PULL_DIR}/ansible" fi for image_archive in $(root_or_sudo find /var/lib/docker-archives -maxdepth 1 -type f -name "*.tar.gz" 2>/dev/null); do echo "Loading '$(basename "$image_archive")' image archive..." root_or_sudo docker load -i "$image_archive" root_or_sudo rm "$image_archive" done echo "Provisioning SatNOGS station..." root_or_sudo docker run \ --rm \ --read-only \ ${offline:+--pull never} \ --name "${DOCKER_ANSIBLE_NAME}" \ --hostname "${DOCKER_ANSIBLE_NAME}" \ --privileged \ --pid=host \ --tmpfs "/tmp" \ --tmpfs "/root/.ansible/tmp" \ -v "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_CONTAINER_NAME}${DOCKER_ANSIBLE_CONFIG_DIR}:${DOCKER_ANSIBLE_CONFIG_DIR}:ro" \ -v "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_CONTAINER_NAME}${DOCKER_ANSIBLE_PULL_DIR}:${DOCKER_ANSIBLE_PULL_DIR}:ro" \ "${docker_ansible_image}" \ /bin/sh -c '\ cd "'"${DOCKER_ANSIBLE_PULL_DIR}/ansible"'" && \ ansible-playbook \ -i "'"${DOCKER_ANSIBLE_CONFIG_DIR}"'" \ -e local_ansible_enable=true \ '"${offline:+-e docker_pull_policy=never --skip-tags=packages}"' \ local.yml' root_or_sudo git ls-remote -q "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_CONTAINER_NAME}${DOCKER_ANSIBLE_PULL_DIR}/ansible" 2>/dev/null | grep -v refs | awk '{ print $1 }' | root_or_sudo tee "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_CONTAINER_NAME}${DOCKER_ANSIBLE_PULL_DIR}/.applied-ref" >/dev/null } configure_station() { echo "Launching SatNOGS configuration tool..." root_or_sudo docker exec \ -ti \ "$DOCKER_SATNOGS_CONFIG_CONTAINER_NAME" \ satnogs-config < /dev/tty } main() { requirements debian_version="$(get_debian_version || true)" case $debian_version in buster|bullseye|bookworm|trixie) : ;; *) echo "ERROR: Unsupported distribution!" >&2 exit 1 ;; esac parse_args "$@" load_conf pidfile=/var/run/satnogs-setup.pid if [ -f "$pidfile" ] && root_or_sudo kill -0 "$(root_or_sudo cat "$pidfile")" 2>/dev/null; then echo "Another instance of 'satnogs-setup' is already running!" >&2 echo "If this is the first boot, wait until initial provisioning is completed." >&2 echo "Otherwise, close any other 'satnogs-setup' you are already running and try again." >&2 exit 1 fi echo "$$" | root_or_sudo tee "$pidfile" >/dev/null # Assume installation is requested when file is sourced if [ "$(basename "$0")" != "satnogs-setup" ]; then do_install=1 fi applied_ref="$(root_or_sudo cat "${DOCKER_BINDMOUNTS_DIR}/${DOCKER_ANSIBLE_CONTAINER_NAME}${DOCKER_ANSIBLE_PULL_DIR}/.applied-ref" 2>/dev/null || true)" if [ -z "$offline" ]; then install_docker_ce_repository install_packages fi configure_docker_ce configure_ansible if [ -z "$do_install" ]; then if [ -n "$applied_ref" ]; then if [ -z "$offline" ] && needs_update \ "$applied_ref" \ "$satnogs_ansible_playbook_url" \ "$satnogs_ansible_checkout_ref"; then if [ -z "$do_update" ]; then while true; do echo "A new version has been detected. Do you wish to update now? [y/n]" read -r yesno