2025-11-01 22:51:57 -04:00
|
|
|
#!/usr/bin/env python3
|
2026-03-11 23:15:07 -04:00
|
|
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
2025-11-01 22:51:57 -04:00
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import os
|
|
|
|
|
import stat
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
[FIX] systemd : lancer run.sh par bash, contre les echecs 203/EXEC
erplibre.service mourait en 3 ms sur openSUSE s390x, « status=203/EXEC »,
sans jamais entrer dans le script. Ce code ne dit pas que run.sh a
echoue : il dit que systemd n'a pas pu l'EXECUTER.
Quatre causes le produisent — bit x absent, shebang qui ne resout pas,
/home monte noexec, SELinux refusant l'execve (Leap 16 est passe a
SELinux). Les distinguer demande un acces a la machine ; les traiter
ensemble ne le demande pas.
Passe a « /bin/bash run.sh », le fichier n'est plus qu'une donnee lue :
noexec et SELinux ne portent que sur l'execve, et le bit x devient sans
objet. Mesure : un script en 644 refuse en direct, execute par bash.
Les trois generateurs ecrivaient la meme ligne, les trois sont corriges.
Le venv n'y est pour rien — odoo_bin.sh l'active deja, et c'est celui
d'Odoo, pas celui des outils.
--- EN ---
erplibre.service died in 3 ms on openSUSE s390x, "status=203/EXEC",
never entering the script. That code does not say run.sh failed: it says
systemd could not EXECUTE it.
Four causes produce it — missing x bit, unresolvable shebang, /home
mounted noexec, SELinux denying execve (Leap 16 switched to SELinux).
Telling them apart needs access to the machine; handling them together
does not.
Run as "/bin/bash run.sh", the file is merely data being read: noexec
and SELinux only cover execve, and the x bit becomes moot. Measured: a
644 script refused directly, executed fine through bash.
All three generators wrote the same line; all three are fixed. The venv
is not involved — odoo_bin.sh already activates it, and it is Odoo's,
not the tooling one.
Assisted-by: Claude Opus 5
(cherry picked from commit 6f8cee84b72fd016fa188d204f280b011594627c)
2026-08-13 18:14:45 -04:00
|
|
|
# « ExecStart=/bin/bash …/run.sh » et non le script seul.
|
|
|
|
|
#
|
|
|
|
|
# Lancé seul, systemd doit EXÉCUTER le fichier, et l'échoue en « 203/EXEC »
|
|
|
|
|
# dans quatre cas au moins : bit x absent, shebang qui ne résout pas, /home
|
[FIX] outillage : désimbriquer les f-strings que seul 3.12 sait lire
Cinq fichiers employaient des f-strings PEP 701 — guillemets imbriqués du même
type, backslash dans l'expression, expression sur plusieurs lignes — que
Python refuse avant 3.12. Or les hooks de script/git/hooks portent
« #!/usr/bin/env python3 » et tournent donc sur l'interpréteur du système,
qu'une distribution livre encore en 3.10 : la SyntaxError tombe au chargement,
sans qu'aucun garde puisse nommer la commande à taper. Les chaînes traduites
gardent leur texte à l'octet près, sans quoi leur clé se perdrait. Les
commentaires de ces fichiers passent du récit au présent, comme la règle le
demande de ce qu'on touche.
Vérifié : tout l'arbre Python parse sous 3.10, 3.11, 3.12 et 3.14.
--- EN ---
Five files used PEP 701 f-strings — quotes of the same kind nested, a backslash
in the expression, an expression spanning lines — which Python refuses before
3.12. The hooks in script/git/hooks carry "#!/usr/bin/env python3" and so run
on the system interpreter, which a distribution still ships as 3.10: the
SyntaxError lands at load, before any guard can name the command to type.
Translated strings keep their text byte for byte, else their key would be lost.
The comments of those files move from tale to present tense, as the rule asks
of what one touches.
Checked: the whole Python tree parses under 3.10, 3.11, 3.12 and 3.14.
Assisted-by: Claude Opus 5
2026-09-24 13:30:47 -04:00
|
|
|
# monté noexec, SELinux refusant l'execve. L'échec est alors MUET : le
|
|
|
|
|
# processus meurt avant d'entrer dans le script, sans une ligne de sortie, et
|
|
|
|
|
# 203 ressemble à une erreur d'application — le diagnostic part donc dans la
|
|
|
|
|
# mauvaise direction.
|
[FIX] systemd : lancer run.sh par bash, contre les echecs 203/EXEC
erplibre.service mourait en 3 ms sur openSUSE s390x, « status=203/EXEC »,
sans jamais entrer dans le script. Ce code ne dit pas que run.sh a
echoue : il dit que systemd n'a pas pu l'EXECUTER.
Quatre causes le produisent — bit x absent, shebang qui ne resout pas,
/home monte noexec, SELinux refusant l'execve (Leap 16 est passe a
SELinux). Les distinguer demande un acces a la machine ; les traiter
ensemble ne le demande pas.
Passe a « /bin/bash run.sh », le fichier n'est plus qu'une donnee lue :
noexec et SELinux ne portent que sur l'execve, et le bit x devient sans
objet. Mesure : un script en 644 refuse en direct, execute par bash.
Les trois generateurs ecrivaient la meme ligne, les trois sont corriges.
Le venv n'y est pour rien — odoo_bin.sh l'active deja, et c'est celui
d'Odoo, pas celui des outils.
--- EN ---
erplibre.service died in 3 ms on openSUSE s390x, "status=203/EXEC",
never entering the script. That code does not say run.sh failed: it says
systemd could not EXECUTE it.
Four causes produce it — missing x bit, unresolvable shebang, /home
mounted noexec, SELinux denying execve (Leap 16 switched to SELinux).
Telling them apart needs access to the machine; handling them together
does not.
Run as "/bin/bash run.sh", the file is merely data being read: noexec
and SELinux only cover execve, and the x bit becomes moot. Measured: a
644 script refused directly, executed fine through bash.
All three generators wrote the same line; all three are fixed. The venv
is not involved — odoo_bin.sh already activates it, and it is Odoo's,
not the tooling one.
Assisted-by: Claude Opus 5
(cherry picked from commit 6f8cee84b72fd016fa188d204f280b011594627c)
2026-08-13 18:14:45 -04:00
|
|
|
#
|
|
|
|
|
# Passé à bash, run.sh n'est plus qu'une DONNÉE lue : les quatre causes
|
|
|
|
|
# disparaissent ensemble, y compris noexec et SELinux, qui ne portent que sur
|
|
|
|
|
# l'execve. Rien n'est perdu au passage — le shebang du script désigne déjà
|
|
|
|
|
# bash. C'est aussi ce que font install_daemon.sh et le générateur de todo.py,
|
|
|
|
|
# les trois écrivant la même unité.
|
2025-11-01 22:51:57 -04:00
|
|
|
UNIT_TEMPLATE = """[Unit]
|
|
|
|
|
Description=ERPLibre for {user}
|
|
|
|
|
Requires=postgresql.service
|
|
|
|
|
After=network.target network-online.target postgresql.service
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=simple
|
|
|
|
|
SyslogIdentifier={user}
|
|
|
|
|
PermissionsStartOnly=true
|
|
|
|
|
User={user}
|
|
|
|
|
Group={user}
|
|
|
|
|
Restart=always
|
|
|
|
|
RestartSec=5
|
|
|
|
|
PIDFile={home_erplibre}/.venv.erplibre/service.pid
|
[FIX] systemd : lancer run.sh par bash, contre les echecs 203/EXEC
erplibre.service mourait en 3 ms sur openSUSE s390x, « status=203/EXEC »,
sans jamais entrer dans le script. Ce code ne dit pas que run.sh a
echoue : il dit que systemd n'a pas pu l'EXECUTER.
Quatre causes le produisent — bit x absent, shebang qui ne resout pas,
/home monte noexec, SELinux refusant l'execve (Leap 16 est passe a
SELinux). Les distinguer demande un acces a la machine ; les traiter
ensemble ne le demande pas.
Passe a « /bin/bash run.sh », le fichier n'est plus qu'une donnee lue :
noexec et SELinux ne portent que sur l'execve, et le bit x devient sans
objet. Mesure : un script en 644 refuse en direct, execute par bash.
Les trois generateurs ecrivaient la meme ligne, les trois sont corriges.
Le venv n'y est pour rien — odoo_bin.sh l'active deja, et c'est celui
d'Odoo, pas celui des outils.
--- EN ---
erplibre.service died in 3 ms on openSUSE s390x, "status=203/EXEC",
never entering the script. That code does not say run.sh failed: it says
systemd could not EXECUTE it.
Four causes produce it — missing x bit, unresolvable shebang, /home
mounted noexec, SELinux denying execve (Leap 16 switched to SELinux).
Telling them apart needs access to the machine; handling them together
does not.
Run as "/bin/bash run.sh", the file is merely data being read: noexec
and SELinux only cover execve, and the x bit becomes moot. Measured: a
644 script refused directly, executed fine through bash.
All three generators wrote the same line; all three are fixed. The venv
is not involved — odoo_bin.sh already activates it, and it is Odoo's,
not the tooling one.
Assisted-by: Claude Opus 5
(cherry picked from commit 6f8cee84b72fd016fa188d204f280b011594627c)
2026-08-13 18:14:45 -04:00
|
|
|
# bash explicite : voir install_daemon.py (evite 203/EXEC).
|
|
|
|
|
ExecStart=/bin/bash {home_erplibre}/run.sh{EXEC_PARAM}
|
2025-11-01 22:51:57 -04:00
|
|
|
WorkingDirectory={home_erplibre}
|
|
|
|
|
StandardOutput=journal+console
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_root():
|
|
|
|
|
if os.geteuid() != 0:
|
|
|
|
|
sys.exit("❌ This script must be run as root (use sudo or root).")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run(cmd: list[str]) -> None:
|
|
|
|
|
subprocess.run(cmd, check=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def w_cmd(param: str, arg: str = None) -> str:
|
|
|
|
|
if not arg:
|
|
|
|
|
if type(arg) is str:
|
|
|
|
|
return ""
|
|
|
|
|
return param
|
|
|
|
|
return f"{param} {arg}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
description="Create and enable a SystemD service for ERPLibre (Python equivalent of the bash script)."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Exposed variables as configurable parameters
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--user",
|
|
|
|
|
required=True,
|
|
|
|
|
help="Linux user running the service (e.g. erplibre)",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--home-erplibre",
|
|
|
|
|
help="Path to erplibre directory, workspace root path.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument("--port", help="Application port (for information).")
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--database",
|
|
|
|
|
help="Database name to run only with it (for information).",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--config-name",
|
|
|
|
|
help="Service name (and systemd file name without .service). Default: user",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Utility flags
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--no-enable",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Do not enable service on boot.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--no-restart",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Do not restart the service after setup.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--dry-run",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Show what would be done without changing the system.",
|
|
|
|
|
)
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
ensure_root()
|
|
|
|
|
|
|
|
|
|
# Default paths and fallback values (same logic as in the bash script)
|
|
|
|
|
el_user = args.user
|
|
|
|
|
el_home_erplibre = args.home_erplibre or os.getcwd()
|
2025-11-07 02:39:44 -05:00
|
|
|
el_config_name = (
|
|
|
|
|
args.config_name
|
|
|
|
|
or f"erplibre_{el_user}_{os.path.basename(os.getcwd())}"
|
|
|
|
|
)
|
[FIX] outillage : désimbriquer les f-strings que seul 3.12 sait lire
Cinq fichiers employaient des f-strings PEP 701 — guillemets imbriqués du même
type, backslash dans l'expression, expression sur plusieurs lignes — que
Python refuse avant 3.12. Or les hooks de script/git/hooks portent
« #!/usr/bin/env python3 » et tournent donc sur l'interpréteur du système,
qu'une distribution livre encore en 3.10 : la SyntaxError tombe au chargement,
sans qu'aucun garde puisse nommer la commande à taper. Les chaînes traduites
gardent leur texte à l'octet près, sans quoi leur clé se perdrait. Les
commentaires de ces fichiers passent du récit au présent, comme la règle le
demande de ce qu'on touche.
Vérifié : tout l'arbre Python parse sous 3.10, 3.11, 3.12 et 3.14.
--- EN ---
Five files used PEP 701 f-strings — quotes of the same kind nested, a backslash
in the expression, an expression spanning lines — which Python refuses before
3.12. The hooks in script/git/hooks carry "#!/usr/bin/env python3" and so run
on the system interpreter, which a distribution still ships as 3.10: the
SyntaxError lands at load, before any guard can name the command to type.
Translated strings keep their text byte for byte, else their key would be lost.
The comments of those files move from tale to present tense, as the rule asks
of what one touches.
Checked: the whole Python tree parses under 3.10, 3.11, 3.12 and 3.14.
Assisted-by: Claude Opus 5
2026-09-24 13:30:47 -04:00
|
|
|
db_param = w_cmd("-d", args.database or "")
|
|
|
|
|
port_param = w_cmd("-p", args.port or "")
|
|
|
|
|
exec_param = " " + f" {db_param} {port_param}".strip()
|
2025-11-01 22:51:57 -04:00
|
|
|
|
|
|
|
|
# Render the systemd service file content
|
|
|
|
|
unit_content = UNIT_TEMPLATE.format(
|
|
|
|
|
user=el_user,
|
|
|
|
|
home_erplibre=el_home_erplibre,
|
|
|
|
|
EXEC_PARAM=exec_param,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
unit_dest_path = Path(f"/etc/systemd/system/{el_config_name}.service")
|
|
|
|
|
|
|
|
|
|
print("* Final systemd file location:", unit_dest_path)
|
|
|
|
|
|
|
|
|
|
if args.dry_run:
|
|
|
|
|
print("\n--- GENERATED CONTENT ---\n")
|
|
|
|
|
print(unit_content)
|
|
|
|
|
print("\n--- END ---")
|
|
|
|
|
print("\n(dry-run) No files written, no systemctl commands executed.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Write temporary unit file
|
|
|
|
|
unit_dest_path.write_text(unit_content, encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
# chmod 755
|
|
|
|
|
print("* Setting permissions to 755")
|
|
|
|
|
unit_dest_path.chmod(
|
|
|
|
|
stat.S_IRUSR
|
|
|
|
|
| stat.S_IWUSR
|
|
|
|
|
| stat.S_IXUSR
|
|
|
|
|
| stat.S_IRGRP
|
|
|
|
|
| stat.S_IXGRP
|
|
|
|
|
| stat.S_IROTH
|
|
|
|
|
| stat.S_IXOTH
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# chown root:root
|
|
|
|
|
print("* Changing owner to root:root")
|
|
|
|
|
os.chown(unit_dest_path, 0, 0)
|
|
|
|
|
|
|
|
|
|
# Sanity checks
|
|
|
|
|
if not Path(el_home_erplibre).exists():
|
|
|
|
|
print(f"⚠️ Warning: {el_home_erplibre} does not exist.")
|
|
|
|
|
if not Path(el_home_erplibre, "run.sh").exists():
|
|
|
|
|
print(f"⚠️ Warning: {el_home_erplibre}/run.sh does not exist.")
|
|
|
|
|
|
|
|
|
|
# Reload systemd and enable service
|
|
|
|
|
print("* Reloading systemd daemon")
|
|
|
|
|
run(["systemctl", "daemon-reload"])
|
|
|
|
|
|
|
|
|
|
if not args.no_enable:
|
|
|
|
|
print(f"* Enabling service at boot: {el_config_name}.service")
|
|
|
|
|
run(["systemctl", "enable", f"{el_config_name}.service"])
|
|
|
|
|
else:
|
|
|
|
|
print("* (skip) enable step")
|
|
|
|
|
|
|
|
|
|
if not args.no_restart:
|
|
|
|
|
print(f"* Restarting service: {el_config_name}.service")
|
|
|
|
|
run(["systemctl", "restart", f"{el_config_name}.service"])
|
|
|
|
|
else:
|
|
|
|
|
print("* (skip) restart step")
|
|
|
|
|
|
|
|
|
|
# Summary (equivalent of bash echo block)
|
|
|
|
|
print("\n-----------------------------------------------------------")
|
|
|
|
|
print("Done! ERPLibre service created and started. Details:")
|
|
|
|
|
print(f"Port: {args.port}")
|
|
|
|
|
print(f"Service user: {el_user}")
|
|
|
|
|
print(f"Systemd file: {unit_dest_path}")
|
|
|
|
|
print(f"Start: systemctl start {el_config_name}")
|
|
|
|
|
print(f"Stop: systemctl stop {el_config_name}")
|
|
|
|
|
print(f"Restart: systemctl restart {el_config_name}")
|
|
|
|
|
print(f"Status: systemctl status {el_config_name}")
|
|
|
|
|
print(f"Logs: journalctl -feu {el_config_name}")
|
|
|
|
|
print("-----------------------------------------------------------")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|