{"id":49182270,"url":"https://github.com/franckferman/do-manager","last_synced_at":"2026-04-23T02:01:12.807Z","repository":{"id":346044584,"uuid":"1188037103","full_name":"franckferman/do-manager","owner":"franckferman","description":"Modular Go CLI \u0026 library for managing DigitalOcean infrastructure. Provision, inspect, and destroy Droplets via API — no doctl required.","archived":false,"fork":false,"pushed_at":"2026-03-22T02:00:04.000Z","size":245,"stargazers_count":1,"open_issues_count":10,"forks_count":0,"subscribers_count":0,"default_branch":"stable","last_synced_at":"2026-03-22T14:29:36.636Z","etag":null,"topics":["digitalocean","digitalocean-api","digitalocean-droplets"],"latest_commit_sha":null,"homepage":"https://franckferman.github.io/do-manager/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/franckferman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-21T14:32:50.000Z","updated_at":"2026-03-22T02:00:08.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/franckferman/do-manager","commit_stats":null,"previous_names":["franckferman/do-manager"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/franckferman/do-manager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franckferman%2Fdo-manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franckferman%2Fdo-manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franckferman%2Fdo-manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franckferman%2Fdo-manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/franckferman","download_url":"https://codeload.github.com/franckferman/do-manager/tar.gz/refs/heads/stable","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franckferman%2Fdo-manager/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32162611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-22T17:06:48.269Z","status":"online","status_checked_at":"2026-04-23T02:00:06.710Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["digitalocean","digitalocean-api","digitalocean-droplets"],"created_at":"2026-04-23T02:01:11.984Z","updated_at":"2026-04-23T02:01:12.767Z","avatar_url":"https://github.com/franckferman.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# do-manager\n\n**A modular Go CLI and library for managing DigitalOcean infrastructure.**\n\n[![CI](https://github.com/franckferman/do-manager/actions/workflows/ci.yml/badge.svg)](https://github.com/franckferman/do-manager/actions/workflows/ci.yml)\n[![Go Version](https://img.shields.io/badge/Go-1.23+-00ADD8?style=flat-square\u0026logo=go)](https://go.dev)\n[![License](https://img.shields.io/badge/license-AGPL--3.0-blue?style=flat-square)](LICENSE)\n[![DigitalOcean API](https://img.shields.io/badge/API-DigitalOcean_v2-0080ff?style=flat-square\u0026logo=digitalocean)](https://docs.digitalocean.com/reference/api/)\n\n\u003c/div\u003e\n\n---\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Why do-manager?](#why-do-manager)\n- [Features](#features)\n- [Project Structure](#project-structure)\n- [Installation](#installation)\n- [Configuration](#configuration)\n- [CLI Usage](#cli-usage)\n  - [Droplets](#droplets)\n  - [SSH Keys](#ssh-keys)\n  - [Regions / Sizes / Images](#regions--sizes--images)\n  - [DNS](#dns)\n  - [Firewalls](#firewalls)\n  - [Reserved IPs](#reserved-ips)\n  - [Snapshots](#snapshots)\n  - [VPC](#vpc)\n  - [Campaign](#campaign)\n  - [Audit](#audit)\n  - [Templates](#templates)\n  - [JSON Output](#json-output)\n  - [Shell Completion](#shell-completion)\n- [Library Usage](#library-usage)\n- [License](#license)\n\n---\n\n## Overview\n\n`do-manager` is built directly on [`godo`](https://github.com/digitalocean/godo), the official DigitalOcean Go client. Two interfaces, one codebase:\n\n- A **CLI tool** for operational use from the terminal - campaign orchestration, IP rotation, node rebuild, security audit.\n- A **Go library** (`pkg/`) that can be imported directly into any Go program.\n\nUnlike `doctl`, `do-manager` calls the API directly via `godo` - no subprocess, no string parsing, proper Go types throughout.\n\nThe design targets **Red Team infrastructure**: deploy a full engagement stack in one command, rotate IPs when they get burned, rebuild a single compromised node without touching the rest, tear everything down cleanly. The primitive operations (create/list/delete/power) are there, but the value is in the higher-level abstractions: `campaign`, firewall presets, `rotate-ips`, `rebuild`, `audit`.\n\n---\n\n## Why do-manager?\n\nThree tools are commonly used to manage DO infrastructure from a Red Team perspective:\n\n| | doctl | bash + curl | Terraform / Pulumi | do-manager |\n|---|---|---|---|---|\n| **Type** | Official CLI | Ad-hoc scripts | Infrastructure-as-Code | CLI + Go library |\n| **Campaign orchestration** | No | Manual | Partial | **Yes** (deploy/status/destroy/rotate) |\n| **Firewall presets** | No | No | No | **Yes** (c2/phishing/redirector/bastion/lockdown) |\n| **IP rotation** | No | Script it yourself | No | **Yes** (rotate-ips) |\n| **Parallel batch ops** | No | `\u0026` + wait | Partial | **Yes** (goroutines, per-slot errors) |\n| **Security audit** | No | No | No | **Yes** (exit 1 on CRITICAL/HIGH) |\n| **Importable as Go lib** | No | No | No | **Yes** (`import \"pkg/droplet\"`) |\n| **Requires state file** | No | No | Yes (.tfstate) | No (stateless) |\n| **Requires external binary** | Yes (doctl) | curl, jq | Yes (terraform) | **No** |\n\n**doctl** is fine for manual one-off ops. It covers every DO resource but has no campaign concept, no firewall presets, and no Red Team abstractions.\n\n**bash + curl** is where most people start. It breaks down under engagement pressure: parallel provisioning races, ID extraction with fragile `grep`/`jq`, no error aggregation per Droplet, scripts that diverge between operators.\n\n**Terraform** is the right choice for long-lived stable infrastructure. It is overkill for engagements: statefile management is a liability during ops, the feedback loop is slow, and there are no Red Team primitives.\n\n**do-manager** is the choice when you need to deploy a full stack fast, rotate IPs when they get burned, rebuild a single node without disrupting the campaign, tear everything down cleanly, and optionally embed all of that into a Go automation tool.\n\n---\n\n## Features\n\n| Area | Operations |\n|---|---|\n| **Droplets** | list, get, create (batch, `--wait`), delete, power on/off/reboot, ssh, exec (parallel), ips, rebuild |\n| **SSH Keys** | list, get, add (file or string), delete |\n| **Regions / Sizes / Images** | list with full specs |\n| **DNS** | domain CRUD + record CRUD (A, AAAA, CNAME, MX, TXT, NS, SRV, CAA) |\n| **Firewalls** | list, get, create, delete, attach, detach + 5 opinionated presets |\n| **Reserved IPs** | list, get, reserve, delete, assign, unassign |\n| **Snapshots** | list, get, create (`--wait`), delete |\n| **VPC** | list, get, create, delete, members |\n| **Campaign** | deploy (multi-role), status, destroy, rotate-ips |\n| **Audit** | security scan with CRITICAL/HIGH/MEDIUM/INFO findings, CI-friendly exit codes |\n| **Templates** | list, show, dump + `--template` on droplet/campaign + `text/template` variable substitution |\n\nAdditional:\n- `-o json` on every command for scripting and pipelines\n- `--user-data` / `--user-data-file` on `droplet create` and `campaign deploy`\n- `--vpc-uuid` on `droplet create` and `campaign deploy`\n- Shell completion for bash, zsh, fish, PowerShell\n- `ldflags` version embedding (Version, Commit, BuildDate)\n- GoReleaser for multi-platform binary releases (linux/darwin/windows, amd64/arm64)\n\n---\n\n## Project Structure\n\n```\ndo-manager/\n├── main.go\n├── go.mod\n│\n├── cmd/                      # CLI commands (not imported externally)\n│   ├── root.go\n│   ├── output.go             # -o json global flag\n│   ├── completion.go\n│   ├── version.go\n│   ├── droplet.go            # droplet + ssh/exec/ips/rebuild\n│   ├── sshkey.go\n│   ├── region.go\n│   ├── dns.go\n│   ├── firewall.go           # firewall + preset command\n│   ├── reservedip.go\n│   ├── snapshot.go\n│   ├── vpc.go\n│   ├── campaign.go           # campaign deploy/status/destroy/rotate-ips\n│   ├── audit.go\n│   └── template.go           # template list/show/dump\n│\n├── internal/\n│   └── config/\n│       └── config.go\n│\n└── pkg/                      # importable library packages\n    ├── client/               # authenticated godo.Client factory\n    ├── droplet/              # Droplet CRUD + batch + rebuild\n    ├── sshkey/\n    ├── region/               # Regions, Sizes, Images\n    ├── dns/                  # Domains + DNS records\n    ├── firewall/             # Firewalls + RuleSpec parser\n    ├── reservedip/\n    ├── snapshot/\n    ├── vpc/\n    └── tmpl/                 # embedded cloud-init templates + renderer\n```\n\n`pkg/` is stable API surface. `cmd/` is CLI-only and not meant to be imported.\n\n---\n\n## Installation\n\n**Prerequisites:** Go 1.23+\n\n### go install\n\n```bash\ngo install github.com/franckferman/do-manager@latest\n```\n\n### From source\n\n```bash\ngit clone https://github.com/franckferman/do-manager.git\ncd do-manager\ngo mod tidy\nmake build\n./do-manager version\n```\n\n### Binary release\n\nPre-built binaries for linux/darwin/windows on amd64/arm64 are available on the [Releases](https://github.com/franckferman/do-manager/releases) page.\n\n---\n\n## Configuration\n\nToken resolution order (first match wins):\n\n| Method | Example |\n|---|---|\n| `--token` flag | `do-manager --token dop_v1_xxx droplet list` |\n| `DO_TOKEN` env var | `export DO_TOKEN=dop_v1_xxx` |\n| Config file | `~/.do-manager.yaml` |\n\n**Config file** (`~/.do-manager.yaml`):\n\n```yaml\ntoken: dop_v1_your_token_here\n```\n\nGenerate a token at: https://cloud.digitalocean.com/account/api/tokens\n\n---\n\n## CLI Usage\n\n```\ndo-manager [command] [subcommand] [flags]\n```\n\nGlobal flags available on every command:\n\n```\n--token string     DigitalOcean API token (overrides DO_TOKEN)\n-o, --output string  Output format: table (default) | json\n```\n\n---\n\n### Droplets\n\n```bash\n# List / inspect\ndo-manager droplet list\ndo-manager droplet get \u003cid-or-name\u003e\n\n# Create - single\ndo-manager droplet create --name web-01 --region fra1 --size s-1vcpu-1gb --wait\n\n# Create - batch (5 Droplets in parallel, named lab-01 to lab-05)\ndo-manager droplet create --name lab --count 5 --region fra1 --wait\n\n# Create with startup script and VPC\ndo-manager droplet create --name c2-node \\\n  --region fra1 --snapshot gophish-v2 \\\n  --vpc-uuid \u003cuuid\u003e \\\n  --user-data-file ./setup.sh \\\n  --wait\n\n# Create with root password set via cloud-init\ndo-manager droplet create --name test-01 --region fra1 --password \"S3cr3t!\" --wait\n\n# Password composes with templates and scripts\ndo-manager droplet create --name redir-01 --region fra1 \\\n  --template redirector-nginx \\\n  --template-var C2BackendHost=10.20.0.2 \\\n  --password \"S3cr3t!\" --wait\n\n# Delete\ndo-manager droplet delete \u003cid\u003e           # single\ndo-manager droplet delete 111 222 333    # multiple in parallel\ndo-manager droplet delete --tag lab --force  # by tag\n\n# Power\ndo-manager droplet power on \u003cid\u003e\ndo-manager droplet power off \u003cid\u003e\ndo-manager droplet power reboot \u003cid\u003e\n\n# Rebuild - wipe disk, reinstall from new image, keep ID + reserved IP\ndo-manager droplet rebuild \u003cid\u003e --image ubuntu-22-04-x64 --wait\n\n# SSH - interactive session\ndo-manager droplet ssh \u003cid-or-name\u003e\ndo-manager droplet ssh \u003cid-or-name\u003e --user ubuntu --port 2222\n\n# Print IPs (one per line, pipeable)\ndo-manager droplet ips\ndo-manager droplet ips --tag c2 | xargs nmap -sV -p 443\n\n# Execute a command on all Droplets with a tag (parallel)\ndo-manager droplet exec --tag lab -- \"uname -r\"\ndo-manager droplet exec --tag c2 -- \"systemctl status havoc\"\n```\n\n**`droplet create` flags:**\n\n| Flag | Default | Description |\n|---|---|---|\n| `--name`, `-n` | required | Droplet name |\n| `--count`, `-c` | `1` | Provision N Droplets in parallel |\n| `--region`, `-r` | `nyc1` | Region slug |\n| `--size`, `-s` | `s-1vcpu-1gb` | Size slug |\n| `--image`, `-i` | `ubuntu-22-04-x64` | Image slug |\n| `--ssh-keys` | none | SSH key IDs (comma-separated) |\n| `--tags` | none | Tags |\n| `--user-data` | none | Cloud-init script (inline) |\n| `--user-data-file` | none | Path to startup script |\n| `--template` | none | Built-in cloud-init template |\n| `--template-var` | none | Template variable `KEY=VALUE` (repeatable) |\n| `--password` | none | Set root password via cloud-init (composable with `--template` and `--user-data-file`) |\n| `--vpc-uuid` | none | Place inside this VPC |\n| `--ipv6` | false | Enable IPv6 |\n| `--backups` | false | Enable automatic backups |\n| `--wait`, `-w` | false | Poll until active, print IP |\n\n---\n\n### SSH Keys\n\n```bash\ndo-manager ssh-key list\ndo-manager ssh-key get \u003cfingerprint\u003e\ndo-manager ssh-key add --name homelab --file ~/.ssh/id_ed25519.pub\ndo-manager ssh-key add --name ci --public-key \"ssh-ed25519 AAAA...\"\ndo-manager ssh-key delete \u003cfingerprint\u003e\n```\n\n---\n\n### Regions / Sizes / Images\n\n```bash\ndo-manager region list\ndo-manager size list\ndo-manager image list                        # distribution images\ndo-manager image list --type application\ndo-manager image list --type user            # your own snapshots\n```\n\n---\n\n### DNS\n\n```bash\n# Domains\ndo-manager dns list\ndo-manager dns get example.com\ndo-manager dns create --domain example.com --ip 1.2.3.4\ndo-manager dns delete example.com\n\n# Records\ndo-manager dns records example.com\ndo-manager dns add --domain example.com --type A     --name @    --data 1.2.3.4\ndo-manager dns add --domain example.com --type CNAME --name mail --data @\ndo-manager dns add --domain example.com --type TXT   --name @    --data \"v=spf1 mx -all\"\ndo-manager dns add --domain example.com --type MX    --name @    --data \"mail.example.com\" --ttl 3600\ndo-manager dns rm-record example.com \u003crecord-id\u003e\n```\n\n---\n\n### Firewalls\n\nRule format: `\"proto:ports:addresses\"`\n- `proto`: `tcp` | `udp` | `icmp`\n- `ports`: single port, range (`80-443`), or `0` for icmp\n- `addresses`: comma-separated CIDRs/IPs, or `any` (expands to `0.0.0.0/0,::/0`)\n\n```bash\ndo-manager firewall list\ndo-manager firewall get \u003cid\u003e\ndo-manager firewall create --name my-fw \\\n  --inbound \"tcp:443:any\" \\\n  --inbound \"tcp:22:203.0.113.1\" \\\n  --outbound \"tcp:0-65535:any\" \\\n  --droplets 12345678\ndo-manager firewall attach \u003cid\u003e --droplets 12345678,87654321\ndo-manager firewall detach \u003cid\u003e --droplets 12345678\ndo-manager firewall delete \u003cid\u003e\n```\n\n#### Firewall Presets\n\nApply an opinionated ruleset for a common role. SSH is restricted to `--operator-ip` when specified.\n\n| Profile | Inbound | Outbound | Use case |\n|---|---|---|---|\n| `c2` | 443, 80, 53/tcp+udp, SSH(op-ip) | all | Command \u0026 Control server |\n| `phishing` | 443, 80, 8080, SSH(op-ip) | all | GoPhish / evilginx2 |\n| `redirector` | 443, 80, SSH(op-ip) | 443, 80 | Traffic redirector |\n| `bastion` | SSH(op-ip) only | all | Jump host |\n| `lockdown` | SSH(op-ip) only | none | Fully locked node |\n\n```bash\ndo-manager firewall preset c2 \\\n  --name c2-fw \\\n  --droplets 12345678 \\\n  --operator-ip 203.0.113.1\n```\n\n---\n\n### Reserved IPs\n\nStatic addresses that survive Droplet deletion or rebuilds. Point DNS to the reserved IP and swap the underlying Droplet freely.\n\n```bash\ndo-manager reservedip list\ndo-manager reservedip get 1.2.3.4\ndo-manager reservedip reserve --region fra1 --droplet 12345678\ndo-manager reservedip assign 1.2.3.4 --droplet 87654321\ndo-manager reservedip unassign 1.2.3.4\ndo-manager reservedip delete 1.2.3.4\n```\n\n---\n\n### Snapshots\n\nSnapshots are the foundation for repeatable deployments. The workflow: provision one Droplet, install and configure your tooling (GoPhish, Havoc, evilginx2, nginx redirector config, WireGuard), take a snapshot, delete the source Droplet. On engagement day, `campaign deploy --snapshot \u003cslug\u003e` restores that exact state across N nodes in parallel. No reinstalling, no drift between nodes.\n\n```bash\ndo-manager snapshot list\ndo-manager snapshot get \u003cid\u003e\n\n# Freeze a configured Droplet into a reusable image\ndo-manager snapshot create --droplet 12345678 --name gophish-v2 --wait\n\n# Now deploy that exact state to 5 nodes\ndo-manager campaign deploy --name phish-q2 --snapshot gophish-v2 --count 5 ...\n\ndo-manager snapshot delete \u003cid\u003e\n```\n\n---\n\n### VPC\n\nAn isolated private network scoped to a region. Droplets share a private IP range; traffic between them never leaves the DO fabric.\n\n**What VPC is and is not.** A VPC is a network isolation layer - it gives your C2 node a private IP that only other Droplets in the same VPC can reach. It is not the channel you use to proxy C2 traffic; that is the redirector's job. The redirector (nginx, socat, Apache mod_proxy) forwards inbound HTTPS to the C2 over the private IP. No public firewall rule needed on the C2 node.\n\nThree common approaches to route traffic from redirector to C2:\n\n| Method | How it works | Notes |\n|---|---|---|\n| **DO VPC private IP** | Both nodes same region/VPC. nginx forwards to `10.x.x.x:port`. | Zero extra setup. Same region required. |\n| **WireGuard** | VPN tunnel between redirector and C2. C2 listens on `wg0` only. | Cross-provider, cross-region. Extra cloud-init setup. |\n| **SSH reverse tunnel** | C2 runs `ssh -R 8080:localhost:8080 user@redirector`. | Simple. Use autossh/systemd to survive disconnects. |\n\n```\n           Internet\n               |\n   Agent HTTPS :443\n               |\n        [Redirector]  1.2.3.4  public: 80/443 open\n         nginx proxy_pass http://10.20.0.2:443\n               |\n  VPC 10.20.0.0/24  (private, stays inside datacenter fabric)\n               |\n        [C2 Server]  10.20.0.2  no public C2 port\n                       public firewall: SSH only from operator IP\n```\n\n```bash\ndo-manager vpc list\ndo-manager vpc get \u003cid\u003e\ndo-manager vpc create --name c2-net --region fra1 --ip-range 10.20.0.0/24\ndo-manager vpc members \u003cid\u003e\ndo-manager vpc delete \u003cid\u003e\n\n# Attach a Droplet to the VPC at creation time\ndo-manager droplet create --name c2-node --vpc-uuid \u003cid\u003e ...\n\n# Or let campaign create the VPC automatically\ndo-manager campaign deploy --name op-ghost --vpc-auto ...\n```\n\n---\n\n### Campaign\n\nDeploy and tear down a complete infrastructure campaign with a single command. A campaign groups all resources under the tag `campaign:\u003cname\u003e`.\n\nThe campaign lifecycle exists because Red Team infra has a specific tempo: provision fast, rotate IPs when they get burned, rebuild compromised nodes without disrupting the rest, tear down cleanly at the end. `campaign deploy` creates Droplets, firewall, and reserved IPs in the right order and tags everything. `campaign destroy` deletes all of it in one shot. Nothing left behind.\n\n#### Simple (single role)\n\n```bash\ndo-manager campaign deploy \\\n  --name phish-q2 \\\n  --count 3 \\\n  --region fra1 \\\n  --snapshot gophish-v2 \\\n  --inbound \"tcp:443:any\" --inbound \"tcp:22:203.0.113.1\" \\\n  --outbound \"tcp:0-65535:any\" \\\n  --reserve-ips \\\n  --user-data-file ./setup_gophish.sh\n\ndo-manager campaign status --name phish-q2\ndo-manager campaign destroy --name phish-q2 --force\n```\n\n#### Multi-role (heterogeneous infrastructure)\n\nDeploy C2 nodes and redirectors in one command. Each role gets its own image, firewall, and reserved IPs.\n\n```bash\ndo-manager campaign deploy \\\n  --name op-ghost \\\n  --region fra1 \\\n  --operator-ip 203.0.113.1 \\\n  --vpc-auto \\\n  --role \"c2:count=2:snapshot=c2-snap:preset=c2\" \\\n  --role \"redirector:count=3:snapshot=redir-snap:preset=redirector:reserve-ips\"\n```\n\n**Role spec format:** `name[:count=N][:snapshot=slug][:preset=name][:size=slug][:reserve-ips][:user-data-file=path]`\n\n| Token | Description |\n|---|---|\n| `count=N` | Number of Droplets for this role |\n| `snapshot=slug` | Image slug (falls back to `--snapshot`) |\n| `preset=name` | Firewall preset: c2, phishing, redirector, bastion, lockdown |\n| `size=slug` | Size slug (falls back to `--size`) |\n| `reserve-ips` | Reserve one IP per Droplet |\n| `user-data-file=path` | Startup script for this role |\n\n`--vpc-auto` creates a VPC automatically and places all Droplets inside it.\n`--operator-ip` is forwarded to all preset-based firewall rules for SSH restriction.\n\n#### Rotate IPs\n\nWhen an IP is burned (blacklisted, domain seized), rotate all reserved IPs without downtime:\n\n```bash\ndo-manager campaign rotate-ips --name op-ghost\n#  op-ghost-redirector-01  167.99.10.1 -\u003e 45.33.1.10\n#  op-ghost-redirector-02  167.99.10.2 -\u003e 45.33.1.11\n```\n\n#### Rebuild a single node\n\nRe-image one burned Droplet while the campaign stays live. Reserved IP stays assigned.\n\n```bash\ndo-manager droplet rebuild op-ghost-c2-01 --image c2-snap --wait\n```\n\n---\n\n### Audit\n\nScan your account for security misconfigurations. Exits with code `1` on CRITICAL or HIGH findings.\n\n```bash\ndo-manager audit\ndo-manager audit --max-snapshot-age 30\ndo-manager audit -o json | jq '.findings[] | select(.severity==\"CRITICAL\")'\n```\n\n| Severity | Check |\n|---|---|\n| CRITICAL | Droplet with no firewall attached |\n| HIGH | Firewall rule exposes port 22/3389/5900 to `0.0.0.0/0` |\n| MEDIUM | Reserved IP not assigned to any Droplet (idle billing) |\n| INFO | Snapshot older than `--max-snapshot-age` days |\n\n---\n\n### Templates\n\nBuilt-in cloud-init bash scripts embedded in the binary. Variable substitution via Go's `text/template` (`{{.VarName}}` syntax). Unset variables fall back to declared defaults.\n\n```bash\n# List all templates with their variables and defaults\ndo-manager template list\n\n# Inspect a template before deploying\ndo-manager template show redirector-nginx\n\n# Render a template with variable overrides and print to stdout (preview before deploy)\ndo-manager template dump c2-havoc \\\n  --template-var C2Host=1.2.3.4 \\\n  --template-var TeamserverPassword=s3cr3t\n\n# Use directly on droplet create\ndo-manager droplet create --name c2-01 --region fra1 --image ubuntu-22-04-x64 \\\n  --template c2-havoc \\\n  --template-var C2Host=1.2.3.4 \\\n  --template-var TeamserverPassword=s3cr3t \\\n  --wait\n\n# Use in multi-role campaign (template= token in role spec)\ndo-manager campaign deploy \\\n  --name op-phantom --region fra1 --operator-ip 203.0.113.5 --vpc-auto \\\n  --role \"c2:count=1:preset=c2:template=c2-havoc\" \\\n  --role \"redirector:count=3:preset=redirector:reserve-ips:template=redirector-nginx\" \\\n  --template-var C2BackendHost=10.10.0.2 \\\n  --template-var Domain=cdn.example.com\n```\n\n| Template | Description | Key variables |\n|---|---|---|\n| `c2-havoc` | Havoc C2 - build from source, teamserver as systemd service | `C2Port`, `C2Host`, `TeamserverPassword` |\n| `c2-sliver` | Sliver C2 - latest release, operator config, systemd service | `C2Port`, `OperatorName` |\n| `redirector-nginx` | nginx HTTPS reverse proxy, URI path filtering, certbot/self-signed TLS | `C2BackendHost`, `C2BackendPort`, `Domain`, `C2URIPath` |\n| `redirector-apache` | Apache2 mod_rewrite, User-Agent + URI filtering for C2 profiles | `C2BackendHost`, `C2UserAgent`, `C2URIPath` |\n| `wireguard-server` | WireGuard server - generates keys, configures wg0, enables IP forwarding | `WGListenPort`, `WGServerNet` |\n| `wireguard-client` | WireGuard peer - connects to a WireGuard server | `WGServerEndpoint`, `WGServerPublicKey`, `WGClientNet` |\n| `gophish` | GoPhish phishing framework - latest release, admin panel on localhost | `AdminListenPort`, `PhishListenPort` |\n| `evilginx2` | evilginx2 reverse proxy phishing - latest release, auto-detect public IP | `Domain`, `ExternalIP`, `RedirectURL` |\n| `hardening` | SSH hardening, fail2ban, non-root operator user | `SSHPort`, `OperatorUser`, `OperatorPubKey` |\n\nTemplates are stored in `pkg/tmpl/scripts/` and embedded in the binary via `//go:embed`. To add a custom template, add a `.sh` file with the metadata header:\n\n```bash\n#!/bin/bash\n# @name: my-tool\n# @desc: One-line description shown in template list\n# @var: Port=443 - Listening port\n# @var: Password= - Admin password (required, no default)\n```\n\n---\n\n### JSON Output\n\nPass `-o json` to any command to get machine-readable output:\n\n```bash\ndo-manager droplet list -o json | jq '.[].id'\ndo-manager campaign status --name op-ghost -o json\ndo-manager audit -o json | jq '.findings | length'\n```\n\n---\n\n### Shell Completion\n\n```bash\n# zsh\ndo-manager completion zsh \u003e \"${fpath[1]}/_do-manager\"\n\n# bash\ndo-manager completion bash \u003e /etc/bash_completion.d/do-manager\n\n# fish\ndo-manager completion fish \u003e ~/.config/fish/completions/do-manager.fish\n\n# PowerShell\ndo-manager completion powershell | Out-String | Invoke-Expression\n```\n\n---\n\n## Library Usage\n\nAll `pkg/` packages are stateless and accept a `context.Context` on every call.\n\n```go\nimport (\n    \"context\"\n    \"os\"\n\n    \"github.com/franckferman/do-manager/pkg/client\"\n    \"github.com/franckferman/do-manager/pkg/droplet\"\n    \"github.com/franckferman/do-manager/pkg/vpc\"\n    \"github.com/franckferman/do-manager/pkg/dns\"\n    \"github.com/franckferman/do-manager/pkg/firewall\"\n    \"github.com/franckferman/do-manager/pkg/reservedip\"\n    \"github.com/franckferman/do-manager/pkg/snapshot\"\n    \"github.com/franckferman/do-manager/pkg/tmpl\"\n)\n\nfunc main() {\n    c, _ := client.New(os.Getenv(\"DO_TOKEN\"))\n    ctx  := context.Background()\n\n    // Droplets\n    dropSvc := droplet.New(c)\n    list, _ := dropSvc.List(ctx)\n    d, _    := dropSvc.Create(ctx, droplet.CreateOptions{\n        Name: \"worker-01\", Region: \"fra1\",\n        Size: \"s-1vcpu-1gb\", Image: \"ubuntu-22-04-x64\",\n        VPCUUID: \"\u003cvpc-id\u003e\",\n        UserData: \"#!/bin/bash\\napt-get update -y\",\n    })\n    // Batch: 5 in parallel\n    results := dropSvc.CreateBatch(ctx, droplet.CreateOptions{Name: \"worker\"}, 5)\n    // Rebuild a burned node\n    action, _ := dropSvc.Rebuild(ctx, d.ID, \"ubuntu-22-04-x64\")\n\n    // VPC\n    vpcSvc := vpc.New(c)\n    v, _ := vpcSvc.Create(ctx, vpc.CreateOptions{Name: \"lab-net\", Region: \"fra1\"})\n    members, _ := vpcSvc.Members(ctx, v.ID, \"droplet\")\n\n    // DNS\n    dnsSvc := dns.New(c)\n    dnsSvc.CreateDomain(ctx, \"example.com\", \"1.2.3.4\")\n    dnsSvc.CreateRecord(ctx, \"example.com\", \"A\", \"@\", \"1.2.3.4\", 1800)\n\n    // Firewall\n    fwSvc := firewall.New(c)\n    inRule, _ := firewall.ParseRuleSpec(\"tcp:443:any\")\n    fw, _ := fwSvc.Create(ctx, firewall.CreateOptions{\n        Name:         \"my-fw\",\n        InboundRules: []firewall.RuleSpec{inRule},\n        DropletIDs:   []int{d.ID},\n    })\n\n    // Reserved IPs\n    ripSvc := reservedip.New(c)\n    rip, _ := ripSvc.Reserve(ctx, \"fra1\", d.ID)\n    ripSvc.Unassign(ctx, rip.IP)\n\n    // Snapshots\n    snapSvc := snapshot.New(c)\n    snaps, _ := snapSvc.List(ctx)\n    snapSvc.CreateFromDroplet(ctx, d.ID, \"my-snapshot\", true) // true = wait\n\n    // Templates\n    metas, _ := tmpl.List()\n    script, _ := tmpl.Render(\"redirector-nginx\", map[string]string{\n        \"C2BackendHost\": \"10.20.0.2\",\n        \"Domain\":        \"cdn.example.com\",\n    })\n    _ = metas; _ = script\n\n    _ = list; _ = results; _ = action; _ = members; _ = fw; _ = snaps\n}\n```\n\n---\n\n## License\n\nThis project is licensed under the [GNU Affero General Public License v3.0](LICENSE) (AGPL-3.0).\n\nAny use, modification, or distribution - including over a network - requires the full source code to remain open under the same license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffranckferman%2Fdo-manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffranckferman%2Fdo-manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffranckferman%2Fdo-manager/lists"}