|
1 | | -# netcup-api · `netcup` CLI |
| 1 | +<div align="center"> |
2 | 2 |
|
3 | | -A solid, spec-driven Python client **library** and **command-line interface** for the |
4 | | -[netcup Server Control Panel (SCP) REST API](https://www.netcup.com/en/helpcenter/documentation/server/rest-api). |
| 3 | +# 🚀 netcup-api |
5 | 4 |
|
6 | | -> The legacy SOAP webservice was shut down on **2026-05-01**. This tool targets the |
7 | | -> modern REST API (`scp-core`, OAuth2 / Keycloak device flow). |
| 5 | +### A modern, spec-driven Python client & CLI for the netcup Server Control Panel (SCP) REST API |
8 | 6 |
|
9 | | -## Why this design |
| 7 | +[](https://github.com/fullya99/netcup-cli/actions/workflows/ci.yml) |
| 8 | +[](https://www.python.org/) |
| 9 | +[](#-license) |
| 10 | +[](https://www.servercontrolpanel.de/scp-core/api/v1/openapi) |
10 | 11 |
|
11 | | -Everything is driven by the API's own **OpenAPI specification**, which is bundled and |
12 | | -can be refreshed at any time (`netcup spec update`). This means: |
| 12 | +*Manage your netcup VPS fleet from the terminal — power, snapshots, firewall, rDNS, metrics — with **every** API endpoint one command away.* |
13 | 13 |
|
14 | | -- **All endpoints are covered** — not just the hand-written convenience commands. |
15 | | - `netcup endpoints` lists them; `netcup call <operationId>` invokes any of them. |
16 | | -- **Future-proof** — when netcup adds or changes endpoints, refresh the spec and the |
17 | | - new operations are immediately available via `endpoints` / `call`, with no code change. |
18 | | -- **No hard-coded hostnames or guessing** — the auth flow and base URLs are explicit and |
19 | | - documented in `netcup_api/config.py`. |
| 14 | +[Install](#-install) · [Authenticate](#-authenticate-once) · [Commands](#-everyday-commands) · [Any endpoint](#-reach-any-endpoint) · [Library](#-use-as-a-library) |
20 | 15 |
|
21 | | -## Install |
| 16 | +</div> |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +> [!NOTE] |
| 21 | +> netcup's legacy SOAP webservice was shut down on **2026‑05‑01**. This tool targets the modern |
| 22 | +> **REST API** (`scp-core`) with OAuth2 / Keycloak device‑flow auth. |
| 23 | +
|
| 24 | +## ✨ Why this one |
| 25 | + |
| 26 | +| | | |
| 27 | +|---|---| |
| 28 | +| 🧭 **Spec-driven** | Every command is backed by the API's own OpenAPI spec — no hand-maintained endpoint lists. | |
| 29 | +| 🔌 **All endpoints, always** | Curated commands for daily ops **+** a generic layer (`call` / `api`) that reaches *all* operations. | |
| 30 | +| 🔮 **Future-proof** | New netcup endpoint? `netcup spec update` and it's instantly usable — zero code changes. | |
| 31 | +| 🔐 **Secure by default** | Only the offline refresh token is stored (mode `0600`); the 300 s access token is cached & auto-refreshed. | |
| 32 | +| 🎨 **Pretty output** | Rich tables with humanized sizes, or `--json` for clean `jq` piping. | |
| 33 | +| 🤝 **Shared core** | Same engine powers the [`netcup-mcp`](https://github.com/fullya99/netcup-mcp) server. | |
| 34 | + |
| 35 | +## 📦 Install |
22 | 36 |
|
23 | 37 | ```bash |
24 | 38 | pip install netcup-api # from PyPI (once published) |
25 | | -# or, from source: |
| 39 | +# or from source: |
| 40 | +git clone https://github.com/fullya99/netcup-cli && cd netcup-cli |
26 | 41 | pip install -e . |
27 | 42 | ``` |
28 | 43 |
|
29 | | -## Authenticate (once) |
| 44 | +## 🔑 Authenticate (once) |
30 | 45 |
|
31 | 46 | Authentication uses the OAuth2 **device-code flow** against netcup's Keycloak realm. |
32 | | -Only the long-lived *offline refresh token* is stored (`~/.config/netcup-api/credentials.json`, mode 0600); |
33 | | -the 300-second access token is cached separately and refreshed automatically. |
34 | 47 |
|
35 | 48 | ```bash |
36 | | -netcup auth login # prints a URL + user code, opens your browser |
37 | | -netcup auth status # show who you are (incl. SCP userId) |
38 | | -netcup auth logout # revoke the refresh token and wipe local creds |
| 49 | +netcup auth login # prints a URL + code, opens your browser |
| 50 | +netcup auth status # who am I? (incl. SCP userId) |
| 51 | +netcup auth logout # revoke the refresh token & wipe local creds |
39 | 52 | ``` |
40 | 53 |
|
41 | | -> The offline refresh token stays valid **as long as it is used at least once every 30 days**. |
42 | | -> If unused longer, re-run `netcup auth login`. |
| 54 | +> [!TIP] |
| 55 | +> The offline refresh token stays valid **as long as it's used at least once every 30 days**. |
| 56 | +> Only the refresh token is persisted (`~/.config/netcup-api/`, `0600`); the access token is cached and refreshed automatically. |
43 | 57 |
|
44 | | -## Daily-driver commands |
| 58 | +## ⚡ Everyday commands |
45 | 59 |
|
46 | 60 | ```bash |
| 61 | +# Servers |
47 | 62 | netcup servers list # table of your servers |
48 | 63 | netcup servers get <serverId> |
49 | 64 | netcup servers start <serverId> # power ON |
50 | 65 | netcup servers stop <serverId> # power OFF |
51 | | -netcup servers set-hostname <serverId> host.example.com |
| 66 | +netcup servers set-hostname <serverId> web01.example.com |
52 | 67 |
|
53 | | -netcup snapshots list <serverId> |
| 68 | +# Snapshots |
54 | 69 | netcup snapshots create <serverId> before-upgrade --online |
55 | 70 | netcup snapshots revert <serverId> before-upgrade |
56 | | -netcup snapshots delete <serverId> before-upgrade |
| 71 | +netcup snapshots list <serverId> |
57 | 72 |
|
| 73 | +# Networking |
58 | 74 | netcup rdns set 203.0.113.10 host.example.com |
59 | | -netcup rdns set 2a03:... host.example.com --v6 |
| 75 | +netcup rdns set 2a03:4000:: host.example.com --v6 |
| 76 | +netcup interfaces <serverId> |
60 | 77 |
|
61 | | -netcup ssh-keys list |
| 78 | +# Keys, firewall, disks, ISO/images, metrics, tasks |
62 | 79 | netcup ssh-keys add laptop --key-file ~/.ssh/id_ed25519.pub |
63 | | - |
64 | 80 | netcup firewall policies |
| 81 | +netcup disks <serverId> |
| 82 | +netcup iso list / netcup iso available <serverId> |
| 83 | +netcup images list |
| 84 | +netcup metrics <serverId> cpu # cpu | disk | network | network/packet |
65 | 85 | netcup tasks list |
66 | | -netcup metrics <serverId> cpu |
67 | | -netcup interfaces <serverId> |
68 | | - |
69 | | -netcup disks <serverId> # disk table (sizes humanized) |
70 | | -netcup images list # your uploaded custom images |
71 | | -netcup images flavours <serverId> # available install flavours |
72 | | -netcup iso list # your uploaded ISOs |
73 | | -netcup iso attached <serverId> # ISO currently attached |
74 | | -netcup iso available <serverId> # ISO images available to attach |
75 | 86 | ``` |
76 | 87 |
|
77 | | -Add `--json` / `-j` to any command for raw JSON output (great for scripting with `jq`). |
| 88 | +Add `--json` / `-j` to **any** command for raw JSON (great with `jq`). |
| 89 | + |
| 90 | +## 🌐 Reach *any* endpoint |
78 | 91 |
|
79 | | -## Reach *any* endpoint (spec-driven) |
| 92 | +The CLI is OpenAPI-driven, so the full API is one command away — even endpoints with no curated wrapper: |
80 | 93 |
|
81 | 94 | ```bash |
82 | | -netcup endpoints # list all endpoints |
83 | | -netcup endpoints snapshot # filter |
| 95 | +netcup endpoints # list every endpoint |
| 96 | +netcup endpoints snapshot # filter by text |
84 | 97 | netcup endpoints --tag "Server Networking" |
85 | | -netcup describe get-servers-by-serverId # params + request schema |
| 98 | +netcup describe get-servers-by-serverId # params + request body schema |
86 | 99 |
|
87 | | -# Invoke any operation by id; path params via -p, query via -q, body via -d/--data-file/--stdin. |
88 | | -# (userId is auto-filled from your identity.) |
| 100 | +# Invoke any operation by id (userId is auto-filled). Path via -p, query via -q, body via -d. |
89 | 101 | netcup call get-servers -q limit=10 |
90 | 102 | netcup call patch-servers-by-serverId -p serverId=12345 -d '{"state":"ON"}' |
91 | | -netcup call post-rdns-ipv4 -d '{"ip":"203.0.113.10","rdns":"host.example.com"}' |
92 | 103 |
|
93 | 104 | # Or the raw escape hatch: |
94 | | -netcup api GET /api/v1/servers -q q=web |
| 105 | +netcup api GET /api/v1/servers -q q=web |
95 | 106 | netcup api PATCH /api/v1/servers/12345 -d '{"hostname":"web01"}' |
96 | 107 | ``` |
97 | 108 |
|
98 | | -## Keep the spec current |
| 109 | +Keep the spec current: |
99 | 110 |
|
100 | 111 | ```bash |
101 | 112 | netcup spec info # bundled spec version + endpoint/tag count |
102 | | -netcup spec update # download the latest spec from the live API |
| 113 | +netcup spec update # pull the latest spec from the live API |
103 | 114 | ``` |
104 | 115 |
|
105 | | -## Use as a library |
| 116 | +## 🐍 Use as a library |
106 | 117 |
|
107 | 118 | ```python |
108 | 119 | from netcup_api import NetcupClient |
109 | 120 |
|
110 | | -c = NetcupClient() # uses your stored credentials |
111 | | -servers = c.list_servers(limit=10) |
| 121 | +c = NetcupClient() # uses your stored credentials |
| 122 | +for s in c.list_servers(limit=10): |
| 123 | + print(s["name"], s["state"]) |
| 124 | + |
112 | 125 | c.set_server_state("12345", "ON") |
113 | 126 | c.call("post-servers-by-serverId-snapshots", |
114 | 127 | path_params={"serverId": "12345"}, |
115 | 128 | body={"name": "snap1", "onlineSnapshot": True}) |
116 | 129 | ``` |
117 | 130 |
|
118 | | -## Known limits & gotchas (from the API) |
| 131 | +## ⚠️ Good to know |
119 | 132 |
|
120 | 133 | | Topic | Detail | |
121 | 134 | |------|--------| |
122 | | -| Access token | valid **300 s**; auto-refreshed by this client | |
123 | | -| Refresh token | offline; dies after **30 days unused** → re-login | |
124 | | -| `PATCH /servers/{id}` | **one attribute per request** (else HTTP 400); `Content-Type: application/merge-patch+json` (handled automatically) | |
125 | | -| Image/ISO uploads | the API returns presigned S3 URLs; the binary upload itself is a separate `PUT` to that URL (not yet wrapped — use the returned URL directly) | |
| 135 | +| Access token | valid **300 s** — refreshed automatically by the client | |
| 136 | +| Refresh token | offline; dies after **30 days unused** → re-run `netcup auth login` | |
| 137 | +| `PATCH /servers/{id}` | **one attribute per request** (else HTTP 400); `Content-Type: application/merge-patch+json` (handled for you) | |
| 138 | +| Image/ISO uploads | the API returns presigned S3 URLs; the binary `PUT` to that URL is a separate step | |
126 | 139 |
|
127 | | -## Configuration (env vars) |
| 140 | +## 🔧 Configuration |
128 | 141 |
|
129 | | -| Var | Default | |
130 | | -|-----|---------| |
| 142 | +| Env var | Default | |
| 143 | +|---------|---------| |
131 | 144 | | `NETCUP_CONFIG_DIR` | `~/.config/netcup-api` | |
132 | 145 | | `NETCUP_BASE_HOST` | `https://www.servercontrolpanel.de` | |
133 | 146 | | `NETCUP_OPENAPI` | path to an explicit spec file (overrides bundled/cached) | |
134 | 147 |
|
135 | | -## License |
| 148 | +## 🛠️ Development |
| 149 | + |
| 150 | +```bash |
| 151 | +pip install -e ".[dev]" |
| 152 | +ruff check . # lint |
| 153 | +pytest -q # offline tests (no network, no auth) |
| 154 | +``` |
| 155 | + |
| 156 | +See [`CLAUDE.md`](./CLAUDE.md) for architecture, the spec-driven principle, and contribution rules. |
| 157 | + |
| 158 | +## 🤖 AI assistant integration |
| 159 | + |
| 160 | +Want Claude / any MCP client to drive your infra in natural language? Pair this with the |
| 161 | +companion MCP server: **[netcup-mcp](https://github.com/fullya99/netcup-mcp)**. |
| 162 | + |
| 163 | +## 📄 License |
| 164 | + |
| 165 | +[MIT](https://opensource.org/licenses/MIT) © Antoine Chenais |
136 | 166 |
|
137 | | -MIT |
| 167 | +<div align="center"><sub>Not affiliated with netcup GmbH. Use at your own risk on production infrastructure.</sub></div> |
0 commit comments