mirror of
https://codeberg.org/Mo8it/AdvLabDB.git
synced 2024-12-20 23:41:20 +00:00
Organize docs and scripts
This commit is contained in:
parent
daf0b6287f
commit
1f89e4aaed
27 changed files with 520 additions and 454 deletions
16
advlabdb/scripts/maintain/root_update.py
Normal file
16
advlabdb/scripts/maintain/root_update.py
Normal file
|
@ -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()
|
11
advlabdb/scripts/maintain/shared.py
Normal file
11
advlabdb/scripts/maintain/shared.py
Normal file
|
@ -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")
|
20
advlabdb/scripts/maintain/update_docs.py
Normal file
20
advlabdb/scripts/maintain/update_docs.py
Normal file
|
@ -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
|
23
advlabdb/scripts/maintain/user_update.py
Normal file
23
advlabdb/scripts/maintain/user_update.py
Normal file
|
@ -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()
|
|
@ -1,77 +1,16 @@
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from email_validator import validate_email
|
from email_validator import validate_email
|
||||||
from flask_security import hash_password
|
from flask_security import hash_password
|
||||||
|
|
||||||
from advlabdb import app, db, user_datastore
|
from advlabdb import app, db, user_datastore
|
||||||
from advlabdb.independent_funs import randomPassword
|
from advlabdb.independent_funs import randomPassword
|
||||||
from advlabdb.models import MAX_YEAR, MIN_YEAR, Admin, Semester
|
from advlabdb.models import MAX_YEAR, MIN_YEAR, Admin, Semester
|
||||||
|
from advlabdb.scripts.terminal_utils import box, confirm, validating_input
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("This script should only be used to initialize the database after setting up a server")
|
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.")
|
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?"):
|
if not confirm("Are you sure that you want to continue?"):
|
||||||
print("Aborted!")
|
print("Aborted!")
|
||||||
return
|
return
|
||||||
|
@ -137,7 +76,7 @@ def main():
|
||||||
|
|
||||||
box(admin_password, "Admin password")
|
box(admin_password, "Admin password")
|
||||||
|
|
||||||
print("Done! Successfull database initialization.")
|
print("Done database initialization!")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
97
advlabdb/scripts/setup/logged_server_setup.py
Normal file
97
advlabdb/scripts/setup/logged_server_setup.py
Normal file
|
@ -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()
|
20
advlabdb/scripts/setup/server_setup.py
Normal file
20
advlabdb/scripts/setup/server_setup.py
Normal file
|
@ -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()
|
17
advlabdb/scripts/setup/shared.py
Normal file
17
advlabdb/scripts/setup/shared.py
Normal file
|
@ -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)
|
95
advlabdb/scripts/terminal_utils.py
Normal file
95
advlabdb/scripts/terminal_utils.py
Normal file
|
@ -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
|
150
advlabdb/scripts/test/test_database.py
Normal file
150
advlabdb/scripts/test/test_database.py
Normal file
|
@ -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()
|
|
@ -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
|
|
68
docs/user_docs/server_setup.adoc
Normal file
68
docs/user_docs/server_setup.adoc
Normal file
|
@ -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`
|
|
@ -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")
|
|
|
@ -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")
|
|
|
@ -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}")
|
|
|
@ -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")
|
|
|
@ -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)
|
|
145
testDB.py
145
testDB.py
|
@ -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()
|
|
Loading…
Reference in a new issue