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.
- redock_vps-0.1.1/.env.example +13 -0
- redock_vps-0.1.1/.githooks/pre-commit +3 -0
- redock_vps-0.1.1/.gitignore +16 -0
- redock_vps-0.1.1/.gitlab-ci.yml +101 -0
- redock_vps-0.1.1/.python-version +1 -0
- redock_vps-0.1.1/LICENSE +21 -0
- redock_vps-0.1.1/PKG-INFO +250 -0
- redock_vps-0.1.1/README.md +210 -0
- redock_vps-0.1.1/Taskfile.yml +61 -0
- redock_vps-0.1.1/pyproject.toml +54 -0
- redock_vps-0.1.1/src/redock/__init__.py +0 -0
- redock_vps-0.1.1/src/redock/application/__init__.py +0 -0
- redock_vps-0.1.1/src/redock/application/add_catalog_item.py +26 -0
- redock_vps-0.1.1/src/redock/application/catalog_commands.py +67 -0
- redock_vps-0.1.1/src/redock/application/check_catalog_item.py +20 -0
- redock_vps-0.1.1/src/redock/application/clean_dns.py +10 -0
- redock_vps-0.1.1/src/redock/application/commands.py +60 -0
- redock_vps-0.1.1/src/redock/application/delete_catalog_item.py +13 -0
- redock_vps-0.1.1/src/redock/application/deploy_app.py +91 -0
- redock_vps-0.1.1/src/redock/application/enrich_catalog_item.py +28 -0
- redock_vps-0.1.1/src/redock/application/ensure_dns.py +17 -0
- redock_vps-0.1.1/src/redock/application/ensure_vps.py +26 -0
- redock_vps-0.1.1/src/redock/application/exceptions.py +78 -0
- redock_vps-0.1.1/src/redock/application/get_catalog_item.py +15 -0
- redock_vps-0.1.1/src/redock/application/list_apps.py +10 -0
- redock_vps-0.1.1/src/redock/application/list_catalog_items.py +14 -0
- redock_vps-0.1.1/src/redock/application/prepare_host.py +21 -0
- redock_vps-0.1.1/src/redock/application/scan_catalog_item.py +74 -0
- redock_vps-0.1.1/src/redock/application/search_docker_hub.py +11 -0
- redock_vps-0.1.1/src/redock/application/stop_app.py +30 -0
- redock_vps-0.1.1/src/redock/application/up.py +94 -0
- redock_vps-0.1.1/src/redock/application/update_catalog_item.py +37 -0
- redock_vps-0.1.1/src/redock/domain/__init__.py +0 -0
- redock_vps-0.1.1/src/redock/domain/catalog_item.py +56 -0
- redock_vps-0.1.1/src/redock/domain/catalog_suggestion.py +16 -0
- redock_vps-0.1.1/src/redock/domain/deployment.py +37 -0
- redock_vps-0.1.1/src/redock/domain/dns_record.py +11 -0
- redock_vps-0.1.1/src/redock/domain/docker_hub.py +11 -0
- redock_vps-0.1.1/src/redock/domain/docker_hub_image.py +12 -0
- redock_vps-0.1.1/src/redock/domain/ports.py +137 -0
- redock_vps-0.1.1/src/redock/domain/scan_result.py +25 -0
- redock_vps-0.1.1/src/redock/domain/value_objects.py +81 -0
- redock_vps-0.1.1/src/redock/domain/vps.py +22 -0
- redock_vps-0.1.1/src/redock/domain/vuln_scorer.py +52 -0
- redock_vps-0.1.1/src/redock/infrastructure/__init__.py +0 -0
- redock_vps-0.1.1/src/redock/infrastructure/caddy_router.py +10 -0
- redock_vps-0.1.1/src/redock/infrastructure/config.py +26 -0
- redock_vps-0.1.1/src/redock/infrastructure/docker_hub_search.py +25 -0
- redock_vps-0.1.1/src/redock/infrastructure/docker_scout_scanner.py +78 -0
- redock_vps-0.1.1/src/redock/infrastructure/hetzner_dns_provider.py +120 -0
- redock_vps-0.1.1/src/redock/infrastructure/hetzner_provisioner.py +71 -0
- redock_vps-0.1.1/src/redock/infrastructure/infomaniak_metadata_enricher.py +97 -0
- redock_vps-0.1.1/src/redock/infrastructure/json_repository.py +73 -0
- redock_vps-0.1.1/src/redock/infrastructure/printing_dns_provider.py +75 -0
- redock_vps-0.1.1/src/redock/infrastructure/public_dns_resolver.py +16 -0
- redock_vps-0.1.1/src/redock/infrastructure/remote_docker_runtime.py +59 -0
- redock_vps-0.1.1/src/redock/infrastructure/requests_image_details_fetcher.py +146 -0
- redock_vps-0.1.1/src/redock/infrastructure/sqlite_catalog_repository.py +129 -0
- redock_vps-0.1.1/src/redock/infrastructure/ssh_host_preparer.py +65 -0
- redock_vps-0.1.1/src/redock/infrastructure/trivy_scanner.py +45 -0
- redock_vps-0.1.1/src/redock/interfaces/__init__.py +0 -0
- redock_vps-0.1.1/src/redock/interfaces/cli.py +814 -0
- redock_vps-0.1.1/src/redock/interfaces/composition.py +133 -0
- redock_vps-0.1.1/src/redock/interfaces/rich_renderer.py +72 -0
- redock_vps-0.1.1/src/redock/server-assets/Caddyfile +3 -0
- redock_vps-0.1.1/src/redock/server-assets/docker-compose.caddy.yml +23 -0
- 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,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
|
redock_vps-0.1.1/LICENSE
ADDED
|
@@ -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}}
|