#!/usr/bin/env bash # SPDX-FileCopyrightText: 2022 Citadel and contributors # # SPDX-License-Identifier: GPL-3.0-or-later set -euo pipefail CITADEL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)" CLI_NAME="$(basename $0)" CLI_VERSION="0.0.1" CLI_DIR="$(dirname "$(readlink -f "$0")")" SERVICE_NAME="citadel-startup" EDITOR="${EDITOR:-micro}" source $CLI_DIR/utils/functions.sh source $CLI_DIR/utils/multiselect.sh source $CLI_DIR/utils/spinner.sh source $CLI_DIR/utils/helpers.sh if [ -z ${1+x} ]; then command="" else command="$1" fi # Check Citadel Status if [[ "$command" = "status" ]]; then POSITIONAL_ARGS=() long=false while [[ $# -gt 0 ]]; do case $1 in -l | --long) long=true shift ;; -* | --*) echo "Unknown option $1" exit 1 ;; *) POSITIONAL_ARGS+=("$1") shift ;; esac done set -- "${POSITIONAL_ARGS[@]}" free -m | awk 'NR==2{printf "Memory Usage: %s/%sMB (%.2f%%)\n", $3,$2,$3*100/$2 }' df -h | awk '$NF=="/"{printf "Disk Usage: %d/%dGB (%s)\n", $3,$2,$5}' top -bn1 | grep load | awk '{printf "CPU Load: %.2f\n", $(NF-2)}' echo if $long; then docker container ls --all --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" else docker container ls --all --format "table {{.Names}}\t{{.Status}}" fi if [[ $(pgrep -f karen) ]]; then printf "\nKaren is listening.\n" else printf "\nERROR: Karen is not listening.\n" fi exit fi # Update Citadel if [[ "$command" = "update" ]]; then POSITIONAL_ARGS=() branch=$(get_update_channel) force=false while [[ $# -gt 0 ]]; do case $1 in -b | --branch) branch="$2" shift # past argument shift # past value ;; --force) force=true shift # past argument ;; -* | --*) echo "Unknown option $1" exit 1 ;; *) POSITIONAL_ARGS+=("$1") # save positional arg shift # past argument ;; esac done set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters if $force; then sudo rm -f $CITADEL_ROOT/statuses/update-in-progress fi sudo $CITADEL_ROOT/scripts/update/update --repo runcitadel/core#$branch exit fi # Start Citadel if [[ "$command" = "start" ]]; then if $(is_managed_by_systemd); then if $(is_service_active); then echo 'Citadel is already running.' else sudo systemctl start citadel-startup fi else sudo $CITADEL_ROOT/scripts/start fi exit fi # Stop Citadel if [[ "$command" = "stop" ]]; then active=$(is_service_active) if $(is_managed_by_systemd) && $active; then if $(is_service_active); then sudo systemctl stop citadel-startup else echo 'Citadel is not running.' fi else sudo $CITADEL_ROOT/scripts/stop fi exit fi # Restart Citadel or individual services if [[ "$command" = "restart" ]]; then shift # restart Docker containers if [ ! -z ${1+x} ]; then docker restart $@ || { echo "To see all installed services & apps use \`$CLI_NAME list\`" echo "Usage: \`$CLI_NAME $command \`" exit 1 } exit fi # restart Citadel if $(is_managed_by_systemd); then sudo systemctl restart $SERVICE_NAME else sudo $CITADEL_ROOT/scripts/stop sudo $CITADEL_ROOT/scripts/start fi exit fi # Reboot the system if [[ "$command" = "reboot" ]]; then $CLI_NAME stop || true sudo reboot exit fi # Shutdown the system if [[ "$command" = "shutdown" ]]; then $CLI_NAME stop || true sudo shutdown now exit fi # List all installed services apps if [[ "$command" = "list" ]]; then # TODO: make this a bit nicer echo 'karen' docker ps --format "{{.Names}}" exit fi # Run a command inside a container if [[ "$command" = "run" ]]; then shift if [ -z ${1+x} ]; then echo "Specify an app or service." echo "Usage: \`$CLI_NAME $command \"\"\`" exit 1 fi if [ -z ${2+x} ]; then echo "Specify a command to run." echo "Usage: \`$CLI_NAME $command \"\"\`" exit 1 fi docker exec -t $1 sh -c "$2" || { echo "To see all installed services & apps use \`$CLI_NAME list\`" echo "Usage: \`$CLI_NAME $command \"\"\`" exit 1 } exit fi # Configure Citadel if [[ "$command" = "set" ]]; then shift if [ -z ${1+x} ]; then echo "Missing subcommand." echo "Usage: \`$CLI_NAME $command \`" exit 1 else subcommand="$1" fi # Switch update channel if [[ "$subcommand" = "update-channel" ]]; then if [ -z ${2+x} ]; then echo "Specify an update channel to switch to." echo "Usage: \`$CLI_NAME $subcommand \`" exit 1 fi case $2 in "stable" | "beta" | "c-lightning") # continue ;; *) echo "Not a valid update channel: \"$2\"" exit 1 ;; esac sudo $CITADEL_ROOT/scripts/set-update-channel $2 $CLI_NAME update exit fi # Switch Bitcoin/Electrum implementation if [[ "$subcommand" = "implementation" ]] || [[ "$subcommand" = "impl" ]]; then shift sudo $CITADEL_ROOT/services/manage.py set $@ $CLI_NAME restart exit fi # Switch Bitcoin network if [[ "$subcommand" = "network" ]]; then shift if [ -z ${1+x} ]; then echo "Specify a network to switch to." echo "Usage: \`$CLI_NAME $subcommand \`" else case $1 in "mainnet" | "testnet" | "signet" | "regtest") sudo $CITADEL_ROOT/scripts/stop sudo OVERWRITE_NETWORK=$1 $CITADEL_ROOT/scripts/configure sudo $CITADEL_ROOT/scripts/start ;; *) echo "Not a valid value for network" exit 1 ;; esac fi exit fi echo "\"$subcommand\" is not a valid subcommand." exit 1 fi # App commands if [[ "$command" = "app" ]]; then shift sudo $CITADEL_ROOT/scripts/app $@ exit fi # Edit common app configuration files if [[ "$command" = "configure" ]]; then if [ -z ${2+x} ]; then echo "Specify an app or service to configure." echo "Usage: \`$CLI_NAME $command \`" exit 1 fi POSITIONAL_ARGS=() persist=false while [[ $# -gt 0 ]]; do case $1 in --persist) persist=true shift # past argument ;; -* | --*) echo "Unknown option $1" exit 1 ;; *) POSITIONAL_ARGS+=("$1") # save positional arg shift # past argument ;; esac done set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters # These service and app configs are already persisted # TODO: add more apps if [[ "$2" = "nextcloud" ]]; then edit_file --priviledged $CITADEL_ROOT/app-data/nextcloud/data/nextcloud/config/config.php prompt_apply_config nextcloud-web-1 false exit fi #if [[ "$2" = "nginx" ]]; then #edit_file $CITADEL_ROOT/nginx/nginx.conf #prompt_apply_config nginx false #exit #fi if $persist; then echo "NOTE: As of now persisted config changes will not be kept when updating Citadel." else echo "NOTE: Some changes to this configuration file may be overwritten the next time you start Citadel." echo "To persist the changes run the command again with \`$CLI_NAME configure $2 --persist\`" fi read -p "Continue? [Y/n] " should_continue echo if [[ $should_continue =~ [Nn]$ ]]; then exit fi # Service and app configs below may be overwritten # TODO: check which implementation is running # and do "bitcoin" / "lightning" / "electrum" if [[ "$2" = "bitcoin" ]]; then if $persist; then edit_file $CITADEL_ROOT/templates/bitcoin-sample.conf prompt_apply_config bitcoin true else edit_file $CITADEL_ROOT/bitcoin/bitcoin.conf prompt_apply_config bitcoin false fi exit fi if [[ "$2" = "lnd" ]]; then edit_file $CITADEL_ROOT/app-data/lnd/lnd.conf prompt_apply_config lnd-service-1 false exit fi if [[ "$2" = "electrs" ]]; then edit_file $CITADEL_ROOT/app-data/electrs/electrs.toml prompt_apply_config electrs-service-1 true exit fi if [[ "$2" = "fulcrum" ]]; then edit_file $CITADEL_ROOT/app-date/fulcrum/fulcrum.conf prompt_apply_config fulcrum-service-1 true exit fi echo "No service or app \"$2\" not found." exit 1 fi # Show logs for apps & services if [[ "$command" = "logs" ]]; then shift POSITIONAL_ARGS=() follow=false while [[ $# -gt 0 ]]; do case $1 in -f | --follow) follow=true shift # past argument ;; -n | --tail) number_of_lines="$2" shift # past argument shift # past value ;; -* | --*) echo "Unknown option $1" exit 1 ;; *) POSITIONAL_ARGS+=("$1") # save positional arg shift # past argument ;; esac done set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters # Set default number_of_lines if not set by user if [ -z ${number_of_lines+x} ]; then if [[ ${#POSITIONAL_ARGS[@]} == 0 ]] || [[ ${#POSITIONAL_ARGS[@]} == 1 ]]; then number_of_lines=40 else number_of_lines=10 fi fi if [ -z ${1+x} ] || [[ "$1" = "karen" ]]; then if [[ ${#POSITIONAL_ARGS[@]} == 2 ]]; then echo "Karen logs cannot be viewed together with other services." echo "Usage: \`$CLI_NAME $command karen\`" exit 1 fi tail $($follow && echo "-f") -n $number_of_lines $CITADEL_ROOT/logs/karen.log exit fi if [[ ${#POSITIONAL_ARGS[@]} == 1 ]]; then docker logs $($follow && echo "-f") --tail $number_of_lines $@ || { echo "To see all installed services & apps use \`$CLI_NAME list\`" echo "Usage: \`$CLI_NAME $command \`" exit 1 } else # TODO: can only show logs for services in docker-compose.yml docker compose logs $($follow && echo "-f") --tail $number_of_lines $@ || { echo "To see all installed services & apps use \`$CLI_NAME list\`" echo "Usage: \`$CLI_NAME $command \`" exit 1 } fi exit fi # Debug Citadel if [[ "$command" = "debug" ]]; then shift sudo $CITADEL_ROOT/scripts/debug $@ exit fi # Show version information for this CLI if [[ "$command" = "--version" ]] || [[ "$command" = "-v" ]]; then citadel_version=$(jq -r '.version' $CITADEL_ROOT/info.json) echo "Citadel v$citadel_version" echo "citadel-cli v$CLI_VERSION" exit fi # Show usage information for this CLI if [[ "$command" = "--help" ]] || [[ "$command" = "-h" ]]; then show_help exit fi # If we get here it means no valid command was supplied # Show help and exit show_help exit