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 84bbc4743..1f5ec25cd 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,40 +1,204 @@ #!/usr/bin/env bash set -e +set -o pipefail -pre_run() { - php artisan down - git pull - composer install --no-interaction --no-dev --prefer-dist +USER=${1:-www-data} # Default user is www-data +REPO_ROOT=$(git rev-parse --show-toplevel) + +# Colors for fancy output +GREEN="\033[1;32m" +CYAN="\033[1;36m" +YELLOW="\033[1;33m" +RED="\033[1;31m" +RESET="\033[0m" +TRWL_RED="\033[38;2;255;0;0m" + +ASCII_ART="\ + + + + _______ _ _ _ + |__ __| | | (_) + | |_ __ __ _ _____ _____| | |_ _ __ __ _ + | | '__/ _\` \|/ _ \ \ /\ / / _ \ | | | '_ \ / _\` | + | | | | (_| | __/\ V V / __/ | | | | | | (_| | + |_|_| \__,_|\___| \_/\_/ \___|_|_|_|_| |_|\__, | + __/ | + |___/ + + + " + +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 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 } -update_ui() { - npm ci --no-audit --no-progress && npm run build +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 +} + +check_dependencies() { + if [ "$USER" != "$(whoami)" ]; then + echo -e "${RED}Please run this script as ${USER} user.${RESET}" + exit 1 + fi + + if ! command -v php &> /dev/null; then + echo -e "${RED}PHP is not installed. Please install PHP and try again.${RESET}" + exit 1 + fi + + if ! command -v composer &> /dev/null; then + echo -e "${RED}Composer is not installed. Please install Composer and try again.${RESET}" + exit 1 + fi + + if ! command -v npm &> /dev/null; then + echo -e "${RED}NPM is not installed. Please install NPM and try again.${RESET}" + exit 1 + fi + + if ! command -v git &> /dev/null; then + echo -e "${RED}Git is not installed. Please install Git and try again.${RESET}" + exit 1 + fi +} + +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 +} + +install_composer_dependencies() { + echo -e "${YELLOW}Installing composer dependencies...${RESET}" + composer install --no-interaction --no-dev --prefer-dist --optimize-autoloader +} + +install_npm_dependencies() { + echo -e "${YELLOW}Installing npm dependencies...${RESET}" + npm ci --no-audit --no-progress + npm run build } run_migrations() { - php artisan migrate --force + echo -e "${YELLOW}Running migrations...${RESET}" + php artisan migrate --force } -post_run() { - php artisan optimize +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 - php artisan db:seed --class=Database\\Seeders\\Constants\\PermissionSeeder --force - php artisan up + echo -e "${YELLOW}Disabling maintenance mode...${RESET}" + php artisan up } -restart_queue() { - if [ -f /etc/systemd/system/traewelling-queue.service ]; then - sudo systemctl restart traewelling-queue - fi +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" - if [ -f /etc/systemd/system/traewelling-queue-webhook.service ]; then - sudo systemctl restart traewelling-queue-webhook + services=( + "traewelling-queue" + "traewelling-queue-webhook" + "traewelling-queue-export" + ) + + for service in "${services[@]}"; do + service_path="/etc/systemd/system/${service}.service" + if [ -f "$service_path" ]; then + echo -e "${YELLOW}Restarting ${service} service...${RESET}" + sudo systemctl restart "$service" + echo -e "${GREEN}${service} service restarted successfully!${RESET}" + else + ask_to_install_service "$service" fi + done +} + +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}" } -pre_run -run_migrations -update_ui -post_run -restart_queue +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