Compare commits
No commits in common. "master" and "master" have entirely different histories.
8
.gitignore
vendored
8
.gitignore
vendored
@ -169,13 +169,5 @@ Thumbs.db
|
|||||||
.vscode/
|
.vscode/
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
|
|
||||||
# PyCharm
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
copy
|
copy
|
||||||
|
|
||||||
incoming_dumps/
|
|
||||||
upload-dump-*
|
|
||||||
counter
|
|
||||||
# Le .env contient le token et ne DOIT PAS être sous git
|
|
||||||
.env
|
|
||||||
|
80
README.md
80
README.md
@ -17,57 +17,55 @@ Mini-app Flask remplaçant les CGI scripts de `scodoc.iutv`.
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Créer un utilisateur
|
1. Créer un utilisateur
|
||||||
```bash
|
|
||||||
adduser installmgr
|
adduser installmgr
|
||||||
```
|
|
||||||
|
|
||||||
2. Cloner le dépot
|
2. Cloner le dépot
|
||||||
```bash
|
|
||||||
git clone https://scodoc.org/git/ScoDoc/installmgr.git /opt/installmgr
|
cd /opt
|
||||||
chown -R installmgr:installmgr /opt/installmgr
|
git clone https://scodoc.org/git/viennet/installmgr.git
|
||||||
```
|
chown -R installmgr installmgr
|
||||||
|
|
||||||
3. Créer l'environnement
|
3. Créer l'environnement
|
||||||
```bash
|
|
||||||
su - installmgr
|
su - installmgr
|
||||||
cd /opt/installmgr
|
cd /opt/installmgr
|
||||||
python3 -m venv venv
|
python3 -m venv venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
pip install wheel
|
pip install wheel
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
|
||||||
|
|
||||||
4. Créer les répertoires
|
4. Créer les répertoires
|
||||||
```bash
|
|
||||||
mkdir incoming_dumps
|
mkdir incoming_dumps
|
||||||
# et éventuellement:
|
# et éventuellement:
|
||||||
echo 1000 > counter
|
echo 1000 > counter
|
||||||
```
|
|
||||||
|
|
||||||
5. Configurer nginx
|
5. Configurer nginx
|
||||||
```nginx
|
|
||||||
location /scodoc-installmgr {
|
location /scodoc-installmgr {
|
||||||
# forward application requests to the gunicorn server
|
# forward application requests to the gunicorn server
|
||||||
proxy_pass http://localhost:8010;
|
proxy_pass http://localhost:8010;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto https;
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
proxy_connect_timeout 120;
|
proxy_connect_timeout 120;
|
||||||
client_max_body_size 100m;
|
client_max_body_size 100m;
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
6. Installer le service
|
6. Installer le service
|
||||||
```bash
|
|
||||||
cp etc/installmgr.service /etc/systemd/system/
|
cp etc/installmgr.service /etc/systemd/system/
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl start installmgr
|
systemctl start installmgr
|
||||||
```
|
|
||||||
7. Tester
|
7. Tester
|
||||||
|
|
||||||
Depuis un client extérieur,
|
Depuis un client extérieur,
|
||||||
```bash
|
|
||||||
http https://scodoc.org/scodoc-installmgr/last_stable_version
|
http https://scodoc.org/scodoc-installmgr/last_stable_version
|
||||||
```
|
|
||||||
|
191
app/routes.py
191
app/routes.py
@ -1,35 +1,27 @@
|
|||||||
import json, datetime, fcntl, glob, os, re, socket, subprocess, time, requests
|
import json, datetime, fcntl, glob, os, re, socket, subprocess, time
|
||||||
|
|
||||||
from flask import jsonify, request, abort
|
from flask import jsonify, request, abort
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from app import email
|
from app import email
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
bp = Blueprint("routes", __name__)
|
bp = Blueprint("routes", __name__)
|
||||||
|
|
||||||
# --------------------------------------------------------------
|
# --------------------------------------------------------------
|
||||||
DIR = "/opt/installmgr/"
|
DIR = "/opt/installmgr/"
|
||||||
REPOSIT_DIR = "/opt/installmgr/incoming_dumps"
|
REPOSIT_DIR = "/opt/installmgr/incoming_dumps"
|
||||||
MAX_REPOSIT_SIZE = 2000 * 20 * 1024 # kB (here, max 2000 dumps of 20MB)
|
MAX_REPOSIT_SIZE = 300 * 20 * 1024 # kB (here, max 300 dumps of 20MB)
|
||||||
|
|
||||||
ALERT_MAIL_FROM = "root@scodoc.org"
|
ALERT_MAIL_FROM = "root@scodoc.org"
|
||||||
ALERT_MAIL_TO = "viennet"
|
ALERT_MAIL_TO = "emmanuel.viennet@gmail.com"
|
||||||
|
|
||||||
LOG_FILENAME = os.path.join(DIR, "upload-dump-errors.log")
|
LOG_FILENAME = os.path.join(DIR, "upload-dump-errors.log")
|
||||||
UPLOAD_LOG_FILENAME = os.path.join(DIR, "upload-dump-log.json")
|
UPLOAD_LOG_FILENAME = os.path.join(DIR, "upload-dump-log.json")
|
||||||
DEBUG = False # if false, don't publish error messages
|
DEBUG = False # if false, don't publish error messages
|
||||||
|
|
||||||
# Les paquets publiés:
|
# Les paquets publiés:
|
||||||
#DEBIAN_PACKAGES_EXP = "/srv/packages/pool/main/s/scodoc9/scodoc9_*.deb"
|
DEBIAN_PACKAGES_EXP = "/srv/packages/pool/main/s/scodoc9/scodoc9_*.deb"
|
||||||
DEBIAN_PACKAGES_EXP = "/srv/bookworm/pool/main/s/scodoc9/scodoc9_*.deb"
|
|
||||||
RELEASE_LOG_FILE = "/home/viennet/scodoc-releases.log"
|
RELEASE_LOG_FILE = "/home/viennet/scodoc-releases.log"
|
||||||
|
|
||||||
GITEA_URL = "https://git.scodoc.org/"
|
|
||||||
GITEA_REPO = "ScoDoc/ScoDoc"
|
|
||||||
GITEA_LABEL_ID = None
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/scodoc-installmgr/check_version/<client_version>")
|
@bp.route("/scodoc-installmgr/check_version/<client_version>")
|
||||||
def check_version(client_version: str):
|
def check_version(client_version: str):
|
||||||
@ -115,173 +107,25 @@ def last_stable_version():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/scodoc-installmgr/report", methods=["POST"])
|
|
||||||
def report():
|
|
||||||
"""Création d'un ticket Gitea depuis ScoDoc
|
|
||||||
=> json
|
|
||||||
"""
|
|
||||||
log = open(LOG_FILENAME, "a")
|
|
||||||
log.write("report\n")
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
fulltime = now.isoformat()
|
|
||||||
remote_addr = request.environ.get("HTTP_X_REAL_IP", request.remote_addr)
|
|
||||||
log.write(fulltime + " request from " + remote_addr + "\n")
|
|
||||||
log.flush()
|
|
||||||
|
|
||||||
request_data = request.get_json(silent=True)
|
|
||||||
|
|
||||||
if request_data is None:
|
|
||||||
log.write("json proccessing error\n")
|
|
||||||
log.close()
|
|
||||||
return (
|
|
||||||
jsonify(
|
|
||||||
{
|
|
||||||
"message": "Une erreur est survenue lors du traitement de la requête. Veuillez réessayer."
|
|
||||||
}
|
|
||||||
),
|
|
||||||
400,
|
|
||||||
)
|
|
||||||
|
|
||||||
ticket = request_data.get("ticket", "")
|
|
||||||
user = request_data.get("user", "")
|
|
||||||
dump = request_data.get("dump", "")
|
|
||||||
scodoc = request_data.get("scodoc", "")
|
|
||||||
|
|
||||||
if (
|
|
||||||
not ticket
|
|
||||||
or not user
|
|
||||||
or not dump
|
|
||||||
or not scodoc
|
|
||||||
or not ticket.get("title")
|
|
||||||
or not ticket.get("message")
|
|
||||||
):
|
|
||||||
log.write("missing json fields\n")
|
|
||||||
log.close()
|
|
||||||
return (
|
|
||||||
jsonify(
|
|
||||||
{
|
|
||||||
"message": "Une erreur est survenue lors du traitement de la requête (données requises manquantes). Veuillez réessayer."
|
|
||||||
}
|
|
||||||
),
|
|
||||||
400,
|
|
||||||
)
|
|
||||||
|
|
||||||
meta = "Informations complémentaires :"
|
|
||||||
meta += "\n- Version de ScoDoc : " + scodoc.get("version", "inconnue")
|
|
||||||
meta += "\n- Dump : " + (
|
|
||||||
("inclus (id : " + dump.get("id", "inconnu") + ")")
|
|
||||||
if dump.get("included", False)
|
|
||||||
else "non inclus"
|
|
||||||
)
|
|
||||||
meta += "\n- Établissement : " + ticket.get("etab") if ticket.get("etab") else ""
|
|
||||||
meta += "\n- Département : " + ticket.get("dept") if ticket.get("dept") else ""
|
|
||||||
|
|
||||||
ticket_body = ticket.get("message") + "\n\n---\n\n" + meta
|
|
||||||
|
|
||||||
response = requests.post(
|
|
||||||
GITEA_URL + "/api/v1/repos/" + GITEA_REPO + "/issues",
|
|
||||||
json={
|
|
||||||
"title": ticket.get("title"),
|
|
||||||
"body": ticket_body,
|
|
||||||
"labels": [GITEA_LABEL_ID],
|
|
||||||
},
|
|
||||||
headers={"Authorization": "token " + os.getenv("GITEA_TOKEN")},
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code != 201:
|
|
||||||
log.write(f"gitea error: status code={response.status_code}\n")
|
|
||||||
try:
|
|
||||||
log.write("sending notification to {}\n".format(ALERT_MAIL_TO))
|
|
||||||
email.send_email(
|
|
||||||
"[report] Gitea error !",
|
|
||||||
ALERT_MAIL_FROM,
|
|
||||||
[ALERT_MAIL_TO],
|
|
||||||
f"""Error {response.status_code}
|
|
||||||
while creating the gitea ticket :
|
|
||||||
{response.text}
|
|
||||||
|
|
||||||
Ticket info : {ticket.get("title")}
|
|
||||||
|
|
||||||
{ticket_body}
|
|
||||||
|
|
||||||
Utilisateur : {user.get("name", "Nom inconnu")} <{user.get("email", "Adresse email inconnue")}>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
|
||||||
log.write("exception while sending email (1) !\n")
|
|
||||||
log.write(f"{type(exc).__name__}, Exception message: {exc}\n")
|
|
||||||
log.close()
|
|
||||||
return (
|
|
||||||
jsonify(
|
|
||||||
{
|
|
||||||
"message": "Une erreur est survenue lors de la création du ticket. Veuillez réessayer."
|
|
||||||
}
|
|
||||||
),
|
|
||||||
500,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
log.write(f"sending notification to {ALERT_MAIL_TO}\n")
|
|
||||||
email.send_email(
|
|
||||||
f"""[report] Ticket #{response.json()["id"]} créé: {ticket.get("title")}""",
|
|
||||||
ALERT_MAIL_FROM,
|
|
||||||
[ALERT_MAIL_TO],
|
|
||||||
f"""Nouveau ticket utilisateur :
|
|
||||||
{meta}
|
|
||||||
Lien du ticket : {response.json()["html_url"]}
|
|
||||||
Utilisateur : {user.get("name", "Nom inconnu")} <{user.get("email", "Adresse email inconnue")}>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
|
||||||
log.write("exception while sending email (2) !\n")
|
|
||||||
log.write(f"{type(exc).__name__}, Exception message: {exc}\n")
|
|
||||||
log.close()
|
|
||||||
|
|
||||||
return (
|
|
||||||
jsonify(
|
|
||||||
{
|
|
||||||
"message": "Votre demande a été enregistrée. Vous pouvez suivre son avancement sur <a target='_blank' href='"
|
|
||||||
+ response.json()["html_url"]
|
|
||||||
+ "'>"
|
|
||||||
+ response.json()["html_url"]
|
|
||||||
+ "</a>. Vous êtes susceptible d'être contacté(e) par email à l'adresse liée à votre compte ScoDoc si des informations supplémentaires sont nécessaires."
|
|
||||||
}
|
|
||||||
),
|
|
||||||
201,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/scodoc-installmgr/upload-dump", methods=["POST"])
|
@bp.route("/scodoc-installmgr/upload-dump", methods=["POST"])
|
||||||
def upload_scodoc9():
|
def upload_scodoc9():
|
||||||
"""Réception d'un fichier de dump"""
|
"""Réception d'un fichier de dump"""
|
||||||
log = open(LOG_FILENAME, "a")
|
log = open(LOG_FILENAME, "a")
|
||||||
log.write("upload_scodoc9\n")
|
log.write("haallo\n")
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
fulltime = now.isoformat()
|
fulltime = now.isoformat()
|
||||||
# client addr:
|
# client addr:
|
||||||
remote_addr = request.environ.get("HTTP_X_REAL_IP", request.remote_addr)
|
remote_addr = request.environ.get("HTTP_X_REAL_IP", request.remote_addr)
|
||||||
log.write(f"{fulltime} request from {remote_addr}\n")
|
log.write(f"{fulltime} request from {remote_addr}\n")
|
||||||
log.write(f"{request.form}")
|
|
||||||
log.flush()
|
log.flush()
|
||||||
# Avec seulement alphanum et tiret:
|
# Avec seulement alphanum et tiret:
|
||||||
clean_deptname = re.sub(r"[^A-Za-z-]", "", request.form["dept_name"])
|
clean_deptname = re.sub(r"[^A-Za-z-]", "", request.form["dept_name"])
|
||||||
if not clean_deptname:
|
if not clean_deptname:
|
||||||
return (
|
abort(400, "missing argument: dept_name")
|
||||||
jsonify(
|
|
||||||
{
|
|
||||||
"message": 'Erreur: champ dept_name manquant.\n Merci de contacter <a href="mailto:'
|
|
||||||
+ ALERT_MAIL_TO
|
|
||||||
+ '">'
|
|
||||||
+ ALERT_MAIL_TO
|
|
||||||
+ "</a></p>"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
400,
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
remote_host = socket.gethostbyaddr(remote_addr)[0]
|
remote_host = socket.gethostbyaddr(remote_addr)[0]
|
||||||
except:
|
except:
|
||||||
log.write(f"reverse DNS lookup failed for {remote_addr}")
|
log.write("reverse DNS lookup failed for {}".format(remote_addr))
|
||||||
remote_host = ""
|
remote_host = ""
|
||||||
|
|
||||||
the_file = request.files["file"]
|
the_file = request.files["file"]
|
||||||
@ -294,7 +138,6 @@ def upload_scodoc9():
|
|||||||
"sent_by": request.form["sent_by"],
|
"sent_by": request.form["sent_by"],
|
||||||
"sco_version": request.form.get("sco_version", ""), # release
|
"sco_version": request.form.get("sco_version", ""), # release
|
||||||
"sco_subversion": request.form.get("sco_subversion", ""),
|
"sco_subversion": request.form.get("sco_subversion", ""),
|
||||||
"traceback_str": request.form.get("traceback_str", ""),
|
|
||||||
"dump_filename": fulltime + "_" + clean_deptname + ".gz",
|
"dump_filename": fulltime + "_" + clean_deptname + ".gz",
|
||||||
"dump_size": len(data),
|
"dump_size": len(data),
|
||||||
"message": request.form.get("message", ""),
|
"message": request.form.get("message", ""),
|
||||||
@ -328,18 +171,7 @@ def upload_scodoc9():
|
|||||||
except:
|
except:
|
||||||
log.write("exception while sending email !\n")
|
log.write("exception while sending email !\n")
|
||||||
log.close()
|
log.close()
|
||||||
return (
|
abort(507, "Insufficient Storage")
|
||||||
jsonify(
|
|
||||||
{
|
|
||||||
"message": 'Erreur: espace de stockage insuffisant.\n Merci de contacter <a href="mailto:'
|
|
||||||
+ ALERT_MAIL_TO
|
|
||||||
+ '">'
|
|
||||||
+ ALERT_MAIL_TO
|
|
||||||
+ "</a></p>"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
507,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
log.write("writing dump to {}\n".format(D["dump_filename"]))
|
log.write("writing dump to {}\n".format(D["dump_filename"]))
|
||||||
# dump:
|
# dump:
|
||||||
@ -364,12 +196,7 @@ def upload_scodoc9():
|
|||||||
log.write("exception while sending email !\n")
|
log.write("exception while sending email !\n")
|
||||||
|
|
||||||
log.close()
|
log.close()
|
||||||
return jsonify(
|
return "Données envoyées."
|
||||||
{
|
|
||||||
"message": "Données envoyées",
|
|
||||||
"dump_id": fulltime + "_" + clean_deptname,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/scodoc-installmgr/scodoc9")
|
@bp.route("/scodoc-installmgr/scodoc9")
|
||||||
|
@ -10,11 +10,12 @@ Jinja2==3.0.1
|
|||||||
MarkupSafe==2.0.1
|
MarkupSafe==2.0.1
|
||||||
mypy-extensions==0.4.3
|
mypy-extensions==0.4.3
|
||||||
pathspec==0.9.0
|
pathspec==0.9.0
|
||||||
|
pkg-resources==0.0.0
|
||||||
platformdirs==2.3.0
|
platformdirs==2.3.0
|
||||||
python-dotenv==0.19.0
|
python-dotenv==0.19.0
|
||||||
regex==2021.8.28
|
regex==2021.8.28
|
||||||
tomli==1.2.1
|
tomli==1.2.1
|
||||||
|
typed-ast==1.4.3
|
||||||
typing-extensions==3.10.0.2
|
typing-extensions==3.10.0.2
|
||||||
Werkzeug==2.0.1
|
Werkzeug==2.0.1
|
||||||
zipp==3.6.0
|
zipp==3.6.0
|
||||||
requests==2.32.2
|
|
Loading…
Reference in New Issue
Block a user