agentsclimarketplace

Hetzner vm

Skill gabriel-f-santos/coding-agents/skills/hetzner-vm

Curated catalog of portable Agent Skills for Claude Code, OpenAI Codex, and opencode — an opinionated product→build pipeline (brainstorming, discovery, PRD, research, planning, review) plus skill authoring, security scanning, and infra helpers. Author once, install into any project.

Install
npx -y skills add gabriel-f-santos/coding-agents --skill hetzner-vm

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Provisiona VM Hetzner completa com Coolify via Terraform. Copia templates terraform para infra/hetzner/, chama /linux-vm-hardening para gerar cloud-init seguro, e executa terraform apply. Funciona em qualquer projeto — não exige estrutura pré-existente. Use para criar nova VM Hetzner com Coolify em qualquer projeto. Não usar para atualizar infra existente sem revisar o plan primeiro.

SKILL.md

13.3 KB, as published. Nobody here has run it

Hetzner VM — Provisionamento Completo

Wizard que provisiona do zero: copia templates → chama /linux-vm-hardening → terraform apply.

Estado atual do ambiente

echo "=== Ferramentas ===" \
  && (terraform version 2>/dev/null | head -1 || tofu version 2>/dev/null | head -1 || echo "terraform/tofu: NAO INSTALADO") \
  && (hcloud version 2>/dev/null | head -1 || echo "hcloud: nao instalado (opcional)") \
  && echo "" && echo "=== Projeto ===" \
  && (ls infra/hetzner/ 2>/dev/null && echo "infra/hetzner/ ja existe" || echo "infra/hetzner/ sera criado") \
  && (ls infra/hetzner/terraform.tfvars 2>/dev/null && echo "terraform.tfvars: JA EXISTE" || echo "terraform.tfvars: sera criado") \
  && (ls infra/hetzner/cloud-init.yaml 2>/dev/null && echo "cloud-init.yaml: JA EXISTE" || echo "cloud-init.yaml: sera gerado") \
  && echo "" && echo "=== Seu IP publico ===" && (curl -s --max-time 3 ifconfig.me || echo "nao obtido") \
  && echo "" && echo "=== SSH Key ===" \
  && (cat ~/.ssh/id_ed25519.pub 2>/dev/null || echo "~/.ssh/id_ed25519.pub nao encontrada")

Passo 1 — Ferramentas

Se terraform/tofu não instalado, parar e orientar:

  • OpenTofu (recomendado): snap install --classic opentofu ou brew install opentofu
  • Terraform: brew install terraform

hcloud CLI é opcional mas útil para validar o token:

Passo 2 — Copiar templates para o projeto

mkdir -p infra/hetzner
cp -r "${CLAUDE_SKILL_DIR}/templates/." infra/hetzner/
ls infra/hetzner/

Passo 3 — Coletar parâmetros (um por vez)

Obrigatórios:

  1. project_name — nome do projeto em kebab-case (ex: meuapp). Nomeia todos os recursos no Hetzner.

  2. hetzner_api_token — token de API Hetzner com permissão Read+Write.

    Console Hetzner > selecione o projeto > Security > API Tokens > Generate API Token Permissão: Read & Write — token mostrado uma única vez, copiar agora.

  3. ssh_public_key — conteúdo da chave pública SSH (já detectado acima).

    • Se não existe: ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -C "deploy@hetzner"
  4. ssh_allowed_ips — IP público atual já detectado acima. Confirmar com o usuário.

    • Se IP for dinâmico: avisar que precisará atualizar e rodar terraform apply quando mudar.
    • Formato: ["1.2.3.4/32"] — múltiplos: ["1.2.3.4/32", "5.6.7.8/32"]
  5. admin_username — usuário não-root no servidor (default: deploy).

  6. ssh_mode — como quer acessar o servidor via SSH:

    [1] Público (padrão) — porta 22 aberta, restrita ao seu IP no firewall Hetzner
    [2] Tailscale — porta 22 fechada na internet, acesso só via VPN Tailscale
    

    Se escolher Tailscale, pedir o auth key:

    1. Acesse https://login.tailscale.com/admin/settings/keys
    2. Generate auth key com: Reusable + Pre-authorized + tag:server + expiry 1 dia
    3. Copie o tskey-auth-...

    Vantagem do Tailscale: funciona com IP dinâmico, times com múltiplos IPs, e fecha completamente a porta 22 na internet. Desvantagem: dependência do serviço Tailscale (se travado, usar o Console web da Hetzner).

Opcionais — mostrar defaults e perguntar se quer alterar:

ParâmetroDefaultOpções
server_typecpx21 (€7.49)cx23 2vCPU/4GB €3.49 · cax21 4vCPU/8GB ARM €9.49
locationnbg1 Nuremberghel1 Helsinki · fsn1 Falkenstein
ssh_port22outro número reduz ruído de bots (só no modo público)
timezoneUTCex: America/Sao_Paulo

Passo 4 — Gerar cloud-init.yaml

