stackctl

Los equipos pequeños y los proyectos indie merecen una herramienta de infraestructura sólida — sin la sobrecarga empresarial.

stackctl reúne gestión de configuraciones de Kubernetes, secretos de HashiCorp Vault y VPN NetBird en una única CLI con interfaz consistente. El objetivo es permitir que un equipo pequeño opere de forma segura y con confianza: los secretos nunca se exponen en texto plano, los kubeconfigs viven centralizados en Vault, el acceso a la VPN está automatizado y todo puede ejecutarse dentro de un pipeline de CI/CD sin herramientas adicionales.

Ya seas un desarrollador en solitario, una startup o un pequeño equipo de operaciones, stackctl te da las mismas prácticas de seguridad usadas a escala — sin la complejidad.

Instalar via curl (recomendado):

curl -fsSL https://eliasmeireles.com.br/tools/stackctl/install.sh | bash

Instalar una versión específica:

curl -fsSL https://eliasmeireles.com.br/tools/stackctl/install.sh | bash -s v0.0.9

Instalar desde el código (requiere Go):

go install github.com/eliasmeireles/stackctl/cmd/stackctl@latest

Actualizar una instalación existente:

# Última release estable (omite pre-releases por defecto)
sudo stackctl self-update

# Fijar una versión específica
sudo stackctl self-update --version v0.0.9

# Optar por un release candidate
sudo stackctl self-update --version v0.1.0-rc-05

Ejecuta stackctl sin argumentos para abrir la TUI interactiva.


Autenticación en Vault

Todos los comandos de Vault resuelven las credenciales en este orden:

PrioridadOrigen
1Flags de la CLI: --addr, --token, --role-id/--secret-id, --k8s-role
2Variables de entorno: VAULT_ADDR, VAULT_TOKEN, VAULT_ROLE_ID, VAULT_SECRET_ID, VAULT_K8S_ROLE
3Archivo ~/.vault-token (generado por vault login)
Método de authRequerido
TokenVAULT_ADDR + VAULT_TOKEN
AppRoleVAULT_ADDR + VAULT_ROLE_ID + VAULT_SECRET_ID
Kubernetes SAVAULT_ADDR + VAULT_K8S_ROLE (+ opcional VAULT_K8S_MOUNT_PATH, VAULT_SA_TOKEN_PATH)

Flags Globales

FlagPor defectoDescripción
--output / -otableFormato de salida: table, json, yaml

Cuando se usa --output json o --output yaml, los emojis decorativos y los mensajes de progreso se omiten para que la salida sea legible por máquina.

stackctl database postgres list --host localhost --admin-user postgres --admin-password secret --output json
stackctl vault secret list --output yaml

Contexto del Proyecto — stackctl context

Evita repetir --host, --port, --admin-user en cada comando guardando los valores por defecto en un archivo .stackctl.yaml. El archivo se busca jerárquicamente desde el directorio actual hasta tu home.

# Crea .stackctl.yaml interactivamente en el directorio actual
stackctl context init

# Muestra la configuración activa
stackctl context show

Formato del .stackctl.yaml:

version: "1"
databases:
  postgres:
    host: localhost
    port: 5432
    user: postgres
    vault-login: secret/databases/postgres/admin   # opcional
  mysql:
    host: localhost
    port: 3306
    user: root
  mongodb:
    host: localhost
    port: 27017
    user: admin
messagebrokers:
  rabbitmq:
    host: localhost
    port: 5672
    user: guest

Nota: agrega .stackctl.yaml a tu .gitignore — puede contener contraseñas o paths de Vault.

Las flags explícitas en la CLI siempre sobrescriben los valores por defecto del contexto.


Comandos

TUI Interactiva

stackctl

Navega todas las funciones a través de un menú. Cada submenú muestra una nota contextual explicando el paso actual, y las pantallas de entrada muestran el breadcrumb completo de navegación más un contador de pasos (step N of M).

Funciones interactivas principales:

  • Navegación por paths de Vault — recorre el árbol KV de Vault para elegir credenciales de admin en lugar de escribir el path
  • Auto-generar contraseña — escribe auto o auto:<tamaño> para generar una contraseña aleatoria (impresa al salir de la TUI)
  • Selección de base de datos — elige de una lista numerada de bases existentes o escribe un nombre nuevo
  • KV engine ausente — se crea automáticamente cuando el destino de --vault-path aún no existe

