redock-vps 0.1.1__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.
Files changed (67) hide show
  1. redock_vps-0.1.1/.env.example +13 -0
  2. redock_vps-0.1.1/.githooks/pre-commit +3 -0
  3. redock_vps-0.1.1/.gitignore +16 -0
  4. redock_vps-0.1.1/.gitlab-ci.yml +101 -0
  5. redock_vps-0.1.1/.python-version +1 -0
  6. redock_vps-0.1.1/LICENSE +21 -0
  7. redock_vps-0.1.1/PKG-INFO +250 -0
  8. redock_vps-0.1.1/README.md +210 -0
  9. redock_vps-0.1.1/Taskfile.yml +61 -0
  10. redock_vps-0.1.1/pyproject.toml +54 -0
  11. redock_vps-0.1.1/src/redock/__init__.py +0 -0
  12. redock_vps-0.1.1/src/redock/application/__init__.py +0 -0
  13. redock_vps-0.1.1/src/redock/application/add_catalog_item.py +26 -0
  14. redock_vps-0.1.1/src/redock/application/catalog_commands.py +67 -0
  15. redock_vps-0.1.1/src/redock/application/check_catalog_item.py +20 -0
  16. redock_vps-0.1.1/src/redock/application/clean_dns.py +10 -0
  17. redock_vps-0.1.1/src/redock/application/commands.py +60 -0
  18. redock_vps-0.1.1/src/redock/application/delete_catalog_item.py +13 -0
  19. redock_vps-0.1.1/src/redock/application/deploy_app.py +91 -0
  20. redock_vps-0.1.1/src/redock/application/enrich_catalog_item.py +28 -0
  21. redock_vps-0.1.1/src/redock/application/ensure_dns.py +17 -0
  22. redock_vps-0.1.1/src/redock/application/ensure_vps.py +26 -0
  23. redock_vps-0.1.1/src/redock/application/exceptions.py +78 -0
  24. redock_vps-0.1.1/src/redock/application/get_catalog_item.py +15 -0
  25. redock_vps-0.1.1/src/redock/application/list_apps.py +10 -0
  26. redock_vps-0.1.1/src/redock/application/list_catalog_items.py +14 -0
  27. redock_vps-0.1.1/src/redock/application/prepare_host.py +21 -0
  28. redock_vps-0.1.1/src/redock/application/scan_catalog_item.py +74 -0
  29. redock_vps-0.1.1/src/redock/application/search_docker_hub.py +11 -0
  30. redock_vps-0.1.1/src/redock/application/stop_app.py +30 -0
  31. redock_vps-0.1.1/src/redock/application/up.py +94 -0
  32. redock_vps-0.1.1/src/redock/application/update_catalog_item.py +37 -0
  33. redock_vps-0.1.1/src/redock/domain/__init__.py +0 -0
  34. redock_vps-0.1.1/src/redock/domain/catalog_item.py +56 -0
  35. redock_vps-0.1.1/src/redock/domain/catalog_suggestion.py +16 -0
  36. redock_vps-0.1.1/src/redock/domain/deployment.py +37 -0
  37. redock_vps-0.1.1/src/redock/domain/dns_record.py +11 -0
  38. redock_vps-0.1.1/src/redock/domain/docker_hub.py +11 -0
  39. redock_vps-0.1.1/src/redock/domain/docker_hub_image.py +12 -0
  40. redock_vps-0.1.1/src/redock/domain/ports.py +137 -0
  41. redock_vps-0.1.1/src/redock/domain/scan_result.py +25 -0
  42. redock_vps-0.1.1/src/redock/domain/value_objects.py +81 -0
  43. redock_vps-0.1.1/src/redock/domain/vps.py +22 -0
  44. redock_vps-0.1.1/src/redock/domain/vuln_scorer.py +52 -0
  45. redock_vps-0.1.1/src/redock/infrastructure/__init__.py +0 -0
  46. redock_vps-0.1.1/src/redock/infrastructure/caddy_router.py +10 -0
  47. redock_vps-0.1.1/src/redock/infrastructure/config.py +26 -0
  48. redock_vps-0.1.1/src/redock/infrastructure/docker_hub_search.py +25 -0
  49. redock_vps-0.1.1/src/redock/infrastructure/docker_scout_scanner.py +78 -0
  50. redock_vps-0.1.1/src/redock/infrastructure/hetzner_dns_provider.py +120 -0
  51. redock_vps-0.1.1/src/redock/infrastructure/hetzner_provisioner.py +71 -0
  52. redock_vps-0.1.1/src/redock/infrastructure/infomaniak_metadata_enricher.py +97 -0
  53. redock_vps-0.1.1/src/redock/infrastructure/json_repository.py +73 -0
  54. redock_vps-0.1.1/src/redock/infrastructure/printing_dns_provider.py +75 -0
  55. redock_vps-0.1.1/src/redock/infrastructure/public_dns_resolver.py +16 -0
  56. redock_vps-0.1.1/src/redock/infrastructure/remote_docker_runtime.py +59 -0
  57. redock_vps-0.1.1/src/redock/infrastructure/requests_image_details_fetcher.py +146 -0
  58. redock_vps-0.1.1/src/redock/infrastructure/sqlite_catalog_repository.py +129 -0
  59. redock_vps-0.1.1/src/redock/infrastructure/ssh_host_preparer.py +65 -0
  60. redock_vps-0.1.1/src/redock/infrastructure/trivy_scanner.py +45 -0
  61. redock_vps-0.1.1/src/redock/interfaces/__init__.py +0 -0
  62. redock_vps-0.1.1/src/redock/interfaces/cli.py +814 -0
  63. redock_vps-0.1.1/src/redock/interfaces/composition.py +133 -0
  64. redock_vps-0.1.1/src/redock/interfaces/rich_renderer.py +72 -0
  65. redock_vps-0.1.1/src/redock/server-assets/Caddyfile +3 -0
  66. redock_vps-0.1.1/src/redock/server-assets/docker-compose.caddy.yml +23 -0
  67. redock_vps-0.1.1/uv.lock +970 -0