Invocar /linux-vm-hardening com todos os parâmetros coletados:

  • output_path: infra/hetzner/cloud-init.yaml
  • admin_username, ssh_public_key, ssh_port, timezone, ssh_mode
  • tailscale_auth_key se ssh_mode=tailscale

Aguardar a geração do arquivo antes de continuar.

Passo 5 — Criar terraform.tfvars

Criar infra/hetzner/terraform.tfvars com os valores coletados. Não exibir o token em output.

Passo 6 — Terraform init + plan

cd infra/hetzner && terraform init
cd infra/hetzner && terraform plan -out=tfplan

Resumir o plano: recursos criados, server_type, location. Se aparecer qualquer destroy, parar e pedir confirmação explícita com aviso de impacto.

Verificação obrigatória por ssh_mode

Antes de pedir confirmação, validar que o plano bate com o modo escolhido:

ssh_mode = tailscale — plano deve ter apenas:

  • ✅ UDP 41641 — Tailscale WireGuard
  • ✅ TCP 80 / 443 — Cloudflare
  • ✅ ICMP
  • ❌ TCP 22 — não deve aparecer
  • ❌ TCP 8000 / 6001 / 6002 — não devem aparecer (Coolify via Tailscale IP)

ssh_mode = public — plano deve ter:

  • ✅ TCP 22 restrito a ssh_allowed_ips
  • ✅ TCP 8000 / 6001 / 6002 restrito a ssh_allowed_ips
  • ✅ TCP 80 / 443 — Cloudflare
  • ✅ ICMP

Se qualquer porta errada aparecer, parar, corrigir o tfvars e re-rodar o plan.

Passo 7 — Confirmação e apply

Pedir confirmação antes de aplicar. Após OK do usuário:

cd infra/hetzner && terraform apply tfplan

Passo 8 — Outputs e próximos passos

cd infra/hetzner && terraform output

Apresentar ao usuário — mensagem varia por ssh_mode:

Modo public:

SERVIDOR PROVISIONADO
=====================
IP público: [server_ip]
SSH:        ssh [admin_username]@[server_ip] -p [ssh_port]
Coolify:    http://[server_ip]:8000  (disponível em ~10 min)

O servidor ainda está instalando Docker e Coolify via cloud-init.
Para acompanhar: ssh [admin_username]@[server_ip] 'sudo cloud-init status --wait'

⚠️  ATENÇÃO — WIZARD COOLIFY (leia antes de abrir o browser):
O Coolify vai mostrar "Server is not reachable" ao escolher "Localhost" — isso é esperado.
A chave SSH que ele usa é gerada durante a instalação, não existe antes do cloud-init rodar.

Quando aparecer a tela com a chave pública, copie-a e rode:
  ssh [admin_username]@[server_ip] -p [ssh_port] \
    'sudo mkdir -p /root/.ssh && sudo chmod 700 /root/.ssh && \
     echo "CHAVE_AQUI" | sudo tee -a /root/.ssh/authorized_keys && \
     sudo chmod 600 /root/.ssh/authorized_keys'
Depois clique "Check Again" — deve conectar na primeira tentativa.

PRÓXIMOS PASSOS:
1. Acesse http://[server_ip]:8000 → crie conta admin
2. Setup wizard → "Choose Server" → "Localhost"
3. "Server is not reachable" → copie a chave exibida → rode o comando acima → Check Again
4. Configure DNS no Cloudflare:
   coolify.seudominio.com → A → [server_ip] → Proxied (nuvem laranja)
   SSL/TLS → Full  (NAO "Full Strict" — causa erro 526 com o cert self-signed do Coolify)
   O Cloudflare faz a terminacao SSL; o Coolify recebe HTTP interno — funciona com Full.
5. Coolify Settings > Instance > campo "URL" → https://coolify.seudominio.com → Save
6. Conecte GitHub → veja Passo 9

Modo tailscale:

SERVIDOR PROVISIONADO
=====================
IP público:   [server_ip]  (só para tráfego web — SSH/Coolify fechados)
IP Tailscale: aguardando... rode: tailscale status | grep [project_name]
SSH:          ssh [admin_username]@<tailscale-ip>
Coolify:      http://<tailscale-ip>:8000  (disponível em ~10 min, só via Tailscale)

O servidor ainda está instalando Docker, Coolify e Tailscale via cloud-init.
Para acompanhar: ssh [admin_username]@<tailscale-ip> 'sudo cloud-init status --wait'

⚠️  ATENÇÃO — WIZARD COOLIFY (leia antes de abrir o browser):
O Coolify vai mostrar "Server is not reachable" ao escolher "Localhost" — isso é esperado.
A chave SSH que ele usa é gerada durante a instalação, não existe antes do cloud-init rodar.

Quando aparecer a tela com a chave pública, copie-a e rode:
  ssh [admin_username]@<tailscale-ip> \
    'sudo mkdir -p /root/.ssh && sudo chmod 700 /root/.ssh && \
     echo "CHAVE_AQUI" | sudo tee -a /root/.ssh/authorized_keys && \
     sudo chmod 600 /root/.ssh/authorized_keys'
Depois clique "Check Again" — deve conectar na primeira tentativa.

