agentsclimarketplace

Hetzner vm

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

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.From its SKILL.md

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.

SKILL.md

13.3 KB, ~3.8k tokens by cl100k_base, 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

What ships with it: 6 files

8.6 KB alongside SKILL.md

Keep looking

Skills are one crate of 325,949. 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.