- 🤖 LLM usage: $6.7749 (101 commits)
- 👤 Human dev: ~$4201 (42.0h @ $100/h, 30min dedup)
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# 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.5Registry 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.yamlWhat --apply-config does:
- Applies display transforms via
wlr-randr(Wayland compositor) - Updates kanshi config (
~/.config/kanshi/config) for persistent display rotation - Sets backlight brightness and power state
- Supports both YAML and JSON config files
Supported commands:
redeploy hardware --apply-config FILEredeploy device-map --apply-config FILEredeploy blueprint show FILE --apply-config FILE
Extract 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 jsonJMESPath features:
- Simple paths:
kernel,host,board - Array indexing:
drm_outputs[0].transform - Filtering:
[?name==\11-0045`]` - Projections:
backlights[0].[name,brightness] - Wildcards:
drm_outputs[*].transform
Supported commands:
redeploy hardware --query EXPRredeploy device-map --query EXPRredeploy blueprint show FILE --query EXPR
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.yamlAnalyze and trigger GitHub Actions workflows from your repo on demand.
Prerequisites:
- GitHub CLI installed:
gh - Authenticated session:
gh auth login - Workflow must define
workflow_dispatchunderon:to be runnable manually
Common 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-runNotes:
redeploy workflow ...is for workflows fromredeploy.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_failurealways
Minimal example:
hooks:
- id: refresh_cachephase: after_applyaction: local_cmdcommand: "curl -fsS -X POST http://localhost:8100/api/v3/cache/clear || true"on_failure: warn
- id: open_browserphase: after_applyaction: open_urlurl: http://localhost:8100/on_failure: warn
- id: before_sync_env_notephase: before_stepwhen: "step.id == 'sync_env'"action: local_cmdcommand: "echo '[hook] about to run sync_env'"on_failure: continueNotes:
when:currently supports simple conditions likestep.id == 'sync_env'andstep.id != 'sync_env'.- Legacy
post_deploy/pre_deployblocks are still accepted and auto-migrated internally. - New specs should use
hooks:only.
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.
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-onlyAfter successful deploy, a DeployRecord is saved to the device in registry (timestamp, strategy, version, ok/fail).
redeploy 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 |
Generated 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
rsync_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
sync_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/.
sync_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
name: "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 | systemdhost: root@87.106.87.183 # SSH target (user@ip) or "local"app: myappversion: "1.0.19"domain: myapp.example.comremote_dir: ~/myapptarget:
strategy: docker_fullhost: root@87.106.87.183app: myappversion: "1.0.20"domain: myapp.example.comremote_dir: ~/myappcompose_files:
- docker-compose.vps.ymlenv_file: envs/vps.envverify_url: https://myapp.example.com/api/v1/healthverify_version: "1.0.20"extra_steps: # optional — appended or inserted
- id: flush_k3s_iptables # StepLibrary name — no action neededinsert_before: docker_build_pull # inject before specific step
- id: docker_prune # StepLibrary: prune unused images
- id: notify_slack # custom step (needs action:)action: ssh_cmddescription: "Send deploy notification"command: "curl -s -X POST $SLACK_WEBHOOK -d '{\"text\":\"deployed 1.0.20\"}'"risk: lowReference 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_checkurl: 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}) |
By 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_iptablesinsert_before: docker_build_pull # runs before build, not after verifyBeyond 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/.aqlscenario.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 — nostr.format).
extra_steps:
- id: post_deploy_oqlaction: oqldescription: "OQL: real sensor read from the deployed firmware"query_source: deploy-tests/oql/firmware-sensor-read.oqlquery_mode: executeurl: http://192.168.188.122:8202 # oqlctl --firmware-url
- id: post_deploy_testqlaction: testqldescription: "TestQL: API smoke of the deployed app"query_source: deploy-tests/testql/post-deploy-api-smoke.testql.toon.yamlflags: ["--output", "json", "--quiet"]url: http://192.168.188.109:8100 # testql run --url
- id: post_deploy_aqlaction: aqldescription: "AQL: decision model returns the expected variant"query_source: deploy-tests/aql/personalization.en.aqlcommand: "python3 /path/to/aql/cli.py {source} {locale}"query_locale: enexpect: technical_deepA 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_kioskaction: pluginplugin_type: browser_reloaddescription: Reload kiosk browser after deployplugin_params:
port: 9222ignore_cache: trueurl_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.pyfromredeploy.pluginsimportregister_plugin, PluginContextfromredeploy.modelsimportStepStatus@register_plugin("notify_slack")defnotify_slack(ctx: PluginContext) ->None:
webhook=ctx.params["webhook"]
ctx.probe.run(f"curl -X POST {webhook} -d '{{\"text\":\"deployed!\"}}'")
ctx.step.result="notified"ctx.step.status=StepStatus.DONEPluginContext 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_kioskaction: inline_scriptdescription: "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.shrisk: mediumtimeout: 60The script is base64-encoded and executed via SSH with automatic temp file cleanup. Use command field for multiline script content (YAML | preserves newlines).
Instead of duplicating scripts in YAML, reference a script defined in a markdown codeblock:
extra_steps:
- id: configure_kioskaction: inline_scriptdescription: "Execute kiosk script from markdown"command_ref: "#kiosk-browser-configuration-script"risk: mediumIn your migration markdown file, define the script in a section:
## Kiosk Browser Configuration Script```bash#!/bin/bash# Auto-detect browser...ifcommand -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:
- One-off operations defined in markdown docs
- Testing individual scripts before full migration
- Running maintenance tasks
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-runFor more explicit script identification, use markpact:ref <id> in codeblock:
```bash markpact:ref kiosk-browser-configuration-script
#!/bin/bash# Auto-detect browser...ifcommand -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=.envredeploy 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 redeploydoql 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):
- Docker Compose + Dockerfile
- nginx configs (
nginx.conf,*.conf) - Kubernetes manifests (
apiVersion+kindYAML) - Terraform (
*.tf,*.tfvars) - TOML (
pyproject.toml, app/tool TOML) - Vite config (
vite.config.ts/js/mjs/cjs) - CI/CD: GitHub Actions, GitLab CI, Jenkinsfile
Parser plugin extension:
- Python entry points:
redeploy.iac.parsers - Project-local parsers:
./redeploy_iac_parsers/*.py - User-global parsers:
~/.redeploy/iac_parsers/*.py
Built-in template generator:
redeploy import --list-plugin-templatesredeploy import --plugin-template helm-kustomizeredeploy import --plugin-template argocd-flux --plugin-dir redeploy_iac_parsers
Example external plugin (Helm + Ansible):
- Source template:
examples/redeploy_iac_parsers/helm_ansible.py - Quick start:
mkdir -p redeploy_iac_parserscp examples/redeploy_iac_parsers/helm_ansible.py redeploy_iac_parsers/redeploy import path/to/Chart.yamlredeploy import path/to/playbook.yml
Example external plugin (Helm templates + Kustomize):
- Source template:
examples/redeploy_iac_parsers/helm_kustomize.py - Quick start:
mkdir -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.yaml
Example external plugin (ArgoCD Application + Flux Kustomization):
- Source template:
examples/redeploy_iac_parsers/argocd_flux.py - Quick start:
mkdir -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.yaml
Example external plugin (GitOps CI for ArgoCD/Flux):
- Source template:
examples/redeploy_iac_parsers/gitops_ci.py - Quick start:
mkdir -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-onlyredeploy fix is the recommended day-to-day deploy command. It:
- bumps the patch version in
VERSION+ spec header - applies the migration spec with
--healenabled - if a step fails, calls an LLM (via LiteLLM / OpenRouter) to suggest a fix and retries automatically
# 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-bumpSpec discovery from .:
./migration.mdor./migration.yaml— direct match./redeploy/<target>/migration.md— project pattern (lists targets, asks if multiple)- Recursive fallback anywhere under the directory
redeploy fix automatically discovers migration specs — running from project root
with multiple targets prompts interactively.
# 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 . --majorUpdates VERSION file and all version references in the migration spec
(version:, name: "... vX.Y.Z", description: "... vX.Y.Z").
redeploy 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 5LLM 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=...).
# 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-schemaThe 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 exposes an MCP (Model Context Protocol) server, letting AI assistants (Claude Desktop, VS Code Copilot, custom agents) call redeploy operations as structured tools.
# 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]"Probe and diagnose hardware on a remote host (DSI display, DRM connectors, backlight, I2C, config.txt, Wayland compositor).
| Option | Description |
|---|---|
| `--format [yaml | json]` |
--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"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]` |
--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"Manage device blueprints (capture, show, list, twin, migrate).
Capture device state as blueprint.
| Option | Description |
|---|---|
| `--format [yaml | json]` |
--save | Persist to ~/.config/redeploy/blueprints/ |
--out PATH | Save to specific file |
--ssh-key PATH | SSH private key path |
Display saved blueprint.
| Option | Description |
|---|---|
| `--format [yaml | json]` |
--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 reporting
Licensed under Apache-2.0.