feat(monero): minage planifie selon le calendrier Nextcloud
Le mineur ne tourne que 01:00-09:00, plus en semaine 09:00-18:00 quand un evenement du calendrier sg est actif. Le wallet vient de secrets/monero.yml via LoadCredential (plus dans la ligne de commande) ; meme planning sur le Deck.
This commit is contained in:
@@ -72,6 +72,25 @@
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
sops.defaultSopsFile = ../../secrets/common.yml;
|
||||
|
||||
sops.secrets."monero/wallet" = {
|
||||
sopsFile = ../../secrets/monero.yml;
|
||||
key = "wallet";
|
||||
owner = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
sops.secrets."monero/nextcloud-user" = {
|
||||
sopsFile = ../../secrets/common.yml;
|
||||
key = "nextcloud_user";
|
||||
owner = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
sops.secrets."monero/nextcloud-password" = {
|
||||
sopsFile = ../../secrets/common.yml;
|
||||
key = "nextcloud_password";
|
||||
owner = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
programs.zsh.enable = true; # requis par users.nix
|
||||
services.openssh.enable = true;
|
||||
services.automatic-timezoned.enable = false;
|
||||
@@ -192,7 +211,7 @@
|
||||
# charge protegent la machine sur le long terme.
|
||||
services.monero-miner = {
|
||||
enable = true;
|
||||
wallet = "46WTCDFTfzZ7uxKToJ27nN3v1RcGqEGACHWjVxgdcJLMGfZZLdyeTo2JoGksFCrpS7HMEm8yT8iKHPNdiMHXGEY3CS8drV6";
|
||||
walletFile = config.sops.secrets."monero/wallet".path;
|
||||
pool = "gulf.moneroocean.stream:10008";
|
||||
tls = false;
|
||||
threads = 6;
|
||||
@@ -202,6 +221,11 @@
|
||||
thermalGuard.enable = true;
|
||||
thermalGuard.maxTemperature = 88;
|
||||
thermalGuard.resumeTemperature = 84;
|
||||
schedule.enable = true;
|
||||
calendar.enable = true;
|
||||
calendar.userFile = config.sops.secrets."monero/nextcloud-user".path;
|
||||
calendar.passwordFile = config.sops.secrets."monero/nextcloud-password".path;
|
||||
calendar.calendars = [ "sg" ];
|
||||
};
|
||||
|
||||
networking.hostName = "deck";
|
||||
|
||||
@@ -13,6 +13,7 @@ in {
|
||||
../../hosts/tower/disko.nix
|
||||
../../hosts/laptop/hardware-configuration.nix
|
||||
../../modules/nixos/herdr
|
||||
../../services/monero-miner
|
||||
];
|
||||
|
||||
# Sessions hermes lancees par marc (panes herdr, HERMES_HOME partage, cf
|
||||
@@ -460,6 +461,45 @@ in {
|
||||
};
|
||||
# Aucun port UDP à ouvrir : wg-quick en mode client n'écoute pas de port fixe.
|
||||
|
||||
# ========================================================================
|
||||
# MINEUR MONERO - planning sommeil / travail Nextcloud
|
||||
# ========================================================================
|
||||
sops.secrets."monero/wallet" = {
|
||||
sopsFile = ../../secrets/monero.yml;
|
||||
key = "wallet";
|
||||
owner = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
sops.secrets."monero/nextcloud-user" = {
|
||||
sopsFile = ../../secrets/common.yml;
|
||||
key = "nextcloud_user";
|
||||
owner = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
sops.secrets."monero/nextcloud-password" = {
|
||||
sopsFile = ../../secrets/common.yml;
|
||||
key = "nextcloud_password";
|
||||
owner = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
services.monero-miner = {
|
||||
enable = true;
|
||||
walletFile = config.sops.secrets."monero/wallet".path;
|
||||
pool = "gulf.moneroocean.stream:10008";
|
||||
tls = false;
|
||||
threads = 8;
|
||||
cpuQuota = 800;
|
||||
rigId = "tower";
|
||||
thermalGuard.enable = true;
|
||||
thermalGuard.maxTemperature = 88;
|
||||
thermalGuard.resumeTemperature = 82;
|
||||
schedule.enable = true;
|
||||
calendar.enable = true;
|
||||
calendar.userFile = config.sops.secrets."monero/nextcloud-user".path;
|
||||
calendar.passwordFile = config.sops.secrets."monero/nextcloud-password".path;
|
||||
calendar.calendars = [ "sg" ];
|
||||
};
|
||||
|
||||
# ========================================================================
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
wallet: ENC[AES256_GCM,data:bvxmwN3aV/UYepLlE4enAtwMy2PMwxOw3tQY5wtqUdTco6Uj6RTVqWOjnzfCE2HnQwEDxJ61fSEVELpf64+RVvcyFcias0lbwmsBCA5trJrDLIio8Dbifn6BO1D/5Zo=,iv:gl4+K2f1HxLnOI3MfkFX1KPp/hUzY6cuHOefpNCwIyU=,tag:krTcZmZg18j2t9uPAq4Hxw==,type:str]
|
||||
pool_password: ENC[AES256_GCM,data:YA==,iv:MSc20uI2nLhI/hMDP5Jsdxp1197dvkWDTT/QW6ydVlk=,tag:27vrXRWatWhEdkmibzpH7A==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6N1AzWnUrcVZQRU43aWZJ
|
||||
NGpuOFJBc1UvOWlVcWFQV0VIdHFpMTFWZUV3CmZpbElqbXFJSWFUUU5IL2Q5dHNt
|
||||
enExSnVtcW9XUzdPdVUxaUFLRElFUlUKLS0tIHAzbTZNU3UzNGIyV2JKOE5iQjFY
|
||||
aGtacDdpY2VsNXBvUW8wbWtiU1hnc28KsFzoLNKR7yDQaJCXtHIxZq5P6lbhdkR5
|
||||
O1Z+qN8z4nPx4IEXg8zM2aDm9H+jBBMR5MlRVzUNqCpRBkUFlvJdsA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1usm3v0ugducrzcwud60dda7xfnj49fdhecj7kcsfm7dzwzrzeyqq2jc2hm
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKY2FUMWdVQ1AzUjBMUjFO
|
||||
QkdIaW80dHpMdWZtRS9iY0ZzeGRNQ0tBNG1JCmwremlNL1JZZlVMRVNqUGdMTWxU
|
||||
UzBOYWJFc0RvVlhoYWs2bHVjb1JBRVUKLS0tIDdySFhIY0J1RnJRS3JIUXI1Tng0
|
||||
aXFQa2U4TDFQV2xmdWViZG5ENFF3OXcKuTjZiX68d1jJLxItYfefuy4HE91kUOVV
|
||||
DW9bBzHqK/nTkMWRTKyc3P2giG3dYPDiFy0/cMsUxYqPQWLne0Krog==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age154uz4pavaj5qth25ytj0q8chsya4y65pgel04zcrm02je9w6uc9sug7dp2
|
||||
lastmodified: "2026-10-19T05:56:58Z"
|
||||
mac: ENC[AES256_GCM,data:VBPkrMB+mM72Oid8CTspuptrqDsqKxvCRulKpF7YPvTbeuzv1hVSZs83QhGom120vhOT8W2PyXk/F14xFWFKK/PLybnz7oz048SPXSvfxBQ+ZMWY+JccEVahqATWCFqGSC7zu0rWR+k96DbBoio98vIfau6iMPQW8csgujxC0Ng=,iv:OkFCCUjqOcTXVFCuCOczUVDpG2367wIBUJmj0RpAiqc=,tag:FpMOYmTkPLXv04JzCHqS5g==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.3
|
||||
@@ -7,12 +7,13 @@
|
||||
let
|
||||
cfg = config.services.monero-miner;
|
||||
|
||||
isTime = value: (builtins.match "(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]" value) != null;
|
||||
|
||||
# XMRig est pilote par la ligne de commande : pas de fichier de config a
|
||||
# generer, donc rien a synchroniser dans /etc et aucun secret sur disque.
|
||||
args =
|
||||
# generer, donc rien a synchroniser dans /etc et aucun secret dans le store.
|
||||
baseArgs =
|
||||
[
|
||||
"--url=${cfg.pool}"
|
||||
"--user=${cfg.wallet}"
|
||||
"--pass=${cfg.poolPassword}"
|
||||
"--coin=monero"
|
||||
"--threads=${toString cfg.threads}"
|
||||
@@ -26,6 +27,29 @@ let
|
||||
++ lib.optionals cfg.pauseOnBattery [ "--pause-on-battery" ]
|
||||
++ lib.optionals (cfg.rigId != "") [ "--rig-id=${cfg.rigId}" ];
|
||||
|
||||
args = baseArgs ++ [ "--user=${cfg.wallet}" ];
|
||||
|
||||
# Quand le wallet vient de sops, il est lu dans le credential systemd et
|
||||
# n'apparait jamais dans la ligne de commande ni dans le store.
|
||||
startScript = pkgs.writeShellApplication {
|
||||
name = "monero-miner-start";
|
||||
runtimeInputs = [ cfg.package pkgs.coreutils ];
|
||||
text = ''
|
||||
wallet="$(cat "''${CREDENTIALS_DIRECTORY}/wallet")"
|
||||
exec ${cfg.package}/bin/xmrig ${lib.escapeShellArgs baseArgs} --user="$wallet"
|
||||
'';
|
||||
};
|
||||
|
||||
schedulerPython = pkgs.python3.withPackages (ps: [ ps.caldav ]);
|
||||
|
||||
schedulerScript = pkgs.writeShellApplication {
|
||||
name = "monero-miner-scheduler";
|
||||
runtimeInputs = [ schedulerPython pkgs.systemd pkgs.coreutils ];
|
||||
text = ''
|
||||
exec ${schedulerPython}/bin/python3 ${./scheduler.py}
|
||||
'';
|
||||
};
|
||||
|
||||
# Plafond de charge batterie : le controleur steamdeck_hwmon expose
|
||||
# max_battery_charge_level (0 = pas de plafond, 80 = charge a 80 %). Le
|
||||
# chemin hwmonN varie selon l'ordre de detection, d'ou la recherche par nom.
|
||||
@@ -95,13 +119,92 @@ in
|
||||
|
||||
wallet = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
example = "48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWZgDJRh3jpbZ7knX2aMVJpXyF5bZ1...";
|
||||
description = ''
|
||||
Adresse Monero qui recoit les gains. Une adresse principale (commence
|
||||
par 4) ou une sous-adresse (commence par 8) fait 95 caracteres.
|
||||
Laisser vide si walletFile est utilise.
|
||||
'';
|
||||
};
|
||||
|
||||
walletFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Chemin d'un fichier contenant l'adresse Monero, typiquement un secret
|
||||
sops lu par systemd via LoadCredential. Prioritaire sur wallet.
|
||||
'';
|
||||
};
|
||||
|
||||
schedule = {
|
||||
enable = lib.mkEnableOption "la planification du minage";
|
||||
|
||||
weekdayStart = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "01:00";
|
||||
description = "Debut de la fenetre de sommeil en semaine (HH:MM).";
|
||||
};
|
||||
|
||||
weekdayWorkStart = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "09:00";
|
||||
description = "Heure a partir de laquelle le calendrier decide en semaine (HH:MM).";
|
||||
};
|
||||
|
||||
weekdayEnd = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "18:00";
|
||||
description = "Fin de la fenetre de semaine (HH:MM).";
|
||||
};
|
||||
|
||||
weekendStart = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "01:00";
|
||||
description = "Debut de la fenetre du week-end (HH:MM).";
|
||||
};
|
||||
|
||||
weekendEnd = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "09:00";
|
||||
description = "Fin de la fenetre du week-end (HH:MM).";
|
||||
};
|
||||
};
|
||||
|
||||
calendar = {
|
||||
enable = lib.mkEnableOption "la restriction aux evenements de travail Nextcloud";
|
||||
|
||||
url = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "https://cloud.marcpartensky.com";
|
||||
description = "URL de base CalDAV Nextcloud.";
|
||||
};
|
||||
|
||||
userFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Fichier contenant le nom d'utilisateur Nextcloud.";
|
||||
};
|
||||
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Fichier contenant le mot de passe ou app password Nextcloud.";
|
||||
};
|
||||
|
||||
calendars = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "sg" ];
|
||||
description = "Calendriers dont un evenement actif autorise le minage.";
|
||||
};
|
||||
|
||||
failOpen = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Miner pendant la plage travail si le calendrier est indisponible.";
|
||||
};
|
||||
};
|
||||
|
||||
pool = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "gulf.moneroocean.stream:10008";
|
||||
@@ -218,16 +321,34 @@ in
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.wallet != "";
|
||||
message = "services.monero-miner.wallet doit contenir une adresse Monero.";
|
||||
assertion = cfg.wallet != "" || cfg.walletFile != null;
|
||||
message = "services.monero-miner.wallet ou walletFile doit etre renseigne.";
|
||||
}
|
||||
{
|
||||
assertion = (builtins.match "[48][1-9A-HJ-NP-Za-km-z]{94}" cfg.wallet) != null;
|
||||
assertion = cfg.wallet == "" || (builtins.match "[48][1-9A-HJ-NP-Za-km-z]{94}" cfg.wallet) != null;
|
||||
message = ''
|
||||
services.monero-miner.wallet n'a pas la forme d'une adresse Monero
|
||||
standard ou de sous-adresse (95 caracteres, commence par 4 ou 8).
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = lib.all isTime [
|
||||
cfg.schedule.weekdayStart
|
||||
cfg.schedule.weekdayWorkStart
|
||||
cfg.schedule.weekdayEnd
|
||||
cfg.schedule.weekendStart
|
||||
cfg.schedule.weekendEnd
|
||||
];
|
||||
message = "Les horaires de services.monero-miner.schedule doivent etre au format HH:MM.";
|
||||
}
|
||||
{
|
||||
assertion = !cfg.schedule.enable || !cfg.calendar.enable
|
||||
|| (cfg.calendar.userFile != null && cfg.calendar.passwordFile != null);
|
||||
message = ''
|
||||
services.monero-miner.calendar.enable exige userFile et passwordFile
|
||||
(secrets sops) pour interroger Nextcloud.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = cfg.thermalGuard.resumeTemperature < cfg.thermalGuard.maxTemperature;
|
||||
message = "services.monero-miner.thermalGuard.resumeTemperature doit etre inferieure a maxTemperature.";
|
||||
@@ -240,12 +361,15 @@ in
|
||||
|
||||
systemd.services.monero-miner = {
|
||||
description = "XMRig Monero CPU miner";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wantedBy = lib.optionals (!cfg.schedule.enable) [ "multi-user.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/xmrig ${lib.escapeShellArgs args}";
|
||||
ExecStart =
|
||||
if cfg.walletFile != null
|
||||
then "${startScript}/bin/monero-miner-start"
|
||||
else "${cfg.package}/bin/xmrig ${lib.escapeShellArgs args}";
|
||||
|
||||
# Utilisateur jetable : aucun fichier, aucun home, aucun droit persistant.
|
||||
DynamicUser = true;
|
||||
@@ -253,6 +377,9 @@ in
|
||||
RestartSec = 15;
|
||||
KillSignal = "SIGINT";
|
||||
TimeoutStopSec = 30;
|
||||
LoadCredential = lib.optionals (cfg.walletFile != null) [
|
||||
"wallet:${cfg.walletFile}"
|
||||
];
|
||||
|
||||
# Priorite basse et plafond : le Deck reste utilisable pendant le minage.
|
||||
Nice = cfg.nice;
|
||||
@@ -309,6 +436,46 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.monero-miner-scheduler = lib.mkIf cfg.schedule.enable {
|
||||
description = "Planificateur du mineur Monero (calendrier Nextcloud)";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
environment = {
|
||||
MONERO_SCHEDULE_WEEKDAY_START = cfg.schedule.weekdayStart;
|
||||
MONERO_SCHEDULE_WEEKDAY_WORK_START = cfg.schedule.weekdayWorkStart;
|
||||
MONERO_SCHEDULE_WEEKDAY_END = cfg.schedule.weekdayEnd;
|
||||
MONERO_SCHEDULE_WEEKEND_START = cfg.schedule.weekendStart;
|
||||
MONERO_SCHEDULE_WEEKEND_END = cfg.schedule.weekendEnd;
|
||||
MONERO_CALENDAR_ENABLE = if cfg.calendar.enable then "1" else "0";
|
||||
MONERO_CALENDAR_URL = cfg.calendar.url;
|
||||
MONERO_CALENDAR_USER_FILE =
|
||||
if cfg.calendar.userFile != null then cfg.calendar.userFile else "";
|
||||
MONERO_CALENDAR_PASSWORD_FILE =
|
||||
if cfg.calendar.passwordFile != null then cfg.calendar.passwordFile else "";
|
||||
MONERO_CALENDAR_NAMES = lib.concatStringsSep "," cfg.calendar.calendars;
|
||||
MONERO_CALENDAR_FAIL_OPEN = if cfg.calendar.failOpen then "1" else "0";
|
||||
MONERO_MINER_UNIT = "monero-miner.service";
|
||||
MONERO_SYSTEMCTL = "${pkgs.systemd}/bin/systemctl";
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${schedulerScript}/bin/monero-miner-scheduler";
|
||||
TimeoutStartSec = 90;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.monero-miner-scheduler = lib.mkIf cfg.schedule.enable {
|
||||
description = "Application periodique du planning du mineur Monero";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnBootSec = "2min";
|
||||
OnUnitActiveSec = "5min";
|
||||
AccuracySec = "30s";
|
||||
Unit = "monero-miner-scheduler.service";
|
||||
};
|
||||
};
|
||||
|
||||
# Permet de lancer `xmrig --bench=1M` a la main pour comparer les reglages.
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Planificateur du mineur Monero.
|
||||
|
||||
Le service tourne en root et applique la regle :
|
||||
- semaine : 01:00-09:00 toujours, 09:00-18:00 seulement pendant un
|
||||
evenement du calendrier de travail Nextcloud ;
|
||||
- week-end : 01:00-09:00.
|
||||
Si le calendrier est indisponible, le comportement depend de
|
||||
MONERO_CALENDAR_FAIL_OPEN (defaut : ne pas miner pendant la plage travail).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def env(name: str, default: str | None = None) -> str | None:
|
||||
value = os.environ.get(name)
|
||||
return default if value is None or value == "" else value
|
||||
|
||||
|
||||
def parse_time(name: str) -> dt.time:
|
||||
raw = env(name)
|
||||
if raw is None:
|
||||
raise SystemExit(f"{name} manquant")
|
||||
hour, minute = raw.split(":", 1)
|
||||
return dt.time(int(hour), int(minute))
|
||||
|
||||
|
||||
def read_secret(path: str) -> str:
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
return handle.read().strip()
|
||||
|
||||
|
||||
def calendar_event_active(now: dt.datetime) -> bool | None:
|
||||
"""True si un evenement de travail est actif, None si erreur."""
|
||||
url = env("MONERO_CALENDAR_URL")
|
||||
user_file = env("MONERO_CALENDAR_USER_FILE")
|
||||
password_file = env("MONERO_CALENDAR_PASSWORD_FILE")
|
||||
names = [
|
||||
item.strip().lower()
|
||||
for item in (env("MONERO_CALENDAR_NAMES", "sg") or "").split(",")
|
||||
if item.strip()
|
||||
]
|
||||
if not url or not user_file or not password_file:
|
||||
print("calendar: configuration incomplete", flush=True)
|
||||
return None
|
||||
|
||||
try:
|
||||
import caldav
|
||||
|
||||
client = caldav.DAVClient(
|
||||
url=url,
|
||||
username=read_secret(user_file),
|
||||
password=read_secret(password_file),
|
||||
)
|
||||
try:
|
||||
principal = client.principal()
|
||||
for calendar in principal.calendars():
|
||||
calendar_url = str(calendar.url).rstrip("/")
|
||||
calendar_name = (calendar.name or "").lower()
|
||||
if not any(
|
||||
calendar_name == name or calendar_url.endswith("/" + name)
|
||||
for name in names
|
||||
):
|
||||
continue
|
||||
events = calendar.search(
|
||||
start=now - dt.timedelta(days=1),
|
||||
end=now + dt.timedelta(days=1),
|
||||
event=True,
|
||||
expand=True,
|
||||
)
|
||||
for event in events:
|
||||
for component in event.icalendar_instance.walk("VEVENT"):
|
||||
if str(component.get("STATUS", "")).upper() == "CANCELLED":
|
||||
continue
|
||||
start_prop = component.get("DTSTART")
|
||||
if start_prop is None:
|
||||
continue
|
||||
start = start_prop.dt
|
||||
if not isinstance(start, dt.datetime):
|
||||
continue
|
||||
end_prop = component.get("DTEND")
|
||||
end = (
|
||||
end_prop.dt
|
||||
if end_prop is not None
|
||||
else start + dt.timedelta(hours=1)
|
||||
)
|
||||
if not isinstance(end, dt.datetime):
|
||||
continue
|
||||
if start.tzinfo is None:
|
||||
start = start.replace(tzinfo=now.tzinfo)
|
||||
if end.tzinfo is None:
|
||||
end = end.replace(tzinfo=now.tzinfo)
|
||||
if start <= now < end:
|
||||
return True
|
||||
print(
|
||||
"calendar: aucun evenement actif dans "
|
||||
+ ",".join(names),
|
||||
flush=True,
|
||||
)
|
||||
return False
|
||||
finally:
|
||||
close = getattr(client, "close", None)
|
||||
if close is not None:
|
||||
close()
|
||||
except Exception as exc: # noqa: BLE001 - le detail part dans le journal
|
||||
print(f"calendar: erreur {type(exc).__name__}: {exc}", flush=True)
|
||||
return None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
override = env("MONERO_SCHEDULE_NOW")
|
||||
if override is not None:
|
||||
now = dt.datetime.fromisoformat(override)
|
||||
if now.tzinfo is None:
|
||||
now = now.astimezone()
|
||||
else:
|
||||
now = dt.datetime.now().astimezone()
|
||||
weekday = now.isoweekday() <= 5
|
||||
current = now.time().replace(tzinfo=None)
|
||||
|
||||
weekday_start = parse_time("MONERO_SCHEDULE_WEEKDAY_START")
|
||||
work_start = parse_time("MONERO_SCHEDULE_WEEKDAY_WORK_START")
|
||||
weekday_end = parse_time("MONERO_SCHEDULE_WEEKDAY_END")
|
||||
weekend_start = parse_time("MONERO_SCHEDULE_WEEKEND_START")
|
||||
weekend_end = parse_time("MONERO_SCHEDULE_WEEKEND_END")
|
||||
|
||||
calendar_enabled = env("MONERO_CALENDAR_ENABLE", "0") == "1"
|
||||
fail_open = env("MONERO_CALENDAR_FAIL_OPEN", "0") == "1"
|
||||
|
||||
if weekday:
|
||||
if weekday_start <= current < work_start:
|
||||
desired, reason = True, "sommeil"
|
||||
elif work_start <= current < weekday_end:
|
||||
if calendar_enabled:
|
||||
active = calendar_event_active(now)
|
||||
if active is None:
|
||||
desired = fail_open
|
||||
reason = "calendrier indisponible"
|
||||
else:
|
||||
desired = active
|
||||
reason = "travail" if active else "hors travail"
|
||||
else:
|
||||
desired, reason = True, "fenetre semaine"
|
||||
else:
|
||||
desired, reason = False, "hors fenetre semaine"
|
||||
else:
|
||||
desired = weekend_start <= current < weekend_end
|
||||
reason = "fenetre week-end" if desired else "hors fenetre week-end"
|
||||
|
||||
unit = env("MONERO_MINER_UNIT", "monero-miner.service") or "monero-miner.service"
|
||||
print(
|
||||
f"{now.isoformat()} desired={desired} reason={reason} unit={unit}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
if env("MONERO_SCHEDULE_DRY_RUN", "0") == "1":
|
||||
return 0
|
||||
|
||||
systemctl = env("MONERO_SYSTEMCTL", "systemctl") or "systemctl"
|
||||
active = (
|
||||
subprocess.run(
|
||||
[systemctl, "is-active", "--quiet", unit],
|
||||
check=False,
|
||||
).returncode
|
||||
== 0
|
||||
)
|
||||
if desired and not active:
|
||||
rc = subprocess.run([systemctl, "start", unit], check=False).returncode
|
||||
print(f"systemctl start {unit} -> {rc}", flush=True)
|
||||
elif not desired and active:
|
||||
rc = subprocess.run([systemctl, "stop", unit], check=False).returncode
|
||||
print(f"systemctl stop {unit} -> {rc}", flush=True)
|
||||
else:
|
||||
print(f"{unit} deja {'actif' if active else 'inactif'}", flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user