Aller au contenu

Migrer depuis UptimeRobot

Recopiez vos monitors UptimeRobot dans OKStatus en une commande, faites tourner les deux outils en parallèle quelques jours, puis basculez. Votre compte UptimeRobot n’est jamais modifié.

Un script Python lit vos monitors avec l’API d’UptimeRobot et les recrée avec l’API d’OKStatus. Il n’utilise que la bibliothèque standard : rien à installer.

  • Une clé UptimeRobot en lecture seule : Integrations → API → Read-only API key. Elle ne peut rien modifier.
  • Une clé OKStatus Complète, sur l’offre Max (Paramètres → Clés API). Sur Free ou Pro, l’essai à blanc de l’étape 3 vous donne quand même la liste exacte à recréer à la main.
  • Python 3.9 ou plus récent.
UptimeRobotOKStatusDétail
HTTP(s)HTTPmême URL, même méthode
KeywordHTTP + mot-clé« n’existe pas » devient keywordInvert
PingPing
PortTCPHTTP 80, HTTPS 443, FTP 21, SMTP 25, POP3 110, IMAP 143 ou votre port personnalisé
HeartbeatHeartbeat (push)une nouvelle URL de ping pour vos tâches cron ; les périodes de plus d’un jour sont ignorées
En pauseEn pausele monitor est créé, puis mis en pause
IntervalleIntervallerelevé au minimum de votre offre si besoin, un jour au plus

Téléchargez uptimerobot_to_okstatus.py, ou copiez-le. Ses messages sont en anglais.

uptimerobot_to_okstatus.py
#!/usr/bin/env python3
"""Copy UptimeRobot monitors to OKStatus.
Reads your monitors with UptimeRobot's API (v2, read-only key is enough) and
creates them in OKStatus with its REST API (Full key, Max plan). Without
--apply it only prints what it would create. Your UptimeRobot account is
never changed. Python 3.9+, standard library only.
export UPTIMEROBOT_API_KEY=ur...
export OKSTATUS_REGIONS=fr-dc3,de-fra,nl-ams # regions of your plan
export OKSTATUS_MIN_INTERVAL=30 # 300 Free, 60 Pro, 30 Max
python3 uptimerobot_to_okstatus.py # dry run
export OKSTATUS_API_KEY=oks_live_...
python3 uptimerobot_to_okstatus.py --apply
Guide: https://okstatus.eu/docs/tutorials/migrate-from-uptimerobot/
"""
import json
import os
import re
import sys
import urllib.error
import urllib.parse
import urllib.request
UR_API = os.environ.get("UPTIMEROBOT_API_URL", "https://api.uptimerobot.com/v2")
OK_API = os.environ.get("OKSTATUS_API_URL", "https://api.okstatus.eu/api/v1")
REGIONS = [r.strip() for r in os.environ.get("OKSTATUS_REGIONS", "fr-dc3,de-fra").split(",") if r.strip()]
MIN_INTERVAL = int(os.environ.get("OKSTATUS_MIN_INTERVAL", "60"))
APPLY = "--apply" in sys.argv
# UptimeRobot v2 codes (uptimerobot.com/api/legacy)
PORTS = {1: 80, 2: 443, 3: 21, 4: 25, 5: 110, 6: 143} # port monitor sub_type
METHODS = {1: "HEAD", 2: "GET", 3: "POST", 4: "PUT", 5: "PATCH", 6: "DELETE", 7: "OPTIONS"}
PAUSED = 0 # monitor status
def uptimerobot_monitors():
key = os.environ.get("UPTIMEROBOT_API_KEY")
if not key:
sys.exit("Set UPTIMEROBOT_API_KEY (Integrations → API → read-only key).")
offset = 0
while True:
form = {"api_key": key, "format": "json", "offset": offset, "limit": 50,
"http_request_details": "true"}
req = urllib.request.Request(f"{UR_API}/getMonitors", urllib.parse.urlencode(form).encode())
with urllib.request.urlopen(req, timeout=30) as r:
page = json.load(r)
if page.get("stat") != "ok":
sys.exit(f"UptimeRobot: {page.get('error')}")
yield from page.get("monitors", [])
offset += 50
if offset >= int(page.get("pagination", {}).get("total", 0)):
return
def as_int(value, default=0):
try:
return int(value)
except (TypeError, ValueError):
return default
def convert(m):
"""(body of POST /api/v1/monitors, None) or (None, why it is skipped)."""
kind = as_int(m.get("type"))
body = {
"name": str(m.get("friendly_name") or m.get("url") or "Monitor")[:100],
"regions": REGIONS,
# OKStatus accepts 30 s to 1 day, and never less than the plan's floor.
"interval": min(max(as_int(m.get("interval"), 300), MIN_INTERVAL), 86400),
}
if kind in (1, 2): # HTTP(s), Keyword
body.update(type="http", url=m["url"])
method = METHODS.get(as_int(m.get("http_method"), 2), "GET")
if method != "GET":
body["httpMethod"] = method
if kind == 2:
body["keyword"] = m.get("keyword_value") or ""
body["keywordInvert"] = as_int(m.get("keyword_type")) == 2 # 2 = "not exists"
elif kind == 3: # Ping
body.update(type="ping", url=m["url"])
elif kind == 4: # Port
sub = as_int(m.get("sub_type"))
port = as_int(m.get("port")) if sub == 99 else PORTS.get(sub)
if not port:
return None, "port monitor without a port"
body.update(type="tcp", url=m["url"], port=port)
elif kind == 5: # Heartbeat
if as_int(m.get("interval")) > 86400:
# A shorter period would report the job down between two runs.
return None, "heartbeat period over one day, the OKStatus maximum"
slug = re.sub(r"[^a-z0-9]+", "-", body["name"].lower()).strip("-")
body.update(type="push", url=slug or "heartbeat")
else:
return None, f"UptimeRobot type {m.get('type')} has no equivalent"
return body, None
def okstatus(method, url, body):
req = urllib.request.Request(url, json.dumps(body).encode(), method=method, headers={
"Authorization": f"Bearer {os.environ['OKSTATUS_API_KEY']}",
"Content-Type": "application/json",
})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)["data"]
def main():
if APPLY and not os.environ.get("OKSTATUS_API_KEY"):
sys.exit("Set OKSTATUS_API_KEY (a Full key, Settings → API Keys) to use --apply.")
created = skipped = failed = 0
for m in uptimerobot_monitors():
name = m.get("friendly_name", "?")
body, why = convert(m)
if body is None:
print(f"SKIPPED {name} ({why})")
skipped += 1
continue
paused = as_int(m.get("status"), -1) == PAUSED
if not APPLY:
print(f"TO CREATE {body['type']:5} {body['name']} -> {body['url']}"
+ (" (paused)" if paused else ""))
continue
try:
new = okstatus("POST", f"{OK_API}/monitors", body)
if paused:
okstatus("PATCH", f"{OK_API}/monitors/{new['id']}", {"status": "paused"})
extra = f" · new ping URL: {new['pushUrl']}" if body["type"] == "push" else ""
print(f"CREATED {body['name']}{extra}")
created += 1
except urllib.error.HTTPError as e:
print(f"FAILED {body['name']}: {e.code} {e.read().decode(errors='replace')[:200]}")
failed += 1
if APPLY:
print(f"\n{created} created, {skipped} skipped, {failed} failed.")
if failed:
sys.exit(1)
else:
print("\nDry run: nothing created. Add --apply to create these monitors.")
if __name__ == "__main__":
main()
Fenêtre de terminal
export UPTIMEROBOT_API_KEY=ur…
export OKSTATUS_REGIONS=fr-dc3,de-fra,nl-ams # regions of your plan
export OKSTATUS_MIN_INTERVAL=30 # 300 on Free, 60 on Pro, 30 on Max
python3 uptimerobot_to_okstatus.py
TO CREATE http Website -> https://example.com
TO CREATE http API health -> https://api.example.com/health
TO CREATE tcp SMTP -> mail.example.com
TO CREATE push Nightly backup -> nightly-backup
TO CREATE ping Office router -> 203.0.113.10 (paused)
Dry run: nothing created. Add --apply to create these monitors.

