Initial implementation of IPFS CLI tool
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
IPFS_API_URL=http://127.0.0.1:5001
|
||||||
|
# Optional:
|
||||||
|
# IPFS_API_TOKEN=token_here
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
|
# OS-specific
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE / Editor
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
### IPFS CLI (Python)
|
||||||
|
|
||||||
|
Простая утилита для загрузки файлов в IPFS и скачивания по CID через HTTP API Kubo (`http://127.0.0.1:5001` по умолчанию).
|
||||||
|
|
||||||
|
### Установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m venv .venv
|
||||||
|
|
||||||
|
# Windows PowerShell
|
||||||
|
.\.venv\Scripts\Activate.ps1
|
||||||
|
|
||||||
|
# Linux / macOS / WSL
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Конфигурация
|
||||||
|
|
||||||
|
Создайте файл `.env` в корне проекта (можно скопировать из `.env.example`):
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
По умолчанию используется:
|
||||||
|
|
||||||
|
```text
|
||||||
|
IPFS_API_URL = http://127.0.0.1:5001
|
||||||
|
IPFS_API_TOKEN = (пусто)
|
||||||
|
```
|
||||||
|
|
||||||
|
Перед работой IPFS‑узел должен быть запущен:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ipfs init # один раз
|
||||||
|
ipfs daemon # при каждом запуске
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Использование
|
||||||
|
|
||||||
|
Загрузка файла:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python ipfs_cli.py upload path/to/file.txt
|
||||||
|
# вывод: Qm... (CID)
|
||||||
|
```
|
||||||
|
|
||||||
|
Скачивание файла по CID (сохранится в `./download/<CID>.bin`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python ipfs_cli.py download QmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||||
|
```
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
API_URL = os.getenv("IPFS_API_URL", "http://127.0.0.1:5001").rstrip("/")
|
||||||
|
TOKEN = os.getenv("IPFS_API_TOKEN")
|
||||||
|
|
||||||
|
|
||||||
|
def _auth_headers() -> dict[str, str]:
|
||||||
|
return {"Authorization": f"Bearer {TOKEN}"} if TOKEN else {}
|
||||||
|
|
||||||
|
|
||||||
|
def upload_file(path: Path) -> str:
|
||||||
|
if not path.is_file():
|
||||||
|
raise FileNotFoundError(f"Файл не найден: {path}")
|
||||||
|
|
||||||
|
url = f"{API_URL}/api/v0/add"
|
||||||
|
with path.open("rb") as f:
|
||||||
|
files = {"file": (path.name, f)}
|
||||||
|
r = requests.post(url, headers=_auth_headers(), files=files, timeout=120)
|
||||||
|
|
||||||
|
if r.status_code != 200:
|
||||||
|
raise RuntimeError(f"Ошибка загрузки (status={r.status_code}): {r.text}")
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
cid = data.get("Hash")
|
||||||
|
if not cid:
|
||||||
|
raise RuntimeError(f"Не удалось получить CID из ответа: {data}")
|
||||||
|
return cid
|
||||||
|
|
||||||
|
|
||||||
|
def download_file(cid: str) -> Path:
|
||||||
|
if not cid:
|
||||||
|
raise ValueError("CID пустой")
|
||||||
|
|
||||||
|
download_dir = Path("download")
|
||||||
|
download_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
out_path = download_dir / f"{cid}.bin"
|
||||||
|
|
||||||
|
url = f"{API_URL}/api/v0/cat"
|
||||||
|
params = {"arg": cid}
|
||||||
|
|
||||||
|
with requests.post(
|
||||||
|
url,
|
||||||
|
headers=_auth_headers(),
|
||||||
|
params=params,
|
||||||
|
stream=True,
|
||||||
|
timeout=120,
|
||||||
|
) as r:
|
||||||
|
if r.status_code != 200:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Ошибка скачивания (status={r.status_code}): {r.text}"
|
||||||
|
)
|
||||||
|
with out_path.open("wb") as f:
|
||||||
|
for chunk in r.iter_content(chunk_size=8192):
|
||||||
|
if chunk:
|
||||||
|
f.write(chunk)
|
||||||
|
|
||||||
|
return out_path
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
|
sub = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
up = sub.add_parser("upload", add_help=False)
|
||||||
|
up.add_argument("file")
|
||||||
|
|
||||||
|
dl = sub.add_parser("download", add_help=False)
|
||||||
|
dl.add_argument("cid")
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if args.command == "upload":
|
||||||
|
cid = upload_file(Path(args.file))
|
||||||
|
print(cid)
|
||||||
|
elif args.command == "download":
|
||||||
|
out = download_file(args.cid)
|
||||||
|
print(out)
|
||||||
|
else:
|
||||||
|
raise ValueError("Неизвестная команда")
|
||||||
|
return 0
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"Ошибка: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
python-dotenv>=1.0.0
|
||||||
|
requests>=2.32.0
|
||||||
Reference in New Issue
Block a user