libs.zazac.link
Libs/capy/network

capy.network v0.5.4

capy à travers tout internet, grâce à un relais, le tout chiffré (TLS)

Inclus dans capy : installe capy pour l'avoir.

import capy.network · PyPI : capyy · Python 3.9+ · aucune dépendance

Guide

capy à travers tout internet. Une machine chez toi est cachée derrière ta box : personne ne peut la joindre directement depuis l'extérieur. capy.network contourne ça avec un relais, un petit programme qui tourne sur une machine joignable depuis internet (un VPS, un serveur loué quelques euros par mois).

Comment ça marche#

Le serveur (chez toi) se connecte au relais et y reste branché, sous un nom. Le client (n'importe où) demande au relais de transmettre son appel à ce nom. Le relais fait passer la demande et la réponse.

schéma
 client (n'importe où)          relais (VPS)               serveur (chez toi, derrière la box)
        │                            │                                 │
        │                            │ ◄──── se connecte : « maison » ─┤
        ├── call("maison", "test") ─►│                                 │
        │                            ├──── {"action": "test"} ────────►│
        │                            │◄─── {"text": "Hello"} ──────────┤
        │◄─── {"text": "Hello"} ─────┤                                 │

Comme c'est le serveur qui appelle le relais, aucun port à ouvrir sur ta box. Seul le VPS doit avoir un port ouvert. Tout est chiffré de bout en bout jusqu'au relais, et le relais demande une clé (un mot de passe).

Mettre en place le relais#

Sur le VPS (Linux), une seule fois :

  1. Installe capy : python3 -m pip install --user capyy.
  2. Choisis une clé longue et aléatoire (voir l'astuce juste en dessous).
  3. Ouvre le port dans le pare-feu : sudo ufw allow 3879/tcp.
  4. Lance le relais, et note l'empreinte qu'il affiche (si capy n'est pas trouvé sur le VPS : python3 -m capy.network 3879 …).
terminal
capy relay 3879 MA_CLE_SECRETE
résultat
Relais capy.network en écoute sur le port 3879, chiffré (TLS), avec clé
Empreinte : 9b734c758696262e758f8664db40fafe3601f3804ced5dc331527704319f3049

Générer une bonne clé

python3 -c "import secrets; print(secrets.token_urlsafe(24))" donne une clé impossible à deviner. La même clé va ensuite dans le relais, dans les serveurs et dans les clients.

Sans clé, le relais est ouvert à tous

capy relay 3879 (sans clé) marche, mais n'importe qui peut alors brancher un serveur ou appeler les tiens. Le relais le rappelle au démarrage : SANS clé : ouvert à tous.

Le serveur (chez toi)#

serveur.py
import capy.network

capy.network.use("mon-vps.fr:3879", key="MA_CLE_SECRETE")

api = capy.network.Server("maison")

@api.action("test")
def test():
    return {"text": "Hello"}

api.run()   # se reconnecte tout seul si le relais redémarre ou si internet coupe
résultat
Connecté au relais mon-vps.fr:3879 sous le nom 'maison' (chiffré)
réseau → {"action": "test"}

Le client (n'importe où)#

client.py
import capy
import capy.network

capy.network.use(
    "mon-vps.fr:3879",
    key="MA_CLE_SECRETE",
    fingerprint="9b734c758696262e758f8664db40fafe3601f3804ced5dc331527704319f3049",
)

try:
    reponse = capy.network.call("maison", "test")
except ConnectionError as e:
    exit(str(e))

print(capy.get(reponse, "text"))
résultat
Hello

Donne l'empreinte au client

Avec fingerprint=, le client vérifie dès la 1re connexion qu'il parle bien à ton relais. Sans, il retient l'empreinte à la 1re connexion (comme SSH) et ne vérifie qu'à partir de la 2e.

Tout dans un capy.config.json

{"name": "maison", "relay": "mon-vps.fr:3879", "key": "$CAPY_KEY", "fingerprint": "9b73…"} dans un capy.config.json : plus besoin de use(), ni de recopier l'adresse et la clé dans chaque fichier. capy.project().server() côté serveur, capy.project().call("test") côté client. La clé reste dans la variable d'environnement CAPY_KEY.

Depuis le terminal#

terminal
capy network call maison test --relay mon-vps.fr:3879 --key "$CAPY_KEY"
résultat
{
  "text": "Hello"
}
  • Le relais et la clé peuvent venir d'ailleurs que des options : du capy.config.json du dossier (mode network), ou des variables d'environnement CAPY_RELAY et CAPY_KEY. Avec export CAPY_RELAY=mon-vps.fr:3879 dans ~/.zshrc, c'est juste capy network call maison test.
  • --fingerprint … vérifie l'empreinte du relais dès le 1er appel.
  • Lancer un relais : capy network relay 3879 CLÉ, ou plus court capy relay 3879 CLÉ.
  • Dans un projet en mode network, cy call test passe par le relais de capy.config.json.

Les erreurs#

MessageCe qu'il veut dire
le relais mon-vps.fr:3879 ne répond pasle relais n'est pas lancé, mauvais port, ou le pare-feu du VPS bloque
mauvaise clé pour ce relaisla clé de use() n'est pas celle donnée au relais
aucun serveur 'maison' sur le relais (connectés : salon)le serveur n'est pas lancé, ou il a un autre nom
nom déjà utilisé, un autre serveur est connecté au relais sous le nom 'maison'un autre serveur (souvent le tien, encore ouvert ailleurs) a déjà ce nom
'maison' s'est déconnectéle serveur s'est arrêté pendant l'appel
capy.network.SecurityErrorl'empreinte du relais a changé : ce n'est peut-être pas ton relais

Un nom libéré tout seul

Si l'ancien serveur ne répond plus (programme tué, ordi éteint), le relais s'en rend compte et laisse le nouveau prendre le nom. Le message « nom déjà utilisé » ne s'affiche que si l'autre répond encore.

Rapidité#

  • Le 1er appel ouvre la connexion chiffrée vers le relais : environ 1 s vers un VPS lointain.
  • Les suivants réutilisent cette connexion : environ un aller-retour (≈ 200 ms) + le trajet relais → serveur.
  • Plusieurs appels en parallèle (depuis plusieurs threads) passent par la même connexion, sans se gêner.
  • Le relais attend la réponse du serveur 10 s au maximum.

Garder le relais allumé#

Lancé dans un terminal ssh, le relais s'arrête quand tu fermes la session. Pour qu'il tourne toujours et redémarre tout seul (même après un redémarrage du VPS), fais-en un service systemd : voir l'exemple « Garder le relais allumé (systemd) » plus bas.

Voir ce qui passe par le relais

Le relais affiche les connexions (+ maison connecté, - maison déconnecté) et chaque demande transmise. Les mots de passe y sont masqués ("***"), comme sur un serveur capy. Avec systemd : journalctl -u capy-relay -f.

Astuces et pièges#

Oublier use()

Sans capy.network.use(...) avant Server ou call, capy le rappelle : ValueError: capy.network : aucun relais, utilise capy.network.use("ip:port").

Plusieurs serveurs, un seul relais

Chaque serveur a son nom ("maison", "garage", "pi"…) et le client choisit qui appeler. Le même client peut appeler tous les serveurs du relais.

Ne mets pas l'IP de ton VPS dans un dépôt public

Mets-la plutôt dans une variable d'environnement ou un fichier ignoré par git, avec la clé. Un nom de domaine (mon-vps.fr) est encore plus pratique : tu peux changer de VPS sans toucher au code.

Le relais peut-il lire mes données ?

Le relais déchiffre les messages pour savoir à qui les transmettre, puis les rechiffre vers le serveur. C'est ton relais, sur ta machine : garde-le pour toi, et ne branche pas tes serveurs sur le relais de quelqu'un d'autre.

Quelle différence avec capy.wifi ?

capy.wifi marche sans rien installer d'autre, mais seulement sur le même wifi. capy.network marche partout, mais il lui faut un relais sur une machine joignable depuis internet.

Exemples

capy.network : à travers tout internet, chiffré

Un relais tourne sur une machine joignable depuis internet (un VPS). Les serveurs s'y connectent même derrière une box, les clients les appellent par leur nom.

1. Sur le VPS : ouvrir le port, puis lancer le relais (il affiche son empreinte)

relais.sh
ufw allow 3879/tcp
capy relay 3879 motdepasse
# Relais capy.network en écoute sur le port 3879, chiffré (TLS), avec clé
# Empreinte : 9b734c758696262e758f8664db40fafe3601f3804ced5dc331527704319f3049

Garder le relais allumé (systemd)

Sur le VPS : le relais tourne en fond, redémarre s'il plante et se relance au démarrage du VPS. Remplace MA_CLE_SECRETE par ta clé, et /root si capy est installé pour un autre utilisateur.

relais_systemd.sh
sudo tee /etc/systemd/system/capy-relay.service > /dev/null <<'SERVICE'
[Unit]
Description=Relais capy.network
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/bin/python3 -u -m capy.network 3879 MA_CLE_SECRETE
WorkingDirectory=/root
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
SERVICE

sudo systemctl daemon-reload
sudo systemctl enable --now capy-relay   # Lancer maintenant + à chaque démarrage
systemctl status capy-relay              # Il doit être "active (running)"
journalctl -u capy-relay -f              # Voir son journal en direct (Ctrl+C pour sortir)

Un projet complet : capy.config.json

On décrit le projet une fois (nom, version, port, adresse, chiffrement, fichiers…) ; serveur et client le lisent avec capy.project(), et cy lance ses scripts et ses fichiers (comme npm).

1. Créer le projet (modes : local, wifi ou network), puis tout lancer d'un coup

creer_le_projet.sh
capy new mon-jeu
cd mon-jeu
cy          # Voir ce que capy a compris
# mon-jeu v1.0
#   mode     local (localhost:4000)
#   tls      oui (chiffré)
#   server   server.py  ✓
#   client   client.py  ✓

cy run      # Serveur en fond + client, puis arrêt du serveur
# server │ API en écoute sur le port 4000, chiffrée (TLS) · empreinte : …
# Bonjour toi !

cy server   # Ou chacun dans son terminal
cy client

Référence

Classes

class Relay(port, key=None)

Relais : à lancer sur une machine joignable depuis internet

Excapy.network.Relay(3879, key="motdepasse").run()

Relay.run()

Pas de description.

Voir le code
python
def run(self):
self.context, fingerprint = _relay_context()
with socket.create_server(("", self.port)) as server:
    _log(f"Relais capy.network en écoute sur le port {self.port}, chiffré (TLS)"
         + (", avec clé" if self.key else ", SANS clé : ouvert à tous"))
    _log(f"Empreinte : {fingerprint}")
    try:
        while True:
            conn, addr = server.accept()
            threading.Thread(target=self._client, args=(conn, addr), daemon=True).start()
    except KeyboardInterrupt:
        _log("\nRelais arrêté")

class Server(name, relay=None, key=None)

Serveur joignable depuis tout internet sous un nom, via le relais

Exapi = capy.network.Server("maison") puis api.run()

Hérite de capy.Server et de toutes ses méthodes.

Server.run()

Se connecter au relais et répondre aux demandes (se reconnecte tout seul si ça coupe)

Voir le code
python
def run(self):
host, port = _address(self.relay)
key = self.key if self.key is not None else _config["key"]
while True:
    try:
        conn = _connect(host, port)
        _send(conn, {"capy": "register", "name": self.name, "key": key})
        line, rest = _recv_line(conn)
        hello = jsonlib.loads(line)
        if hello.get("capy") != "ok":
            conn.close()
            raise PermissionError(hello.get("message", "capy.network : le relais a refusé la connexion"))
        conn.settimeout(None)
        _log(f"Connecté au relais {host}:{port} sous le nom {self.name!r} (chiffré)")
        channel = _Channel(conn, lambda message: self._request(channel, message), rest)
        channel.run(on_idle=lambda: channel.send({"ping": True}))
        _log("Connexion au relais perdue, reconnexion dans 3 s")
    except PermissionError:
        raise
    except (OSError, ValueError) as e:
        _log(f"Relais {host}:{port} injoignable ({e}), nouvel essai dans 3 s")
    except KeyboardInterrupt:
        _log("\nServeur arrêté")
        return
    time.sleep(3)

Fonctions

use(relay, key=None, fingerprint=None)

Choisir le relais une fois pour toutes (pour Server et call)

fingerprint (optionnel) : l'empreinte affichée par le relais au démarrage, pour être sûr dès la 1re fois

Exuse("mon-vps.fr:3879", key="motdepasse")

Voir le code
python
def use(relay, key=None, fingerprint=None):
    _config.update(relay=relay, key=key, fingerprint=fingerprint)
    _close_pipes()

call(name, action, /, **params) -> Any

Appeler une action d'un serveur par son nom, via le relais (comme capy.call)

La connexion chiffrée reste ouverte : seul le 1er appel paie la connexion TLS

Excapy.network.call("maison", "test") -> {"text": "Hello"}

Voir le code
python
def call(name, action, /, **params) -> Any:
    host, port = _address(None)
    message = {"capy": "call", "to": name, "key": _config["key"], "request": {"action": action, **params}}
    reply = None
    for _ in range(2):  # Connexion morte (relais relancé) -> on se reconnecte une fois
        if (host, port) in _old_relays:
            reply = _call_once(host, port, message)
        else:
            reply = _pipe(host, port).ask(message, timeout=15)
        if reply is not None:
            break
    if reply is None:
        raise _unreachable(host, port, None)
    if reply.get("capy") != "ok":
        raise ConnectionError(reply.get("message", "capy.network : erreur du relais"))
    return reply["response"]

cli(args=None)

Lancer un relais depuis le terminal : capy relay 3879 motdepasse (ou capy relay 3879 motdepasse)

Voir le code
python
def cli(args=None):
    args = sys.argv[1:] if args is None else args
    if not args or not args[0].isdigit():
        sys.exit("Usage : capy relay PORT [CLÉ]   (ex : capy relay 3879 \"$CAPY_KEY\")")
    Relay(int(args[0]), key=args[1] if len(args) > 1 else None).run()

Constantes

PING_EVERY

PING_EVERY = 25  # Secondes sans rien avant un "je suis toujours là" (sinon la box coupe la connexion)