Update install.sh
This commit is contained in:
parent
f5efd82ed9
commit
60ed8cd244
1 changed files with 17 additions and 44 deletions
61
install.sh
61
install.sh
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue