#!/usr/bin/env bash # Copyright (c) 2021-2025 community-scripts ORG # Author: GitHub Copilot # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # Source: https://github.com/Dictionarry-Hub/profilarr source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" color verb_ip6 catch_errors setting_up_container network_check update_os APPLICATION="profilarr" msg_info "Installing Dependencies" $STD apt-get install -y \ unzip \ build-essential \ libyaml-dev \ python3-dev \ git msg_ok "Installed Dependencies" msg_info "Installing Python" $STD apt-get install -y \ python3 \ python3-venv msg_ok "Installed Python" msg_info "Setup uv" setup_uv msg_ok "Setup uv" msg_info "Installing Node.js" NODE_VERSION="20" install_node_and_modules msg_ok "Installed Node.js" msg_info "Setup ${APPLICATION}" RELEASE=$(curl -fsSL https://api.github.com/repos/BiluliB/profilarr/releases/latest | grep "tag_name" | cut -d'"' -f4) if [[ -z "$RELEASE" ]]; then msg_error "Failed to fetch latest release version" exit 1 fi temp_file=$(mktemp) $STD curl -fsSL -o "$temp_file" "https://github.com/BiluliB/profilarr/archive/refs/tags/${RELEASE}.zip" $STD unzip -q "$temp_file" -d /tmp $STD mkdir -p /opt/${APPLICATION} $STD mkdir -p /opt/${APPLICATION}_config # Find the actual extracted directory name EXTRACTED_DIR=$(find /tmp -maxdepth 1 -name "profilarr-*" -type d | head -n1) if [[ -z "$EXTRACTED_DIR" ]]; then msg_error "Failed to find extracted directory" exit 1 fi $STD mv "${EXTRACTED_DIR}/backend" /opt/${APPLICATION}/ $STD mv "${EXTRACTED_DIR}/frontend" /opt/${APPLICATION}/ echo "${RELEASE}" >/opt/${APPLICATION}_version.txt $STD uv venv /opt/${APPLICATION}/venv # Install compatible PyYAML first, then exclude it from requirements $STD uv pip install --python /opt/${APPLICATION}/venv/bin/python "PyYAML>=6.0" # Create modified requirements file temp_req_file=$(mktemp) grep -v "^PyYAML" /opt/${APPLICATION}/backend/requirements.txt >"$temp_req_file" echo "PyYAML>=6.0" >>"$temp_req_file" $STD uv pip install --python /opt/${APPLICATION}/venv/bin/python -r "$temp_req_file" $STD uv pip install --python /opt/${APPLICATION}/venv/bin/python gunicorn # Store the modified requirements checksum for future updates md5sum "$temp_req_file" | awk '{print $1}' >/opt/${APPLICATION}/.requirements_checksum rm -f "$temp_req_file" msg_ok "Setup ${APPLICATION}" msg_info "Building Frontend" cd /opt/${APPLICATION}/frontend $STD npm install $STD npm run build $STD mkdir -p /opt/${APPLICATION}/backend/app/static $STD cp -r dist/* /opt/${APPLICATION}/backend/app/static/ msg_ok "Built Frontend" msg_info "Creating Service" cat </etc/systemd/system/${APPLICATION}.service [Unit] Description=Profilarr Profile Manager After=network.target [Service] Type=simple User=root WorkingDirectory=/opt/${APPLICATION}/backend Environment=PATH=/opt/${APPLICATION}/venv/bin:/usr/local/bin:/usr/bin:/bin Environment=CONFIG_PATH=/opt/${APPLICATION}_config Environment=PYTHONPATH=/opt/${APPLICATION}/backend Environment=GIT_PYTHON_REFRESH=quiet ExecStart=/opt/${APPLICATION}/venv/bin/python -m gunicorn --bind 0.0.0.0:6868 --workers 2 --timeout 120 --pythonpath /opt/${APPLICATION}/backend "app.main:create_app()" Restart=always RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target EOF $STD systemctl daemon-reload # Test the application manually first msg_info "Testing Application" cd /opt/${APPLICATION}/backend # Check if the main module exists and is importable if ! /opt/${APPLICATION}/venv/bin/python -c " import sys import os sys.path.insert(0, '/opt/${APPLICATION}/backend') os.environ['GIT_PYTHON_REFRESH'] = 'quiet' try: import app.main print('✓ app.main imported successfully') except ImportError as e: print(f'✗ Import error: {e}') sys.exit(1) except Exception as e: print(f'✗ Other error: {e}') sys.exit(1) "; then msg_error "Application import test failed" # Try alternative approaches msg_info "Trying alternative startup methods" # Check if there's a direct run.py or main.py if [[ -f "/opt/${APPLICATION}/backend/run.py" ]]; then cat </etc/systemd/system/${APPLICATION}.service [Unit] Description=Profilarr Profile Manager After=network.target [Service] Type=simple User=root WorkingDirectory=/opt/${APPLICATION}/backend Environment=PATH=/opt/${APPLICATION}/venv/bin:/usr/local/bin:/usr/bin:/bin Environment=CONFIG_PATH=/opt/${APPLICATION}_config Environment=GIT_PYTHON_REFRESH=quiet ExecStart=/opt/${APPLICATION}/venv/bin/python run.py Restart=always RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target EOF elif [[ -f "/opt/${APPLICATION}/backend/main.py" ]]; then cat </etc/systemd/system/${APPLICATION}.service [Unit] Description=Profilarr Profile Manager After=network.target [Service] Type=simple User=root WorkingDirectory=/opt/${APPLICATION}/backend Environment=PATH=/opt/${APPLICATION}/venv/bin:/usr/local/bin:/usr/bin:/bin Environment=CONFIG_PATH=/opt/${APPLICATION}_config Environment=GIT_PYTHON_REFRESH=quiet ExecStart=/opt/${APPLICATION}/venv/bin/python main.py Restart=always RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target EOF else # Fallback to Flask development server cat </etc/systemd/system/${APPLICATION}.service [Unit] Description=Profilarr Profile Manager After=network.target [Service] Type=simple User=root WorkingDirectory=/opt/${APPLICATION}/backend Environment=PATH=/opt/${APPLICATION}/venv/bin:/usr/local/bin:/usr/bin:/bin Environment=CONFIG_PATH=/opt/${APPLICATION}_config Environment=GIT_PYTHON_REFRESH=quiet Environment=FLASK_APP=app.main:create_app Environment=FLASK_RUN_HOST=0.0.0.0 Environment=FLASK_RUN_PORT=6868 ExecStart=/opt/${APPLICATION}/venv/bin/python -m flask run Restart=always RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target EOF fi $STD systemctl daemon-reload fi systemctl enable ${APPLICATION} systemctl start ${APPLICATION} # Wait and check status sleep 5 if systemctl is-active --quiet ${APPLICATION}; then msg_ok "Service started successfully" else msg_error "Service failed to start. Checking logs..." journalctl -u ${APPLICATION} --no-pager -n 20 exit 1 fi msg_ok "Created Service" motd_ssh customize msg_info "Cleaning up" rm -f "$temp_file" rm -rf "$EXTRACTED_DIR" $STD apt-get -y autoremove $STD apt-get -y autoclean msg_ok "Cleaned"