From 1f89e4aaed05e9dba8fa98622b94f6ef50826fa4 Mon Sep 17 00:00:00 2001 From: Mo8it Date: Sun, 8 May 2022 17:29:45 +0200 Subject: [PATCH] Organize docs and scripts --- .../scripts/maintain/copy_admin_templates.py | 0 advlabdb/scripts/maintain/root_update.py | 16 ++ advlabdb/scripts/maintain/shared.py | 11 ++ advlabdb/scripts/maintain/update_docs.py | 20 +++ advlabdb/scripts/maintain/user_update.py | 23 +++ .../scripts/setup}/advlabdb.conf | 0 .../scripts/setup}/gunicorn.service | 0 .../scripts/setup/init_db.py | 67 +------- advlabdb/scripts/setup/logged_server_setup.py | 97 +++++++++++ advlabdb/scripts/setup/server_setup.py | 20 +++ advlabdb/scripts/setup/shared.py | 17 ++ advlabdb/scripts/terminal_utils.py | 95 +++++++++++ advlabdb/scripts/test/test_database.py | 150 ++++++++++++++++++ DB.drawio => docs/DB.drawio | 0 .../database_import/database_import.adoc | 0 .../example_database_import.txt | 0 docs/update_docs.sh | 23 --- docs/{ => user_docs}/README.adoc | 0 docs/{ => user_docs}/admin.adoc | 0 docs/{ => user_docs}/assistant.adoc | 0 docs/user_docs/server_setup.adoc | 68 ++++++++ scripts/logged_server_setup.py | 93 ----------- scripts/root_update.py | 23 --- scripts/server_setup.py | 22 --- scripts/shared.py | 55 ------- scripts/user_update.py | 29 ---- testDB.py | 145 ----------------- 27 files changed, 520 insertions(+), 454 deletions(-) rename copy_admin_templates.py => advlabdb/scripts/maintain/copy_admin_templates.py (100%) create mode 100644 advlabdb/scripts/maintain/root_update.py create mode 100644 advlabdb/scripts/maintain/shared.py create mode 100644 advlabdb/scripts/maintain/update_docs.py create mode 100644 advlabdb/scripts/maintain/user_update.py rename {scripts => advlabdb/scripts/setup}/advlabdb.conf (100%) rename {scripts => advlabdb/scripts/setup}/gunicorn.service (100%) rename init_db.py => advlabdb/scripts/setup/init_db.py (68%) create mode 100644 advlabdb/scripts/setup/logged_server_setup.py create mode 100644 advlabdb/scripts/setup/server_setup.py create mode 100644 advlabdb/scripts/setup/shared.py create mode 100644 advlabdb/scripts/terminal_utils.py create mode 100644 advlabdb/scripts/test/test_database.py rename DB.drawio => docs/DB.drawio (100%) rename database_import.adoc => docs/database_import/database_import.adoc (100%) rename example_database_import.txt => docs/database_import/example_database_import.txt (100%) delete mode 100644 docs/update_docs.sh rename docs/{ => user_docs}/README.adoc (100%) rename docs/{ => user_docs}/admin.adoc (100%) rename docs/{ => user_docs}/assistant.adoc (100%) create mode 100644 docs/user_docs/server_setup.adoc delete mode 100644 scripts/logged_server_setup.py delete mode 100644 scripts/root_update.py delete mode 100644 scripts/server_setup.py delete mode 100644 scripts/shared.py delete mode 100644 scripts/user_update.py delete mode 100644 testDB.py diff --git a/copy_admin_templates.py b/advlabdb/scripts/maintain/copy_admin_templates.py similarity index 100% rename from copy_admin_templates.py rename to advlabdb/scripts/maintain/copy_admin_templates.py diff --git a/advlabdb/scripts/maintain/root_update.py b/advlabdb/scripts/maintain/root_update.py new file mode 100644 index 0000000..65999d5 --- /dev/null +++ b/advlabdb/scripts/maintain/root_update.py @@ -0,0 +1,16 @@ +from advlabdb.scripts.maintain.shared import show_update_datetime +from advlabdb.scripts.terminal_utils import box, run + + +def main(): + show_update_datetime() + + box("Update system packages") + run("sudo apt update") + run("sudo apt upgrade -y") + + run("sudo reboot") + + +if __name__ == "__main__": + main() diff --git a/advlabdb/scripts/maintain/shared.py b/advlabdb/scripts/maintain/shared.py new file mode 100644 index 0000000..9b98eab --- /dev/null +++ b/advlabdb/scripts/maintain/shared.py @@ -0,0 +1,11 @@ +from datetime import datetime + +from advlabdb.scripts.terminal_utils import box, spaced_hl + + +def show_update_datetime(): + spaced_hl() + + dt = datetime.now().strftime("%d.%m.%Y %H:%M:%S") + + box(dt, "Update on") diff --git a/advlabdb/scripts/maintain/update_docs.py b/advlabdb/scripts/maintain/update_docs.py new file mode 100644 index 0000000..4103132 --- /dev/null +++ b/advlabdb/scripts/maintain/update_docs.py @@ -0,0 +1,20 @@ +# TODO: Port to Python +# Needed packages: asciidoctor + +# List of documentation files +# DOC_FILE_NAMES=$(fd -d 1 -t f --extension adoc --exclude "README.adoc") +# +# for doc_file_name in "${DOC_FILE_NAMES[@]}"; do +# DOC_FILE_NAME_WITHOUT_EXTENSION=${doc_file_name::-5} +# OUTPUT_PATH=../advlabdb/templates/docs/$DOC_FILE_NAME_WITHOUT_EXTENSION.html +# +# # Convert to html with asciidoctor +# asciidoctor -v --backend html5 "$doc_file_name" -o "$OUTPUT_PATH" +# +# # Add the Jinja raw tag +# sed -i "1i {% raw %}" "$OUTPUT_PATH" +# echo -e "\n{% endraw %}" >>"$OUTPUT_PATH" +# +# # Done +# echo "Generated $OUTPUT_PATH" +# done diff --git a/advlabdb/scripts/maintain/user_update.py b/advlabdb/scripts/maintain/user_update.py new file mode 100644 index 0000000..59a66e7 --- /dev/null +++ b/advlabdb/scripts/maintain/user_update.py @@ -0,0 +1,23 @@ +from advlabdb.scripts.maintain.shared import show_update_datetime +from advlabdb.scripts.setup.shared import LOCAL_BIN, install_latest_pipx, poetry_update +from advlabdb.scripts.terminal_utils import box, run + + +def main(): + show_update_datetime() + + box("Upgrade pipx") + install_latest_pipx() + + box("Upgrade pipx packages") + pipx_bin = LOCAL_BIN / "pipx" + run(f"{pipx_bin} upgrade-all --include-injected") + + # TODO: Backup + + box("Install latest Poetry packages") + poetry_update() + + +if __name__ == "__main__": + main() diff --git a/scripts/advlabdb.conf b/advlabdb/scripts/setup/advlabdb.conf similarity index 100% rename from scripts/advlabdb.conf rename to advlabdb/scripts/setup/advlabdb.conf diff --git a/scripts/gunicorn.service b/advlabdb/scripts/setup/gunicorn.service similarity index 100% rename from scripts/gunicorn.service rename to advlabdb/scripts/setup/gunicorn.service diff --git a/init_db.py b/advlabdb/scripts/setup/init_db.py similarity index 68% rename from init_db.py rename to advlabdb/scripts/setup/init_db.py index 462cffc..1c40326 100644 --- a/init_db.py +++ b/advlabdb/scripts/setup/init_db.py @@ -1,77 +1,16 @@ -import sys -from pathlib import Path - from email_validator import validate_email from flask_security import hash_password from advlabdb import app, db, user_datastore from advlabdb.independent_funs import randomPassword from advlabdb.models import MAX_YEAR, MIN_YEAR, Admin, Semester - -scripts_dir = Path(__file__).parent.absolute() / "scripts" - -sys.path.insert(0, str(scripts_dir)) - -from shared import box - - -def validating_input( - prompt, - options=None, - format_function=lambda ans: ans, - check_constraints_function=lambda ans: True, -): - if options is not None: - prompt += " [" - - for opt in options[:-1]: - prompt += opt + "/" - - prompt += options[-1] + "]: " - - lowered_options = [opt.lower() for opt in options] - - def adj_check_constraints_function(ans): - return ans.lower() in lowered_options and check_constraints_function(ans) - - else: - prompt += ": " - adj_check_constraints_function = check_constraints_function - - ans = None - first_run = True - - while ans is None or not adj_check_constraints_function(ans): - if not first_run: - print("Invalid input!") - else: - first_run = False - - ans = input(prompt) - try: - ans = format_function(ans) - except Exception as ex: - ans = None - - return ans - - -def confirm(prompt): - ans = validating_input( - prompt, - options=("y", "n"), - format_function=lambda ans: ans.lower(), - ) - - if ans == "y": - return True - else: - return False +from advlabdb.scripts.terminal_utils import box, confirm, validating_input def main(): print("This script should only be used to initialize the database after setting up a server") print("The old database will be DELETED and a new database will be created.") + if not confirm("Are you sure that you want to continue?"): print("Aborted!") return @@ -137,7 +76,7 @@ def main(): box(admin_password, "Admin password") - print("Done! Successfull database initialization.") + print("Done database initialization!") if __name__ == "__main__": diff --git a/advlabdb/scripts/setup/logged_server_setup.py b/advlabdb/scripts/setup/logged_server_setup.py new file mode 100644 index 0000000..78ab2ed --- /dev/null +++ b/advlabdb/scripts/setup/logged_server_setup.py @@ -0,0 +1,97 @@ +from pathlib import Path + +from advlabdb.scripts.setup.shared import ( + LOCAL_BIN, + LOGS_DIR, + install_latest_pipx, + poetry_update, +) +from advlabdb.scripts.terminal_utils import run, step + + +def main(): + file_dir = Path(__file__).parent.absolute() + + step("Update system packages") + run("sudo apt update") + run("sudo apt dist-upgrade") + + step("Remove unused packages") + run("sudo apt autoremove") + + step("Install needed system packages") + run("sudo apt install python3 python3-pip python3-venv ufw nginx systemd asciidoctor -y") + + step("Install optional system packages") + run("sudo apt install htop") + + step("Setup firewall") + run("sudo ufw default allow outgoing") + run("sudo ufw default deny incoming") + run("sudo ufw allow ssh") + run("sudo ufw allow http/tcp") + run("sudo ufw allow https/tcp") + run("sudo ufw enable") + run("sudo ufw status") + + step("Enable Gunicorn") + gunicorn_service_file = file_dir / "gunicorn.service" + run(f"sudo cp -v {gunicorn_service_file} /etc/systemd/system/") + run("sudo systemctl enable gunicorn") + + step("Setup Nginx") + for dir_appendix in ("available", "enabled"): + run(f"sudo rm -v /etc/nginx/sites-{dir_appendix}/default") + + nginx_conf_file = file_dir / "advlabdb.conf" + run(f"sudo cp -v {nginx_conf_file} /etc/nginx/sites-available/") + run("sudo ln -v -s /etc/nginx/sites-available/advlabdb.conf /etc/nginx/sites-enabled/") + run("sudo systemctl enable nginx") + + step("Install pipx") + install_latest_pipx() + + pipx_bin = LOCAL_BIN / "pipx" + + step("Install Poetry") + run(f"{pipx_bin} install poetry") + # Place virtual environments in the root directory of the project + # The virtual environment will then be found in /home/admin/advlabdb/.venv + poetry_bin = LOCAL_BIN / "poetry" + run(f"{poetry_bin} config virtualenvs.in-project true") + + step("Install Certbot") + run(f"{pipx_bin} install certbot") + run(f"{pipx_bin} inject certbot certbot-nginx") + + step("Setup Certbot") + certbot_bin = LOCAL_BIN / "certbot" + run(f"sudo {certbot_bin} --nginx") + run( + f"echo \"0 0,12 * * * root python3 -c 'import random; import time; time.sleep(random.random() * 3600)' && sudo {certbot_bin} renew -q\" | sudo tee -a /etc/crontab" + ) + + step("Setup update cron jobs") + + user_update_script = file_dir / "user_update.py" + user_update_log = LOGS_DIR / "user_update.log" + # Every Sunday at 04:00 + run(f'echo "0 4 * * 0 admin python3 -u {user_update_script} &>> {user_update_log}" | sudo tee -a /etc/crontab') + + root_update_script = file_dir / "root_update.py" + root_update_log = LOGS_DIR / "root_update.log" + # Every Sunday at 04:15 + run(f'echo "15 4 * * 0 root python3 -u {root_update_script} &>> {root_update_log}" | sudo tee -a /etc/crontab') + + step("Install latest Poetry packages") + poetry_update() + + step("Deactivate the 'root' user") + run("sudo passwd -l root") + + step("Reboot") + run("sudo reboot") + + +if __name__ == "__main__": + main() diff --git a/advlabdb/scripts/setup/server_setup.py b/advlabdb/scripts/setup/server_setup.py new file mode 100644 index 0000000..4a5c1e4 --- /dev/null +++ b/advlabdb/scripts/setup/server_setup.py @@ -0,0 +1,20 @@ +from advlabdb.scripts.setup.logged_server_setup import ( + __file__ as logged_server_setup_script, +) +from advlabdb.scripts.setup.shared import LOGS_DIR +from advlabdb.scripts.terminal_utils import run + + +def main(): + # Create logs directory + run(f"sudo mkdir -v -p {LOGS_DIR}") + run(f"sudo chown -R admin:admin {LOGS_DIR}") + + log_file = LOGS_DIR / "server_setup.log" + + # Start actual server setup script with logging + run(f"python3 -u {logged_server_setup_script} | tee {log_file}") + + +if __name__ == "__main__": + main() diff --git a/advlabdb/scripts/setup/shared.py b/advlabdb/scripts/setup/shared.py new file mode 100644 index 0000000..e223bca --- /dev/null +++ b/advlabdb/scripts/setup/shared.py @@ -0,0 +1,17 @@ +from pathlib import Path + +from advlabdb import __file__ as advlabdb_init_path # Points to __init__.py +from advlabdb.scripts.terminal_utils import run + +LOCAL_BIN = Path("/home/admin/.local/bin/") +LOGS_DIR = Path("/var/log/advlabdb") + + +def install_latest_pipx(): + run("pip install --user --upgrade pipx") + + +def poetry_update(): + poetry_bin = LOCAL_BIN / "poetry" + advlabdb_root_dir = Path(advlabdb_init_path).parents[1] + run(f"{poetry_bin} update", cwd=advlabdb_root_dir) diff --git a/advlabdb/scripts/terminal_utils.py b/advlabdb/scripts/terminal_utils.py new file mode 100644 index 0000000..fe7786a --- /dev/null +++ b/advlabdb/scripts/terminal_utils.py @@ -0,0 +1,95 @@ +import subprocess +from getpass import getpass + + +def run(command, **kwargs): + return subprocess.run(command, shell=True, **kwargs) + + +def box(message, context=None): + text_line = "| " + + if context is not None: + text_line += context + ": " + + text_line += message + " |" + + separator = "=" * len(text_line) + + print() + print(separator) + print(text_line) + print(separator) + print() + + +def step(message): + continue_message = "-> Press ENTER to continue or Ctrl+C to interrupt the script <-" + upper_separator = "_" * len(continue_message) + + print() + print(upper_separator) + + box(message, "Next step") + + print(continue_message) + getpass("") + print() + + +def spaced_hl(): + print("\n\n" + "_" * 20 + "\n\n") + + +def validating_input( + prompt, + options=None, + format_function=lambda ans: ans, + check_constraints_function=lambda ans: True, +): + if options is not None: + prompt += " [" + + for opt in options[:-1]: + prompt += opt + "/" + + prompt += options[-1] + "]: " + + lowered_options = [opt.lower() for opt in options] + + def adj_check_constraints_function(ans): + return ans.lower() in lowered_options and check_constraints_function(ans) + + else: + prompt += ": " + adj_check_constraints_function = check_constraints_function + + ans = None + first_run = True + + while ans is None or not adj_check_constraints_function(ans): + if not first_run: + print("Invalid input!") + else: + first_run = False + + ans = input(prompt) + try: + ans = format_function(ans) + except Exception as ex: + ans = None + + return ans + + +def confirm(prompt): + ans = validating_input( + prompt, + options=("y", "n"), + format_function=lambda ans: ans.lower(), + ) + + if ans == "y": + return True + else: + return False diff --git a/advlabdb/scripts/test/test_database.py b/advlabdb/scripts/test/test_database.py new file mode 100644 index 0000000..9547eed --- /dev/null +++ b/advlabdb/scripts/test/test_database.py @@ -0,0 +1,150 @@ +from datetime import date + +from flask_security import hash_password + +from advlabdb import app, db, user_datastore +from advlabdb.models import * + + +def main(): + with app.app_context(): + with db.session.begin(): + db.drop_all() + db.create_all() + + program1 = Program(label="BS") + program2 = Program(label="MS") + program3 = Program(label="BE") + + db.session.add(program1) + db.session.add(program2) + db.session.add(program3) + + sem1 = Semester.customInit(label="SS", year=22) + sem2 = Semester.customInit(label="WS", year=22) + + db.session.add(sem1) + db.session.add(sem2) + + partKwargs = [ + {"program": program1, "number": 1}, + {"program": program1, "number": 2}, + {"program": program2, "number": 1}, + {"program": program2, "number": 2}, + {"program": program3, "number": 1}, + ] + for kwargs in partKwargs: + db.session.add(Part(semester=sem1, **kwargs)) + + sem2.transferPartsFrom(sem1) + + part1 = sem2.parts[0] + part2 = sem2.parts[2] + + student1 = Student(student_number=123, first_name="Mo", last_name="Bit", uni_email="m@test.com") + student2 = Student(student_number=1232, first_name="Mo2", last_name="Bit", uni_email="m2@test.com") + student3 = Student(student_number=1233, first_name="Mo3", last_name="Bit3", uni_email="m3@test.com") + + db.session.add(student1) + db.session.add(student2) + db.session.add(student3) + + ps1 = PartStudent.customInit(student=student1, part=part1) + ps2 = PartStudent.customInit(student=student2, part=part1) + ps3 = PartStudent.customInit(student=student3, part=part2) + + db.session.add(ps1) + db.session.add(ps2) + db.session.add(ps3) + + g1 = Group.customInit(part_students=[ps1, ps2]) + g2 = Group.customInit(part_students=[ps3]) + + db.session.add(g1) + db.session.add(g2) + + ex1 = Experiment( + number=1, + program=program1, + title="exp", + room="123", + building="phy", + responsibility="none", + duration_in_days=2, + ) + + ex2 = Experiment( + number=1, + program=program2, + title="exp2", + room="123", + building="phy", + responsibility="none", + duration_in_days=2, + ) + + db.session.add(ex1) + db.session.add(ex2) + + sx1 = SemesterExperiment(experiment=ex1, semester=sem2) + sx2 = SemesterExperiment(experiment=ex2, semester=sem2) + + db.session.add(sx1) + db.session.add(sx2) + + gx1 = GroupExperiment.customInit(semester_experiment=sx1, group=g1) + gx2 = GroupExperiment.customInit(semester_experiment=sx2, group=g2) + + db.session.add(gx1) + db.session.add(gx2) + + adminRole = user_datastore.create_role(name="admin") + assistantRole = user_datastore.create_role(name="assistant") + + admin_user = user_datastore.create_user( + email="admin@advlabdb.de", + password=hash_password("admin"), + roles=[adminRole], + first_name="Peter", + last_name="Blümler", + ) + + admin = Admin(user=admin_user) + + db.session.add(admin) + + us1 = user_datastore.create_user( + email="test@protonmail.com", + password=hash_password("h1"), + roles=[assistantRole], + first_name="As1", + last_name="l", + phone_number="012333212", + mobile_phone_number="012334123", + ) + us2 = user_datastore.create_user( + email="test2@protonmail.com", + password=hash_password("h2"), + roles=[assistantRole], + first_name="As2", + last_name="l", + ) + + as1 = Assistant(user=us1) + as2 = Assistant(user=us2) + + as1.semester_experiments.append(sx1) + as2.semester_experiments.append(sx2) + + db.session.add(as1) + db.session.add(as2) + + ap1 = Appointment.customInit(date=date(2021, 3, 21), special=True, group_experiment=gx1, assistant=as1) + ap2 = Appointment.customInit(date=date(2021, 3, 22), special=True, group_experiment=gx2, assistant=as2) + + db.session.add(ap1) + db.session.add(ap2) + + +if __name__ == "__main__": + main() diff --git a/DB.drawio b/docs/DB.drawio similarity index 100% rename from DB.drawio rename to docs/DB.drawio diff --git a/database_import.adoc b/docs/database_import/database_import.adoc similarity index 100% rename from database_import.adoc rename to docs/database_import/database_import.adoc diff --git a/example_database_import.txt b/docs/database_import/example_database_import.txt similarity index 100% rename from example_database_import.txt rename to docs/database_import/example_database_import.txt diff --git a/docs/update_docs.sh b/docs/update_docs.sh deleted file mode 100644 index 8ab90a3..0000000 --- a/docs/update_docs.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/bash - -# Needed packages: fd (find alternative), asciidoctor - -# You have to cd into the docs directory where this script is located before running it! - -# List of documentation files -DOC_FILE_NAMES=$(fd -d 1 -t f --extension adoc --exclude "README.adoc") - -for doc_file_name in "${DOC_FILE_NAMES[@]}"; do - DOC_FILE_NAME_WITHOUT_EXTENSION=${doc_file_name::-5} - OUTPUT_PATH=../advlabdb/templates/docs/$DOC_FILE_NAME_WITHOUT_EXTENSION.html - - # Convert to html with asciidoctor - asciidoctor -v --backend html5 "$doc_file_name" -o "$OUTPUT_PATH" - - # Add the Jinja raw tag - sed -i "1i {% raw %}" "$OUTPUT_PATH" - echo -e "\n{% endraw %}" >>"$OUTPUT_PATH" - - # Done - echo "Generated $OUTPUT_PATH" -done diff --git a/docs/README.adoc b/docs/user_docs/README.adoc similarity index 100% rename from docs/README.adoc rename to docs/user_docs/README.adoc diff --git a/docs/admin.adoc b/docs/user_docs/admin.adoc similarity index 100% rename from docs/admin.adoc rename to docs/user_docs/admin.adoc diff --git a/docs/assistant.adoc b/docs/user_docs/assistant.adoc similarity index 100% rename from docs/assistant.adoc rename to docs/user_docs/assistant.adoc diff --git a/docs/user_docs/server_setup.adoc b/docs/user_docs/server_setup.adoc new file mode 100644 index 0000000..bf962df --- /dev/null +++ b/docs/user_docs/server_setup.adoc @@ -0,0 +1,68 @@ += Server setup + +== Setup scripts + +`ssh` as `root`: + +[source,bash] +---- +ssh root@SERVER_NAME +---- + +Run the following (as root): + +[source,bash] +---- +# Install needed packages +apt update +apt install sudo python3 -y + +# Add a sudo user with the name 'admin' +sudo useradd admin +sudo usermod -aG sudo admin +sudo mkhomedir_helper admin + +# Enter a new password for 'admin' +sudo passwd admin + +# Break the SSH connection +exit +---- + +Copy the repository to the server into `/home/admin/advlabdb`. + +`ssh` again with the new user `admin` and password: + +[source,bash] +---- +ssh admin@SERVER_NAME +---- + +Run the following: + +[source,bash] +---- +# Run server setup script +python3 ~/advlabdb/scripts/server_setup.py +---- + +Change server_name in advlabdb.conf + +== Stop ssh to root +// TODO: Add blocking password access + +IMPORTANT: This step is important for security! + +Change + +---- +PermitRootLogin yes +---- + +to + +---- +PermitRootLogin no +---- + +in the config file `/etc/ssh/sshd_config` diff --git a/scripts/logged_server_setup.py b/scripts/logged_server_setup.py deleted file mode 100644 index c9be8fa..0000000 --- a/scripts/logged_server_setup.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from pathlib import Path - -script_dir = Path(__file__).parent.absolute() - -sys.path.insert(0, str(script_dir)) - -from shared import install_latest_pipx, local_bin, poetry_update, run, step - -logs_dir = Path("/var/log/advlabdb/") - -step("Update system packages") -run("sudo apt update") -run("sudo apt dist-upgrade") - -step("Remove unused packages") -run("sudo apt autoremove") - -step("Install needed system packages") -run("sudo apt install python3 python3-pip python3-venv ufw nginx systemd -y") - -step("Install optional system packages") -run("sudo apt install htop") - -step("Setup firewall") -run("sudo ufw default allow outgoing") -run("sudo ufw default deny incoming") -run("sudo ufw allow ssh") -run("sudo ufw allow http/tcp") -# TODO: Setup https -# run("sudo ufw allow https/tcp") -run("sudo ufw enable") -run("sudo ufw status") - -step("Enable Gunicorn") -gunicorn_service_file = script_dir / "gunicorn.service" -run(f"sudo cp -v {gunicorn_service_file} /etc/systemd/system/") -run("sudo systemctl enable gunicorn") - -step("Setup Nginx") -for dir_appendix in ("available", "enabled"): - run(f"sudo rm -v /etc/nginx/sites-{dir_appendix}/default") - -nginx_conf_file = script_dir / "advlabdb.conf" -run(f"sudo cp -v {nginx_conf_file} /etc/nginx/sites-available/") -run("sudo ln -v -s /etc/nginx/sites-available/advlabdb.conf /etc/nginx/sites-enabled/") -run("sudo systemctl enable nginx") - -step("Install pipx") -install_latest_pipx() - -pipx_bin = local_bin / "pipx" - -step("Install Poetry") -run(f"{pipx_bin} install poetry") -# Place virtual environments in the root directory of the project -# The virtual environment will then be found in /home/admin/advlabdb/.venv -poetry_bin = local_bin / "poetry" -run(f"{poetry_bin} config virtualenvs.in-project true") - -step("Install Certbot") -run(f"{pipx_bin} install certbot") -run(f"{pipx_bin} inject certbot certbot-nginx") - -step("Setup Certbot") -certbot_bin = local_bin / "certbot" -run(f"sudo {certbot_bin} --nginx") -run( - f"echo \"0 0,12 * * * root python3 -c 'import random; import time; time.sleep(random.random() * 3600)' && sudo {certbot_bin} renew -q\" | sudo tee -a /etc/crontab" -) - -step("Setup update cron jobs") - -user_update_script = script_dir / "user_update.py" -user_update_log = logs_dir / "user_update.log" -# Every Sunday at 04:00 -run(f'echo "0 4 * * 0 admin python3 -u {user_update_script} &>> {user_update_log}" | sudo tee -a /etc/crontab') - -root_update_script = script_dir / "root_update.py" -root_update_log = logs_dir / "root_update.log" -# Every Sunday at 04:15 -run(f'echo "15 4 * * 0 root python3 -u {root_update_script} &>> {root_update_log}" | sudo tee -a /etc/crontab') - -step("Install latest Poetry packages") -poetry_update(script_dir) - -step("Deactivate the 'root' user") -run("sudo passwd -l root") - -step("Reboot") -run("sudo reboot") diff --git a/scripts/root_update.py b/scripts/root_update.py deleted file mode 100644 index 46bd8c3..0000000 --- a/scripts/root_update.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from datetime import datetime -from pathlib import Path - -script_dir = Path(__file__).parent.absolute() - -sys.path.insert(0, str(script_dir)) - -from shared import box, run, spaced_hl - -spaced_hl() - -dt = datetime.now().strftime("%d.%m.%Y %H:%M:%S") - -box(dt, "Update on") - -box("Update system packages") -run("sudo apt update") -run("sudo apt upgrade -y") - -run("sudo reboot") diff --git a/scripts/server_setup.py b/scripts/server_setup.py deleted file mode 100644 index 51faa93..0000000 --- a/scripts/server_setup.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from pathlib import Path - -script_dir = Path(__file__).parent.absolute() - -sys.path.insert(0, str(script_dir)) - -from shared import run - -logs_dir = Path("/var/log/advlabdb/") - -# Create logs directory -run(f"sudo mkdir -v -p {logs_dir}") -run(f"sudo chown -R admin:admin {logs_dir}") - -logged_server_setup_script = script_dir / "logged_server_setup.py" -log_file = logs_dir / "server_setup.log" - -# Start actual server setup script with logging -run(f"python3 -u {logged_server_setup_script} | tee {log_file}") diff --git a/scripts/shared.py b/scripts/shared.py deleted file mode 100644 index ee31334..0000000 --- a/scripts/shared.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 - -import subprocess -from getpass import getpass -from pathlib import Path - -local_bin = Path("/home/admin/.local/bin/") - - -def run(command, **kwargs): - return subprocess.run(command, shell=True, **kwargs) - - -def box(message, context=None): - text_line = "| " - - if context is not None: - text_line += context + ": " - - text_line += message + " |" - - separator = "=" * len(text_line) - - print() - print(separator) - print(text_line) - print(separator) - print() - - -def step(message): - continue_message = "-> Press ENTER to continue or Ctrl+C to interrupt the script <-" - upper_separator = "_" * len(continue_message) - - print() - print(upper_separator) - - box(message, "Next step") - - print(continue_message) - getpass("") - print() - - -def install_latest_pipx(): - run("pip install --user --upgrade pipx") - - -def poetry_update(script_dir): - poetry_bin = local_bin / "poetry" - run(f"{poetry_bin} update", cwd=script_dir / "..") - - -def spaced_hl(): - print("\n\n___________________\n\n") diff --git a/scripts/user_update.py b/scripts/user_update.py deleted file mode 100644 index fd46859..0000000 --- a/scripts/user_update.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from datetime import datetime -from pathlib import Path - -script_dir = Path(__file__).parent.absolute() - -sys.path.insert(0, str(script_dir)) - -from shared import box, install_latest_pipx, local_bin, poetry_update, run, spaced_hl - -spaced_hl() - -dt = datetime.now().strftime("%d.%m.%Y %H:%M:%S") - -box(dt, "Update on") - -box("Upgrade pipx") -install_latest_pipx() - -box("Upgrade pipx packages") -pipx_bin = local_bin / "pipx" -run(f"{pipx_bin} upgrade-all --include-injected") - -# TODO: Backup - -box("Install latest Poetry packages") -poetry_update(script_dir) diff --git a/testDB.py b/testDB.py deleted file mode 100644 index e45bce4..0000000 --- a/testDB.py +++ /dev/null @@ -1,145 +0,0 @@ -from datetime import date - -from flask_security import hash_password - -from advlabdb import app, db, user_datastore -from advlabdb.models import * - -with app.app_context(): - db.drop_all() - db.create_all() - - program1 = Program(label="BS") - program2 = Program(label="MS") - program3 = Program(label="BE") - - db.session.add(program1) - db.session.add(program2) - db.session.add(program3) - - sem1 = Semester.customInit(label="SS", year=22) - sem2 = Semester.customInit(label="WS", year=22) - - db.session.add(sem1) - db.session.add(sem2) - - partKwargs = [ - {"program": program1, "number": 1}, - {"program": program1, "number": 2}, - {"program": program2, "number": 1}, - {"program": program2, "number": 2}, - {"program": program3, "number": 1}, - ] - for kwargs in partKwargs: - db.session.add(Part(semester=sem1, **kwargs)) - - sem2.transferPartsFrom(sem1) - - part1 = sem2.parts[0] - part2 = sem2.parts[2] - - student1 = Student(student_number=123, first_name="Mo", last_name="Bit", uni_email="m@test.com") - student2 = Student(student_number=1232, first_name="Mo2", last_name="Bit", uni_email="m2@test.com") - student3 = Student(student_number=1233, first_name="Mo3", last_name="Bit3", uni_email="m3@test.com") - - db.session.add(student1) - db.session.add(student2) - db.session.add(student3) - - ps1 = PartStudent.customInit(student=student1, part=part1) - ps2 = PartStudent.customInit(student=student2, part=part1) - ps3 = PartStudent.customInit(student=student3, part=part2) - - db.session.add(ps1) - db.session.add(ps2) - db.session.add(ps3) - - g1 = Group.customInit(part_students=[ps1, ps2]) - g2 = Group.customInit(part_students=[ps3]) - - db.session.add(g1) - db.session.add(g2) - - ex1 = Experiment( - number=1, - program=program1, - title="exp", - room="123", - building="phy", - responsibility="none", - duration_in_days=2, - ) - - ex2 = Experiment( - number=1, - program=program2, - title="exp2", - room="123", - building="phy", - responsibility="none", - duration_in_days=2, - ) - - db.session.add(ex1) - db.session.add(ex2) - - sx1 = SemesterExperiment(experiment=ex1, semester=sem2) - sx2 = SemesterExperiment(experiment=ex2, semester=sem2) - - db.session.add(sx1) - db.session.add(sx2) - - gx1 = GroupExperiment.customInit(semester_experiment=sx1, group=g1) - gx2 = GroupExperiment.customInit(semester_experiment=sx2, group=g2) - - db.session.add(gx1) - db.session.add(gx2) - - adminRole = user_datastore.create_role(name="admin") - assistantRole = user_datastore.create_role(name="assistant") - - admin_user = user_datastore.create_user( - email="admin@advlabdb.de", - password=hash_password("admin"), - roles=[adminRole], - first_name="Peter", - last_name="Blümler", - ) - - admin = Admin(user=admin_user) - - db.session.add(admin) - - us1 = user_datastore.create_user( - email="test@protonmail.com", - password=hash_password("h1"), - roles=[assistantRole], - first_name="As1", - last_name="l", - phone_number="012333212", - mobile_phone_number="012334123", - ) - us2 = user_datastore.create_user( - email="test2@protonmail.com", - password=hash_password("h2"), - roles=[assistantRole], - first_name="As2", - last_name="l", - ) - - as1 = Assistant(user=us1) - as2 = Assistant(user=us2) - - as1.semester_experiments.append(sx1) - as2.semester_experiments.append(sx2) - - db.session.add(as1) - db.session.add(as2) - - ap1 = Appointment.customInit(date=date(2021, 3, 21), special=True, group_experiment=gx1, assistant=as1) - ap2 = Appointment.customInit(date=date(2021, 3, 22), special=True, group_experiment=gx2, assistant=as2) - - db.session.add(ap1) - db.session.add(ap2) - - db.session.commit()