Update install.sh

This commit is contained in:
DualStackAdmin 2025-10-09 01:48:37 +04:00 committed by GitHub
parent f5efd82ed9
commit 60ed8cd244
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,13 +1,13 @@
#!/bin/bash #!/bin/bash
#================================================================================= #=================================================================================
# M365 Mailbox Backup Tool - Automatic Installation Script (v2.0 - Final English) # M365 Mailbox Backup Tool - Automatic Installation Script (v2.1 - Interactive IP)
#================================================================================= #=================================================================================
# --- Color Codes --- # --- Color Codes ---
GREEN='\033[0;32m' GREEN='\033[0;32m'
RED='\033[0;31m' RED='\033[0;31m'
NC='\033[0m' NC='\033[0m' # No Color
if [ "$EUID" -ne 0 ]; then if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Please run this script with 'sudo': sudo ./install.sh${NC}" echo -e "${RED}Please run this script with 'sudo': sudo ./install.sh${NC}"
@ -16,12 +16,16 @@ fi
echo "=== M365 Backup Tool Installation Started ===" echo "=== M365 Backup Tool Installation Started ==="
# --- User Input Block --- # --- NEW: INTERACTIVE IP PROMPT ---
IP_ADDR=$(hostname -I | awk '{print $1}') DETECTED_IP=$(hostname -I | awk '{print $1}')
read -p "Enter the server IP address or press Enter to use the detected IP [$DETECTED_IP]: " USER_IP
IP_ADDR=${USER_IP:-$DETECTED_IP}
# ------------------------------------
REDIRECT_URI="https://$IP_ADDR/get_token" REDIRECT_URI="https://$IP_ADDR/get_token"
echo "---------------------------------------------------------------------" echo "---------------------------------------------------------------------"
echo "IMPORTANT: Please go to your Azure AD App Registration and add the following" echo "IMPORTANT: Please ensure you have set the following Redirect URI"
echo "Redirect URI under the 'Web' platform section:" echo "in your Azure AD App Registration under the 'Web' platform section:"
echo "" echo ""
echo "$REDIRECT_URI" echo "$REDIRECT_URI"
echo "" echo ""
@ -35,13 +39,13 @@ read CLIENT_ID
echo -n "Enter your Client Secret Value (will not be visible): " echo -n "Enter your Client Secret Value (will not be visible): "
read -s CLIENT_SECRET read -s CLIENT_SECRET
echo "" echo ""
echo -n "Enter comma-separated emails of allowed admins (e.g., admin1@company.com): " echo -n "Enter comma-separated emails of allowed admins: "
read ALLOWED_ADMINS read ALLOWED_ADMINS
echo "" echo ""
echo "Credentials received." echo "Credentials received."
# --- System Dependency Installation --- # --- System Dependency Installation ---
echo "--> Updating system and installing required packages (nginx, redis, python3-venv)..." echo "--> Updating system and installing required packages..."
apt-get update > /dev/null 2>&1 apt-get update > /dev/null 2>&1
apt-get install -y nginx redis-server python3-pip python3-venv > /dev/null 2>&1 apt-get install -y nginx redis-server python3-pip python3-venv > /dev/null 2>&1
@ -62,8 +66,6 @@ pip install Flask gunicorn "celery[redis]" msal requests python-decouple > /dev/
deactivate deactivate
# --- Application File Creation --- # --- Application File Creation ---
# .env file
echo "--> Creating .env file for credentials..." echo "--> Creating .env file for credentials..."
cat <<EOF > .env cat <<EOF > .env
TENANT_ID=${TENANT_ID} TENANT_ID=${TENANT_ID}
@ -74,8 +76,8 @@ ALLOWED_ADMINS=${ALLOWED_ADMINS}
REDIRECT_URI=${REDIRECT_URI} REDIRECT_URI=${REDIRECT_URI}
EOF EOF
echo "--> Creating application files and templates..."
# app.py file # app.py file
echo "--> Creating main application file (app.py)..."
cat <<'EOF' > app.py cat <<'EOF' > app.py
import os, re, msal, requests, json, secrets import os, re, msal, requests, json, secrets
from flask import Flask, request, redirect, url_for, render_template, send_from_directory, session from flask import Flask, request, redirect, url_for, render_template, send_from_directory, session
@ -86,7 +88,6 @@ from datetime import datetime, timedelta
from decouple import config from decouple import config
from functools import wraps from functools import wraps
# --- CONFIGURATION ---
TENANT_ID = config('TENANT_ID', default='') TENANT_ID = config('TENANT_ID', default='')
CLIENT_ID = config('CLIENT_ID', default='') CLIENT_ID = config('CLIENT_ID', default='')
CLIENT_SECRET = config('CLIENT_SECRET', default='') CLIENT_SECRET = config('CLIENT_SECRET', default='')
@ -115,8 +116,7 @@ def login_required(f):
return decorated_function return decorated_function
@app.route('/login_page') @app.route('/login_page')
def show_login_page(): def show_login_page(): return render_template('login.html')
return render_template('login.html')
@app.route('/login') @app.route('/login')
def login(): def login():
@ -139,8 +139,7 @@ def get_token():
@app.route('/logout') @app.route('/logout')
def logout(): def logout():
session.clear() session.clear()
logout_uri = f"https://login.microsoftonline.com/common/oauth2/v2.0/logout?post_logout_redirect_uri={url_for('show_login_page', _external=True, _scheme='https')}" return redirect(f"https://login.microsoftonline.com/common/oauth2/v2.0/logout?post_logout_redirect_uri={url_for('show_login_page', _external=True, _scheme='https')}")
return redirect(logout_uri)
def sanitize_filename(filename): return re.sub(r'[\\/*?:"<>|]', "", filename) def sanitize_filename(filename): return re.sub(r'[\\/*?:"<>|]', "", filename)
@ -191,11 +190,9 @@ def trigger_backup(user_email, date_option='all_time', start_date_str=None, end_
messages_url = messages_data.get('@odata.nextLink') messages_url = messages_data.get('@odata.nextLink')
total_message_count += message_count_in_folder total_message_count += message_count_in_folder
return f"Total {total_message_count} emails downloaded." return f"Total {total_message_count} emails downloaded."
@app.route('/') @app.route('/')
@login_required @login_required
def index(): return render_template('index.html') def index(): return render_template('index.html')
@app.route('/get_users') @app.route('/get_users')
@login_required @login_required
def get_users(): def get_users():
@ -209,7 +206,6 @@ def get_users():
if response.status_code != 200: return render_template('partials/user_list_error.html', error_message="Error fetching users.") if response.status_code != 200: return render_template('partials/user_list_error.html', error_message="Error fetching users.")
data = response.json(); users.extend(data.get('value', [])); users_url = data.get('@odata.nextLink') data = response.json(); users.extend(data.get('value', [])); users_url = data.get('@odata.nextLink')
return render_template('partials/user_list.html', users=sorted(users, key=lambda u: u.get('displayName') or '')) return render_template('partials/user_list.html', users=sorted(users, key=lambda u: u.get('displayName') or ''))
@app.route('/backup', methods=['POST']) @app.route('/backup', methods=['POST'])
@login_required @login_required
def backup(): def backup():
@ -220,7 +216,6 @@ def backup():
session['tasks'].append({'id': task.id, 'email': email}) session['tasks'].append({'id': task.id, 'email': email})
session.modified = True session.modified = True
return redirect(url_for('list_tasks')) return redirect(url_for('list_tasks'))
@app.route('/tasks') @app.route('/tasks')
@login_required @login_required
def list_tasks(): def list_tasks():
@ -229,13 +224,11 @@ def list_tasks():
task = trigger_backup.AsyncResult(task_meta.get('id')) task = trigger_backup.AsyncResult(task_meta.get('id'))
task_info.append({'id': task_meta.get('id'), 'email': task_meta.get('email'), 'status': task.state, 'result': task.result if task.state in ['SUCCESS', 'FAILURE'] else str(task.info)}) task_info.append({'id': task_meta.get('id'), 'email': task_meta.get('email'), 'status': task.state, 'result': task.result if task.state in ['SUCCESS', 'FAILURE'] else str(task.info)})
return render_template('tasks.html', tasks=reversed(task_info)) return render_template('tasks.html', tasks=reversed(task_info))
@app.route('/backups') @app.route('/backups')
@login_required @login_required
def list_backups(): def list_backups():
if not os.path.exists(BACKUP_DIR): os.makedirs(BACKUP_DIR) if not os.path.exists(BACKUP_DIR): os.makedirs(BACKUP_DIR)
return render_template('backups.html', users=os.listdir(BACKUP_DIR)) return render_template('backups.html', users=os.listdir(BACKUP_DIR))
@app.route('/backups/<path:user_email>') @app.route('/backups/<path:user_email>')
@login_required @login_required
def restore_portal(user_email): def restore_portal(user_email):
@ -259,12 +252,9 @@ def restore_portal(user_email):
if search_query not in content_to_search.lower(): continue if search_query not in content_to_search.lower(): continue
emails.append({'filename': filename, 'subject': msg['subject'], 'from': msg['from'], 'date': msg['date']}) emails.append({'filename': filename, 'subject': msg['subject'], 'from': msg['from'], 'date': msg['date']})
return render_template('portal.html', user_email=user_email, folders=sorted(all_folders), emails=emails, active_folder=active_folder_name, search_query=search_query) return render_template('portal.html', user_email=user_email, folders=sorted(all_folders), emails=emails, active_folder=active_folder_name, search_query=search_query)
@app.route('/download/<path:user_email>/<path:folder_name>/<path:filename>') @app.route('/download/<path:user_email>/<path:folder_name>/<path:filename>')
@login_required @login_required
def download_file(user_email, folder_name, filename): def download_file(user_email, folder_name, filename): return send_from_directory(directory=os.path.join(BACKUP_DIR, user_email, folder_name), path=filename, as_attachment=True)
return send_from_directory(directory=os.path.join(BACKUP_DIR, user_email, folder_name), path=filename, as_attachment=True)
@app.route('/preview/<path:user_email>/<path:folder_name>/<path:filename>') @app.route('/preview/<path:user_email>/<path:folder_name>/<path:filename>')
@login_required @login_required
def preview_file(user_email, folder_name, filename): def preview_file(user_email, folder_name, filename):
@ -283,18 +273,13 @@ def preview_file(user_email, folder_name, filename):
else: return {"body": "Could not display content."} else: return {"body": "Could not display content."}
EOF EOF
# --- Template File Creation ---
echo "--> Creating HTML templates..."
mkdir -p templates/partials mkdir -p templates/partials
# login.html
cat <<'EOF' > templates/login.html cat <<'EOF' > templates/login.html
<!doctype html><html lang="en" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Login - M365 Backup Tool</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css"></head><body><main class="container"><article style="margin-top: 5rem;"><h1 style="text-align: center;">M365 Backup Tool</h1><p style="text-align: center;">Please log in with your Microsoft account to continue.</p><div style="text-align: center; margin-top: 2rem;"><a href="/login" role="button">Login with Microsoft</a></div></article></main></body></html> <!doctype html><html lang="en" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Login - M365 Backup Tool</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css"></head><body><main class="container"><article style="margin-top: 5rem;"><h1 style="text-align: center;">M365 Backup Tool</h1><p style="text-align: center;">Please log in with your Microsoft account to continue.</p><div style="text-align: center; margin-top: 2rem;"><a href="/login" role="button">Login with Microsoft</a></div></article></main></body></html>
EOF EOF
# layout.html
cat <<'EOF' > templates/layout.html cat <<'EOF' > templates/layout.html
<!doctype html><html lang="en" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>{% block title %}{% endblock %} - M365 Backup Tool</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css"><style>:root { --pico-font-size: 100%; } .htmx-indicator{opacity:0;transition:opacity 200ms ease-in;text-align:center;margin-top:1rem;} .htmx-request .htmx-indicator,.htmx-request.htmx-indicator{opacity:1}</style></head><body><main class="container"><nav><ul><li><strong>M365 Backup Tool</strong></li></ul><ul><li><a href="/">New Backup</a></li><li><a href="/backups">Existing Backups</a></li><li><a href="/tasks">Tasks</a></li>{% if session.user %}<li><a href="/logout" role="button" class="secondary outline">Logout ({{ session.user.name }})</a></li>{% endif %}</ul></nav><hr>{% block content %}{% endblock %}</main><script src="https://unpkg.com/htmx.org@1.9.10"></script></body></html> <!doctype html><html lang="en" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>{% block title %}{% endblock %} - M365 Backup Tool</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css"><style>:root { --pico-font-size: 100%; } .htmx-indicator{opacity:0;transition:opacity 200ms ease-in;text-align:center;margin-top:1rem;} .htmx-request .htmx-indicator,.htmx-request.htmx-indicator{opacity:1}</style></head><body><main class="container"><nav><ul><li><strong>M365 Backup Tool</strong></li></ul><ul><li><a href="/">New Backup</a></li><li><a href="/backups">Existing Backups</a></li><li><a href="/tasks">Tasks</a></li>{% if session.user %}<li><a href="/logout" role="button" class="secondary outline">Logout ({{ session.user.name }})</a></li>{% endif %}</ul></nav><hr>{% block content %}{% endblock %}</main><script src="https://unpkg.com/htmx.org@1.9.10"></script></body></html>
EOF EOF
# index.html
cat <<'EOF' > templates/index.html cat <<'EOF' > templates/index.html
{% extends 'layout.html' %}{% block title %}New Backup{% endblock %}{% block content %}<h2>Start New Backup</h2><form action="/backup" method="post"><div style="position: sticky; top: 0; background-color: var(--background-color); padding-top: 1rem; padding-bottom: 1rem; z-index: 10;"><div class="grid"><label for="date_option">Select Date Range<select id="date_option" name="date_option" onchange="toggleCustomDate()"><option value="all_time" selected>All Time</option><option value="last_3_months">Last 3 Months</option><option value="last_6_months">Last 6 Months</option><option value="last_1_year">Last 1 Year</option><option value="last_2_years">Last 2 Years</option><option value="custom_range">Custom Range</option></select></label><div></div></div><div id="custom_date_fields" style="display: none;" class="grid"><label for="start_date">Start Date<input type="date" id="start_date" name="start_date"></label><label for="end_date">End Date<input type="date" id="end_date" name="end_date"></label></div><button type="submit" style="margin-top: 1rem;">Backup Selected Users</button><hr></div><p>Select users to back up:</p><div hx-get="/get_users" hx-trigger="load" hx-indicator="#user-list-indicator"><p id="user-list-indicator" class="htmx-indicator"><progress></progress><br>Loading user list from Azure...</p></div></form><script> {% extends 'layout.html' %}{% block title %}New Backup{% endblock %}{% block content %}<h2>Start New Backup</h2><form action="/backup" method="post"><div style="position: sticky; top: 0; background-color: var(--background-color); padding-top: 1rem; padding-bottom: 1rem; z-index: 10;"><div class="grid"><label for="date_option">Select Date Range<select id="date_option" name="date_option" onchange="toggleCustomDate()"><option value="all_time" selected>All Time</option><option value="last_3_months">Last 3 Months</option><option value="last_6_months">Last 6 Months</option><option value="last_1_year">Last 1 Year</option><option value="last_2_years">Last 2 Years</option><option value="custom_range">Custom Range</option></select></label><div></div></div><div id="custom_date_fields" style="display: none;" class="grid"><label for="start_date">Start Date<input type="date" id="start_date" name="start_date"></label><label for="end_date">End Date<input type="date" id="end_date" name="end_date"></label></div><button type="submit" style="margin-top: 1rem;">Backup Selected Users</button><hr></div><p>Select users to back up:</p><div hx-get="/get_users" hx-trigger="load" hx-indicator="#user-list-indicator"><p id="user-list-indicator" class="htmx-indicator"><progress></progress><br>Loading user list from Azure...</p></div></form><script>
function toggleCustomDate() { function toggleCustomDate() {
@ -303,22 +288,18 @@ if (dateOption.value === 'custom_range') { customDateFields.style.display = 'gri
} }
</script>{% endblock %} </script>{% endblock %}
EOF EOF
# partials/user_list.html
cat <<'EOF' > templates/partials/user_list.html cat <<'EOF' > templates/partials/user_list.html
<figure><table><thead><tr><th scope="col" style="width:50px;">Select</th><th scope="col">Display Name</th><th scope="col">Email</th></tr></thead><tbody> <figure><table><thead><tr><th scope="col" style="width:50px;">Select</th><th scope="col">Display Name</th><th scope="col">Email</th></tr></thead><tbody>
{% for user in users %}<tr><td><input type="checkbox" name="emails" value="{{ user.userPrincipalName }}"></td><td>{{ user.displayName }}</td><td>{{ user.userPrincipalName }}</td></tr> {% for user in users %}<tr><td><input type="checkbox" name="emails" value="{{ user.userPrincipalName }}"></td><td>{{ user.displayName }}</td><td>{{ user.userPrincipalName }}</td></tr>
{% else %}<tr><td colspan="3">No users found or error loading users.</td></tr>{% endfor %} {% else %}<tr><td colspan="3">No users found or error loading users.</td></tr>{% endfor %}
</tbody></table></figure> </tbody></table></figure>
EOF EOF
# partials/user_list_error.html
cat <<'EOF' > templates/partials/user_list_error.html cat <<'EOF' > templates/partials/user_list_error.html
<article><h5 style="color:var(--pico-color-red);">Error Loading Users</h5><p>Could not load the user list.</p><p><small><strong>Details:</strong> {{ error_message }}</small></p></article> <article><h5 style="color:var(--pico-color-red);">Error Loading Users</h5><p>Could not load the user list.</p><p><small><strong>Details:</strong> {{ error_message }}</small></p></article>
EOF EOF
# backups.html
cat <<'EOF' > templates/backups.html cat <<'EOF' > templates/backups.html
{% extends 'layout.html' %}{% block title %}Existing Backups{% endblock %}{% block content %}<h2>Backed Up Users</h2><ul>{% for user in users %}<li><a href="/backups/{{ user }}">{{ user }}</a></li>{% else %}<li>No backups found yet.</li>{% endfor %}</ul>{% endblock %} {% extends 'layout.html' %}{% block title %}Existing Backups{% endblock %}{% block content %}<h2>Backed Up Users</h2><ul>{% for user in users %}<li><a href="/backups/{{ user }}">{{ user }}</a></li>{% else %}<li>No backups found yet.</li>{% endfor %}</ul>{% endblock %}
EOF EOF
# portal.html
cat <<'EOF' > templates/portal.html cat <<'EOF' > templates/portal.html
{% extends 'layout.html' %}{% block title %}{{ user_email }} - Restore Portal{% endblock %}{% block content %} {% extends 'layout.html' %}{% block title %}{{ user_email }} - Restore Portal{% endblock %}{% block content %}
<style>.portal-grid{display:grid;grid-template-columns:250px 1fr 1fr;gap:1rem;height:80vh;}.folder-list,.email-list,.preview-pane{overflow-y:auto;height:100%;}.folder-list ul{list-style-type:none;padding:0;margin:0;}.folder-list a{display:block;padding:0.5rem 1rem;border-radius:0.25rem;text-decoration:none;color:var(--pico-contrast);}.folder-list a:hover{background-color:var(--pico-muted-background-color);}.folder-list a.active{background-color:var(--pico-primary);color:var(--pico-primary-inverse);}.email-list table tbody tr{cursor:pointer;}.preview-pane iframe{width:100%;height:100%;border:none;filter:blur(8px);transition:filter 0.3s ease-in-out;}.preview-pane:hover iframe{filter:blur(0);}</style> <style>.portal-grid{display:grid;grid-template-columns:250px 1fr 1fr;gap:1rem;height:80vh;}.folder-list,.email-list,.preview-pane{overflow-y:auto;height:100%;}.folder-list ul{list-style-type:none;padding:0;margin:0;}.folder-list a{display:block;padding:0.5rem 1rem;border-radius:0.25rem;text-decoration:none;color:var(--pico-contrast);}.folder-list a:hover{background-color:var(--pico-muted-background-color);}.folder-list a.active{background-color:var(--pico-primary);color:var(--pico-primary-inverse);}.email-list table tbody tr{cursor:pointer;}.preview-pane iframe{width:100%;height:100%;border:none;filter:blur(8px);transition:filter 0.3s ease-in-out;}.preview-pane:hover iframe{filter:blur(0);}</style>
@ -340,7 +321,6 @@ iframe.contentWindow.document.open();iframe.contentWindow.document.write(data.bo
} }
</script>{% endblock %} </script>{% endblock %}
EOF EOF
# tasks.html
cat <<'EOF' > templates/tasks.html cat <<'EOF' > templates/tasks.html
{% extends 'layout.html' %}{% block title %}Task Status{% endblock %}{% block content %}<h2>Task Status</h2><figure><table><thead><tr><th>Task ID</th><th>User</th><th>Status</th><th>Result</th></tr></thead><tbody> {% extends 'layout.html' %}{% block title %}Task Status{% endblock %}{% block content %}<h2>Task Status</h2><figure><table><thead><tr><th>Task ID</th><th>User</th><th>Status</th><th>Result</th></tr></thead><tbody>
{% for task in tasks %}<tr><td><small>{{ task.id }}</small></td><td>{{ task.email }}</td><td>{{ task.status }}</td><td><small>{{ task.result }}</small></td></tr> {% for task in tasks %}<tr><td><small>{{ task.id }}</small></td><td>{{ task.email }}</td><td>{{ task.status }}</td><td><small>{{ task.result }}</small></td></tr>
@ -348,15 +328,13 @@ cat <<'EOF' > templates/tasks.html
</tbody></table></figure><a href="{{ url_for('list_tasks') }}" role="button" class="secondary outline">Refresh Page</a>{% endblock %} </tbody></table></figure><a href="{{ url_for('list_tasks') }}" role="button" class="secondary outline">Refresh Page</a>{% endblock %}
EOF EOF
# --- SSL Certificate Creation ---
echo "--> Creating self-signed SSL certificate..." echo "--> Creating self-signed SSL certificate..."
mkdir -p /etc/nginx/ssl mkdir -p /etc/nginx/ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/m365-backup.key \ -keyout /etc/nginx/ssl/m365-backup.key \
-out /etc/nginx/ssl/m365-backup.crt \ -out /etc/nginx/ssl/m365-backup.crt \
-subj "/C=AZ/ST=Baku/L=Baku/O=DevOps/OU=IT/CN=$IP_ADDR" -subj "/C=XX/ST=Global/L=Global/O=Global/OU=IT/CN=$IP_ADDR"
# --- Service File Creation ---
echo "--> Creating systemd service files..." echo "--> Creating systemd service files..."
cat <<EOF > /etc/systemd/system/m365-web.service cat <<EOF > /etc/systemd/system/m365-web.service
[Unit] [Unit]
@ -386,7 +364,6 @@ Restart=always
WantedBy=multi-user.target WantedBy=multi-user.target
EOF EOF
# --- Nginx Configuration ---
echo "--> Creating Nginx proxy configuration..." echo "--> Creating Nginx proxy configuration..."
cat <<EOF > /etc/nginx/sites-available/m365-backup cat <<EOF > /etc/nginx/sites-available/m365-backup
server { server {
@ -400,7 +377,6 @@ server {
ssl_certificate /etc/nginx/ssl/m365-backup.crt; ssl_certificate /etc/nginx/ssl/m365-backup.crt;
ssl_certificate_key /etc/nginx/ssl/m365-backup.key; ssl_certificate_key /etc/nginx/ssl/m365-backup.key;
ssl_protocols TLSv1.2 TLSv1.3; ssl_protocols TLSv1.2 TLSv1.3;
location / { location / {
include proxy_params; include proxy_params;
proxy_pass http://unix:$PROJECT_DIR/m365-backup.sock; proxy_pass http://unix:$PROJECT_DIR/m365-backup.sock;
@ -410,11 +386,9 @@ EOF
rm -f /etc/nginx/sites-enabled/default rm -f /etc/nginx/sites-enabled/default
ln -sf /etc/nginx/sites-available/m365-backup /etc/nginx/sites-enabled/ ln -sf /etc/nginx/sites-available/m365-backup /etc/nginx/sites-enabled/
# --- Folder Permissions ---
chown -R $USERNAME:www-data "$PROJECT_DIR" chown -R $USERNAME:www-data "$PROJECT_DIR"
chmod -R 775 "$PROJECT_DIR" chmod -R 775 "$PROJECT_DIR"
# --- Service Start and Verification ---
echo "--> Starting and enabling all services..." echo "--> Starting and enabling all services..."
systemctl daemon-reload systemctl daemon-reload
systemctl restart redis-server nginx systemctl restart redis-server nginx
@ -433,7 +407,6 @@ if systemctl is-active --quiet m365-worker.service; then
else else
echo -e "${RED}❌ Background Worker (Celery) failed to start!${NC}"; FINAL_STATUS=1; fi echo -e "${RED}❌ Background Worker (Celery) failed to start!${NC}"; FINAL_STATUS=1; fi
# --- Final Message ---
echo "" echo ""
echo "============================================================" echo "============================================================"
if [ $FINAL_STATUS -eq 0 ]; then if [ $FINAL_STATUS -eq 0 ]; then