diff --git a/scripts/ct/2fauth.sh b/scripts/ct/2fauth.sh new file mode 100644 index 0000000..b301cf2 --- /dev/null +++ b/scripts/ct/2fauth.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(dirname "$0")" +source "$SCRIPT_DIR/../core/build.func" +# Copyright (c) 2021-2025 community-scripts ORG +# Author: jkrgr0 +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://docs.2fauth.app/ + +APP="2FAuth" +var_tags="${var_tags:-2fa;authenticator}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-512}" +var_disk="${var_disk:-2}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + if [[ ! -d "/opt/2fauth" ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + if check_for_gh_release "2fauth" "Bubka/2FAuth"; then + $STD apt update + $STD apt -y upgrade + + msg_info "Creating Backup" + mv "/opt/2fauth" "/opt/2fauth-backup" + if ! dpkg -l | grep -q 'php8.3'; then + cp /etc/nginx/conf.d/2fauth.conf /etc/nginx/conf.d/2fauth.conf.bak + fi + msg_ok "Backup Created" + + if ! dpkg -l | grep -q 'php8.3'; then + $STD apt-get install -y \ + lsb-release \ + gnupg2 + PHP_VERSION="8.3" PHP_MODULE="common,ctype,fileinfo,mysql,cli" PHP_FPM="YES" setup_php + sed -i 's/php8.2/php8.3/g' /etc/nginx/conf.d/2fauth.conf + fi + fetch_and_deploy_gh_release "2fauth" "Bubka/2FAuth" + setup_composer + mv "/opt/2fauth-backup/.env" "/opt/2fauth/.env" + mv "/opt/2fauth-backup/storage" "/opt/2fauth/storage" + cd "/opt/2fauth" || return + chown -R www-data: "/opt/2fauth" + chmod -R 755 "/opt/2fauth" + export COMPOSER_ALLOW_SUPERUSER=1 + $STD composer install --no-dev --prefer-source + php artisan 2fauth:install + $STD systemctl restart nginx + + msg_info "Cleaning Up" + if dpkg -l | grep -q 'php8.2'; then + $STD apt remove --purge -y php8.2* + fi + $STD apt -y autoremove + $STD apt -y autoclean + $STD apt -y clean + msg_ok "Cleanup Completed" + msg_ok "Updated Successfully" + fi + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:80${CL}" diff --git a/scripts/install/2fauth-install.sh b/scripts/install/2fauth-install.sh new file mode 100644 index 0000000..2ef85c8 --- /dev/null +++ b/scripts/install/2fauth-install.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: jkrgr0 +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://docs.2fauth.app/ + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +msg_info "Installing Dependencies" +$STD apt install -y \ + lsb-release \ + nginx +msg_ok "Installed Dependencies" + +PHP_VERSION="8.3" PHP_MODULE="common,ctype,fileinfo,mysql,cli" PHP_FPM="YES" setup_php +setup_composer +setup_mariadb + +msg_info "Setting up Database" +DB_NAME=2fauth_db +DB_USER=2fauth +DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13) +$STD mariadb -u root -e "CREATE DATABASE $DB_NAME;" +$STD mariadb -u root -e "CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';" +$STD mariadb -u root -e "GRANT ALL ON $DB_NAME.* TO '$DB_USER'@'localhost'; FLUSH PRIVILEGES;" +{ + echo "2FAuth Credentials" + echo "Database User: $DB_USER" + echo "Database Password: $DB_PASS" + echo "Database Name: $DB_NAME" +} >>~/2FAuth.creds +msg_ok "Set up Database" + +fetch_and_deploy_gh_release "2fauth" "Bubka/2FAuth" + +msg_info "Setup 2FAuth" +cd /opt/2fauth || exit +cp .env.example .env +IPADDRESS=$(hostname -I | awk '{print $1}') +sed -i -e "s|^APP_URL=.*|APP_URL=http://$IPADDRESS|" \ + -e "s|^DB_CONNECTION=$|DB_CONNECTION=mysql|" \ + -e "s|^DB_DATABASE=$|DB_DATABASE=$DB_NAME|" \ + -e "s|^DB_HOST=$|DB_HOST=127.0.0.1|" \ + -e "s|^DB_PORT=$|DB_PORT=3306|" \ + -e "s|^DB_USERNAME=$|DB_USERNAME=$DB_USER|" \ + -e "s|^DB_PASSWORD=$|DB_PASSWORD=$DB_PASS|" .env +export COMPOSER_ALLOW_SUPERUSER=1 +$STD composer update --no-plugins --no-scripts +$STD composer install --no-dev --prefer-source --no-plugins --no-scripts +$STD php artisan key:generate --force +$STD php artisan migrate:refresh +$STD php artisan passport:install -q -n +$STD php artisan storage:link +$STD php artisan config:cache +chown -R www-data: /opt/2fauth +chmod -R 755 /opt/2fauth +msg_ok "Setup 2fauth" + +msg_info "Configure Service" +cat </etc/nginx/conf.d/2fauth.conf +server { + listen 80; + root /opt/2fauth/public; + server_name $IPADDRESS; + index index.php; + charset utf-8; + + location / { + try_files \$uri \$uri/ /index.php?\$query_string; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + error_page 404 /index.php; + + location ~ \.php\$ { + fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; + fastcgi_param SCRIPT_FILENAME \$realpath_root\$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\.(?!well-known).* { + deny all; + } +} +EOF +systemctl reload nginx +msg_ok "Configured Service" + +motd_ssh +customize + +msg_info "Cleaning up" +$STD apt -y autoremove +$STD apt -y autoclean +$STD apt -y clean +msg_ok "Cleaned" diff --git a/src/server/lib/autoSyncInit.js b/src/server/lib/autoSyncInit.js index 2ecf2fb..7c9a587 100644 --- a/src/server/lib/autoSyncInit.js +++ b/src/server/lib/autoSyncInit.js @@ -1,14 +1,21 @@ import { AutoSyncService } from '../services/autoSyncService.js'; let autoSyncService = null; +let isInitialized = false; /** * Initialize auto-sync service and schedule cron job if enabled */ export function initializeAutoSync() { + if (isInitialized) { + console.log('Auto-sync service already initialized, skipping...'); + return; + } + try { console.log('Initializing auto-sync service...'); autoSyncService = new AutoSyncService(); + isInitialized = true; console.log('AutoSyncService instance created'); // Load settings and schedule if enabled @@ -39,6 +46,7 @@ export function stopAutoSync() { console.log('Stopping auto-sync service...'); autoSyncService.stopAutoSync(); autoSyncService = null; + isInitialized = false; console.log('Auto-sync service stopped'); } } catch (error) { diff --git a/src/server/services/autoSyncService.js b/src/server/services/autoSyncService.js index fec2ec4..0d1c30d 100644 --- a/src/server/services/autoSyncService.js +++ b/src/server/services/autoSyncService.js @@ -6,6 +6,9 @@ import { readFile, writeFile, readFileSync, writeFileSync } from 'fs'; import { join } from 'path'; import cronValidator from 'cron-validator'; +// Global lock to prevent multiple autosync instances from running simultaneously +let globalAutoSyncLock = false; + export class AutoSyncService { constructor() { this.cronJob = null; @@ -49,8 +52,8 @@ export class AutoSyncService { notificationEnabled: false, appriseUrls: [], lastAutoSync: '', - lastAutoSyncError: null, - lastAutoSyncErrorTime: null + lastAutoSyncError: '', + lastAutoSyncErrorTime: '' }; const lines = envContent.split('\n'); @@ -118,8 +121,8 @@ export class AutoSyncService { notificationEnabled: false, appriseUrls: [], lastAutoSync: '', - lastAutoSyncError: null, - lastAutoSyncErrorTime: null + lastAutoSyncError: '', + lastAutoSyncErrorTime: '' }; } } @@ -221,6 +224,12 @@ export class AutoSyncService { return; } + // Check if there's already a global autosync running + if (globalAutoSyncLock) { + console.log('Auto-sync is already running globally, not scheduling new cron job'); + return; + } + let cronExpression; if (settings.syncIntervalType === 'custom') { @@ -248,8 +257,14 @@ export class AutoSyncService { console.log(`Scheduling auto-sync with cron expression: ${cronExpression}`); this.cronJob = cron.schedule(cronExpression, async () => { + // Check global lock first + if (globalAutoSyncLock) { + console.log('Auto-sync already running globally, skipping cron execution...'); + return; + } + if (this.isRunning) { - console.log('Auto-sync already running, skipping...'); + console.log('Auto-sync already running locally, skipping...'); return; } @@ -289,11 +304,19 @@ export class AutoSyncService { * Execute auto-sync process */ async executeAutoSync() { - if (this.isRunning) { - console.log('Auto-sync already running, skipping...'); - return { success: false, message: 'Auto-sync already running' }; + // Check global lock first + if (globalAutoSyncLock) { + console.log('Auto-sync already running globally, skipping...'); + return { success: false, message: 'Auto-sync already running globally' }; } + if (this.isRunning) { + console.log('Auto-sync already running locally, skipping...'); + return { success: false, message: 'Auto-sync already running locally' }; + } + + // Set global lock + globalAutoSyncLock = true; this.isRunning = true; const startTime = new Date(); @@ -434,8 +457,9 @@ export class AutoSyncService { // Step 3: Send notifications if enabled if (settings.notificationEnabled && settings.appriseUrls?.length > 0) { - console.log('Sending notifications...'); + console.log('Sending success notifications...'); await this.sendSyncNotification(results); + console.log('Success notifications sent'); } // Step 4: Update last sync time and clear any previous errors @@ -504,6 +528,7 @@ export class AutoSyncService { }; } finally { this.isRunning = false; + globalAutoSyncLock = false; // Release global lock } }