From 729c1d80c6e88db693120599f0a3614058e04ee6 Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 27 Oct 2025 13:04:23 -0500 Subject: [PATCH 1/5] chore: Add `PORTAINER_` prefix to env vars --- README.md | 10 +++++----- src/env.ts | 18 ++++++++++++++---- src/utils/portainer.ts | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2a27221..ce09bbc 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ services: ports: - 3000:3000 environment: - BASE_URL: https://portainer.example.com/api # Required, full URL including /api - USERNAME: your-username # Required, username to login with - PASSWORD: your-password # Required, password to login with - PORT: 3000 # Optional, default 3000 - API_KEY: your-api-key # Optional, set to a any string to require authentication + PORTAINER_BASE_URL: https://portainer.example.com/api # Required, full URL including /api + PORTAINER_USERNAME: your-username # Required, username to login with + PORTAINER_PASSWORD: your-password # Required, password to login with + PORT: 3000 # Optional, default 3000 + API_KEY: your-api-key # Optional, set to a any string to require authentication ``` To tell Portainer to pull the latest images and update the stack, make a simple POST request: diff --git a/src/env.ts b/src/env.ts index 074fd36..024f3bc 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,4 +1,4 @@ -import { violet, bold, cyan, red } from "./colors"; +import { violet, bold, cyan, red, yellow } from "./colors"; function requireEnv(key: string): string { const value = process.env[key]; @@ -13,9 +13,11 @@ function requireEnv(key: string): string { export const env = { port: Number(process.env.PORT || 3000), - baseUrl: requireEnv("BASE_URL"), - username: requireEnv("USERNAME"), - password: requireEnv("PASSWORD"), + portainer: { + baseUrl: process.env.BASE_URL || requireEnv("PORTAINER_BASE_URL"), + username: process.env.USERNAME || requireEnv("PORTAINER_USERNAME"), + password: process.env.PASSWORD || requireEnv("PORTAINER_PASSWORD"), + }, apiKey: process.env.API_KEY || undefined, }; @@ -29,4 +31,12 @@ export function logEnvWarnings() { `${cyan(bold("ℹ"))} ${violet("API_KEY")} not set - endpoints are not protected`, ); } + + for (const key of ["BASE_URL", "USERNAME", "PASSWORD"]) { + if (process.env[key]) { + console.log( + `${yellow(bold("⚠"))} ${violet(key)} is deprecated, use ${violet("PORTAINER_" + key)} instead`, + ); + } + } } diff --git a/src/utils/portainer.ts b/src/utils/portainer.ts index e1dd178..10d137a 100644 --- a/src/utils/portainer.ts +++ b/src/utils/portainer.ts @@ -9,7 +9,7 @@ export interface PortainerApi { } export async function createPortainerApi(): Promise { - const { baseUrl, username, password } = env; + const { baseUrl, username, password } = env.portainer; const checkResponse = (response: Response, expectedStatus = 200) => { if (response.status !== expectedStatus) From 5a0a9a03052d2e11c5c6483bcefea5097bd325af Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 27 Oct 2025 14:33:03 -0500 Subject: [PATCH 2/5] WIP --- .env.template | 6 +++--- src/env.ts | 16 ++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.env.template b/.env.template index 48e72df..6446f21 100644 --- a/.env.template +++ b/.env.template @@ -1,3 +1,3 @@ -BASE_URL=https://portainer.example.com/api -USERNAME=admin -PASSWORD=password +PORTAINER_BASE_URL=https://portainer.example.com/api +PORTAINER_USERNAME=admin +PORTAINER_PASSWORD=password diff --git a/src/env.ts b/src/env.ts index 024f3bc..80ebc95 100644 --- a/src/env.ts +++ b/src/env.ts @@ -32,11 +32,15 @@ export function logEnvWarnings() { ); } - for (const key of ["BASE_URL", "USERNAME", "PASSWORD"]) { - if (process.env[key]) { - console.log( - `${yellow(bold("⚠"))} ${violet(key)} is deprecated, use ${violet("PORTAINER_" + key)} instead`, - ); - } + maybeLogDeprecated("BASE_URL", "PORTAINER_API_URL"); + maybeLogDeprecated("USERNAME", "PORTAINER_USERNAME"); + maybeLogDeprecated("PASSWORD", "PORTAINER_PASSWORD"); +} + +function maybeLogDeprecated(key: string, replacement: string): void { + if (process.env[key]) { + console.log( + `${yellow(bold("⚠"))} ${violet(key)} is deprecated, use ${violet(replacement)} instead`, + ); } } From 8e03270d16f80f4d9489277d2e8f7451952569dc Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 27 Oct 2025 14:36:16 -0500 Subject: [PATCH 3/5] rename --- .env.template | 2 +- README.md | 2 +- src/env.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.template b/.env.template index 6446f21..add8251 100644 --- a/.env.template +++ b/.env.template @@ -1,3 +1,3 @@ -PORTAINER_BASE_URL=https://portainer.example.com/api +PORTAINER_API_URL=https://portainer.example.com/api PORTAINER_USERNAME=admin PORTAINER_PASSWORD=password diff --git a/README.md b/README.md index ce09bbc..2a3b9db 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ services: ports: - 3000:3000 environment: - PORTAINER_BASE_URL: https://portainer.example.com/api # Required, full URL including /api + PORTAINER_API_URL: https://portainer.example.com/api # Required, full URL including /api PORTAINER_USERNAME: your-username # Required, username to login with PORTAINER_PASSWORD: your-password # Required, password to login with PORT: 3000 # Optional, default 3000 diff --git a/src/env.ts b/src/env.ts index 80ebc95..9f86f07 100644 --- a/src/env.ts +++ b/src/env.ts @@ -14,7 +14,7 @@ function requireEnv(key: string): string { export const env = { port: Number(process.env.PORT || 3000), portainer: { - baseUrl: process.env.BASE_URL || requireEnv("PORTAINER_BASE_URL"), + baseUrl: process.env.BASE_URL || requireEnv("PORTAINER_API_URL"), username: process.env.USERNAME || requireEnv("PORTAINER_USERNAME"), password: process.env.PASSWORD || requireEnv("PORTAINER_PASSWORD"), }, From e9f7ee0746504942dbc420ce4025bd97a292c23d Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 27 Oct 2025 14:37:32 -0500 Subject: [PATCH 4/5] cleanup --- openapi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi.json b/openapi.json index d348759..86bf7a0 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "Portainer Stack Webhook", - "version": "0.2.1" + "version": "0.2.2" }, "paths": { "/api/health": { From cdbba97588deacd752af9d53b44f2a4df99dbf78 Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 27 Oct 2025 14:38:02 -0500 Subject: [PATCH 5/5] Cleanup --- src/env.ts | 2 +- src/utils/portainer.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/env.ts b/src/env.ts index 9f86f07..564f670 100644 --- a/src/env.ts +++ b/src/env.ts @@ -14,7 +14,7 @@ function requireEnv(key: string): string { export const env = { port: Number(process.env.PORT || 3000), portainer: { - baseUrl: process.env.BASE_URL || requireEnv("PORTAINER_API_URL"), + apiUrl: process.env.BASE_URL || requireEnv("PORTAINER_API_URL"), username: process.env.USERNAME || requireEnv("PORTAINER_USERNAME"), password: process.env.PASSWORD || requireEnv("PORTAINER_PASSWORD"), }, diff --git a/src/utils/portainer.ts b/src/utils/portainer.ts index 10d137a..0f040e0 100644 --- a/src/utils/portainer.ts +++ b/src/utils/portainer.ts @@ -9,7 +9,7 @@ export interface PortainerApi { } export async function createPortainerApi(): Promise { - const { baseUrl, username, password } = env.portainer; + const { apiUrl, username, password } = env.portainer; const checkResponse = (response: Response, expectedStatus = 200) => { if (response.status !== expectedStatus) @@ -17,7 +17,7 @@ export async function createPortainerApi(): Promise { }; const login = async (): Promise => { - const res = await fetch(`${baseUrl}/auth`, { + const res = await fetch(`${apiUrl}/auth`, { body: JSON.stringify({ username, password }), method: "POST", headers: { @@ -34,7 +34,7 @@ export async function createPortainerApi(): Promise { }; const listStacks: PortainerApi["listStacks"] = async () => { - const res = await fetch(`${baseUrl}/stacks`, { + const res = await fetch(`${apiUrl}/stacks`, { headers: authHeaders, }); @@ -43,7 +43,7 @@ export async function createPortainerApi(): Promise { }; const getStack: PortainerApi["getStack"] = async (id) => { - const res = await fetch(`${baseUrl}/stacks/${id}`, { + const res = await fetch(`${apiUrl}/stacks/${id}`, { headers: authHeaders, }); @@ -52,7 +52,7 @@ export async function createPortainerApi(): Promise { }; const getStackFile: PortainerApi["getStackFile"] = async (id) => { - const res = await fetch(`${baseUrl}/stacks/${id}/file`, { + const res = await fetch(`${apiUrl}/stacks/${id}/file`, { headers: authHeaders, }); @@ -64,7 +64,7 @@ export async function createPortainerApi(): Promise { id, options, ): Promise => { - const updateUrl = new URL(`${baseUrl}/stacks/${id}`); + const updateUrl = new URL(`${apiUrl}/stacks/${id}`); updateUrl.searchParams.set("endpointId", String(options.endpointId)); const res = await fetch(updateUrl.href, {