Reintenta la autenticación de Vault cada 5 segundos si el token aún no está disponible.

Personalización de colores de la TUI (códigos de color ANSI 256):

VariablePor defectoControla
STACK_CTL_TITLE_COLOR86Título del menú
STACK_CTL_ITEM_COLOR86Items de la lista
STACK_CTL_SELECTED_ITEM_COLOR82Item seleccionado

Kubeconfig — stackctl kubeconfig

SubcomandoDescripción
list-contextsLista todos los contextos locales
get-context <nombre> [--encode]Imprime un contexto (opcionalmente en Base64)
set-context <nombre>Cambia el contexto actual
set-namespace <ns> [--context <nombre>]Define el namespace por defecto
cleanElimina entradas duplicadas
addImporta configuración (ver flags abajo)
remove <nombre>Elimina un contexto
save-to-vault <nombre>Sube el contexto a Vault
add-from-vault <path>Descarga y mezcla desde Vault
contextsLista los kubeconfigs guardados en Vault
from-saConstruye un kubeconfig a partir de un token de SA
apply -f <manifest>Ejecuta un flujo descrito en un manifest YAML
revert -f <manifest>Deshace lo que hizo apply, usando el mismo manifest

Flags de add:

FlagDescripción
<base64>Posicional: importa desde una cadena Base64
--file <path>Importa desde un archivo local
--host <ip> --ssh-user <user>Importa por SSH
--k3sUsa el path por defecto de k3s (/etc/rancher/k3s/k3s.yaml)
-r <nombre>Renombra el contexto importado
stackctl kubeconfig add --k3s --host 192.168.1.10 --ssh-user root -r home-lab
stackctl kubeconfig save-to-vault home-lab
stackctl kubeconfig add-from-vault secret/data/kubeconfig/home-lab

Flags de from-sa:

FlagDescripción
--sa <nombre>Nombre de la ServiceAccount (requerido)
--namespace <ns>Namespace donde reside la SA/Secret (por defecto kube-system)
--secret <nombre>Nombre del Secret del token (por defecto: <sa>-token)
--cluster-name <nombre>Nombre del cluster a embeber (por defecto: kubernetes)
--context-name <nombre>Nombre del contexto (por defecto: <sa>@<cluster-name>)
--default-namespaceNamespace por defecto para el nuevo contexto
--server <url>Sobrescribe la URL del API server (por defecto: lee del kubeconfig activo)
--kube-context <nombre>Contexto kube desde el cual leer server/CA (por defecto: actual)
--output-file <path>Escribe el kubeconfig en un archivo en lugar de mezclar en el kubeconfig activo
# Mezcla un kubeconfig para la SA dentro del kubeconfig activo
stackctl kubeconfig from-sa \
  --sa <nombre-sa> --secret <token-secret> \
  --cluster-name <nombre-cluster> \
  --default-namespace <ns-defecto>

# O escribe en un archivo separado (útil para pasarle el kubeconfig a un compañero)
stackctl kubeconfig from-sa --sa <nombre-sa> --secret <token-secret> \
  --output-file ./<nombre-sa>.kubeconfig

apply — flujos de kubeconfig basados en manifest

El discriminador kind selecciona el flujo. Actualmente soportado:

KindComando equivalente de la CLI
KubeconfigFromSAstackctl kubeconfig from-sa ...

El manifest se valida antes de cualquier llamada al cluster — un kind ausente, un spec ausente o un campo requerido del spec faltante aborta con un error claro.

# kubeconfig-from-sa.yaml
apiVersion: stackctl/v1
kind: KubeconfigFromSA
spec:
  serviceAccount: dev-user           # requerido
  namespace: kube-system             # por defecto: kube-system
  secret: dev-user-token             # por defecto: <serviceAccount>-token
  clusterName: homelab               # por defecto: kubernetes
  contextName: dev-user@homelab      # por defecto: <sa>@<clusterName>
  defaultNamespace: homelab-dev      # por defecto: default
  # server: https://10.0.0.1:6443    # override opcional
  # kubeContext: my-cluster          # opcional, usa el actual por defecto
  # outputFile: ./dev-user.kubeconfig  # opcional; mezcla en el kubeconfig activo si está vacío