Rien n’est créé sans --apply. Relisez la liste, ajustez les variables, recommencez autant que nécessaire.

Fenêtre de terminal
export OKSTATUS_API_KEY=oks_live_…
python3 uptimerobot_to_okstatus.py --apply
CREATED Website
CREATED API health
CREATED SMTP
CREATED Nightly backup · new ping URL: https://backend.okstatus.eu/push/…
CREATED Office router
5 created, 0 skipped, 0 failed.
  1. Recréez vos canaux d’alerte dans le tableau de bord, puis rattachez-les aux monitors ou à un groupe. Le script ne les copie pas : ils contiennent des secrets.
  2. Remplacez l’URL de ping de chaque heartbeat dans vos tâches cron. Gardez le même appel : GET, POST et HEAD comptent tous comme un ping. Voir Heartbeat.
  3. Faites tourner les deux outils une semaine et comparez les alertes. Avec plusieurs régions, OKStatus n’alerte que si la majorité confirme : attendez-vous à moins d’alertes isolées.
  4. Recréez votre page de statut et pointez votre domaine (CNAME) vers OKStatus. Voir Pages de statut.
  5. Mettez en pause les monitors UptimeRobot, puis fermez le compte quand vous êtes prêt.
  • l’historique de disponibilité : OKStatus commence à compter le jour de la migration ;
  • les canaux d’alerte, les pages de statut et les maintenances ;
  • les identifiants, en-têtes et corps de requête des monitors HTTP : ajoutez-les dans le tableau de bord ;
  • les types sans équivalent (UDP, services tiers) et les heartbeats de plus d’un jour : le script les liste comme ignorés (SKIPPED).