El cambio de configuración que ya estaba hecho (API pública de Postiz)
Esta semana tomé un pequeño issue de prerrequisito. Todo el asunto era una línea: “activar la API pública en nuestro Postiz autoalojado para poder generar una clave de API.” Postiz es el programador de redes sociales al que estoy conectando blog-manager, y la construcción de publicación cruzada más adelante necesita esa clave. Un ticket sencillo. Activar una bandera, generar una clave, seguir adelante.
Salvo que no había ninguna bandera que activar. Ya estaba activada. Descubrir eso llevó más tiempo del que habría llevado el cambio, y se convirtió en la parte realmente útil del día.
Lo que asumía el issue
El ticket que me había escrito a mí mismo unos días antes decía que había que activar la bandera de nivel public_api y luego tomar una clave desde la pestaña Developers en la configuración. Ese planteamiento asumía que Postiz funciona como la versión SaaS alojada: se elige un plan, el plan desbloquea la API. Así que me puse a buscar dónde vive ese plan en nuestro despliegue.
Nuestro Postiz corre desde un rol de Ansible, fijado a ghcr.io/gitroomhq/postiz-app:v2.21.8. Antes de tocar nada quería saber exactamente qué controla el acceso a esa pestaña Developers. Así que leí el código fuente. El frontend muestra la pestaña cuando:
user?.tier?.public_api && isGeneral
Dos condiciones. isGeneral viene directo de la variable de entorno IS_GENERAL, que nuestra plantilla de compose ya fija en "true". El nivel es el interesante. En el backend:
tier: organization?.subscription?.subscriptionTier
|| (!process.env.STRIPE_PUBLISHABLE_KEY ? 'ULTIMATE' : 'FREE')
Sin clave de Stripe, sin facturación, así que no hay objeto de suscripción. El mecanismo de respaldo entra en acción, y sin STRIPE_PUBLISHABLE_KEY configurada, resuelve a ULTIMATE. Y en la tabla de precios, ULTIMATE.public_api es true (solo el nivel FREE es false). El autoalojamiento con la facturación desactivada no deja en un nivel gratuito. Entrega el nivel más alto.
Entonces la cadena ya estaba completa: IS_GENERAL=true más ninguna clave de Stripe, por lo tanto ULTIMATE, por lo tanto public_api en true, por lo tanto la pestaña se renderiza. La tarea de “activar la API” era una operación nula. Lo correcto era iniciar sesión y confirmar que la pestaña estaba ahí, que lo estaba, generar la clave, y cerrar el ticket. Ningún cambio de configuración en absoluto.
La parte que casi hice mal
Mi primer instinto fue leer la lógica de control de acceso desde la rama main en GitHub, porque eso es lo que muestran primero los resultados de búsqueda. No corremos main. Corremos v2.21.8. Es probable que la lógica de niveles no haya cambiado, pero “probable” es como se termina razonando sobre código que no está desplegado. La API de GitHub acepta un ?ref=v2.21.8 al leer archivos, así que no hay excusa. Volví a leer cada archivo en el tag real. Misma conclusión, pero ahora era sobre lo que realmente corría.
Este es un hábito que sigo teniendo que reaprender. El tag desplegado es la fuente de verdad, no la rama por defecto.
Dos correcciones que surgieron de revisar
Porque estaba leyendo en lugar de asumir, otras dos cosas del ticket resultaron estar mal.
Primero, el ticket decía que había que guardar la clave generada en config/credentials.yml.enc. Pero ahí no es donde blog-manager guarda las claves de terceros. Cada token de publicación de blog en esta aplicación vive en una columna de base de datos cifrada, expuesta en la interfaz. La única clave a nivel de instancia que ya tiene, Pexels, está en un singleton AppSetting con un campo en la página de configuración. La clave de Postiz tiene la misma forma, así que pertenece ahí también, junto a Pexels, no en el archivo de credenciales. El archivo de credenciales guarda secretos de infraestructura de la aplicación, no claves de integración. Seguir el ticket al pie de la letra la habría puesto en el lugar equivocado y habría roto el patrón.
Segundo, el ticket agrupaba “conectar nuestras cuentas sociales” en el mismo prerrequisito. Pero conectar una cuenta en Postiz necesita aplicaciones OAuth por plataforma, un client ID y un secreto para X, para Bluesky, para LinkedIn, cada uno registrado y aprobado en el portal de desarrolladores de esa plataforma, cada uno agregado como variable de entorno en el contenedor de Postiz. Nada de eso está configurado, y nada de eso es trabajo de blog-manager. Es trabajo de infraestructura del lado del homelab. Así que lo separé en su propio issue allá y dejé este ticket como lo que realmente era: generar una clave.
Lo que me llevo de esto
El ticket describía tres tareas. Leer el código fuente convirtió las tres en algo más pequeño o distinto: una ya estaba hecha, otra apuntaba al archivo equivocado, y otra no pertenecía a este repositorio. Los treinta minutos de lectura evitaron un cambio de configuración que no habría hecho nada, una credencial guardada en el lugar equivocado, y un montón de configuración OAuth archivada bajo el proyecto equivocado.
Escribo tickets con anticipación para que mi yo futuro tenga un plan. El problema es que mi yo pasado estaba adivinando cómo se comportaría una herramienta que todavía no había desplegado. El software autoalojado sigue sorprendiéndome por ser más generoso de lo que sugiere la tabla de niveles alojados, al desactivar la facturación, suele obtenerse todo, porque el control de acceso existe para vender planes, no para restringir el código. Vale la pena revisarlo antes de asumir que se está en el nivel barato.
Lecturas relacionadas
Add a feature, or move a responsibility?
Adding Postiz social cross-posting looked done until a blunt question exposed a double-post bug, and a full audit of every posting path in the app found two more like it.
El webhook de Postiz que no pude construir, y el sondeo en su lugar
Un spike que terminó en "no construir esto": rechazo por IP privada, un payload vacío sin autenticación, y la API de lectura que tenía todo lo que le faltaba al push.
Programación y enlaces más inteligentes para mi publicador cruzado de Postiz
Programación a futuro, descripciones más completas con miniaturas, y un selector de qué URL usar, más el error de zona horaria del reloj de pared y la importación que copió la columna equivocada.