stackctl kubeconfig apply  -f kubeconfig-from-sa.yaml   # avanza
stackctl kubeconfig revert -f kubeconfig-from-sa.yaml   # deshace usando el mismo archivo

revert usa el mismo switch de kind que apply. Para KubeconfigFromSA, elimina el contexto generado (y entradas huérfanas de cluster/usuario) del kubeconfig activo, o borra el archivo apuntado por spec.outputFile cuando se define. La operación es idempotente — un archivo o contexto ausente produce un aviso en lugar de un error.

Hay un ejemplo funcional en example/kubeconfig-from-sa.yaml. Nuevos kinds se añadirán aquí a medida que se vayan lanzando.


Vault — stackctl vault

Secrets

stackctl vault secret list [path]
stackctl vault secret get <path>
stackctl vault secret put <path> clave=valor [clave=valor ...]
stackctl vault secret delete <path>

Path por defecto del listado: secret/metadata/resources/kubeconfig

Policies

stackctl vault policy list
stackctl vault policy get <nombre>
stackctl vault policy put <nombre> <archivo.hcl>
stackctl vault policy delete <nombre>

Métodos de auth

stackctl vault auth list
stackctl vault auth enable <tipo> [--path <path>] [--description <desc>]
stackctl vault auth disable <path>

Engines de secrets

stackctl vault engine list
stackctl vault engine enable <tipo> [--path <path>] [--description <desc>]
stackctl vault engine disable <path>

Roles

stackctl vault role list <auth-mount>
stackctl vault role get <auth-mount> <nombre>
stackctl vault role put <auth-mount> <nombre> [flags]
stackctl vault role delete <auth-mount> <nombre>

Flags de role put: --bound-sa-names, --bound-sa-namespaces, --policies, --token-policies, --ttl, --token-max-ttl, --secret-id-ttl, --secret-id-num-uses

Apply declarativo

stackctl vault apply   -f config.yaml      # valida primero; aborta ante cualquier error
stackctl vault revert  -f config.yaml      # deshace lo aplicado
stackctl vault generate-manifest --all     # genera una plantilla totalmente comentada

Aplica engines → auth → policies → roles → service_accounts → users → secrets → kubernetes en ese orden. La validación se ejecuta antes de cualquier cambio — todos los problemas de schema en el manifest se reportan de una vez para poder corregirlos en una sola pasada.

El bloque kubernetes: puede gestionar declarativamente:

RecursoNotas
namespacesCrea/actualiza con labels/annotations
registry_secretsSecrets dockerconfigjson, credenciales inline o tomadas de un path KV de Vault
service_accountsServiceAccounts de Kubernetes con image pull secrets y control de automount
secretsSecrets genéricos (Opaque por defecto) — soporta kubernetes.io/service-account-token
config_mapsConfigMaps con data arbitrario
role_bindingsRoleBinding namespaced a un Role o ClusterRole, múltiples subjects
cluster_role_bindingsBinding a nivel de cluster a un ClusterRole

Mira example/vault-config.yaml y example/homelab-rbac.yaml para referencias funcionales.

Fetch (CI/CD)

Obtiene un secret y lo mezcla como kubeconfig, o exporta los campos como variables de entorno. Las flags de auth (--addr, --token, --role-id, etc.) se heredan del comando padre vault.

stackctl vault fetch \
  --addr $VAULT_ADDR \
  --role-id $VAULT_ROLE_ID --secret-id $VAULT_SECRET_ID \
  --secret-path secret/data/ci/kubeconfig/prod \
  -r prod-cluster
FlagDescripción
--secret-pathPath KV v2 al secret
--secret-fieldCampo a leer (por defecto: kubeconfig)
--as-kubeconfigMezcla el valor del campo (Base64) en el kubeconfig local (default)
--export-envExporta todos los campos como variables de entorno
--github-envTambién escribe en $GITHUB_ENV
-rRenombra el contexto al importarlo

Gestión de secretos — stackctl get secret

