From f9d4c0d3c8db53af0d8c06b431b6f3e47dc4d51a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20St=C3=B6ckel?= Date: Wed, 1 Jan 2025 02:13:57 +0100 Subject: [PATCH] :hammer: improve installation script --- .gitignore | 5 +- scripts/install.sh | 200 ++++++++++++------ scripts/services/README.md | 4 + .../services/traewelling-queue-export.service | 13 ++ .../traewelling-queue-webhook.service | 13 ++ scripts/services/traewelling-queue.service | 13 ++ .../services/traewelling-scheduler.service | 9 + scripts/services/traewelling-scheduler.timer | 8 + 8 files changed, 202 insertions(+), 63 deletions(-) create mode 100644 scripts/services/README.md create mode 100644 scripts/services/traewelling-queue-export.service create mode 100644 scripts/services/traewelling-queue-webhook.service create mode 100644 scripts/services/traewelling-queue.service create mode 100644 scripts/services/traewelling-scheduler.service create mode 100644 scripts/services/traewelling-scheduler.timer diff --git a/.gitignore b/.gitignore index 94ff4a11d..cce6485d8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,17 +13,16 @@ public/uploads/avatars/* # vendor-views !resources/views/vendor -# Laravel 4 specific +# Laravel specific bootstrap/compiled.php app/storage/ - -# Laravel 5 & Lumen specific public/storage public/hot storage/*.key .env* Homestead.yaml Homestead.json +storage/framework/maintenance.php # Rocketeer PHP task runner and deployment package. https://github.com/rocketeers/rocketeer .rocketeer/ diff --git a/scripts/install.sh b/scripts/install.sh index cf192d8c7..1f5ec25cd 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash -set -e # Exit on error +set -e +set -o pipefail + +USER=${1:-www-data} # Default user is www-data +REPO_ROOT=$(git rev-parse --show-toplevel) # Colors for fancy output GREEN="\033[1;32m" @@ -26,44 +30,142 @@ ASCII_ART="\ " -echo -e "${TRWL_RED}${ASCII_ART}${RESET}" +ask_to_install_service() { + service_name="$1" -echo -e "${CYAN}Starting update script...${RESET}" + source_file="${REPO_ROOT}/scripts/services/${service_name}.service" + destination_file="/etc/systemd/system/${service_name}.service" -# Get the repository root directory -REPO_ROOT=$(git rev-parse --show-toplevel) -echo -e "${YELLOW}Repository root determined: ${GREEN}$REPO_ROOT${RESET}" -cd "$REPO_ROOT" + if [ -f "$destination_file" ]; then + echo -e "${YELLOW}Service ${service_name} is already installed.${RESET}" + return 1 + fi + + echo -e "${YELLOW}Service ${service_name} is not installed.${RESET}" + echo -e "${YELLOW}Do you want to install it? [Y/n] (default: yes)${RESET}" + + read -r answer + + case "$answer" in + y|Y|yes|Yes|"") + echo -e "${YELLOW}Installing ${service_name} service to ${destination_file}...${RESET}" + sudo cp "$source_file" "$destination_file" + + sudo sed -i "s|REPLACE_ROOT_PATH|${REPO_ROOT}|g" "$destination_file" + sudo sed -i "s|REPLACE_USER|${USER}|g" "$destination_file" + + sudo systemctl enable "$service_name" + sudo systemctl start "$service_name" + + echo -e "${GREEN}${service_name} service installed successfully!${RESET}" + return 0 + ;; + n|N|no|No) + echo -e "${YELLOW}Skipping ${service_name} service installation...${RESET}" + return 1 + ;; + *) + echo -e "${RED}Invalid answer. Please answer with 'y' or 'n'.${RESET}" + ask_to_install_service "$service_name" + ;; + esac +} + +welcome_message() { + echo -e "${TRWL_RED}${ASCII_ART}${RESET}" + echo -e "${CYAN}This script will install / update Träwelling to the latest develop version.${RESET}\n" + echo -e "${YELLOW}Running as: ${GREEN}${USER}${RESET}\n" + + echo -e "${YELLOW}Continue installation in ${GREEN}${REPO_ROOT}${YELLOW} directory?\n[Y/n] (default: yes): ${RESET}" + read -r answer + + case "$answer" in + y|Y|yes|Yes|"") + echo -e "${GREEN}Okay, let's go!${RESET}" + cd "$REPO_ROOT" + echo -e "${YELLOW}Starting installation at $(date --iso-8601=seconds)${RESET}\n\n\n" + ;; + n|N|no|No) + echo -e "${YELLOW}Bye.${RESET}" + exit 1 + ;; + *) + echo -e "${RED}Invalid answer. Please answer with 'y' or 'n'.${RESET}" + welcome_message + ;; + esac +} -echo -e "${CYAN}Running pre-update tasks...${RESET}" +check_dependencies() { + if [ "$USER" != "$(whoami)" ]; then + echo -e "${RED}Please run this script as ${USER} user.${RESET}" + exit 1 + fi -echo -e "${YELLOW}Enabling maintenance mode...${RESET}" -php artisan down | sed "s/^/[AS-DOWN] /" + if ! command -v php &> /dev/null; then + echo -e "${RED}PHP is not installed. Please install PHP and try again.${RESET}" + exit 1 + fi -echo -e "${YELLOW}Pulling latest changes...${RESET}" -git pull | sed "s/^/[GIT-PULL] /" + if ! command -v composer &> /dev/null; then + echo -e "${RED}Composer is not installed. Please install Composer and try again.${RESET}" + exit 1 + fi -echo -e "${YELLOW}Installing composer dependencies...${RESET}" -#composer install --no-interaction --no-dev --prefer-dist --optimize-autoloader | sed "s/^/[COMPOSER] /" + if ! command -v npm &> /dev/null; then + echo -e "${RED}NPM is not installed. Please install NPM and try again.${RESET}" + exit 1 + fi -echo -e "${YELLOW}Installing npm dependencies...${RESET}" -#npm ci --no-audit --no-progress && npm run build | sed "s/^/[NPM] /" + if ! command -v git &> /dev/null; then + echo -e "${RED}Git is not installed. Please install Git and try again.${RESET}" + exit 1 + fi +} -echo -e "${YELLOW}Running migrations...${RESET}" -php artisan migrate --force | sed "s/^/[MIGRATE] /" +activate_maintenance_mode() { + echo -e "${YELLOW}Enabling maintenance mode...${RESET}" + php artisan down +} + +pull_latest_changes() { + echo -e "${YELLOW}Pulling latest changes...${RESET}" + git pull +} -echo -e "${YELLOW}Optimizing application...${RESET}" -php artisan optimize | sed "s/^/[OPTIMIZE] /" +install_composer_dependencies() { + echo -e "${YELLOW}Installing composer dependencies...${RESET}" + composer install --no-interaction --no-dev --prefer-dist --optimize-autoloader +} -echo -e "${YELLOW}Seeding constants to database...${RESET}" -php artisan db:seed --class=Database\\Seeders\\Constants\\PermissionSeeder --force | sed "s/^/[SEED] /" +install_npm_dependencies() { + echo -e "${YELLOW}Installing npm dependencies...${RESET}" + npm ci --no-audit --no-progress + npm run build +} -echo -e "${YELLOW}Disabling maintenance mode...${RESET}" -php artisan up | sed "s/^/[AS-UP] /" +run_migrations() { + echo -e "${YELLOW}Running migrations...${RESET}" + php artisan migrate --force +} -echo -e "\n\n${GREEN}Application updated successfully!${RESET}" +finish_application() { + echo -e "${YELLOW}Optimizing application...${RESET}" + php artisan optimize + + echo -e "${YELLOW}Seeding constants to database...${RESET}" + php artisan db:seed --class=Database\\Seeders\\Constants\\PermissionSeeder --force + + echo -e "${YELLOW}Disabling maintenance mode...${RESET}" + php artisan up +} restart_services() { + if ! command -v systemctl &> /dev/null; then + echo -e "${RED}Systemd is not available on this system. Skipping service restart.${RESET}" + return + fi + echo -e "\n\n${CYAN}Restarting services...${RESET}" echo -e "${YELLOW}You may be asked to enter your password here if you're not running as root.${RESET}\n\n" @@ -85,40 +187,18 @@ restart_services() { done } -ask_to_install_service() { - service_name="$1" - - source_file="${REPO_ROOT}scripts/services/${service_name}.service" - destination_file="/etc/systemd/system/${service_name}.service" - - if [ -f "$destination_file" ]; then - echo -e "${YELLOW}Service ${service_name} is already installed.${RESET}" - return 1 - fi - - echo -e "${YELLOW}Service ${service_name} is not installed.${RESET}" - echo -e "${YELLOW}Do you want to install it? [Y/n] (default: yes)${RESET}" - - read -r answer - - case "$answer" in - y|Y|yes|Yes|"") - echo -e "${YELLOW}Installing ${service_name} service...${RESET}" - sudo cp "$source_file" "$destination_file" - sudo systemctl enable "$service_name" - sudo systemctl start "$service_name" - echo -e "${GREEN}${service_name} service installed successfully!${RESET}" - return 0 - ;; - n|N|no|No) - echo -e "${YELLOW}Skipping ${service_name} service installation...${RESET}" - return 1 - ;; - *) - echo -e "${RED}Invalid answer. Please answer with 'y' or 'n'.${RESET}" - ask_to_install_service "$service_name" - ;; - esac +run_installation() { + welcome_message + check_dependencies | sed "s/^/[DependencyCheck] /" + activate_maintenance_mode | sed "s/^/[PreInstall] /" + pull_latest_changes | sed "s/^/[git] /" + install_composer_dependencies | sed "s/^/[composer] /" + install_npm_dependencies | sed "s/^/[npm] /" + run_migrations | sed "s/^/[Migration] /" + finish_application | sed "s/^/[PostInstall] /" + restart_services | sed "s/^/[ServiceManager] /" + + echo -e "\n\n${GREEN}Application updated successfully at $(date --iso-8601=seconds)!${RESET}" } -restart_services +run_installation 2>&1 | tee -a "${REPO_ROOT}/storage/logs/install-$(date --iso-8601=seconds).log" diff --git a/scripts/services/README.md b/scripts/services/README.md new file mode 100644 index 000000000..9c2a740bf --- /dev/null +++ b/scripts/services/README.md @@ -0,0 +1,4 @@ +# Services for systemd + +This directory contains systemd service files for the various services that are part of Träwelling. +These files are installed to `/etc/systemd/system` by the `install.sh` script. diff --git a/scripts/services/traewelling-queue-export.service b/scripts/services/traewelling-queue-export.service new file mode 100644 index 000000000..dcc18ba6a --- /dev/null +++ b/scripts/services/traewelling-queue-export.service @@ -0,0 +1,13 @@ +[Unit] +Description=Traewelling Export Queue worker + +[Service] +Type=simple +User=REPLACE_USER +Restart=always +RuntimeMaxSec=3600 +WorkingDirectory=REPLACE_ROOT_PATH +ExecStart=php artisan queue:work --max-time=600 --tries=3 --queue=export + +[Install] +WantedBy=multi-user.target diff --git a/scripts/services/traewelling-queue-webhook.service b/scripts/services/traewelling-queue-webhook.service new file mode 100644 index 000000000..9c933369b --- /dev/null +++ b/scripts/services/traewelling-queue-webhook.service @@ -0,0 +1,13 @@ +[Unit] +Description=Traewelling Webhook Queue worker + +[Service] +Type=simple +User=REPLACE_USER +Restart=always +RuntimeMaxSec=3600 +WorkingDirectory=REPLACE_ROOT_PATH +ExecStart=php artisan queue:work --max-time=600 --tries=3 --queue=webhook + +[Install] +WantedBy=multi-user.target diff --git a/scripts/services/traewelling-queue.service b/scripts/services/traewelling-queue.service new file mode 100644 index 000000000..9f8bc8d39 --- /dev/null +++ b/scripts/services/traewelling-queue.service @@ -0,0 +1,13 @@ +[Unit] +Description=Traewelling Queue worker + +[Service] +Type=simple +User=REPLACE_USER +Restart=always +RuntimeMaxSec=3600 +WorkingDirectory=REPLACE_ROOT_PATH +ExecStart=php artisan queue:work --max-time=600 --tries=3 + +[Install] +WantedBy=multi-user.target diff --git a/scripts/services/traewelling-scheduler.service b/scripts/services/traewelling-scheduler.service new file mode 100644 index 000000000..aa351bacd --- /dev/null +++ b/scripts/services/traewelling-scheduler.service @@ -0,0 +1,9 @@ +[Unit] +Description=Traewelling Scheduler + +[Service] +Type=oneshot +User=REPLACE_USER +Restart=on-failure +WorkingDirectory=REPLACE_ROOT_PATH +ExecStart=php artisan schedule:run diff --git a/scripts/services/traewelling-scheduler.timer b/scripts/services/traewelling-scheduler.timer new file mode 100644 index 000000000..85cbceae0 --- /dev/null +++ b/scripts/services/traewelling-scheduler.timer @@ -0,0 +1,8 @@ +[Unit] +Description=Traewelling Scheduler + +[Timer] +OnCalendar=minutely + +[Install] +WantedBy=timers.target