diff --git a/.gitignore b/.gitignore index a1a3871..70f663e 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,8 @@ DB/pg_data .env.* .env -!.env.example + +Docker/config .idea .vscode diff --git a/Cron/autoHolidays.sh b/Cron/autoHolidays.sh index 50607b4..2c937ca 100755 --- a/Cron/autoHolidays.sh +++ b/Cron/autoHolidays.sh @@ -1,3 +1,4 @@ +# cron-timing: 01 00 01 01 * # Calls endpoint to write all public Holidays for the current year inside a database. port=__PORT__ curl localhost:$port/auto/feiertage diff --git a/install.sh b/install.sh index eb7ae33..a31a638 100755 --- a/install.sh +++ b/install.sh @@ -1,193 +1,327 @@ #!/usr/bin/env bash +#©Tom Tröger 2026 set -e envFile=Docker/.env +envBkp=Docker/.env.old envExample=Docker/env.example -autoBackupScript=Cron/autoBackup.sh -autoHolidaysScript=Cron/autoHolidays.sh -autoLogoutScript=Cron/autoLogout.sh +cronFilePath=Cron +customCronFilePath=Docker/config/cron -echo "Checking Docker installation..." -if ! command -v docker >/dev/null 2>&1; then - echo "Docker not found. Install Docker? [y/N]" - read -r install_docker - if [[ "$install_docker" =~ ^[Yy]$ ]]; then - curl -fsSL https://get.docker.com | sh +autoBackupScript=autoBackup.sh +autoHolidaysScript=autoHolidays.sh +autoLogoutScript=autoLogout.sh + +function checkDocker() { + echo "Checking Docker installation..." + if ! command -v docker >/dev/null 2>&1; then + echo "Docker not found. Install Docker? [y/N]" + read -r install_docker + if [[ "$install_docker" =~ ^[Yy]$ ]]; then + curl -fsSL https://get.docker.com | sh + else + echo "Docker is required. Exiting." + exit 1 + fi + else + echo "Docker is already installed." + fi + + ########################################################################### + + echo "Checking Docker Compose..." + if ! docker compose version >/dev/null 2>&1; then + echo "Docker Compose plugin missing. You may need to update Docker." + exit 1 + fi +} +########################################################################### + +function setupConfig() { + local reconfig=false + if [ $# -gt 0 ]; then + if ask_reconfig $1 "Reconfigure .env File?" + then + reconfig=true else - echo "Docker is required. Exiting." - exit 1 + return 0 fi -else - echo "Docker is already installed." -fi - -########################################################################### - -echo "Checking Docker Compose..." -if ! docker compose version >/dev/null 2>&1; then - echo "Docker Compose plugin missing. You may need to update Docker." - exit 1 -fi - -########################################################################### - -echo "Preparing .env file..." -if [ ! -f $envFile ]; then - if [ -f $envExample ]; then - echo ".env not found. Creating interactively from .env.example." - > $envFile - - while IFS= read -r line; do - - #ignore empty lines and comments - [[ "$line" =~ ^#.*$ || -z "$line" ]] && continue - - - key=$(printf "%s" "$line" | cut -d '=' -f 1) - rest=$(printf "%s" "$line" | cut -d '=' -f 2-) - - # extract inline comment portion - comment=$(printf "%s" "$rest" | sed -n 's/.*# \(.*\)$/\1/p') - raw_val=$(printf "%s" "$rest" | sed 's/ *#.*//') - default_value=$(printf "%s" "$raw_val" | sed 's/"//g') - - # Replace __ROOT__ with script pwd - default_value="${default_value/__ROOT__/$(pwd)}" - - regex="" - if [[ "$comment" =~ regex:(.*)$ ]]; then - regex="${BASH_REMATCH[1]}" - fi - - comment=$(printf "%s" "$comment" | sed 's/ regex:.*//') - - while true; do - if [ -z "$comment" ]; then - printf "Value for $key (default: $default_value" - else - printf "Value for $key - $comment (default: $default_value" - fi - if [ -n "$regex" ]; then - printf ", must match: %s" "$regex" - fi - printf "):\n" - - read user_input < /dev/tty - - # empty input -> take default - [ -z "$user_input" ] && user_input="$default_value" - - printf "\e[A$user_input\n" - - # validate - if [ -n "$regex" ]; then - if [[ "$user_input" =~ $regex ]]; then - echo "$key=$user_input" >> $envFile - break - else - printf "Invalid value. Does not match regex: %s\n" "$regex" - continue - fi - else - echo "$key=$user_input" >> $envFile - break - fi - done - - done < $envExample - - echo ".env created." - else - echo "No .env or .env.example found." - echo "Creating an empty .env file for manual editing." - touch $envFile - fi -else - echo "Using existing .env. (found at $envFile)" -fi - -########################################################################### - -LOG_PATH=$(grep -E '^LOG_PATH=' $envFile | cut -d= -f2) -if [ -z "$LOG_PATH" ]; then - echo "LOG_PATH not found in .env using default $(pwd)/logs" - LOG_PATH=$(pwd)/logs -fi -mkdir -p $LOG_PATH -echo "Created logs folder at $LOG_PATH" - -########################################################################### - -echo -e "\n\n" -echo "Setup Crontab for automatic logout, backup and holiday creation? [y/N]" -read -r setup_cron -if [[ "$setup_cron" =~ ^[Yy]$ ]]; then - WEB_PORT=$(grep -E '^WEB_PORT=' $envFile | cut -d= -f2) - if [ -z "$WEB_PORT" ]; then - echo "WEB_PORT not found in .env using default 8000" - WEB_PORT=8000 - fi - - POSTGRES_DB=$(grep -E '^POSTGRES_DB=' $envFile | cut -d= -f2) - if [ -z "$POSTGRES_DB" ]; then - echo "POSTGRES_DB not found in .env using default arbeitszeitmessung" - POSTGRES_DB="arbeitszeitmessung" - fi - - BACKUP_FOLDER=$(grep -E '^BACKUP_FOLDER=' $envFile | cut -d= -f2) - if [ -z "$BACKUP_FOLDER" ]; then - echo "BACKUP_FOLDER not found in .env using default $(pwd)/backup" - BACKUP_FOLDER="$(pwd)/backup" - fi - - sed -i "s/__PORT__/$WEB_PORT/" $autoHolidaysScript - sed -i "s/__PORT__/$WEB_PORT/" $autoLogoutScript - sed -i "s/__DATABASE__/$POSTGRES_DB/" $autoBackupScript - sed -i "s/__BACKUP_FOLDER__/$BACKUP_FOLDER" $autoBackupScript - - chmod +x $autoBackupScript $autoHolidaysScript $autoLogoutScript - - # echo "Scripts build with PORT=$WEB_PORT and DATABSE=$POSTGRES_DB!" - echo "Adding rules to crontab." - - cron_commands=$(mktemp /tmp/arbeitszeitmessung-cron.XXX) - - for file in Cron/*; do - cron_timing=$(grep -E '^# cron-timing:' "$file" | sed 's/^# cron-timing:[[:space:]]*//') - - if [ -z "$cron_timing" ]; then - echo "No cron-timing found in $file, so it's not added to crontab." - continue + fi + echo -e "\r\n==================================================\r\n" + echo "Preparing .env file..." + if [ ! -f $envFile ] || [ $reconfig == true ]; then + if [ -f $envExample ]; then + if [ $reconfig == true ]; then + echo "Reconfiguring env file. Backup stored at $envBkp" + echo "All previous values will be used as defaults!" + cp $envFile $envBkp + else + echo ".env not found. Creating interactively from .env.example." fi + > $envFile - ( crontab -l ; echo "$cron_timing $(pwd)/$file" )| awk '!x[$0]++' | crontab - - echo "Added entry to crontab: $cron_timing $(pwd)/$file." - sleep 2 - done - if systemctl is-active --quiet cron.service ; then - echo "cron.service is running. Everything should be fine now." - else - echo "cron.service is not running. Please start and enable cron.service." - echo "For how to start a service, see: https://wiki.ubuntuusers.de/systemd/systemctl UNITNAME will be cron.service" - fi + while IFS= read -r line; do -else - echo "Please setup cron manually by executing crontab -e and adding all files from inside the Cron directory!" -fi + #ignore empty lines and comments + [[ "$line" =~ ^#.*$ || -z "$line" ]] && continue + + local key=$(printf "%s" "$line" | cut -d '=' -f 1) + local rest=$(printf "%s" "$line" | cut -d '=' -f 2-) + + # extract inline comment portion + local comment=$(printf "%s" "$rest" | sed -n 's/.*# \(.*\)$/\1/p') + local raw_val=$(printf "%s" "$rest" | sed 's/ *#.*//') + + local default_value=$(printf "%s" "$raw_val" | sed 's/"//g') + + if [ $reconfig == true ]; then + local previous_value=$(grep -E "^$key=" $envBkp | cut -d= -f2) + if [ -n "$previous_value" ]; then + default_value=$previous_value + fi + fi + + # Replace __ROOT__ with script pwd + local default_value="${default_value/__ROOT__/$(pwd)}" + + regex="" + if [[ "$comment" =~ regex:(.*)$ ]]; then + regex="${BASH_REMATCH[1]}" + fi + + comment=$(printf "%s" "$comment" | sed 's/ regex:.*//') + + while true; do + if [ -z "$comment" ]; then + printf "Value for $key (default: $default_value" + else + printf "Value for $key - $comment (default: $default_value" + fi + if [ -n "$regex" ]; then + printf ", must match: %s" "$regex" + fi + printf "):\n" + + read user_input < /dev/tty + + # empty input -> take default + [ -z "$user_input" ] && user_input="$default_value" + + printf "\e[A$user_input\n" + + # validate + if [ -n "$regex" ]; then + if [[ "$user_input" =~ $regex ]]; then + echo "$key=$user_input" >> $envFile + break + else + printf "Invalid value. Does not match regex: %s\n" "$regex" + continue + fi + else + echo "$key=$user_input" >> $envFile + break + fi + done + + done < $envExample + + echo ".env created." + else + echo "No .env or .env.example found." + echo "Creating an empty .env file for manual editing." + touch $envFile + fi + else + echo "Using existing .env. (found at $envFile)" + fi +} ########################################################################### -echo -e "\n\n" -echo "Start containers with docker compose up -d? [y/N]" -read -r start_containers -if [[ "$start_containers" =~ ^[Yy]$ ]]; then - cd Docker - docker compose up -d - echo "Containers started." -else - echo "You can start them manually with: docker compose up -d" -fi +function setupFolders(){ + if [ $# -gt 0 ]; then + if ! ask_reconfig $1 "Recreate Folders?" + then + return 0 + fi + fi + LOG_PATH=$(grep -E '^LOG_PATH=' $envFile | cut -d= -f2) + if [ -z "$LOG_PATH" ]; then + echo "LOG_PATH not found in .env using default $(pwd)/logs" + LOG_PATH=$(pwd)/logs + fi + if [ ! -d "$LOG_PATH" ]; then + mkdir -p $LOG_PATH + echo "Created logs folder at $LOG_PATH" + fi -echo "Installation finished, you can re-run the script any time!" + POSTGRES_PATH=$(grep -E '^POSTGRES_PATH=' $envFile | cut -d= -f2) + if [ -z "$POSTGRES_PATH" ]; then + echo "POSTGRES_PATH not found in .env using default $(pwd)/DB" + POSTGRES_PATH=$(pwd)/DB + fi + if [ ! -d "$POSTGRES_PATH" ]; then + mkdir -p $POSTGRES_PATH + echo "Created DB folder at $POSTGRES_PATH" + fi + + BACKUP_FOLDER=$(grep -E '^BACKUP_FOLDER=' $envFile | cut -d= -f2) + if [ -z "$BACKUP_FOLDER" ]; then + echo "BACKUP_FOLDER not found in .env using default $(pwd)/backup" + BACKUP_FOLDER=$(pwd)/backup + fi + if [ ! -d "$BACKUP_FOLDER" ]; then + mkdir -p $BACKUP_FOLDER + echo "Created backup folder at $BACKUP_FOLDER" + fi +} +########################################################################### + +function setupCron(){ + echo -e "\r\n==================================================\r\n" + echo "Setup Crontab for automatic logout, backup and holiday creation? [y/N]" + read -r setup_cron + if [[ "$setup_cron" =~ ^[Yy]$ ]]; then + echo "Copying custom cron files to $customCronFilePath" + mkdir -p "$customCronFilePath" + + if [ ! -s "$customCronFilePath/$autoBackupScript" ];then + cp "$cronFilePath/$autoBackupScript" "$customCronFilePath/$autoBackupScript" + echo "Copied $autoBackupScript" + fi + + if [ ! -s "$customCronFilePath/$autoLogoutScript" ];then + cp "$cronFilePath/$autoLogoutScript" "$customCronFilePath/$autoLogoutScript" + echo "Copied $autoLogoutScript" + fi + + if [ ! -s "$customCronFilePath/$autoHolidaysScript" ];then + cp "$cronFilePath/$autoHolidaysScript" "$customCronFilePath/$autoHolidaysScript" + echo "Copied $autoHolidaysScript" + fi + + WEB_PORT=$(grep -E '^WEB_PORT=' $envFile | cut -d= -f2) + if [ -z "$WEB_PORT" ]; then + echo "WEB_PORT not found in .env using default 8000" + WEB_PORT=8000 + fi + + POSTGRES_DB=$(grep -E '^POSTGRES_DB=' $envFile | cut -d= -f2) + if [ -z "$POSTGRES_DB" ]; then + echo "POSTGRES_DB not found in .env using default arbeitszeitmessung" + POSTGRES_DB="arbeitszeitmessung" + fi + + BACKUP_FOLDER=$(grep -E '^BACKUP_FOLDER=' $envFile | cut -d= -f2) + if [ -z "$BACKUP_FOLDER" ]; then + echo "BACKUP_FOLDER not found in .env using default $(pwd)/backup" + BACKUP_FOLDER="$(pwd)/backup" + fi + + sed -i "s|__PORT__|$WEB_PORT|" $customCronFilePath/$autoHolidaysScript && \ + sed -i "s|__PORT__|$WEB_PORT|" $customCronFilePath/$autoLogoutScript && \ + sed -i "s|__DATABASE__|$POSTGRES_DB|" $customCronFilePath/$autoBackupScript && \ + sed -i "s|__BACKUP_FOLDER__|$BACKUP_FOLDER|" $customCronFilePath/$autoBackupScript + + chmod +x "$customCronFilePath/$autoBackupScript" "$customCronFilePath/$autoHolidaysScript" "$customCronFilePath/$autoLogoutScript" + + # echo "Scripts build with PORT=$WEB_PORT and DATABSE=$POSTGRES_DB!" + echo "Adding rules to crontab." + + cron_commands=$(mktemp /tmp/arbeitszeitmessung-cron.XXX) + pwd + + for file in $customCronFilePath/*; do + cron_timing=$(grep -E '^# cron-timing:' "$file" | sed 's/^# cron-timing:[[:space:]]*//') + + if [ -z "$cron_timing" ]; then + echo "No cron-timing found in $file, so it's not added to crontab." + continue + fi + + ( crontab -l ; echo "$cron_timing $(pwd)/$file" )| awk '!x[$0]++' | crontab - + echo "Added entry to crontab: $cron_timing $(pwd)/$file." + done + + if systemctl is-active --quiet cron.service ; then + echo "cron.service is running. Everything should be fine now." + else + echo "cron.service is not running. Please start and enable cron.service." + echo "For how to start a service, see: https://wiki.ubuntuusers.de/systemd/systemctl UNITNAME will be cron.service" + fi + + else + echo "Please setup cron manually by executing crontab -e and adding all files from inside the Cron directory!" + fi +} +########################################################################### + +function startContainer(){ + echo -e "\r\n==================================================\r\n" + echo "Start containers with docker compose up -d? [y/N]" + read -r start_containers + if [[ "$start_containers" =~ ^[Yy]$ ]]; then + cd Docker + docker compose up -d + echo "Containers started." + else + echo "You can start them manually with: docker compose up -d" + fi +} +########################################################################### + +function help(){ + echo "Installer Script für Arbeitszeitmessung Software" + echo -e "\r\n==================================================\r\n" + echo "Nutzung: ./install.sh [options]" + echo -e "\r\n==================================================\r\n" + echo "Optionen:" + echo " -h zeigt diese Übersicht" + echo " -c .env Datei bearbeiten/aktualisieren && cron neu configurieren" + echo -e "\r\n==================================================" +} +########################################################################### + +function main(){ + echo -e "================Arbeitszeitmessung================\r\n" + if [ $# -gt 0 ];then + if [ $1 == reconfig ]; then + echo -e "================Reconfiguring================\r\n" + setupConfig $1 + setupFolders $1 + setupCron $1 + fi + else + checkDocker + setupConfig + setupFolders + setupCron + startContainer + fi + echo "Installation finished, you can re-run the script any time!" +} +########################################################################### + +function ask_reconfig(){ + echo -e "\r\n==================================================\r\n" + echo "$2 [y/N]" + read -r do_reconfig + + [[ "$do_reconfig" =~ ^[Yy]$ ]] && return # true + echo "Skipping..." + return 1 +} +########################################################################### + +while getopts ":hc" opt; do + case $opt in + h) help; exit 0 ;; + c) main reconfig; exit 0 ;; + *) echo "Ungültiges Argument"; exit 1 ;; + esac +done + +main