Obtiene secretos de Vault y los copia al portapapeles o los guarda en un archivo. El valor del secret nunca se imprime en la terminal.

Manejo de paths: todos los paths reciben automáticamente el prefijo secret/data/ para compatibilidad con KV v2.

# Copia un secret al portapapeles
stackctl get secret <KEY>

# Obtiene de un path personalizado (secret/data/ se prefija automáticamente)
stackctl get secret <KEY> --path resources/vps/elias-oracle

# Guarda en un archivo
stackctl get secret PUB_KEY --path resources/vps/elias-oracle --to-file ~/.ssh/id_rsa.pub

# Decodifica desde base64 antes de guardar
stackctl get secret ENCODED_KEY --path apps/production --to-file ./decoded.txt --decode-from-b64

# Reemplaza un archivo existente
stackctl get secret PUB_KEY --to-file ~/.ssh/id_rsa.pub --replace

Flags:

FlagDescripción
--path <path>Path de Vault (sin el prefijo secret/data/)
--to-file <archivo>Guarda el secret en archivo en vez del portapapeles
--decode-from-b64Decodifica el secret desde base64 antes de guardar/copiar
--replaceReemplaza el archivo si ya existe (solo con --to-file)
STACK_CTL_DEFAULT_SECRET_PATHVariable para definir el path por defecto (sin prefijo secret/data/)
(path por defecto)users/all/passwords (se vuelve secret/data/users/all/passwords)

Comandos de gestión de contraseñas:

# Agrega una contraseña (auto-generada si --pass se omite; también se copia al portapapeles)
stackctl add pass <KEY> [--pass <valor>] [--size <bytes>]

# Actualiza una contraseña
stackctl update pass <KEY> [--pass <valor>] [--size <bytes>]

# Elimina una contraseña
stackctl delete pass <KEY>

Generate — stackctl generate

Genera contraseñas y nombres de usuario aleatorios, copiados automáticamente al portapapeles.

# Genera una contraseña aleatoria (copiada al portapapeles)
stackctl generate password

# Genera una contraseña de tamaño específico (bytes de entropía)
stackctl generate password --size 32

# Genera un username aleatorio
stackctl generate username

# Imprime el valor en lugar de copiarlo (útil en scripts)
stackctl generate password --output json

Cuando el portapapeles no está disponible (ej.: en CI/CD), el valor generado se guarda en ~/.stackctl/pass.


VPN NetBird — stackctl netbird

stackctl netbird install
stackctl netbird up --netbird-key <key> [--api-host <host>] [--wait-dns]
stackctl netbird status
VariableDescripción
STACK_CLT_NETBIRD_KEYSetup key
API_HOSTHost de la API de gestión (por defecto: api.netbird.io)

Gestión de Bases de Datos — stackctl database

Gestiona bases de datos, usuarios, schemas y prueba conexiones (PostgreSQL, MySQL, MongoDB).

Los comandos siguen una jerarquía que prioriza el tipo de base: stackctl database {postgres|mysql|mongodb} {list|create|delete|test} ...

# Lista bases de datos y usuarios
stackctl database postgres list \
  --host localhost \
  --admin-user postgres \
  --admin-password secret

# Crea un usuario (auto-genera la contraseña; lista bases existentes interactivamente)
stackctl database postgres create user \
  --host localhost \
  --admin-user postgres \
  --admin-password secret \
  --username myapp_user \
  --password auto \
  --vault-path secret/databases/postgres/myapp_user

# Crea un usuario con contraseña y base de datos explícitas
stackctl database postgres create user \
  --vault-login secret/databases/postgres/admin \
  --username myapp_user \
  --password myapp_pass \
  --database myapp_db \
  --vault-path secret/databases/postgres/myapp_user

# Elimina un usuario — omite --username para ver una lista numerada y seleccionar interactivamente
stackctl database postgres delete user \
  --host localhost \
  --admin-user postgres \
  --admin-password secret

# Elimina un usuario específico directamente (pide confirmación por ser irreversible)
stackctl database postgres delete user \
  --host localhost \
  --admin-user postgres \
  --admin-password secret \
  --username old_user

