Generated on 2026-05-20 using openrouter/qwen/qwen3-coder-next
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
redeploy detect → live probe host (what is there now)
redeploy plan → migration-plan.yaml (what to do)
redeploy apply → execute plan (do it)
redeploy run → detect + plan + apply (all at once from spec)
redeploy fix → bump + run + LLM heal (smart self-healing deploy)
redeploy bump → bump version in spec (patch/minor/major)
redeploy prompt → NLP → command via LLM (natural language interface)
redeploy mcp → start MCP server (Claude Desktop / VS Code / remote API)
redeploy scan → find devices on LAN (device registry)
redeploy target → deploy to named device (fleet)
redeploy gh-workflow → analyze/run GitHub Actions workflows on demand
# Recommended — installs CLI globally (no venv conflicts)
pipx install redeploy
# Or inside a venv
pip install redeploy
# With doql integration (generates migration.yaml from app.doql):
pip install doql[deploy]
# 1. Create spec file
cat > migration.yaml << 'EOF'
name: "myapp deploy 1.0.19 → 1.0.20"
source:
strategy: docker_full
host: root@YOUR_VPS_IP
app: myapp
version: "1.0.19"
target:
strategy: docker_full
host: root@YOUR_VPS_IP
app: myapp
version: "1.0.20"
domain: myapp.example.com
env_file: envs/prod.env
compose_files:
- docker-compose.prod.yml
verify_url: https://myapp.example.com/api/v1/health
verify_version: "1.0.20"
EOF
# 2. Preview steps (no SSH needed)
redeploy run migration.yaml --plan-only
# 3. Dry run (connects via SSH, makes no changes)
redeploy run migration.yaml --dry-run
# 4. Full deploy (live detect → plan → apply)
redeploy run migration.yaml --detect
# Or without --detect (faster, uses spec source as-is)
redeploy run migration.yaml
Steps roll back completed actions on failure by default. A post-deploy check can fail the run without undoing an otherwise healthy deployment:
extra_steps:
- id: smoke_after_commit
action: testql
query_source: tests/smoke.testql.toon.yaml
url: https://example.test
rollback_on_failure: false
redeploy run SPEC --plan-only evaluates lint and preflight locally without
contacting the target or writing generated state. Use
--preflight-schema-out PATH when a persisted planning artifact is required.
# Register the RPi in the device registry
redeploy device-add pi@192.168.1.42 \
--tag kiosk --tag rpi4 \
--strategy native_kiosk \
--app kiosk-app \
--name "Workshop kiosk #1"
# Preview deploy plan
redeploy target pi@192.168.1.42 migration.yaml --plan-only
# Dry run
redeploy target pi@192.168.1.42 migration.yaml --dry-run
# Deploy
redeploy target pi@192.168.1.42 migration.yaml --detect
# Discover SSH-accessible devices on local network (passive: known_hosts + ARP + mDNS)
redeploy scan
# Active ICMP ping sweep (sends packets)
redeploy scan --ping --subnet 192.168.1.0/24
# Try specific SSH users
redeploy scan --user pi --user ubuntu --timeout 8
# List all known devices
redeploy devices
# Filter by tag or strategy
redeploy devices --tag kiosk
redeploy devices --strategy native_kiosk
redeploy devices --reachable # seen in last 5 minutes
# JSON output for scripting
redeploy devices --json | jq '.[] | select(.tags | index("prod"))'
# Add device manually
redeploy device-add root@10.0.0.5 --tag prod --strategy docker_full --app myapp
# Remove device
redeploy device-rm root@10.0.0.5
Registry is stored at ~/.config/redeploy/devices.yaml (chmod 600 — safe for SSH key paths).
redeploy supports a declarative configuration workflow for hardware settings — scan to YAML, edit locally, and apply to device:
# 1. Scan hardware state to YAML
redeploy hardware pi@192.168.188.109 > hardware.yaml
redeploy device-map pi@192.168.188.109 > device-map.yaml
# 2. Edit YAML locally (e.g., change display rotation)
# hardware.yaml:
# drm_outputs:
# - name: card0-DSI-2
# connector: DSI-2
# transform: '270' # ← edit this value
# 3. Apply config to remote device
redeploy hardware pi@192.168.188.109 --apply-config hardware.yaml
redeploy device-map pi@192.168.188.109 --apply-config device-map.yaml
What --apply-config does:
wlr-randr (Wayland compositor)~/.config/kanshi/config) for persistent display rotationSupported commands:
redeploy hardware --apply-config FILEredeploy device-map --apply-config FILEredeploy blueprint show FILE --apply-config FILEExtract specific values from YAML/JSON output using JMESPath query language (similar to XPath for XML):
# Simple path queries
redeploy hardware pi@192.168.188.109 --query "drm_outputs[0].transform"
redeploy hardware pi@192.168.188.109 --query "kernel"
redeploy device-map pi@192.168.188.109 --query "host"
# Filter queries
redeploy hardware pi@192.168.188.109 --query "backlights[?name==\`11-0045\`].brightness"
redeploy hardware pi@192.168.188.109 --query "drm_outputs[?connector==\`DSI-2\`].transform"
# From saved YAML files
redeploy blueprint show blueprint.yaml --query "hardware.drm_outputs[0].transform"
redeploy device-map --show device-map.yaml --query "tags"
# JSON output
redeploy hardware pi@192.168.188.109 --query "tags" --format json
JMESPath features:
kernel, host, boarddrm_outputs[0].transform[?name==\11-0045`]`backlights[0].[name,brightness]drm_outputs[*].transformSupported commands:
redeploy hardware --query EXPRredeploy device-map --query EXPRredeploy blueprint show FILE --query EXPRredeploy run SPEC [options]Execute deploy from a YAML spec file (or redeploy.yaml project manifest if no arg).
| Option | Description |
|---|---|
--plan-only |
Show steps without connecting via SSH |
--dry-run |
Connect, show steps, make no changes |
--detect |
Live-probe host before planning (recommended for prod) |
--env NAME |
Use named environment from redeploy.yaml (e.g. prod, rpi5) |
--plan-out FILE |
Save generated plan to file |
redeploy run --env prod # use prod env from redeploy.yaml
redeploy run --env rpi5 --detect # deploy to rpi5 with live probe
redeploy run --dry-run # uses .env DEPLOY_* vars if no redeploy.yaml
redeploy gh-workflow [list|analyze|run]Analyze and trigger GitHub Actions workflows from your repo on demand.
Prerequisites:
ghgh auth loginworkflow_dispatch under on: to be runnable manuallyCommon usage:
# List all workflow files and dispatch readiness
redeploy gh-workflow list
# Analyze one workflow (triggers/jobs + hint if not dispatchable)
redeploy gh-workflow analyze version-drift
# Analyze all workflows in a custom repo path
redeploy gh-workflow analyze --repo-root /path/to/repo
# Trigger workflow_dispatch run on demand
redeploy gh-workflow run version-drift --ref main
# Pass workflow inputs (repeat --field)
redeploy gh-workflow run release --field env=prod --field force=true
# Trigger and wait for completion (non-zero exit when workflow fails)
redeploy gh-workflow run version-drift --watch
# Preview gh command without executing
redeploy gh-workflow run version-drift --dry-run
Notes:
redeploy workflow ... is for workflows from redeploy.css.redeploy gh-workflow ... is for GitHub Actions in .github/workflows/.Use top-level hooks: in your migration spec to run custom actions in specific phases.
Supported phases:
before_applybefore_stepafter_stepon_step_failureon_step_retryafter_applyon_failurealwaysMinimal example:
hooks:
- id: refresh_cache
phase: after_apply
action: local_cmd
command: "curl -fsS -X POST http://localhost:8100/api/v3/cache/clear || true"
on_failure: warn
- id: open_browser
phase: after_apply
action: open_url
url: http://localhost:8100/
on_failure: warn
- id: before_sync_env_note
phase: before_step
when: "step.id == 'sync_env'"
action: local_cmd
command: "echo '[hook] about to run sync_env'"
on_failure: continue
Notes:
when: currently supports simple conditions like step.id == 'sync_env' and step.id != 'sync_env'.post_deploy/pre_deploy blocks are still accepted and auto-migrated internally.hooks: only.redeploy scan [options]Discover SSH-accessible devices on the local network.
| Source | Network activity | Requires |
|---|---|---|
known_hosts |
none | ~/.ssh/known_hosts |
arp |
none | ip neigh / arp -a |
mdns |
passive listen | avahi-browse |
ping_sweep |
ICMP — active | --ping flag |
All SSH-reachable devices are saved to registry. Existing entries updated (last_seen, mac, hostname). Old entries never deleted.
redeploy target DEVICE_ID [SPEC] [options]Deploy a spec to a registered device. Device’s host, strategy, app, domain are overlaid onto the spec.
redeploy target pi@192.168.1.42 # uses migration.yaml in cwd
redeploy target pi@192.168.1.42 custom.yaml --dry-run
redeploy target prod-vps --detect --plan-only
After successful deploy, a DeployRecord is saved to the device in registry (timestamp, strategy, version, ok/fail).
redeploy detect / plan / apply / migrate / init / statusredeploy detect --host root@VPS_IP --app myapp -o infra.yaml
redeploy plan --infra infra.yaml --target target.yaml -o plan.yaml
redeploy apply --plan plan.yaml
redeploy migrate --host root@VPS_IP --app myapp --target target.yaml # all in one
redeploy init # scaffold migration.yaml + redeploy.yaml
redeploy status # show project manifest summary
| Strategy | Description | Use case |
|---|---|---|
docker_full |
Docker Compose — build + up | VPS production |
podman_quadlet |
Rootless Podman systemd units | Quadlet/rootless VPS |
native_kiosk |
systemd + Chromium Openbox | RPi kiosk (no Docker) |
docker_kiosk |
Podman Quadlet in kiosk mode | RPi kiosk with container |
k3s |
Kubernetes/k3s | K3s cluster |
systemd |
Native systemd service | Bare metal |
native_kiosk plan stepsGenerated automatically when strategy: native_kiosk:
rsync_build → sync build/ to device
run_kiosk_installer → bash build/infra/install-kiosk.sh
install_kiosk_service → scp kiosk.service → /etc/systemd/system/
enable_kiosk_service → systemctl enable --now
wait_kiosk_start → 20s
http_health_check → curl http://localhost:8080
docker_kiosk plan stepsrsync_build → sync build/ to device
install_kiosk_quadlet → cp *.container → ~/.config/containers/systemd/ + daemon-reload
start_kiosk_container → systemctl --user restart app.service
wait_kiosk_start → 20s
http_health_check → curl http://localhost:8080
podman_quadlet plan stepssync_env → scp .env to remote
install_quadlet_files → cp *.container *.network *.volume → ~/.config/containers/systemd/
podman_daemon_reload → systemctl --user daemon-reload
stop_<app> → systemctl --user stop <app>.service
start_<app> → systemctl --user start <app>.service
wait_startup → 15s
http_health_check → verify_url health endpoint
version_check → verify_version match
For system (root) mode, set stop_services: true in target — switches to systemctl (no --user) and /etc/containers/systemd/.
docker_full plan stepssync_env → scp env_file → remote_dir/.env
docker_build_pull → docker compose build (on remote)
docker_compose_up → docker compose up -d --build
wait_startup → 30s
http_health_check → verify_url health endpoint
version_check → verify_version match
migration.yaml spec formatname: "myapp deploy 1.0.19 → 1.0.20"
description: "Production VPS version bump"
source:
strategy: docker_full # docker_full | podman_quadlet | native_kiosk | docker_kiosk | k3s | systemd
host: root@87.106.87.183 # SSH target (user@ip) or "local"
app: myapp
version: "1.0.19"
domain: myapp.example.com
remote_dir: ~/myapp
target:
strategy: docker_full
host: root@87.106.87.183
app: myapp
version: "1.0.20"
domain: myapp.example.com
remote_dir: ~/myapp
compose_files:
- docker-compose.vps.yml
env_file: envs/vps.env
verify_url: https://myapp.example.com/api/v1/health
verify_version: "1.0.20"
extra_steps: # optional — appended or inserted
- id: flush_k3s_iptables # StepLibrary name — no action needed
insert_before: docker_build_pull # inject before specific step
- id: docker_prune # StepLibrary: prune unused images
- id: notify_slack # custom step (needs action:)
action: ssh_cmd
description: "Send deploy notification"
command: "curl -s -X POST $SLACK_WEBHOOK -d '{\"text\":\"deployed 1.0.20\"}'"
risk: low
Reference any step by id alone — no action needed. Fields can be overridden:
extra_steps:
- id: flush_k3s_iptables # use as-is
- id: stop_k3s
- id: http_health_check
url: https://myapp.example.com/health # override url
- id: wait_startup_long # 60s instead of 30s
| ID | Action | Description |
|---|---|---|
flush_k3s_iptables |
ssh_cmd |
Flush CNI-HOSTPORT-DNAT + KUBE-* chains (stale k3s rules block Docker-proxy on 80/443) |
delete_k3s_ingresses |
kubectl_delete |
Delete all k3s ingresses |
stop_k3s |
systemctl_stop |
Stop k3s service |
disable_k3s |
systemctl_disable |
Disable k3s on boot |
stop_nginx |
systemctl_stop |
Stop host nginx (port 80 conflict) |
restart_traefik |
ssh_cmd |
Restart Traefik container |
docker_prune |
ssh_cmd |
Prune unused images + build cache |
docker_compose_down |
docker_compose_down |
Stop Docker Compose stack |
wait_startup |
wait |
Wait 30s |
wait_startup_long |
wait |
Wait 60s |
http_health_check |
http_check |
Verify health endpoint (expect: healthy) |
version_check |
version_check |
Verify deployed version |
sync_env |
scp |
Copy .env to remote |
podman_daemon_reload |
systemctl_start |
systemctl --user daemon-reload |
stop_podman |
systemctl_stop |
Stop all Podman containers via systemd |
enable_podman_unit |
systemctl_start |
systemctl daemon-reload && enable --now {service}.service |
systemctl_restart |
systemctl_start |
Restart a systemd service (command= to override) |
systemctl_daemon_reload |
ssh_cmd |
systemctl daemon-reload |
git_pull |
ssh_cmd |
git pull --ff-only with rollback (git reset --hard HEAD@{1}) |
insert_beforeBy default extra steps are appended after all generated steps. Use insert_before: <step_id> to inject at a specific position:
extra_steps:
- id: flush_k3s_iptables
insert_before: docker_build_pull # runs before build, not after verify
testql / oql / aqlBeyond http_check/inline_script, redeploy can run query-language test
scenarios as post-deploy verification. Each runs the interpreter locally
(on the controller, where it is installed) and targets the freshly deployed
host via url. The step fails — and thus the deploy fails/rolls back — when the
scenario fails.
| Action | Default runner | Verdict | Purpose |
|---|---|---|---|
testql |
testql run --url <url> <file> |
exit 0 (+ optional expect in output) |
API/GUI smoke of the deployed app |
oql |
oqlctl <file> -m <mode> --json [--firmware-url <url>] |
JSON ok == true (fallback: exit 0) |
run an OQL scenario against the deployed runtime/firmware |
aql |
aql <file> [locale] |
exit 0 (+ optional expect, e.g. a variant name) |
evaluate an AQL decision model and assert the verdict |
Fields (on top of url / expect):
query_source — path to the .testql.toon.yaml / .oql / .aql scenario.query_mode — OQL mode: validate | dry-run | execute.query_runner — override the runner binary (absolute path is fine).query_locale — AQL locale (en | pl).query_context — AQL: path to a context JSON for resolve.command — full command-template override; placeholders {url} {source} {mode} {locale} {context} are substituted (literal {} in the command are left intact — no str.format).extra_steps:
- id: post_deploy_oql
action: oql
description: "OQL: real sensor read from the deployed firmware"
query_source: deploy-tests/oql/firmware-sensor-read.oql
query_mode: execute
url: http://192.168.188.122:8202 # oqlctl --firmware-url
- id: post_deploy_testql
action: testql
description: "TestQL: API smoke of the deployed app"
query_source: deploy-tests/testql/post-deploy-api-smoke.testql.toon.yaml
flags: ["--output", "json", "--quiet"]
url: http://192.168.188.109:8100 # testql run --url
- id: post_deploy_aql
action: aql
description: "AQL: decision model returns the expected variant"
query_source: deploy-tests/aql/personalization.en.aql
command: "python3 /path/to/aql/cli.py {source} {locale}"
query_locale: en
expect: technical_deep
A ready example spec lives in c2004: deploy-tests/post-deploy.redeploy.md
(run with redeploy run deploy-tests/post-deploy.redeploy.md).
Extend the step pipeline with custom action types using action: plugin:
extra_steps:
- id: reload_kiosk
action: plugin
plugin_type: browser_reload
description: Reload kiosk browser after deploy
plugin_params:
port: 9222
ignore_cache: true
url_contains: "localhost:8100"
plugin_type |
Description | plugin_params |
|---|---|---|
browser_reload |
Reload Chromium via CDP (Chrome DevTools Protocol) over SSH | port (9222), ignore_cache (true), url_contains (“”) |
Place a .py file in ./redeploy_plugins/ (project-local) or ~/.redeploy/plugins/ (user-global):
# ./redeploy_plugins/notify.py
from redeploy.plugins import register_plugin, PluginContext
from redeploy.models import StepStatus
@register_plugin("notify_slack")
def notify_slack(ctx: PluginContext) -> None:
webhook = ctx.params["webhook"]
ctx.probe.run(f"curl -X POST {webhook} -d ''")
ctx.step.result = "notified"
ctx.step.status = StepStatus.DONE
PluginContext fields:
| Field | Type | Description |
|---|---|---|
step |
MigrationStep |
Current step — set result and status here |
host |
str |
SSH host (e.g. pi@192.168.1.5) |
probe |
RemoteProbe |
Call probe.run(cmd) for remote SSH commands |
emitter |
ProgressEmitter? |
Emit mid-step progress: emitter.progress(step.id, msg) |
params |
dict |
Shortcut for step.plugin_params |
dry_run |
bool |
Skip side-effects if True |
Execute multiline bash scripts directly from YAML without external files:
extra_steps:
- id: configure_kiosk
action: inline_script
description: "Deploy kiosk launch script"
command: |
#!/bin/bash
mkdir -p ~/c2004/config
cat > ~/c2004/config/kiosk-launch.sh << 'EOF'
#!/bin/bash
if command -v chromium-browser >/dev/null 2>&1; then
chromium-browser --kiosk http://localhost:8100
elif command -v firefox >/dev/null 2>&1; then
firefox --kiosk http://localhost:8100
fi
EOF
chmod +x ~/c2004/config/kiosk-launch.sh
risk: medium
timeout: 60
The script is base64-encoded and executed via SSH with automatic temp file cleanup. Use command field for multiline script content (YAML | preserves newlines).
command_ref)Instead of duplicating scripts in YAML, reference a script defined in a markdown codeblock:
extra_steps:
- id: configure_kiosk
action: inline_script
description: "Execute kiosk script from markdown"
command_ref: "#kiosk-browser-configuration-script"
risk: medium
In your migration markdown file, define the script in a section:
## Kiosk Browser Configuration Script
```bash
#!/bin/bash
# Auto-detect browser...
if command -v chromium-browser >/dev/null 2>&1; then
chromium-browser --kiosk http://localhost:8100
fi
**Benefits:**
- Single source of truth — script lives in one place (markdown codeblock)
- No duplication between markdown documentation and YAML
- Easy to read and maintain
- Changes to the codeblock automatically apply to the deployment
**Reference formats:**
- `"#section-id"` — script from section in current spec file
- `"./file.md#section-id"` — script from section in specific file
The section ID is derived from the heading: spaces become hyphens, lowercase.
Example: `## Kiosk Browser Configuration Script` → `#kiosk-browser-configuration-script`
### Execute Script by Reference (`redeploy exec`)
Run a single script from markdown without running the full migration:
```bash
# Execute script from codeblock on remote host
redeploy exec '#kiosk-browser-configuration-script' \
--host pi@192.168.188.108 \
--file migration.podman-rpi5-resume.md
# With file in reference
redeploy exec './migration.md#install-deps' --host root@server.com
# Using markpact:ref (more explicit)
redeploy exec 'kiosk-script-id' --host pi@192.168.188.108 --file migration.md
# Dry-run to preview script
redeploy exec '#backup-script' --host pi@192.168.188.108 --file ops.md --dry-run
This is useful for:
redeploy exec-multi)Test multiple scripts at once:
# Execute multiple scripts by ref
redeploy exec-multi 'kiosk-script,install-deps,cleanup' \
--host pi@192.168.188.108 \
--file migration.md
# Mix of markpact:ref and section headings
redeploy exec-multi 'script1,#section2,script3' \
--host root@server.com \
--file deploy.md \
--dry-run
markpact:refFor more explicit script identification, use markpact:ref <id> in codeblock:
```bash markpact:ref kiosk-browser-configuration-script
#!/bin/bash
# Auto-detect browser...
if command -v chromium-browser >/dev/null 2>&1; then
chromium-browser --kiosk http://localhost:8100
fi
Benefits of `markpact:ref`:
- Explicit ID assignment (not derived from heading)
- Multiple scripts per section
- Can reference by simple ID instead of full heading
- Self-documenting in markdown
Place in project root — `redeploy run` (no args) uses it automatically.
Supports **named environments** for multi-target projects:
```yaml
spec: migration.yaml # default spec file
app: myapp
environments:
prod:
host: root@87.106.87.183
strategy: docker_full
domain: myapp.example.com
env_file: envs/vps.env
verify_url: https://myapp.example.com/api/v1/health
rpi5:
host: pi@192.168.188.108
strategy: systemd
env_file: .env
verify_url: http://192.168.188.108:8000/api/v1/health
dev:
host: local
strategy: docker_full
env_file: .env.local
verify_url: http://localhost:8000/api/v1/health
Fallback: if no redeploy.yaml found, redeploy run reads DEPLOY_* vars from .env:
# .env
DEPLOY_HOST=pi@192.168.1.5
DEPLOY_APP=myapp
DEPLOY_DOMAIN=myapp.local
DEPLOY_ENV_FILE=.env
redeploy is the deploy engine for doql declarative apps.
# Install with doql integration
pip install doql[deploy]
# doql build generates build/infra/migration.yaml automatically
DEPLOY_HOST=root@YOUR_VPS doql build
# Then deploy — no args needed
doql deploy # calls redeploy API internally
doql deploy --plan-only
doql deploy --dry-run
doql quadlet --install # installs Quadlet units via redeploy
doql DEPLOY.target → redeploy strategy mapping:
| doql | redeploy |
|---|---|
docker-compose |
docker_full |
quadlet |
podman_quadlet |
kiosk-appliance |
native_kiosk |
kubernetes |
k3s |
IaC/CI config coverage (via redeploy import, used by doql/redeploy workflows):
nginx.conf, *.conf)apiVersion + kind YAML)*.tf, *.tfvars)pyproject.toml, app/tool TOML)vite.config.ts/js/mjs/cjs)Parser plugin extension:
redeploy.iac.parsers./redeploy_iac_parsers/*.py~/.redeploy/iac_parsers/*.pyBuilt-in template generator:
redeploy import --list-plugin-templatesredeploy import --plugin-template helm-kustomizeredeploy import --plugin-template argocd-flux --plugin-dir redeploy_iac_parsersExample external plugin (Helm + Ansible):
examples/redeploy_iac_parsers/helm_ansible.pymkdir -p redeploy_iac_parserscp examples/redeploy_iac_parsers/helm_ansible.py redeploy_iac_parsers/redeploy import path/to/Chart.yamlredeploy import path/to/playbook.ymlExample external plugin (Helm templates + Kustomize):
examples/redeploy_iac_parsers/helm_kustomize.pymkdir -p redeploy_iac_parserscp examples/redeploy_iac_parsers/helm_kustomize.py redeploy_iac_parsers/redeploy import path/to/chart/templates/deployment.yamlredeploy import path/to/kustomization.yamlExample external plugin (ArgoCD Application + Flux Kustomization):
examples/redeploy_iac_parsers/argocd_flux.pymkdir -p redeploy_iac_parserscp examples/redeploy_iac_parsers/argocd_flux.py redeploy_iac_parsers/redeploy import path/to/argocd-application.yamlredeploy import path/to/flux-kustomization.yamlExample external plugin (GitOps CI for ArgoCD/Flux):
examples/redeploy_iac_parsers/gitops_ci.pymkdir -p redeploy_iac_parserscp examples/redeploy_iac_parsers/gitops_ci.py redeploy_iac_parsers/redeploy import .github/workflows/deploy-gitops.ymlredeploy import .gitlab-ci.yml| Directory | Scenario | Strategy |
|---|---|---|
01-vps-version-bump |
VPS Docker version bump | docker_full → docker_full |
02-k3s-to-docker |
Migrate off k3s | k3s → docker_full |
03-docker-to-podman-quadlet |
Move to rootless Podman | docker_full → podman_quadlet |
04-rpi-kiosk |
Raspberry Pi kiosk update | native_kiosk → native_kiosk |
05-iot-fleet-ota |
IoT fleet OTA update | docker_full → docker_full |
09-fleet-yaml |
Fleet with stages + scan | fleet + redeploy target |
11-traefik-tls |
Traefik + Let’s Encrypt | docker_full → podman_quadlet |
12-ci-pipeline |
GitHub Actions / GitLab CI | CI-triggered docker_full |
# Run any example in dry-run mode (no SSH required):
redeploy run examples/01-vps-version-bump/migration.yaml --plan-only
redeploy run examples/04-rpi-kiosk/migration.yaml --plan-only
redeploy fixredeploy fix is the recommended day-to-day deploy command. It:
VERSION + spec header--heal enabled# Self-healing deploy: bump version → run → LLM retry on failure
redeploy fix .
redeploy fix redeploy/pi109/migration.md
# With a problem hint for the LLM
redeploy fix . --hint "service not starting after update"
redeploy fix . --hint "brak ikon SVG w menu"
# Preview only (no apply)
redeploy fix . --dry-run
# Bump minor version instead of patch
redeploy fix . --minor
# Bump major version
redeploy fix . --major
redeploy fix . --retries 5
# Skip version bump
redeploy fix . --no-bump
Spec discovery from .:
./migration.md or ./migration.yaml — direct match./redeploy/<target>/migration.md — project pattern (lists targets, asks if multiple)redeploy fix automatically discovers migration specs — running from project root
with multiple targets prompts interactively.
redeploy bump# Bump patch (default): 1.0.31 → 1.0.32
redeploy bump .
redeploy bump redeploy/pi109/migration.md
# Bump minor: 1.0.31 → 1.1.0
redeploy bump . --minor
# Bump major: 1.0.31 → 2.0.0
redeploy bump . --major
Updates VERSION file and all version references in the migration spec
(version:, name: "... vX.Y.Z", description: "... vX.Y.Z").
redeploy runredeploy run also supports --heal mode (enabled by default):
# Run with LLM self-healing (default)
redeploy run migration.yaml
# Disable healing
redeploy run migration.yaml --no-heal
# Pass problem description to LLM
redeploy run migration.yaml --fix "nginx port conflict"
# Max heal retries
redeploy run migration.yaml --max-heal-retries 5
LLM reads the failed step output, runs SSH diagnostics, and patches the spec YAML.
Repairs are logged to REPAIR_LOG.md next to the spec.
Requires OPENROUTER_API_KEY (or OPENAI_API_KEY) in .env or ~/.redeploy/.env.
Model defaults to openrouter/qwen/qwen3-coder-next (override with LLM_MODEL=...).
redeploy prompt# Map a natural language instruction to a redeploy command
redeploy prompt "deploy c2004 to pi109"
redeploy prompt "pokaż plan deployu na pi109"
redeploy prompt "bump version and redeploy" --yes
redeploy prompt "what specs are available?" --schema-only
# Force dry-run on generated command
redeploy prompt "run the pi109 migration" --dry-run
# Skip confirmation
redeploy prompt "fix the frontend service" --yes
# Preview the workspace schema sent to the LLM
redeploy prompt "..." --show-schema
The LLM receives a workspace schema (discovered specs, version, git branch, command catalogue)
and maps the instruction to a concrete redeploy invocation.
Language is auto-detected — Polish, English, or any language the model supports.
redeploy mcpredeploy exposes an MCP (Model Context Protocol) server,
letting AI assistants (Claude Desktop, VS Code Copilot, custom agents) call redeploy operations
as structured tools. Planning is enabled by default. Deployment, version, generated-command, and SSH
mutations require the server-side capability REDEPLOY_MCP_ALLOW_APPLY=1, an
actor, and the exact approval_hash returned for that operation.
# stdio — for Claude Desktop / VS Code local integration
redeploy mcp
# HTTP SSE — for remote/shared access
redeploy mcp --transport sse --port 8811
# Streamable HTTP
redeploy mcp --transport http --port 8811
# Standalone binary (no CLI wrapper)
redeploy-mcp --transport sse
| Tool | Description |
|---|---|
schema |
Discover workspace: specs, version, git branch, command catalogue |
list_specs |
List all migration specs found in a directory |
plan_spec |
Preview a spec (dry-run) — safe, no changes |
run_spec |
Apply a migration spec |
fix_spec |
Self-healing deploy: bump → apply → LLM retry |
bump_version |
Bump patch/minor/major version |
diagnose |
SSH diagnostics on a remote host |
exec_ssh |
Run an ad-hoc command on a remote host |
nlp_command |
Translate NLP instruction → redeploy command |
| URI | Description |
|---|---|
redeploy://workspace |
Current workspace schema as JSON |
redeploy://spec/{path} |
Raw content of a migration spec file |
Add to ~/.config/claude/claude_desktop_config.json:
{
"mcpServers": {
"redeploy": {
"command": "redeploy",
"args": ["mcp"]
}
}
}
Add to .vscode/mcp.json in your workspace:
{
"servers": {
"redeploy": {
"type": "stdio",
"command": "redeploy",
"args": ["mcp"]
}
}
}
Or for an SSE server already running on a remote machine:
{
"servers": {
"redeploy-remote": {
"type": "sse",
"url": "http://192.168.188.109:8811/sse"
}
}
}
pip install "redeploy[mcp]"
redeploy hardware HOST [options]Probe and diagnose hardware on a remote host (DSI display, DRM connectors, backlight, I2C, config.txt, Wayland compositor).
| Option | Description |
|---|---|
--format [yaml|json] |
Output format (default: yaml) |
--fix |
Print fix commands for all issues found |
--apply-fix COMPONENT |
Run fix for specific component via SSH |
--panel PANEL_ID |
Specify panel ID explicitly |
--list-panels |
List available panel definitions |
--set-transform TRANSFORM |
Set display rotation for DSI output (normal, 90, 180, 270, flipped, etc.) |
--apply-config FILE |
Apply display settings from YAML/JSON config file |
--query EXPR |
Extract specific values using JMESPath query |
--ssh-key PATH |
SSH private key path |
redeploy hardware pi@192.168.188.109
redeploy hardware pi@192.168.188.109 --fix
redeploy hardware pi@192.168.188.109 --set-transform 270
redeploy hardware pi@192.168.188.109 --apply-config hardware.yaml
redeploy hardware pi@192.168.188.109 --query "drm_outputs[0].transform"
redeploy device-map HOST [options]Generate full device snapshot (hardware + infra + diagnostics).
| Option | Description |
|---|---|
--name TEXT |
Human-friendly device label |
--tag TEXT |
Tag(s) to attach (repeatable) |
--save |
Persist map to ~/.config/redeploy/device-maps/ |
--out PATH |
Save to specific file |
--format [yaml|json] |
Output format (default: yaml) |
--no-infra |
Skip infra probe (hardware only) |
--list |
List saved device maps |
--show PATH |
Load and display saved device-map file |
--diff PATH... |
Diff two saved device-map files |
--apply-config FILE |
Apply hardware/infra settings from YAML config file |
--query EXPR |
Extract specific values using JMESPath query |
--ssh-key PATH |
SSH private key path |
redeploy device-map pi@192.168.188.109 --save --name "kiosk-lab"
redeploy device-map --list
redeploy device-map --show ~/.config/redeploy/device-maps/pi_at_192.168.188.109.yaml
redeploy device-map pi@192.168.188.109 --apply-config device-map.yaml
redeploy device-map pi@192.168.188.109 --query "hardware.drm_outputs[0].transform"
redeploy blueprint [command]Manage device blueprints (capture, show, list, twin, migrate).
redeploy blueprint capture HOST [options]Capture device state as blueprint.
| Option | Description |
|---|---|
--format [yaml|json] |
Output format (default: yaml) |
--save |
Persist to ~/.config/redeploy/blueprints/ |
--out PATH |
Save to specific file |
--ssh-key PATH |
SSH private key path |
redeploy blueprint show FILE [options]Display saved blueprint.
| Option | Description |
|---|---|
--format [yaml|json] |
Output format (default: yaml) |
--apply-config FILE |
Apply blueprint settings from YAML config file |
--query EXPR |
Extract specific values using JMESPath query |
redeploy blueprint capture pi@192.168.188.109 > blueprint.yaml
redeploy blueprint show blueprint.yaml --apply-config blueprint.yaml
redeploy blueprint show blueprint.yaml --query "hardware.drm_outputs[0].transform"
Core runtime dependencies:
| Package | Purpose |
|---|---|
pydantic>=2.0 |
Data validation and settings |
pyyaml>=6.0 |
YAML parsing/serialization |
markdown-it-py>=3.0 |
Markdown parsing (markpact specs) |
click>=8.0 |
CLI framework |
loguru>=0.7 |
Structured logging |
paramiko>=3.0 |
SSH client |
httpx>=0.25 |
HTTP client |
rich>=13.0 |
Terminal UI |
jmespath>=1.0 |
JSON/YAML query expressions |
goal>=2.1.0 |
Goal tracking |
costs>=0.1.20 |
AI cost tracking |
pfix>=0.1.60 |
Self-healing Python |
Optional dependencies:
| Package | Purpose |
|---|---|
op3>=0.1.8 |
OP3 support |
mcp>=1.0 |
MCP server mode |
markpact — Markdown-native deployment spec format (markpact:config, markpact:steps)goal — Migration goal tracking and validationcosts — AI cost tracking and reportingLicensed under Apache-2.0.