104 lines
4.3 KiB
Plaintext
104 lines
4.3 KiB
Plaintext
|
|
#!/usr/bin/env python3
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import argparse
|
|||
|
|
import json
|
|||
|
|
import os
|
|||
|
|
import sys
|
|||
|
|
|
|||
|
|
from codexpi.remote_ipc import remote_channel_request
|
|||
|
|
from codexpi.remote_service import RemoteError, enqueue_remote_job
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main() -> int:
|
|||
|
|
parser = argparse.ArgumentParser(description="Accès fichier limité à une machine associée à CodexPi")
|
|||
|
|
parser.add_argument("--machine", help="identifiant de machine ; facultatif si une seule est associée")
|
|||
|
|
subparsers = parser.add_subparsers(dest="operation", required=True)
|
|||
|
|
subparsers.add_parser("machines")
|
|||
|
|
listing = subparsers.add_parser("list")
|
|||
|
|
listing.add_argument("path", nargs="?", default="")
|
|||
|
|
reading = subparsers.add_parser("read")
|
|||
|
|
reading.add_argument("path")
|
|||
|
|
writing = subparsers.add_parser("write")
|
|||
|
|
writing.add_argument("path")
|
|||
|
|
writing.add_argument("--expected", help="SHA-256 obtenu lors de la lecture, pour empêcher un écrasement concurrent")
|
|||
|
|
executing = subparsers.add_parser("exec")
|
|||
|
|
executing.add_argument("argv", nargs=argparse.REMAINDER)
|
|||
|
|
args = parser.parse_args()
|
|||
|
|
database = os.environ.get("CODEXPI_DATABASE", "/app/data/codexpi.db")
|
|||
|
|
remote_channel = os.environ.get("CODEXPI_REMOTE_CHANNEL")
|
|||
|
|
project_id = os.environ.get("CODEXPI_PROJECT_ID", "")
|
|||
|
|
if not project_id:
|
|||
|
|
print("Cette commande doit être lancée depuis une session de projet CodexPi.", file=sys.stderr)
|
|||
|
|
return 2
|
|||
|
|
try:
|
|||
|
|
if remote_channel:
|
|||
|
|
payload = None
|
|||
|
|
expected = None
|
|||
|
|
if args.operation == "write":
|
|||
|
|
if sys.stdin.isatty():
|
|||
|
|
raise RemoteError("Le contenu du fichier doit être fourni sur l’entrée standard")
|
|||
|
|
payload = {"content": sys.stdin.read()}
|
|||
|
|
expected = args.expected
|
|||
|
|
elif args.operation == "exec":
|
|||
|
|
if not args.argv:
|
|||
|
|
raise RemoteError("Indiquez la commande distante après exec")
|
|||
|
|
payload = {"argv": args.argv, "timeout": 120}
|
|||
|
|
result = remote_channel_request(remote_channel, {
|
|||
|
|
"operation": args.operation,
|
|||
|
|
"path": getattr(args, "path", ""),
|
|||
|
|
"payload": payload,
|
|||
|
|
"expected_hash": expected,
|
|||
|
|
"machine_id": args.machine,
|
|||
|
|
})
|
|||
|
|
if args.operation == "machines":
|
|||
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|||
|
|
return 0
|
|||
|
|
elif args.operation == "machines":
|
|||
|
|
import sqlite3
|
|||
|
|
connection = sqlite3.connect(database)
|
|||
|
|
connection.row_factory = sqlite3.Row
|
|||
|
|
rows = connection.execute(
|
|||
|
|
"SELECT id,name,folder_name,platform,last_seen FROM remote_machines WHERE project_id=? AND revoked_at IS NULL ORDER BY last_seen DESC",
|
|||
|
|
(project_id,),
|
|||
|
|
).fetchall()
|
|||
|
|
print(json.dumps([dict(row) for row in rows], ensure_ascii=False, indent=2))
|
|||
|
|
return 0
|
|||
|
|
else:
|
|||
|
|
payload = None
|
|||
|
|
expected = None
|
|||
|
|
if args.operation == "write":
|
|||
|
|
if sys.stdin.isatty():
|
|||
|
|
raise RemoteError("Le contenu du fichier doit être fourni sur l’entrée standard")
|
|||
|
|
content = sys.stdin.read()
|
|||
|
|
payload = {"content": content}
|
|||
|
|
expected = args.expected
|
|||
|
|
elif args.operation == "exec":
|
|||
|
|
if not args.argv:
|
|||
|
|
raise RemoteError("Indiquez la commande distante après exec")
|
|||
|
|
payload = {"argv": args.argv, "timeout": 120}
|
|||
|
|
result = enqueue_remote_job(
|
|||
|
|
database,
|
|||
|
|
project_id,
|
|||
|
|
args.operation,
|
|||
|
|
args.path,
|
|||
|
|
payload,
|
|||
|
|
expected,
|
|||
|
|
args.machine,
|
|||
|
|
)
|
|||
|
|
if args.operation == "read":
|
|||
|
|
sys.stdout.write(str(result.get("content", "")))
|
|||
|
|
if result.get("content") and not str(result["content"]).endswith("\n"):
|
|||
|
|
sys.stdout.write("\n")
|
|||
|
|
print(f"[sha256:{result.get('sha256','')}]", file=sys.stderr)
|
|||
|
|
else:
|
|||
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|||
|
|
return 0
|
|||
|
|
except (RemoteError, OSError, ValueError) as exc:
|
|||
|
|
print(f"Erreur distante : {exc}", file=sys.stderr)
|
|||
|
|
return 1
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
raise SystemExit(main())
|