# Elimina una base — omite --database para seleccionar de la lista; --force omite la confirmación
stackctl database postgres delete database \
  --host localhost \
  --admin-user postgres \
  --admin-password secret \
  --database old_db \
  --force

# Prueba las credenciales de un usuario
stackctl database postgres test user \
  --host localhost \
  --username myapp_user \
  --password myapp_pass \
  --database myapp_db

Bases soportadas: PostgreSQL · MySQL · MongoDB

Mira DATABASE_COMMANDS.md para la referencia completa de comandos.


Gestión de Message Brokers — stackctl messagebroker

Gestiona usuarios y credenciales de message broker (RabbitMQ).

# Crea un usuario en RabbitMQ
stackctl messagebroker rabbitmq create user \
  --host localhost \
  --admin-user admin \
  --admin-password secret \
  --username myapp_user \
  --password myapp_pass \
  --tags "administrator,management" \
  --vault-path secret/messagebroker/rabbitmq/myapp_user

# Lista todos los usuarios
stackctl messagebroker rabbitmq list user \
  --host localhost \
  --admin-user admin \
  --admin-password secret

# Elimina un usuario — omite --username para ver una lista numerada y seleccionar interactivamente
stackctl messagebroker rabbitmq delete user \
  --host localhost \
  --admin-user admin \
  --admin-password secret

# Prueba las credenciales del usuario
stackctl messagebroker rabbitmq test-user \
  --host localhost \
  --username myapp_user \
  --password myapp_pass

Brokers soportados: RabbitMQ

Tags comunes de RabbitMQ: administrator · management · policymaker · monitoring

Mira MESSAGEBROKER_COMMANDS.md para la referencia completa de comandos.


Self-update — stackctl self-update

Reemplaza el binario en ejecución por el de la última GitHub Release. Las tags de pre-release se omiten por defecto; usa --version para fijar una versión o para optar por un release candidate.

FlagPor defectoDescripción
--version(última estable)Tag específica para instalar. Salta el filtro de solo-estable.
--install-path/usr/local/bin/stackctlPath a reemplazar (defínelo si stackctl vive en otro lugar).
sudo stackctl self-update                          # última estable
sudo stackctl self-update --version v0.0.9         # fija una versión estable
sudo stackctl self-update --version v0.1.0-rc-05   # opta por un RC
stackctl self-update --install-path "$HOME/bin/stackctl"

La descarga cae en un archivo temporal junto al directorio de instalación cuando es posible (para rename atómico), con un fallback entre filesystems (EXDEV) que copia el binario de forma segura.


Ejemplo de CI/CD (GitHub Actions)

- name: Instalar stackctl
  run: curl -fsSL https://eliasmeireles.com.br/tools/stackctl/install.sh | bash

- name: Conectar VPN
  run: |
    stackctl netbird install
    stackctl netbird up --netbird-key ${{ secrets.NETBIRD_KEY }} --wait-dns

- name: Obtener kubeconfig
  env:
    VAULT_ADDR: ${{ secrets.VAULT_ADDR }}
    VAULT_ROLE_ID: ${{ secrets.VAULT_ROLE_ID }}
    VAULT_SECRET_ID: ${{ secrets.VAULT_SECRET_ID }}
  run: |
    stackctl vault fetch \
      --secret-path secret/data/ci/kubeconfig/prod \
      -r prod-cluster

- name: Deploy
  run: kubectl apply -f k8s/

Tests

Ejecutar los tests

# Ejecuta todos los tests
make test

# Ejecuta los tests con cobertura
go test -cover ./...

# Ejecuta los tests de un paquete específico
go test ./cmd/stackctl/cmd/vault/...

Entorno de Desarrollo Local

Para tests de integración y desarrollo local, puedes levantar un entorno completo de Vault + Kubernetes usando Multipass:

# Bootstrap de un cluster k3s local con Vault
make multipass

# Esto crea una VM con:
# - cluster Kubernetes k3s
# - HashiCorp Vault (auto-inicializado y desbloqueado)
# - NGINX Ingress Controller
# - CLI stackctl pre-instalada

Mira .dev/multipass/README.md para instrucciones detalladas de configuración y requisitos.


Contribuciones

¡Las contribuciones son bienvenidas! Siéntete libre de abrir un Pull Request.