@@ -0,0 +1,13 @@
1
+ # Required
2
+ REDOCK_HETZNER_TOKEN=hcloud_xxxxxxxx
3
+ REDOCK_BASE_DOMAIN=apps.example.com
4
+ REDOCK_ACME_EMAIL=you@example.com
5
+
6
+ # Hetzner (optional — defaults shown)
7
+ REDOCK_HETZNER_SSH_KEY_NAME=redock-key
8
+ REDOCK_HETZNER_REGION=fsn1
9
+ REDOCK_HETZNER_SERVER_TYPE=cx22
10
+
11
+ # Local (optional — defaults shown)
12
+ REDOCK_SSH_KEY_PATH=~/.ssh/redock_ed25519
13
+ REDOCK_STATE_FILE=~/.redock/state.json
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env sh
2
+ set -e
3
+ uv run ruff check src/ tests/
@@ -0,0 +1,16 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Local binaries
13
+ bin/
14
+
15
+ # Superpowers planning artifacts
16
+ docs/superpowers/
@@ -0,0 +1,101 @@
1
+ default:
2
+ image: python:3.14-rc-slim
3
+ before_script:
4
+ - apt-get update -qq && apt-get install -y -qq git
5
+ - pip install uv --quiet
6
+ - uv sync --frozen
7
+
8
+ variables:
9
+ # Full clone so hatch-vcs can walk git history to compute versions from tags.
10
+ GIT_DEPTH: 0
11
+
12
+ stages:
13
+ - lint
14
+ - test
15
+ - publish
16
+
17
+ lint:
18
+ stage: lint
19
+ script:
20
+ - uv run ruff check src/ tests/
21
+
22
+ test:
23
+ stage: test
24
+ script:
25
+ - uv run pytest tests/ -v
26
+
27
+ # ── dev publish ───────────────────────────────────────────────────────────────
28
+ # Builds and uploads a SHA-derived dev package to TestPyPI for any branch
29
+ # whose name contains "packaging" (e.g. feature/packaging, packaging-fix).
30
+ # Version format: 0.1.X.dev<sha_as_int> — unique per commit regardless of
31
+ # squashes; PEP 440 requires an integer in .devN so the hex SHA is converted.
32
+ # Requires CI variable: TEST_PYPI_TOKEN (TestPyPI API token).
33
+ publish-dev:
34
+ stage: publish
35
+ before_script:
36
+ - apt-get update -qq && apt-get install -y -qq git
37
+ - pip install uv --quiet
38
+ rules:
39
+ - if: '$CI_COMMIT_BRANCH =~ /packaging/'
40
+ script:
41
+ - git fetch --tags
42
+ - LAST_TAG=$(git tag -l 'v0.1.*' --sort=-version:refname | head -1)
43
+ - |
44
+ if [ -z "$LAST_TAG" ]; then
45
+ NEXT_BASE="0.1.1"
46
+ else
47
+ PATCH=$(echo "$LAST_TAG" | awk -F'.' '{print $3}')
48
+ NEXT_BASE="0.1.$((PATCH + 1))"
49
+ fi
50
+ - SHA_INT=$(python3 -c "print(int('${CI_COMMIT_SHORT_SHA}', 16))")
51
+ - SETUPTOOLS_SCM_PRETEND_VERSION="${NEXT_BASE}.dev${SHA_INT}" uv build
52
+ - uv publish --publish-url https://test.pypi.org/legacy/ --token $TEST_PYPI_TOKEN
53
+
54
+ # ── version bump ──────────────────────────────────────────────────────────────
55
+ # On every push to main: reads the last v0.1.X tag, increments the patch, and
56
+ # pushes the new tag. The tag push triggers the publish-release job below.
57
+ # Requires CI variable: CI_PUSH_TOKEN (project/group access token with
58
+ # write_repository scope — set it in Settings → CI/CD → Variables).
59
+ bump-version:
60
+ stage: publish
61
+ before_script:
62
+ - apt-get update -qq && apt-get install -y -qq git
63
+ rules:
64
+ - if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
65
+ script:
66
+ - git config user.email "ci@gitlab"
67
+ - git config user.name "CI Bot"
68
+ - git fetch --tags
69
+ - |
70
+ # Skip if the current commit is already tagged (re-run protection).
71
+ if git tag --contains HEAD | grep -q '^v'; then
72
+ echo "HEAD is already tagged, skipping bump."
73
+ exit 0
74
+ fi
75
+ - |
76
+ LAST_TAG=$(git tag -l 'v0.1.*' --sort=-version:refname | head -1)
77
+ if [ -z "$LAST_TAG" ]; then
78
+ NEW_TAG="v0.1.0"
79
+ else
80
+ PATCH=$(echo "$LAST_TAG" | awk -F'.' '{print $3}')
81
+ NEW_TAG="v0.1.$((PATCH + 1))"
82
+ fi
83
+ echo "Tagging $NEW_TAG"
84
+ git tag "$NEW_TAG"
85
+ git remote set-url origin "https://oauth2:${CI_PUSH_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
86
+ git push origin "$NEW_TAG"
87
+
88
+ # ── release publish ───────────────────────────────────────────────────────────
89
+ # Triggered by a v0.1.X tag (created by bump-version above).
90
+ # Builds the exact-version wheel and uploads it to PyPI.
91
+ # Requires CI variable: PYPI_TOKEN (PyPI API token).
92
+ publish-release:
93
+ stage: publish
94
+ before_script:
95
+ - apt-get update -qq && apt-get install -y -qq git
96
+ - pip install uv --quiet
97
+ rules:
98
+ - if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
99
+ script:
100
+ - uv build
101
+ - uv publish --token $PYPI_TOKEN
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Florent Pigout
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,250 @@
1
+ Metadata-Version: 2.4
2
+ Name: redock-vps
3
+ Version: 0.1.1
4
+ Summary: One-command Docker deployment to Hetzner VPS
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 Florent Pigout
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+ License-File: LICENSE
27
+ Requires-Python: >=3.11
28
+ Requires-Dist: dnspython>=2.6
29
+ Requires-Dist: docker>=7.1
30
+ Requires-Dist: fabric>=3.2
31
+ Requires-Dist: hcloud>=2.0
32
+ Requires-Dist: pydantic-settings>=2.4
33
+ Requires-Dist: requests>=2.31
34
+ Requires-Dist: rich>=13.7
35
+ Requires-Dist: typer>=0.12
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest>=8.0; extra == 'dev'
38
+ Requires-Dist: ruff>=0.6; extra == 'dev'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # redock
42
+
43
+ One command from zero to a live HTTPS app on a Hetzner VPS.
44
+
45
+ ```bash
46
+ redock up excalidraw --slug drawing
47
+ ```
48
+
49
+ That's it. redock provisions a server, installs Docker and a Caddy reverse proxy, handles DNS, deploys the container, and hands you back a live URL.
50
+
51
+ ## How it works
52
+
53
+ 1. **Provision** — finds or creates a Hetzner `cx22` (or your chosen type)
54
+ 2. **Prepare** — SSHes in, installs Docker + `caddy-docker-proxy` via Docker Compose
55
+ 3. **DNS** — creates the `A` record automatically (Hetzner DNS) or prints it for manual entry, then polls until it resolves
56
+ 4. **Deploy** — runs the container with Caddy labels; Caddy gets a Let's Encrypt cert automatically
57
+ 5. **Verify** — polls HTTPS until the app responds `200`, then prints the URL
58
+
59
+ The VPS is shared across deployments — each app is one container behind Caddy.
60
+
61
+ ## Prerequisites
62
+
63
+ - A [Hetzner Cloud](https://www.hetzner.com/cloud) account and API token
64
+ - An SSH key uploaded to Hetzner (default name: `redock-key`)
65
+ - A domain you can add DNS records to
66
+ - Python 3.11+ and [uv](https://docs.astral.sh/uv/)
67
+
68
+ For automatic DNS (optional): a [Hetzner DNS](https://www.hetzner.com/dns-console) account with a zone for your domain.
69
+
70
+ ## Install
71
+
72
+ ```bash
73
+ uv pip install redock
74
+ ```
75
+
76
+ Or from source:
77
+
78
+ ```bash
79
+ git clone https://gitlab.com/toopy/redock
80
+ cd redock
81
+ uv sync
82
+ ```
83
+
84
+ ## Configuration
85
+
86
+ Copy `.env.example` to `.env` and fill in the required values:
87
+
88
+ ```bash
89
+ cp .env.example .env
90
+ ```
91
+
92
+ | Variable | Required | Default | Description |
93
+ |----------|----------|---------|-------------|
94
+ | `REDOCK_HETZNER_TOKEN` | yes | — | Hetzner Cloud API token |
95
+ | `REDOCK_BASE_DOMAIN` | yes | — | Domain for deployments, e.g. `apps.example.com` |
96
+ | `REDOCK_ACME_EMAIL` | yes | — | Email for Let's Encrypt notifications |
97
+ | `REDOCK_HETZNER_SSH_KEY_NAME` | no | `redock-key` | SSH key name in Hetzner Cloud |
98
+ | `REDOCK_SSH_KEY_PATH` | no | `~/.ssh/id_ed25519` | Path to local private key |
99
+ | `REDOCK_HETZNER_REGION` | no | `fsn1` | Hetzner datacenter |
100
+ | `REDOCK_HETZNER_SERVER_TYPE` | no | `cx22` | Server type |
101
+ | `REDOCK_STATE_FILE` | no | `~/.redock/state.json` | Where deployment state is stored |
102
+ | `REDOCK_CATALOG_DB` | no | `~/.redock/catalog.db` | Catalog SQLite database path |
103
+ | `REDOCK_INFOMANIAK_API_KEY` | no | — | Required for `catalog update --use-ai` |
104
+ | `REDOCK_INFOMANIAK_PRODUCT_ID` | no | — | Required for `catalog update --use-ai` |
105
+ | `REDOCK_INFOMANIAK_CHAT_MODEL` | no | `google/gemma-4-31B-it` | LLM model for metadata extraction |
106
+ | `REDOCK_DOCKERHUB_USERNAME` | no | — | Docker Hub username for private image metadata |
107
+ | `REDOCK_DOCKERHUB_TOKEN` | no | — | Docker Hub access token (read-only) for private image metadata |
108
+ | `REDOCK_SCAN_TRIVY_WEIGHT` | no | `0.5` | Trivy's share of the combined scan score |
109
+ | `REDOCK_SCAN_SCOUT_WEIGHT` | no | `0.5` | Docker Scout's share of the combined scan score |
110
+ | `REDOCK_SCAN_SEV_CRITICAL` | no | `5` | Penalty coefficient per CRITICAL vulnerability |
111
+ | `REDOCK_SCAN_SEV_HIGH` | no | `2` | Penalty coefficient per HIGH vulnerability |
112
+ | `REDOCK_SCAN_SEV_MEDIUM` | no | `0.5` | Penalty coefficient per MEDIUM vulnerability |
113
+ | `REDOCK_SCAN_SEV_LOW` | no | `0.1` | Penalty coefficient per LOW vulnerability |
114
+
115
+ Then verify everything looks good:
116
+
117
+ ```bash
118
+ redock doctor
119
+ ```
120
+
121
+ ## Automatic DNS with Hetzner DNS
122
+
123
+ redock uses `REDOCK_HETZNER_TOKEN` for both VPS provisioning and DNS management. If your domain zone exists in [console.hetzner.com](https://console.hetzner.com) → **Networking → DNS**, redock will create or update the `A` record automatically on every `redock up`.
124
+
125
+ **Setup:** Add your domain zone in the Hetzner Console and point your registrar's nameservers to Hetzner's — no extra token needed.
126
+
127
+ > **Note:** redock uses the [Hetzner Cloud DNS API](https://docs.hetzner.cloud/reference/cloud#tag/zones) (`api.hetzner.cloud/v1`). The zone must already exist — redock manages records within a zone, not the zone itself. It discovers the right zone by walking the hostname labels (e.g. for `drawing.apps.example.com` it tries `apps.example.com`, then `example.com`).
128
+
129
+ ## A real example
130
+
131
+ Deploy [Excalidraw](https://excalidraw.com) as `drawing.apps.example.com`:
132
+
133
+ ```bash
134
+ redock up excalidraw --slug drawing
135
+ ```
136
+
137
+ ```text
138
+ ● Ensuring VPS
139
+ ● Preparing host (Docker + Caddy)
140
+ ● DNS records — creating and waiting for propagation
141
+ ✓ Created DNS A record: drawing.apps.example.com → 65.21.104.33
142
+ ● Deploying container
143
+ ● Waiting for HTTPS
144
+ ✓ Live: https://drawing.apps.example.com
145
+
146
+ Done! https://drawing.apps.example.com
147
+ ```
148
+
149
+ The `--slug` flag sets the subdomain prefix. If omitted it defaults to the template name:
150
+
151
+ ```bash
152
+ redock up <id> # → <id>.<base_domain>
153
+ redock up <id> -s <slug> # → <slug>.<base_domain>
154
+ redock up <id> --env KEY=VALUE # override an env var at deploy time
155
+ redock up <id> --upgrade # stop existing container, pull latest image, redeploy
156
+ ```
157
+
158
+ ## App catalog
159
+
160
+ ```bash
161
+ # Add an app (all flags optional; Sub-project 2 will fill them via AI)
162
+ redock catalog add nginx:latest --port 80
163
+ redock catalog add louislam/uptime-kuma:latest --port 3001 --volume /app/data
164
+
165
+ # Store environment variables on the catalog item (repeatable)
166
+ redock catalog add myapp:latest --port 8080 --env DB_URL=postgres://localhost/db --env DEBUG=false
167
+ redock catalog update myapp --env SECRET_KEY=abc123 # merges with existing env vars
168
+
169
+ # Reference a host env var — prefix value with $ (use single quotes to prevent shell expansion)
170
+ redock catalog update myapp --env 'DB_PASS=$MY_SECRET' # stored as-is; resolved at deploy time
171
+
172
+ # Mark as ready to deploy (or uncheck with --no)
173
+ redock catalog check nginx
174
+ redock catalog check --no nginx
175
+
176
+ # List, inspect, update, remove
177
+ redock catalog list
178
+ redock catalog list --checked-only
179
+ redock catalog show nginx
180
+ redock catalog update nginx --port 8080
181
+ redock catalog delete nginx
182
+
183
+ # Search the local catalog
184
+ redock catalog search ngi
185
+
186
+ # Scan an image for vulnerabilities (Trivy + Docker Scout)
187
+ redock catalog scan nginx
188
+ redock catalog scan nginx --dry-run # scan without saving results
189
+ redock catalog scan nginx --verbose # show per-scanner breakdown
190
+ ```
191
+
192
+ ## Security scanning
193
+
194
+ `redock catalog scan` runs [Trivy](https://trivy.dev) and [Docker Scout](https://docs.docker.com/scout/) against the image, merges their findings, and computes a weighted 0–100 safety score. At least one scanner must be installed — the command skips unavailable scanners gracefully and only fails if none can run.
195
+
196
+ ### Install Trivy
197
+
198
+ ```bash
199
+ task trivy:install
200
+ ```
201
+
202
+ This runs the official install script and places `trivy` in `~/.local/bin` (no sudo needed). Alternatively, install manually:
203
+
204
+ - macOS: `brew install aquasecurity/trivy/trivy`
205
+ - Linux: `curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b ~/.local/bin`
206
+
207
+ Verify: `trivy --version`
208
+
209
+ ### Install Docker Scout
210
+
211
+ ```bash
212
+ task scout:install
213
+ ```
214
+
215
+ This installs the Docker Scout CLI plugin to `~/.docker/cli-plugins`. Docker Desktop 4.17+ already bundles it — run `docker scout version` to check before installing.
216
+
217
+ Verify: `docker scout version`
218
+
219
+ > **Note:** `redock up` requires the template to exist in the catalog **and** be marked as checked.
220
+ > A missing port also blocks deployment.
221
+
222
+ ## Other commands
223
+
224
+ ```bash
225
+ # See all running deployments
226
+ redock list
227
+
228
+ # Stop a deployment (keeps container and state, can be restarted)
229
+ redock stop <slug>
230
+
231
+ # Remove container + volume + state + DNS record, destroy VPS if no apps remain
232
+ redock purge <slug>
233
+ redock purge <slug> --yes # skip confirmation
234
+
235
+ # VPS - Step-by-step control
236
+ redock vps create # provision VPS only
237
+ redock vps install # install Docker + Caddy only
238
+ redock vps show # print VPS IP and status
239
+ redock vps delete # destroy VPS (blocked if deployments still exist)
240
+
241
+ # DNS - Step-by-step control
242
+ redock dns show <slug> # print required DNS record
243
+ redock dns update <slug> # create/update A record (Hetzner DNS)
244
+ redock dns wait <slug> # wait until DNS resolves
245
+ redock dns clean <slug> # delete A record (Hetzner DNS)
246
+ ```
247
+
248
+ ## License
249
+
250
+ MIT
@@ -0,0 +1,210 @@
1
+ # redock
2
+
3
+ One command from zero to a live HTTPS app on a Hetzner VPS.
4
+
5
+ ```bash
6
+ redock up excalidraw --slug drawing
7
+ ```
8
+
9
+ That's it. redock provisions a server, installs Docker and a Caddy reverse proxy, handles DNS, deploys the container, and hands you back a live URL.
10
+
11
+ ## How it works
12
+
13
+ 1. **Provision** — finds or creates a Hetzner `cx22` (or your chosen type)
14
+ 2. **Prepare** — SSHes in, installs Docker + `caddy-docker-proxy` via Docker Compose
15
+ 3. **DNS** — creates the `A` record automatically (Hetzner DNS) or prints it for manual entry, then polls until it resolves
16
+ 4. **Deploy** — runs the container with Caddy labels; Caddy gets a Let's Encrypt cert automatically
17
+ 5. **Verify** — polls HTTPS until the app responds `200`, then prints the URL
18
+
19
+ The VPS is shared across deployments — each app is one container behind Caddy.
20
+
21
+ ## Prerequisites
22
+
23
+ - A [Hetzner Cloud](https://www.hetzner.com/cloud) account and API token
24
+ - An SSH key uploaded to Hetzner (default name: `redock-key`)
25
+ - A domain you can add DNS records to
26
+ - Python 3.11+ and [uv](https://docs.astral.sh/uv/)
27
+
28
+ For automatic DNS (optional): a [Hetzner DNS](https://www.hetzner.com/dns-console) account with a zone for your domain.
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ uv pip install redock
34
+ ```
35
+
36
+ Or from source:
37
+
38
+ ```bash
39
+ git clone https://gitlab.com/toopy/redock
40
+ cd redock
41
+ uv sync
42
+ ```
43
+
44
+ ## Configuration
45
+
46
+ Copy `.env.example` to `.env` and fill in the required values:
47
+
48
+ ```bash
49
+ cp .env.example .env
50
+ ```
51
+
52
+ | Variable | Required | Default | Description |
53
+ |----------|----------|---------|-------------|
54
+ | `REDOCK_HETZNER_TOKEN` | yes | — | Hetzner Cloud API token |
55
+ | `REDOCK_BASE_DOMAIN` | yes | — | Domain for deployments, e.g. `apps.example.com` |
56
+ | `REDOCK_ACME_EMAIL` | yes | — | Email for Let's Encrypt notifications |
57
+ | `REDOCK_HETZNER_SSH_KEY_NAME` | no | `redock-key` | SSH key name in Hetzner Cloud |
58
+ | `REDOCK_SSH_KEY_PATH` | no | `~/.ssh/id_ed25519` | Path to local private key |
59
+ | `REDOCK_HETZNER_REGION` | no | `fsn1` | Hetzner datacenter |
60
+ | `REDOCK_HETZNER_SERVER_TYPE` | no | `cx22` | Server type |
61
+ | `REDOCK_STATE_FILE` | no | `~/.redock/state.json` | Where deployment state is stored |
62
+ | `REDOCK_CATALOG_DB` | no | `~/.redock/catalog.db` | Catalog SQLite database path |
63
+ | `REDOCK_INFOMANIAK_API_KEY` | no | — | Required for `catalog update --use-ai` |
64
+ | `REDOCK_INFOMANIAK_PRODUCT_ID` | no | — | Required for `catalog update --use-ai` |
65
+ | `REDOCK_INFOMANIAK_CHAT_MODEL` | no | `google/gemma-4-31B-it` | LLM model for metadata extraction |
66
+ | `REDOCK_DOCKERHUB_USERNAME` | no | — | Docker Hub username for private image metadata |
67
+ | `REDOCK_DOCKERHUB_TOKEN` | no | — | Docker Hub access token (read-only) for private image metadata |
68
+ | `REDOCK_SCAN_TRIVY_WEIGHT` | no | `0.5` | Trivy's share of the combined scan score |
69
+ | `REDOCK_SCAN_SCOUT_WEIGHT` | no | `0.5` | Docker Scout's share of the combined scan score |
70
+ | `REDOCK_SCAN_SEV_CRITICAL` | no | `5` | Penalty coefficient per CRITICAL vulnerability |
71
+ | `REDOCK_SCAN_SEV_HIGH` | no | `2` | Penalty coefficient per HIGH vulnerability |
72
+ | `REDOCK_SCAN_SEV_MEDIUM` | no | `0.5` | Penalty coefficient per MEDIUM vulnerability |
73
+ | `REDOCK_SCAN_SEV_LOW` | no | `0.1` | Penalty coefficient per LOW vulnerability |
74
+
75
+ Then verify everything looks good:
76
+
77
+ ```bash
78
+ redock doctor
79
+ ```
80
+
81
+ ## Automatic DNS with Hetzner DNS
82
+
83
+ redock uses `REDOCK_HETZNER_TOKEN` for both VPS provisioning and DNS management. If your domain zone exists in [console.hetzner.com](https://console.hetzner.com) → **Networking → DNS**, redock will create or update the `A` record automatically on every `redock up`.
84
+
85
+ **Setup:** Add your domain zone in the Hetzner Console and point your registrar's nameservers to Hetzner's — no extra token needed.
86
+
87
+ > **Note:** redock uses the [Hetzner Cloud DNS API](https://docs.hetzner.cloud/reference/cloud#tag/zones) (`api.hetzner.cloud/v1`). The zone must already exist — redock manages records within a zone, not the zone itself. It discovers the right zone by walking the hostname labels (e.g. for `drawing.apps.example.com` it tries `apps.example.com`, then `example.com`).
88
+
89
+ ## A real example
90
+
91
+ Deploy [Excalidraw](https://excalidraw.com) as `drawing.apps.example.com`:
92
+
93
+ ```bash
94
+ redock up excalidraw --slug drawing
95
+ ```
96
+
97
+ ```text
98
+ ● Ensuring VPS
99
+ ● Preparing host (Docker + Caddy)
100
+ ● DNS records — creating and waiting for propagation
101
+ ✓ Created DNS A record: drawing.apps.example.com → 65.21.104.33
102
+ ● Deploying container
103
+ ● Waiting for HTTPS
104
+ ✓ Live: https://drawing.apps.example.com
105
+
106
+ Done! https://drawing.apps.example.com
107
+ ```
108
+
109
+ The `--slug` flag sets the subdomain prefix. If omitted it defaults to the template name:
110
+
111
+ ```bash
112
+ redock up <id> # → <id>.<base_domain>
113
+ redock up <id> -s <slug> # → <slug>.<base_domain>
114
+ redock up <id> --env KEY=VALUE # override an env var at deploy time
115
+ redock up <id> --upgrade # stop existing container, pull latest image, redeploy
116
+ ```
117
+
118
+ ## App catalog
119
+
120
+ ```bash
121
+ # Add an app (all flags optional; Sub-project 2 will fill them via AI)
122
+ redock catalog add nginx:latest --port 80
123
+ redock catalog add louislam/uptime-kuma:latest --port 3001 --volume /app/data
124
+
125
+ # Store environment variables on the catalog item (repeatable)
126
+ redock catalog add myapp:latest --port 8080 --env DB_URL=postgres://localhost/db --env DEBUG=false
127
+ redock catalog update myapp --env SECRET_KEY=abc123 # merges with existing env vars
128
+
129
+ # Reference a host env var — prefix value with $ (use single quotes to prevent shell expansion)
130
+ redock catalog update myapp --env 'DB_PASS=$MY_SECRET' # stored as-is; resolved at deploy time
131
+
132
+ # Mark as ready to deploy (or uncheck with --no)
133
+ redock catalog check nginx
134
+ redock catalog check --no nginx
135
+
136
+ # List, inspect, update, remove
137
+ redock catalog list
138
+ redock catalog list --checked-only
139
+ redock catalog show nginx
140
+ redock catalog update nginx --port 8080
141
+ redock catalog delete nginx
142
+
143
+ # Search the local catalog
144
+ redock catalog search ngi
145
+
146
+ # Scan an image for vulnerabilities (Trivy + Docker Scout)
147
+ redock catalog scan nginx
148
+ redock catalog scan nginx --dry-run # scan without saving results
149
+ redock catalog scan nginx --verbose # show per-scanner breakdown
150
+ ```
151
+
152
+ ## Security scanning
153
+
154
+ `redock catalog scan` runs [Trivy](https://trivy.dev) and [Docker Scout](https://docs.docker.com/scout/) against the image, merges their findings, and computes a weighted 0–100 safety score. At least one scanner must be installed — the command skips unavailable scanners gracefully and only fails if none can run.
155
+
156
+ ### Install Trivy
157
+
158
+ ```bash
159
+ task trivy:install
160
+ ```
161
+
162
+ This runs the official install script and places `trivy` in `~/.local/bin` (no sudo needed). Alternatively, install manually:
163
+
164
+ - macOS: `brew install aquasecurity/trivy/trivy`
165
+ - Linux: `curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b ~/.local/bin`
166
+
167
+ Verify: `trivy --version`
168
+
169
+ ### Install Docker Scout
170
+
171
+ ```bash
172
+ task scout:install
173
+ ```
174
+
175
+ This installs the Docker Scout CLI plugin to `~/.docker/cli-plugins`. Docker Desktop 4.17+ already bundles it — run `docker scout version` to check before installing.
176
+
177
+ Verify: `docker scout version`
178
+
179
+ > **Note:** `redock up` requires the template to exist in the catalog **and** be marked as checked.
180
+ > A missing port also blocks deployment.
181
+
182
+ ## Other commands
183
+
184
+ ```bash
185
+ # See all running deployments
186
+ redock list
187
+
188
+ # Stop a deployment (keeps container and state, can be restarted)
189
+ redock stop <slug>
190
+
191
+ # Remove container + volume + state + DNS record, destroy VPS if no apps remain
192
+ redock purge <slug>
193
+ redock purge <slug> --yes # skip confirmation
194
+
195
+ # VPS - Step-by-step control
196
+ redock vps create # provision VPS only
197
+ redock vps install # install Docker + Caddy only
198
+ redock vps show # print VPS IP and status
199
+ redock vps delete # destroy VPS (blocked if deployments still exist)
200
+
201
+ # DNS - Step-by-step control
202
+ redock dns show <slug> # print required DNS record
203
+ redock dns update <slug> # create/update A record (Hetzner DNS)
204
+ redock dns wait <slug> # wait until DNS resolves
205
+ redock dns clean <slug> # delete A record (Hetzner DNS)
206
+ ```
207
+
208
+ ## License
209
+
210
+ MIT
@@ -0,0 +1,61 @@
1
+ version: "3"
2
+
3
+ vars:
4
+ KEY_PATH: '{{.KEY_PATH | default "~/.ssh/redock_ed25519"}}'
5
+
6
+ tasks:
7
+ dev:setup:
8
+ desc: Configure git to use the project's hooks (run once after cloning)
9
+ cmds:
10
+ - git config core.hooksPath .githooks
11
+ - echo "Git hooks installed."
12
+ status:
13
+ - test "$(git config core.hooksPath)" = ".githooks"
14
+
15
+ hcloud:install:
16
+ desc: Download and install the hcloud CLI into ./bin
17
+ vars:
18
+ HCLOUD_OS: '{{if eq OS "darwin"}}darwin{{else}}linux{{end}}'
19
+ HCLOUD_ARCH: '{{if eq ARCH "arm64"}}arm64{{else}}amd64{{end}}'
20
+ cmds:
21
+ - mkdir -p bin
22
+ - |
23
+ curl -fsSL "https://github.com/hetznercloud/cli/releases/latest/download/hcloud-{{.HCLOUD_OS}}-{{.HCLOUD_ARCH}}.tar.gz" \
24
+ | tar -xz -C bin hcloud
25
+ - chmod +x bin/hcloud
26
+ - echo "Installed $(./bin/hcloud version)"
27
+ status:
28
+ - test -f bin/hcloud
29
+
30
+ trivy:install:
31
+ desc: Install the Trivy vulnerability scanner (~/.local/bin)
32
+ cmds:
33
+ - mkdir -p ~/.local/bin
34
+ - curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b ~/.local/bin
35
+ - echo "Installed $(~/.local/bin/trivy --version | head -1)"
36
+ - echo "Ensure ~/.local/bin is in your PATH (add to ~/.bashrc or ~/.zshrc)"
37
+ status:
38
+ - test -f ~/.local/bin/trivy
39
+
40
+ scout:install:
41
+ desc: Install the Docker Scout CLI plugin (~/.docker/cli-plugins)
42
+ cmds:
43
+ - curl -fsSL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh
44
+ - echo "Installed $(docker scout version | head -1)"
45
+ status:
46
+ - docker scout version
47
+
48
+ ssh:keygen:
49
+ desc: Generate an ED25519 SSH key pair for redock (~/.ssh/redock_ed25519)
50
+ cmds:
51
+ - ssh-keygen -t ed25519 -f {{.KEY_PATH}} -C "redock" -N ""
52
+ - echo ""
53
+ - echo "Key pair created:"
54
+ - echo " Private key {{.KEY_PATH}}"
55
+ - echo " Public key {{.KEY_PATH}}.pub"
56
+ - echo ""
57
+ - echo "Upload the public key to Hetzner Cloud, then set in .env:"
58
+ - echo " REDOCK_SSH_KEY_PATH={{.KEY_PATH}}"
59
+ - echo " REDOCK_HETZNER_SSH_KEY_NAME=<name you gave it in Hetzner>"
60
+ status:
61
+ - test -f {{.KEY_PATH}}