forktex-cloud 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- forktex_cloud-0.2.3/LICENSE +45 -0
- forktex_cloud-0.2.3/NOTICE +23 -0
- forktex_cloud-0.2.3/PKG-INFO +226 -0
- forktex_cloud-0.2.3/README.md +189 -0
- forktex_cloud-0.2.3/pyproject.toml +107 -0
- forktex_cloud-0.2.3/src/forktex_cloud/__init__.py +92 -0
- forktex_cloud-0.2.3/src/forktex_cloud/bridge/__init__.py +24 -0
- forktex_cloud-0.2.3/src/forktex_cloud/bridge/local_compose.py +295 -0
- forktex_cloud-0.2.3/src/forktex_cloud/bridge/log_formatter.py +62 -0
- forktex_cloud-0.2.3/src/forktex_cloud/bridge/loki.py +109 -0
- forktex_cloud-0.2.3/src/forktex_cloud/bridge/persistence_defaults.py +120 -0
- forktex_cloud-0.2.3/src/forktex_cloud/client/__init__.py +83 -0
- forktex_cloud-0.2.3/src/forktex_cloud/client/client.py +599 -0
- forktex_cloud-0.2.3/src/forktex_cloud/client/generated/__init__.py +1057 -0
- forktex_cloud-0.2.3/src/forktex_cloud/config.py +63 -0
- forktex_cloud-0.2.3/src/forktex_cloud/manifest/__init__.py +58 -0
- forktex_cloud-0.2.3/src/forktex_cloud/manifest/errors.py +34 -0
- forktex_cloud-0.2.3/src/forktex_cloud/manifest/loader.py +296 -0
- forktex_cloud-0.2.3/src/forktex_cloud/manifest/merge.py +52 -0
- forktex_cloud-0.2.3/src/forktex_cloud/manifest/schema.py +88 -0
- forktex_cloud-0.2.3/src/forktex_cloud/paths.py +297 -0
- forktex_cloud-0.2.3/src/forktex_cloud/scaffold/__init__.py +24 -0
- forktex_cloud-0.2.3/src/forktex_cloud/scaffold/templates.py +143 -0
- forktex_cloud-0.2.3/src/forktex_cloud/secrets/__init__.py +24 -0
- forktex_cloud-0.2.3/src/forktex_cloud/secrets/base.py +52 -0
- forktex_cloud-0.2.3/src/forktex_cloud/secrets/factory.py +57 -0
- forktex_cloud-0.2.3/src/forktex_cloud/secrets/fernet.py +96 -0
- forktex_cloud-0.2.3/src/forktex_cloud/secrets/resolver.py +59 -0
- forktex_cloud-0.2.3/src/forktex_cloud/templates/observability/loki.yml +55 -0
- forktex_cloud-0.2.3/src/forktex_cloud/templates/observability/promtail.yml +46 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
forktex-cloud -- Dual License
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2026 FORKTEX S.R.L.
|
|
4
|
+
|
|
5
|
+
This software is licensed under a dual-license model:
|
|
6
|
+
|
|
7
|
+
============================================================================
|
|
8
|
+
|
|
9
|
+
1. OPEN-SOURCE LICENSE -- GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
10
|
+
|
|
11
|
+
You may use, modify, and redistribute this software under the terms of the
|
|
12
|
+
GNU Affero General Public License v3.0 or later, as published by the Free
|
|
13
|
+
Software Foundation. The full license text is available at:
|
|
14
|
+
https://www.gnu.org/licenses/agpl-3.0.html
|
|
15
|
+
|
|
16
|
+
Key obligations under AGPL-3.0:
|
|
17
|
+
- Any modified version MUST be released under the same AGPL-3.0 license.
|
|
18
|
+
- If you run a modified version on a server and let users interact with it
|
|
19
|
+
over a network, you MUST make the complete source code available to those
|
|
20
|
+
users under AGPL-3.0.
|
|
21
|
+
- You MUST preserve all copyright notices and attribution to FORKTEX S.R.L.
|
|
22
|
+
|
|
23
|
+
============================================================================
|
|
24
|
+
|
|
25
|
+
2. COMMERCIAL LICENSE
|
|
26
|
+
|
|
27
|
+
If you wish to use this software without the obligations of AGPL-3.0 --
|
|
28
|
+
for example, to keep your modifications proprietary, to embed this software
|
|
29
|
+
in a closed-source product, or to operate it as part of a commercial service
|
|
30
|
+
without releasing your source code -- you MUST obtain a commercial license
|
|
31
|
+
from FORKTEX S.R.L.
|
|
32
|
+
|
|
33
|
+
Commercial licensing includes, but is not limited to:
|
|
34
|
+
- SaaS / hosted deployments generating revenue
|
|
35
|
+
- Embedding in proprietary products or platforms
|
|
36
|
+
- Redistribution in closed-source form
|
|
37
|
+
- Use by organizations exceeding the AGPL-3.0 compliance scope
|
|
38
|
+
|
|
39
|
+
For commercial licensing inquiries, contact:
|
|
40
|
+
info@forktex.com
|
|
41
|
+
|
|
42
|
+
============================================================================
|
|
43
|
+
|
|
44
|
+
Unless you have obtained a separate commercial license from FORKTEX S.R.L.,
|
|
45
|
+
your use of this software is governed exclusively by the AGPL-3.0 license.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
forktex-cloud
|
|
2
|
+
Copyright (C) 2026 FORKTEX S.R.L.
|
|
3
|
+
|
|
4
|
+
forktex-cloud is the standalone Python SDK for the ForkTex Cloud platform.
|
|
5
|
+
It provides the typed httpx-based client and Pydantic models for provisioning
|
|
6
|
+
servers, deploying projects, managing static sites, single-container apps and
|
|
7
|
+
native builds, vault secrets, blue-green rollouts, and the declarative
|
|
8
|
+
forktex.json cloud manifest.
|
|
9
|
+
|
|
10
|
+
This program is free software: you can redistribute it and/or modify
|
|
11
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
12
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
13
|
+
(at your option) any later version.
|
|
14
|
+
|
|
15
|
+
This program is distributed in the hope that it will be useful,
|
|
16
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
17
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
18
|
+
GNU Affero General Public License for more details.
|
|
19
|
+
|
|
20
|
+
You should have received a copy of the GNU Affero General Public License
|
|
21
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
22
|
+
|
|
23
|
+
For commercial licensing inquiries, contact: info@forktex.com
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: forktex-cloud
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Typed Python SDK for the ForkTex Cloud platform — provision, deploy, and manage VPS-backed apps via a declarative manifest.
|
|
5
|
+
License-Expression: AGPL-3.0-only
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
License-File: NOTICE
|
|
8
|
+
Keywords: forktex,cloud,deployment,vps,hetzner,ansible,blue-green,iac,infrastructure-as-code,sdk
|
|
9
|
+
Author: FORKTEX
|
|
10
|
+
Author-email: info@forktex.com
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
23
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
24
|
+
Classifier: Topic :: System :: Systems Administration
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Dist: cryptography (>=42.0)
|
|
27
|
+
Requires-Dist: httpx (>=0.27.0,<1.0.0)
|
|
28
|
+
Requires-Dist: pydantic[email] (>=2.11.10,<3.0.0)
|
|
29
|
+
Requires-Dist: pyyaml (>=6.0)
|
|
30
|
+
Project-URL: Changelog, https://github.com/forktex/cloud/blob/master/sdk-py/CHANGELOG.md
|
|
31
|
+
Project-URL: Documentation, https://github.com/forktex/cloud/tree/master/docs
|
|
32
|
+
Project-URL: Homepage, https://forktex.com
|
|
33
|
+
Project-URL: Issues, https://github.com/forktex/cloud/issues
|
|
34
|
+
Project-URL: Repository, https://github.com/forktex/cloud
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# forktex-cloud
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/forktex-cloud/)
|
|
40
|
+
[](https://pypi.org/project/forktex-cloud/)
|
|
41
|
+
[](https://github.com/forktex/cloud/blob/master/sdk-py/LICENSE)
|
|
42
|
+
|
|
43
|
+
Standalone Python SDK for the [ForkTex Cloud](https://cloud.forktex.com) platform.
|
|
44
|
+
|
|
45
|
+
`forktex-cloud` is the typed `httpx` client + manifest plumbing that the `forktex` CLI uses to talk to the ForkTex Cloud control plane: project provisioning, server management, deployments, vault, manifest validation, and the docker-compose / Hetzner / Ansible bridge.
|
|
46
|
+
|
|
47
|
+
You can use it directly from any Python application — no `forktex` CLI required.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install forktex-cloud
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Requires Python 3.12+.** Tested on 3.12, 3.13, 3.14.
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
### Authenticated client
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from forktex_cloud import ForktexCloudClient, CloudContext
|
|
63
|
+
|
|
64
|
+
ctx = CloudContext(controller="https://cloud.forktex.com", account_key="ftx-...")
|
|
65
|
+
with ForktexCloudClient.from_context(ctx) as client:
|
|
66
|
+
projects = client.list_projects()
|
|
67
|
+
servers = client.list_servers()
|
|
68
|
+
health = client.health()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Direct auth
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from forktex_cloud import ForktexCloudClient
|
|
75
|
+
|
|
76
|
+
# JWT bearer (user login)
|
|
77
|
+
with ForktexCloudClient("https://cloud.forktex.com", access_token="eyJ...") as client:
|
|
78
|
+
me = client.me()
|
|
79
|
+
|
|
80
|
+
# Org-scoped API key (CI/CD)
|
|
81
|
+
with ForktexCloudClient(
|
|
82
|
+
"https://cloud.forktex.com",
|
|
83
|
+
account_key="ftx-...",
|
|
84
|
+
org_id="00000000-0000-0000-0000-000000000001",
|
|
85
|
+
) as client:
|
|
86
|
+
events = client.list_events(project_id="...")
|
|
87
|
+
status = client.server_status("server-uuid")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Local dev (point at your `make local` stack)
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
client = ForktexCloudClient(
|
|
94
|
+
base_url="http://localhost:8000",
|
|
95
|
+
account_key="ftx-dev-key-2026",
|
|
96
|
+
org_id="<your-org-uuid>",
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Trigger a deploy pipeline
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# Full up (provision + bootstrap + deploy + DNS + SSL)
|
|
104
|
+
job = client.up(project_dir=Path("./my-project")) # reads forktex.json
|
|
105
|
+
print(job.job_id, job.deployment_id, job.status)
|
|
106
|
+
|
|
107
|
+
# Code push to an existing server (no re-provision)
|
|
108
|
+
job = client.deploy(server_id="...", service="api") # Ansible deploy tag
|
|
109
|
+
|
|
110
|
+
# Tear down
|
|
111
|
+
job = client.down(keep_dns=True) # preserves DNS record
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Manifest loading + validation
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from forktex_cloud import Manifest, ManifestError
|
|
118
|
+
|
|
119
|
+
# Validates eagerly against the canonical Pydantic schema at construction
|
|
120
|
+
try:
|
|
121
|
+
m = Manifest.load("forktex.json", env="production")
|
|
122
|
+
except ManifestError as e:
|
|
123
|
+
print(f"Invalid manifest: {e}")
|
|
124
|
+
raise
|
|
125
|
+
|
|
126
|
+
# Typed access to the cloud block (discriminated union over all 4 kinds)
|
|
127
|
+
print(m.cloud.kind) # "ProjectDeployment" | "StaticSite" | ...
|
|
128
|
+
print(m.cloud.metadata.name)
|
|
129
|
+
for svc in m.services_for_env(env="production"):
|
|
130
|
+
print(svc.id, svc.type, svc.image)
|
|
131
|
+
|
|
132
|
+
# Wire format round-trips cleanly (model_dump → parse_cloud_block)
|
|
133
|
+
as_dict = m.cloud.model_dump(by_alias=True, exclude_none=True)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Secrets vault (server-side, org-scoped)
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
client.vault_set("POSTGRES_PASSWORD", "hunter2")
|
|
140
|
+
secret = client.vault_get("POSTGRES_PASSWORD") # -> VaultGetResponse
|
|
141
|
+
keys = client.vault_list() # -> list[str]
|
|
142
|
+
client.vault_delete("POSTGRES_PASSWORD")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## What's in the package
|
|
146
|
+
|
|
147
|
+
| Module | Purpose |
|
|
148
|
+
|---|---|
|
|
149
|
+
| `forktex_cloud.client` | Typed sync httpx client (`ForktexCloudClient`) + all OpenAPI-codegenned Pydantic models (`ServerRead`, `ProjectRead`, `EventRead`, `VaultGetResponse`, ...) |
|
|
150
|
+
| `forktex_cloud.manifest` | `Manifest` loader, discriminated-union schema (v1beta2), deep-merge for env overlays, `ManifestError` |
|
|
151
|
+
| `forktex_cloud.config` | `CloudContext` — controller URL, JWT / account-key, current org + project keys |
|
|
152
|
+
| `forktex_cloud.scaffold` | `forktex cloud init` template generator (ProjectDeployment / StaticSite / SingleContainer / NativeBuild) |
|
|
153
|
+
| `forktex_cloud.bridge` | docker-compose generator (local mode), Loki config, log formatters used by `forktex cloud up --env local` |
|
|
154
|
+
| `forktex_cloud.secrets` | Fernet vault + `${vault:KEY}` resolver for compile-time secret injection |
|
|
155
|
+
| `forktex_cloud.paths` | Cross-platform `.forktex/` + `~/.forktex/` filesystem spec (V1). See [docs/forktex-directory-spec.md](https://github.com/forktex/cloud/blob/master/docs/forktex-directory-spec.md) |
|
|
156
|
+
|
|
157
|
+
All response models come from the OpenAPI codegen pipeline — **one source of truth** shared between the server and every consumer. No hand-written model drift.
|
|
158
|
+
|
|
159
|
+
## Top-level re-exports
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from forktex_cloud import (
|
|
163
|
+
# Client
|
|
164
|
+
ForktexCloudClient, CloudAPIError,
|
|
165
|
+
# Config
|
|
166
|
+
CloudContext,
|
|
167
|
+
# Manifest
|
|
168
|
+
Manifest, ManifestError,
|
|
169
|
+
# Response models (from OpenAPI codegen)
|
|
170
|
+
ApiKeyCreated, ApiKeyRead,
|
|
171
|
+
EnvironmentRead, EventRead,
|
|
172
|
+
HealthRead, JobResponse, MeResponse,
|
|
173
|
+
OrgRead, ProjectRead, ServerRead, UserRead,
|
|
174
|
+
StatusResponse, TokenResponse, VaultGetResponse,
|
|
175
|
+
WorkspaceRead,
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Versioning
|
|
180
|
+
|
|
181
|
+
The SDK follows [SemVer](https://semver.org/). The client's response models are generated from the server's OpenAPI spec at a fixed `SPEC_VERSION` + `SPEC_HASH` (inspectable at runtime via `forktex_cloud.client.generated.SPEC_VERSION`). When the server OpenAPI changes, the SDK is regenerated and released with a bumped version.
|
|
182
|
+
|
|
183
|
+
## Repository
|
|
184
|
+
|
|
185
|
+
This SDK lives inside the [`forktex/cloud`](https://github.com/forktex/cloud) monorepo alongside the API server (`api/`) and React Native client (`client/`). The SDK package is independently versioned and published to PyPI.
|
|
186
|
+
|
|
187
|
+
- Docs: [https://github.com/forktex/cloud/tree/master/docs](https://github.com/forktex/cloud/tree/master/docs)
|
|
188
|
+
- Production runbook: [production-runbook.md](https://github.com/forktex/cloud/blob/master/docs/production-runbook.md)
|
|
189
|
+
- Issues: [https://github.com/forktex/cloud/issues](https://github.com/forktex/cloud/issues)
|
|
190
|
+
|
|
191
|
+
## Development
|
|
192
|
+
|
|
193
|
+
The [`Makefile`](Makefile) is generated by `forktex fsd makefile sync` from [`forktex.json`](forktex.json) — do not hand-edit.
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
make help # list every available target
|
|
197
|
+
make deps # editable install with the dev group
|
|
198
|
+
make format # ruff format
|
|
199
|
+
make lint # ruff check
|
|
200
|
+
make test # pytest tests/
|
|
201
|
+
make codegen-check # verify the generated client imports cleanly
|
|
202
|
+
make build # python3 -m build → dist/
|
|
203
|
+
make ci # format-check + lint + license-check + audit + test + build
|
|
204
|
+
make clean # remove caches and dist/
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`make ci` is the single command that gates a publish: format-check, lint, dual-license header check, dependency CVE audit, full test suite, and `python -m build` + `twine check`.
|
|
208
|
+
|
|
209
|
+
### License headers
|
|
210
|
+
|
|
211
|
+
Every source file carries the AGPL-3.0 + Commercial dual-license SPDX header, applied idempotently via:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
make license-check # CI gate — fails if any source file is missing the header
|
|
215
|
+
make license-fix # add or refresh headers across src/, tests/, scripts/
|
|
216
|
+
make license-strip # remove headers (used before license-model changes)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
Dual-licensed — **AGPL-3.0-or-later** for open-source use, **commercial** for everything else (proprietary products, SaaS without source release, redistribution in closed-source form). See [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE) for the full terms.
|
|
222
|
+
|
|
223
|
+
Commercial licensing inquiries: info@forktex.com.
|
|
224
|
+
|
|
225
|
+
The 1.0.x releases on PyPI remain under MIT; from **0.2.3** onwards the package ships AGPL-3.0+Commercial.
|
|
226
|
+
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# forktex-cloud
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/forktex-cloud/)
|
|
4
|
+
[](https://pypi.org/project/forktex-cloud/)
|
|
5
|
+
[](https://github.com/forktex/cloud/blob/master/sdk-py/LICENSE)
|
|
6
|
+
|
|
7
|
+
Standalone Python SDK for the [ForkTex Cloud](https://cloud.forktex.com) platform.
|
|
8
|
+
|
|
9
|
+
`forktex-cloud` is the typed `httpx` client + manifest plumbing that the `forktex` CLI uses to talk to the ForkTex Cloud control plane: project provisioning, server management, deployments, vault, manifest validation, and the docker-compose / Hetzner / Ansible bridge.
|
|
10
|
+
|
|
11
|
+
You can use it directly from any Python application — no `forktex` CLI required.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install forktex-cloud
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Requires Python 3.12+.** Tested on 3.12, 3.13, 3.14.
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Authenticated client
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from forktex_cloud import ForktexCloudClient, CloudContext
|
|
27
|
+
|
|
28
|
+
ctx = CloudContext(controller="https://cloud.forktex.com", account_key="ftx-...")
|
|
29
|
+
with ForktexCloudClient.from_context(ctx) as client:
|
|
30
|
+
projects = client.list_projects()
|
|
31
|
+
servers = client.list_servers()
|
|
32
|
+
health = client.health()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Direct auth
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from forktex_cloud import ForktexCloudClient
|
|
39
|
+
|
|
40
|
+
# JWT bearer (user login)
|
|
41
|
+
with ForktexCloudClient("https://cloud.forktex.com", access_token="eyJ...") as client:
|
|
42
|
+
me = client.me()
|
|
43
|
+
|
|
44
|
+
# Org-scoped API key (CI/CD)
|
|
45
|
+
with ForktexCloudClient(
|
|
46
|
+
"https://cloud.forktex.com",
|
|
47
|
+
account_key="ftx-...",
|
|
48
|
+
org_id="00000000-0000-0000-0000-000000000001",
|
|
49
|
+
) as client:
|
|
50
|
+
events = client.list_events(project_id="...")
|
|
51
|
+
status = client.server_status("server-uuid")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Local dev (point at your `make local` stack)
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
client = ForktexCloudClient(
|
|
58
|
+
base_url="http://localhost:8000",
|
|
59
|
+
account_key="ftx-dev-key-2026",
|
|
60
|
+
org_id="<your-org-uuid>",
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Trigger a deploy pipeline
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# Full up (provision + bootstrap + deploy + DNS + SSL)
|
|
68
|
+
job = client.up(project_dir=Path("./my-project")) # reads forktex.json
|
|
69
|
+
print(job.job_id, job.deployment_id, job.status)
|
|
70
|
+
|
|
71
|
+
# Code push to an existing server (no re-provision)
|
|
72
|
+
job = client.deploy(server_id="...", service="api") # Ansible deploy tag
|
|
73
|
+
|
|
74
|
+
# Tear down
|
|
75
|
+
job = client.down(keep_dns=True) # preserves DNS record
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Manifest loading + validation
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from forktex_cloud import Manifest, ManifestError
|
|
82
|
+
|
|
83
|
+
# Validates eagerly against the canonical Pydantic schema at construction
|
|
84
|
+
try:
|
|
85
|
+
m = Manifest.load("forktex.json", env="production")
|
|
86
|
+
except ManifestError as e:
|
|
87
|
+
print(f"Invalid manifest: {e}")
|
|
88
|
+
raise
|
|
89
|
+
|
|
90
|
+
# Typed access to the cloud block (discriminated union over all 4 kinds)
|
|
91
|
+
print(m.cloud.kind) # "ProjectDeployment" | "StaticSite" | ...
|
|
92
|
+
print(m.cloud.metadata.name)
|
|
93
|
+
for svc in m.services_for_env(env="production"):
|
|
94
|
+
print(svc.id, svc.type, svc.image)
|
|
95
|
+
|
|
96
|
+
# Wire format round-trips cleanly (model_dump → parse_cloud_block)
|
|
97
|
+
as_dict = m.cloud.model_dump(by_alias=True, exclude_none=True)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Secrets vault (server-side, org-scoped)
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
client.vault_set("POSTGRES_PASSWORD", "hunter2")
|
|
104
|
+
secret = client.vault_get("POSTGRES_PASSWORD") # -> VaultGetResponse
|
|
105
|
+
keys = client.vault_list() # -> list[str]
|
|
106
|
+
client.vault_delete("POSTGRES_PASSWORD")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## What's in the package
|
|
110
|
+
|
|
111
|
+
| Module | Purpose |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `forktex_cloud.client` | Typed sync httpx client (`ForktexCloudClient`) + all OpenAPI-codegenned Pydantic models (`ServerRead`, `ProjectRead`, `EventRead`, `VaultGetResponse`, ...) |
|
|
114
|
+
| `forktex_cloud.manifest` | `Manifest` loader, discriminated-union schema (v1beta2), deep-merge for env overlays, `ManifestError` |
|
|
115
|
+
| `forktex_cloud.config` | `CloudContext` — controller URL, JWT / account-key, current org + project keys |
|
|
116
|
+
| `forktex_cloud.scaffold` | `forktex cloud init` template generator (ProjectDeployment / StaticSite / SingleContainer / NativeBuild) |
|
|
117
|
+
| `forktex_cloud.bridge` | docker-compose generator (local mode), Loki config, log formatters used by `forktex cloud up --env local` |
|
|
118
|
+
| `forktex_cloud.secrets` | Fernet vault + `${vault:KEY}` resolver for compile-time secret injection |
|
|
119
|
+
| `forktex_cloud.paths` | Cross-platform `.forktex/` + `~/.forktex/` filesystem spec (V1). See [docs/forktex-directory-spec.md](https://github.com/forktex/cloud/blob/master/docs/forktex-directory-spec.md) |
|
|
120
|
+
|
|
121
|
+
All response models come from the OpenAPI codegen pipeline — **one source of truth** shared between the server and every consumer. No hand-written model drift.
|
|
122
|
+
|
|
123
|
+
## Top-level re-exports
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from forktex_cloud import (
|
|
127
|
+
# Client
|
|
128
|
+
ForktexCloudClient, CloudAPIError,
|
|
129
|
+
# Config
|
|
130
|
+
CloudContext,
|
|
131
|
+
# Manifest
|
|
132
|
+
Manifest, ManifestError,
|
|
133
|
+
# Response models (from OpenAPI codegen)
|
|
134
|
+
ApiKeyCreated, ApiKeyRead,
|
|
135
|
+
EnvironmentRead, EventRead,
|
|
136
|
+
HealthRead, JobResponse, MeResponse,
|
|
137
|
+
OrgRead, ProjectRead, ServerRead, UserRead,
|
|
138
|
+
StatusResponse, TokenResponse, VaultGetResponse,
|
|
139
|
+
WorkspaceRead,
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Versioning
|
|
144
|
+
|
|
145
|
+
The SDK follows [SemVer](https://semver.org/). The client's response models are generated from the server's OpenAPI spec at a fixed `SPEC_VERSION` + `SPEC_HASH` (inspectable at runtime via `forktex_cloud.client.generated.SPEC_VERSION`). When the server OpenAPI changes, the SDK is regenerated and released with a bumped version.
|
|
146
|
+
|
|
147
|
+
## Repository
|
|
148
|
+
|
|
149
|
+
This SDK lives inside the [`forktex/cloud`](https://github.com/forktex/cloud) monorepo alongside the API server (`api/`) and React Native client (`client/`). The SDK package is independently versioned and published to PyPI.
|
|
150
|
+
|
|
151
|
+
- Docs: [https://github.com/forktex/cloud/tree/master/docs](https://github.com/forktex/cloud/tree/master/docs)
|
|
152
|
+
- Production runbook: [production-runbook.md](https://github.com/forktex/cloud/blob/master/docs/production-runbook.md)
|
|
153
|
+
- Issues: [https://github.com/forktex/cloud/issues](https://github.com/forktex/cloud/issues)
|
|
154
|
+
|
|
155
|
+
## Development
|
|
156
|
+
|
|
157
|
+
The [`Makefile`](Makefile) is generated by `forktex fsd makefile sync` from [`forktex.json`](forktex.json) — do not hand-edit.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
make help # list every available target
|
|
161
|
+
make deps # editable install with the dev group
|
|
162
|
+
make format # ruff format
|
|
163
|
+
make lint # ruff check
|
|
164
|
+
make test # pytest tests/
|
|
165
|
+
make codegen-check # verify the generated client imports cleanly
|
|
166
|
+
make build # python3 -m build → dist/
|
|
167
|
+
make ci # format-check + lint + license-check + audit + test + build
|
|
168
|
+
make clean # remove caches and dist/
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`make ci` is the single command that gates a publish: format-check, lint, dual-license header check, dependency CVE audit, full test suite, and `python -m build` + `twine check`.
|
|
172
|
+
|
|
173
|
+
### License headers
|
|
174
|
+
|
|
175
|
+
Every source file carries the AGPL-3.0 + Commercial dual-license SPDX header, applied idempotently via:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
make license-check # CI gate — fails if any source file is missing the header
|
|
179
|
+
make license-fix # add or refresh headers across src/, tests/, scripts/
|
|
180
|
+
make license-strip # remove headers (used before license-model changes)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
Dual-licensed — **AGPL-3.0-or-later** for open-source use, **commercial** for everything else (proprietary products, SaaS without source release, redistribution in closed-source form). See [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE) for the full terms.
|
|
186
|
+
|
|
187
|
+
Commercial licensing inquiries: info@forktex.com.
|
|
188
|
+
|
|
189
|
+
The 1.0.x releases on PyPI remain under MIT; from **0.2.3** onwards the package ships AGPL-3.0+Commercial.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Copyright (C) 2026 FORKTEX S.R.L.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
|
|
4
|
+
#
|
|
5
|
+
# This file is part of forktex-cloud.
|
|
6
|
+
#
|
|
7
|
+
# For commercial licensing -- including use in proprietary products, SaaS
|
|
8
|
+
# deployments, or any context where AGPL obligations cannot be met -- you
|
|
9
|
+
# MUST obtain a commercial license from FORKTEX S.R.L. (info@forktex.com).
|
|
10
|
+
#
|
|
11
|
+
# This program is free software: you can redistribute it and/or modify
|
|
12
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
13
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
14
|
+
# (at your option) any later version.
|
|
15
|
+
#
|
|
16
|
+
# This program is distributed in the hope that it will be useful,
|
|
17
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
+
# GNU Affero General Public License for more details.
|
|
20
|
+
#
|
|
21
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
22
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
23
|
+
|
|
24
|
+
[project]
|
|
25
|
+
name = "forktex-cloud"
|
|
26
|
+
version = "0.2.3"
|
|
27
|
+
description = "Typed Python SDK for the ForkTex Cloud platform — provision, deploy, and manage VPS-backed apps via a declarative manifest."
|
|
28
|
+
authors = [
|
|
29
|
+
{name = "FORKTEX", email = "info@forktex.com"}
|
|
30
|
+
]
|
|
31
|
+
readme = "README.md"
|
|
32
|
+
license = "AGPL-3.0-only"
|
|
33
|
+
license-files = ["LICENSE", "NOTICE"]
|
|
34
|
+
requires-python = ">=3.12"
|
|
35
|
+
keywords = [
|
|
36
|
+
"forktex",
|
|
37
|
+
"cloud",
|
|
38
|
+
"deployment",
|
|
39
|
+
"vps",
|
|
40
|
+
"hetzner",
|
|
41
|
+
"ansible",
|
|
42
|
+
"blue-green",
|
|
43
|
+
"iac",
|
|
44
|
+
"infrastructure-as-code",
|
|
45
|
+
"sdk",
|
|
46
|
+
]
|
|
47
|
+
classifiers = [
|
|
48
|
+
"Development Status :: 5 - Production/Stable",
|
|
49
|
+
"Intended Audience :: Developers",
|
|
50
|
+
"Intended Audience :: System Administrators",
|
|
51
|
+
"Operating System :: OS Independent",
|
|
52
|
+
"Programming Language :: Python :: 3",
|
|
53
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
54
|
+
"Programming Language :: Python :: 3.12",
|
|
55
|
+
"Programming Language :: Python :: 3.13",
|
|
56
|
+
"Programming Language :: Python :: 3.14",
|
|
57
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
58
|
+
"Topic :: System :: Distributed Computing",
|
|
59
|
+
"Topic :: System :: Installation/Setup",
|
|
60
|
+
"Topic :: System :: Systems Administration",
|
|
61
|
+
"Typing :: Typed",
|
|
62
|
+
]
|
|
63
|
+
dependencies = [
|
|
64
|
+
"pydantic[email] (>=2.11.10,<3.0.0)",
|
|
65
|
+
"httpx (>=0.27.0,<1.0.0)",
|
|
66
|
+
"pyyaml>=6.0",
|
|
67
|
+
"cryptography>=42.0",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[project.urls]
|
|
71
|
+
"Homepage" = "https://forktex.com"
|
|
72
|
+
"Documentation" = "https://github.com/forktex/cloud/tree/master/docs"
|
|
73
|
+
"Repository" = "https://github.com/forktex/cloud"
|
|
74
|
+
"Issues" = "https://github.com/forktex/cloud/issues"
|
|
75
|
+
"Changelog" = "https://github.com/forktex/cloud/blob/master/sdk-py/CHANGELOG.md"
|
|
76
|
+
|
|
77
|
+
[build-system]
|
|
78
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
79
|
+
build-backend = "poetry.core.masonry.api"
|
|
80
|
+
|
|
81
|
+
[dependency-groups]
|
|
82
|
+
dev = [
|
|
83
|
+
"pytest (>=8.4.2,<9.0.0)",
|
|
84
|
+
"pytest-asyncio (>=1.2.0,<2.0.0)",
|
|
85
|
+
"ruff (>=0.8.0)",
|
|
86
|
+
"pyright>=1.1.350",
|
|
87
|
+
"pip-audit>=2.7.0",
|
|
88
|
+
"build>=1.2.0",
|
|
89
|
+
"twine>=5.0.0",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
[tool.ruff]
|
|
93
|
+
target-version = "py312"
|
|
94
|
+
line-length = 100
|
|
95
|
+
extend-exclude = [
|
|
96
|
+
# Auto-generated by OpenAPI codegen. Canonical import shape comes from
|
|
97
|
+
# datamodel-code-generator + our custom header (SPEC_VERSION / SPEC_HASH
|
|
98
|
+
# before the imports). Linting this file would churn every regen.
|
|
99
|
+
"src/forktex_cloud/client/generated/**",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
[tool.ruff.lint]
|
|
103
|
+
select = ["E", "F", "I", "W"]
|
|
104
|
+
|
|
105
|
+
[tool.pytest.ini_options]
|
|
106
|
+
asyncio_mode = "auto"
|
|
107
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Copyright (C) 2026 FORKTEX S.R.L.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
|
|
4
|
+
#
|
|
5
|
+
# This file is part of forktex-cloud.
|
|
6
|
+
#
|
|
7
|
+
# For commercial licensing -- including use in proprietary products, SaaS
|
|
8
|
+
# deployments, or any context where AGPL obligations cannot be met -- you
|
|
9
|
+
# MUST obtain a commercial license from FORKTEX S.R.L. (info@forktex.com).
|
|
10
|
+
#
|
|
11
|
+
# This program is free software: you can redistribute it and/or modify
|
|
12
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
13
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
14
|
+
# (at your option) any later version.
|
|
15
|
+
#
|
|
16
|
+
# This program is distributed in the hope that it will be useful,
|
|
17
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
+
# GNU Affero General Public License for more details.
|
|
20
|
+
#
|
|
21
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
22
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
23
|
+
|
|
24
|
+
"""forktex_cloud — Standalone Python SDK for the ForkTex Cloud platform.
|
|
25
|
+
|
|
26
|
+
Usage::
|
|
27
|
+
|
|
28
|
+
from forktex_cloud import ForktexCloudClient, CloudContext
|
|
29
|
+
|
|
30
|
+
ctx = CloudContext(controller="https://cloud.forktex.com", account_key="ftx-...")
|
|
31
|
+
with ForktexCloudClient.from_context(ctx) as client:
|
|
32
|
+
projects = client.list_projects()
|
|
33
|
+
servers = client.list_servers()
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
__version__ = "0.2.3"
|
|
37
|
+
|
|
38
|
+
from forktex_cloud import paths
|
|
39
|
+
from forktex_cloud.client.client import CloudAPIError, ForktexCloudClient
|
|
40
|
+
from forktex_cloud.client.generated import (
|
|
41
|
+
SPEC_HASH,
|
|
42
|
+
SPEC_VERSION,
|
|
43
|
+
ApiKeyCreated,
|
|
44
|
+
ApiKeyRead,
|
|
45
|
+
EnvironmentRead,
|
|
46
|
+
EventRead,
|
|
47
|
+
HealthRead,
|
|
48
|
+
JobResponse,
|
|
49
|
+
MeResponse,
|
|
50
|
+
OrgRead,
|
|
51
|
+
ProjectRead,
|
|
52
|
+
ServerRead,
|
|
53
|
+
StatusResponse,
|
|
54
|
+
TokenResponse,
|
|
55
|
+
UserRead,
|
|
56
|
+
VaultGetResponse,
|
|
57
|
+
WorkspaceRead,
|
|
58
|
+
)
|
|
59
|
+
from forktex_cloud.config import CloudContext
|
|
60
|
+
from forktex_cloud.manifest.loader import Manifest, ManifestError
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
# Filesystem layout spec (V1)
|
|
64
|
+
"paths",
|
|
65
|
+
# Codegen contract (wire-compatibility markers)
|
|
66
|
+
"SPEC_VERSION",
|
|
67
|
+
"SPEC_HASH",
|
|
68
|
+
# Client
|
|
69
|
+
"ForktexCloudClient",
|
|
70
|
+
"CloudAPIError",
|
|
71
|
+
# Config
|
|
72
|
+
"CloudContext",
|
|
73
|
+
# Manifest
|
|
74
|
+
"Manifest",
|
|
75
|
+
"ManifestError",
|
|
76
|
+
# Models (from OpenAPI codegen — the single source of truth)
|
|
77
|
+
"ApiKeyCreated",
|
|
78
|
+
"ApiKeyRead",
|
|
79
|
+
"EnvironmentRead",
|
|
80
|
+
"EventRead",
|
|
81
|
+
"HealthRead",
|
|
82
|
+
"JobResponse",
|
|
83
|
+
"MeResponse",
|
|
84
|
+
"OrgRead",
|
|
85
|
+
"ProjectRead",
|
|
86
|
+
"ServerRead",
|
|
87
|
+
"StatusResponse",
|
|
88
|
+
"TokenResponse",
|
|
89
|
+
"UserRead",
|
|
90
|
+
"VaultGetResponse",
|
|
91
|
+
"WorkspaceRead",
|
|
92
|
+
]
|