PRÓXIMOS PASSOS:
1. Aguarde o IP Tailscale: tailscale status
2. Acesse http://<tailscale-ip>:8000 → crie conta admin
3. Setup wizard → "Choose Server" → "Localhost"
4. "Server is not reachable" → copie a chave exibida → rode o comando acima → Check Again
5. Configure DNS no Cloudflare:
   coolify.seudominio.com → A → [server_ip] → Proxied (nuvem laranja)
   SSL/TLS → Full  (NAO "Full Strict" — causa erro 526 com o cert self-signed do Coolify)
   O Cloudflare faz a terminacao SSL; o Coolify recebe HTTP interno — funciona com Full.
6. Coolify Settings > Instance > campo "URL" → https://coolify.seudominio.com → Save
7. Conecte GitHub → veja Passo 9

Passo 9 — GitHub (repos privados + webhook)

Perguntar: "Vai usar repos privados do GitHub?"

Se sim — explicar as duas opções e perguntar qual prefere:

Opção A — GitHub App (recomendado)

Coolify cria um GitHub App na sua conta. Você instala nos repos que quiser liberar. Webhook configurado automaticamente — cada push dispara deploy.

Coolify → Sources → Add → GitHub App → seguir o wizard
→ instalar o App nos repos desejados
→ Resources → New Resource → Public Repository (mesmo sendo privado, já está autorizado)

Pré-requisito: URL configurada no Coolify (Passo 8 item 5) com HTTPS funcionando. O callback OAuth do GitHub App precisa de HTTPS — não funciona via IP:8000.

Opção B — Deploy Key + Webhook manual

Coolify gera uma chave SSH de deploy. Você adiciona manualmente no repo.

1. Coolify → Resources → New → Private Repository (with deploy key)
   Coolify exibe uma chave pública SSH — copie-a
2. GitHub → repo → Settings → Deploy keys → Add deploy key → cole a chave
3. Para webhook automático:
   GitHub → repo → Settings → Webhooks → Add webhook
   Payload URL: https://coolify.seudominio.com/webhooks/source/github
   Content type: application/json
   Events: Just the push event

Mais trabalhoso, mas funciona sem criar um GitHub App e sem OAuth.

Sobre firewall e webhooks

O webhook do GitHub chega via HTTPS (porta 443) → Cloudflare proxy → servidor. O firewall já abre 443 apenas via Cloudflare, então os webhooks passam normalmente.

Porta 8000 não recebe webhooks — o GitHub não consegue alcançá-la. URL configurada no Coolify (campo "URL" em Settings > Instance) é pré-requisito antes de qualquer integração GitHub.

Acesso ao painel Coolify com domínio (modo tailscale)

Com ssh_mode=tailscale, o firewall bloqueia 443 para IPs não-Cloudflare. Isso significa que https://infra.seudominio.com não abre no browser via internet — só via Tailscale (:8000).

Este é o design correto: painel Coolify invisível na internet, webhooks chegam via Cloudflare em 443.

GitHub webhook  → Cloudflare (443) → servidor ✅
Você (admin)    → Tailscale → 100.x.x.x:8000  ✅
Internet aleatória → bloqueado pelo firewall   ✅

Se precisar expor o painel publicamente (não recomendado), adicionar em terraform.tfvars:

coolify_allowed_ips = ["0.0.0.0/0"]  # ou seu IP específico

e adicionar a regra no firewall.tf.

Troubleshooting

ErroCausaSolução
SSH key name already existsChave já no projeto Hetznerterraform import hcloud_ssh_key.main <KEY_ID>
Invalid tokenAPI token errado ou expiradoRegerar no console Hetzner
SSH recusa após 10+ minIP não está em ssh_allowed_ipscurl ifconfig.me e atualizar tfvars
Coolify não abre na 8000cloud-init ainda rodandossh [admin_username]@IP 'sudo cloud-init status'
Server is not reachable no wizard CoolifyChave SSH do Coolify não está em /root/.ssh/authorized_keysCopiar a chave exibida na tela e rodar: echo "CHAVE" | sudo tee -a /root/.ssh/authorized_keys → Check Again
connect to host.docker.internal port 22: Operation timed outUFW bloqueia SSH do container Docker para o host — ufw allow não funciona para bridge Docker, precisa ir em before.rulessudo sed -i '/^# End required lines/a \\n# Allow Docker containers to reach host SSH\n-A ufw-before-input -i docker0 -p tcp --dport 22 -j ACCEPT\n-A ufw-before-input -i br-+ -p tcp --dport 22 -j ACCEPT' /etc/ufw/before.rules && sudo ufw reload
Error acquiring state lockApply anterior travadocd infra/hetzner && terraform force-unlock LOCK_ID
Erro 526 no CloudflareSSL mode "Full Strict" com cert self-signed do CoolifyTrocar para modo "Full" no Cloudflare até o Let's Encrypt ser gerado
Dashboard Coolify abre mas sem HTTPS via CloudflarePorta 8000 não é suportada pelo proxy CloudflareAcesse via IP/Tailscale (:8000) e configure a URL em Settings > Instance

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.