5 Commits

Author SHA1 Message Date
ff30b37c36 docs) add welcome hello world file (#30) 2026-02-21 22:38:35 +00:00
4b3af5e947 trips) portainer API deploy + preflight checks (#5)
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 2m25s
- Switch CI deploy step from Portainer webhook to Portainer API (CE-compatible)
- Add manual workflow trigger (`workflow_dispatch`)
- Add preflight checks for Portainer auth, endpoint ID, and stack ID before redeploy
- Update README with required secrets and deploy flow

Reviewed-on: #5
Co-authored-by: Shaun Campbell <shaun@campbellwireless.net>
Co-committed-by: Shaun Campbell <shaun@campbellwireless.net>
2026-02-21 06:20:51 +00:00
5101129f23 ci) updating docker registry to use registry.campbellwireless.net
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 2m2s
2026-02-21 00:18:56 -05:00
bfe2e91dee ci) Updating docker registry host (#4)
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 1m50s
Reviewed-on: #4
Co-authored-by: Shaun Campbell <shaun@campbellwireless.net>
Co-committed-by: Shaun Campbell <shaun@campbellwireless.net>
2026-02-21 05:12:16 +00:00
cc78d99326 ci) Updating docker container URI (#3)
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 1m47s
Reviewed-on: #3
Co-authored-by: Shaun Campbell <shaun@campbellwireless.net>
Co-committed-by: Shaun Campbell <shaun@campbellwireless.net>
2026-02-21 04:50:46 +00:00
3 changed files with 165 additions and 5 deletions

View File

@@ -4,9 +4,10 @@ on:
push:
branches:
- main
workflow_dispatch:
env:
REGISTRY_HOST: git.campbellwireless.net
REGISTRY_HOST: registry.campbellwireless.net
IMAGE_NAME: ${{ github.repository }}
jobs:
@@ -32,7 +33,7 @@ jobs:
- name: Log in to Gitea registry
uses: docker/login-action@v3
with:
registry: cloud.campbellwireless.net/git
registry: ${{ env.REGISTRY_HOST }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
@@ -50,3 +51,107 @@ jobs:
tags: |
${{ env.REGISTRY_HOST }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY_HOST }}/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.sha_short }}
- name: Preflight Portainer deploy config
env:
PORTAINER_URL: ${{ secrets.PORTAINER_URL }}
PORTAINER_API_KEY: ${{ secrets.PORTAINER_API_KEY }}
PORTAINER_STACK_ID: ${{ secrets.PORTAINER_STACK_ID }}
PORTAINER_ENDPOINT_ID: ${{ secrets.PORTAINER_ENDPOINT_ID }}
PORTAINER_INSECURE_TLS: ${{ secrets.PORTAINER_INSECURE_TLS }}
run: |
set -eu
for required in PORTAINER_URL PORTAINER_API_KEY PORTAINER_STACK_ID PORTAINER_ENDPOINT_ID; do
if [ -z "$(eval "printf '%s' \"\${$required:-}\"")" ]; then
echo "Missing required secret: $required" >&2
exit 1
fi
done
CURL_ARGS=(--fail --show-error --silent --retry 3 --retry-all-errors)
if [ "${PORTAINER_INSECURE_TLS:-false}" = "true" ]; then
CURL_ARGS+=(--insecure)
fi
API_BASE="${PORTAINER_URL%/}/api"
ENDPOINT_JSON="$(
curl "${CURL_ARGS[@]}" \
--header "X-API-Key: ${PORTAINER_API_KEY}" \
"${API_BASE}/endpoints/${PORTAINER_ENDPOINT_ID}"
)"
echo "${ENDPOINT_JSON}" | jq -e --arg id "${PORTAINER_ENDPOINT_ID}" \
'((.Id // .id) | tostring) == $id' >/dev/null
STACK_JSON="$(
curl "${CURL_ARGS[@]}" \
--header "X-API-Key: ${PORTAINER_API_KEY}" \
"${API_BASE}/stacks/${PORTAINER_STACK_ID}?endpointId=${PORTAINER_ENDPOINT_ID}"
)"
echo "${STACK_JSON}" | jq -e --arg id "${PORTAINER_STACK_ID}" \
'((.Id // .id) | tostring) == $id' >/dev/null
echo "Portainer preflight checks passed."
- name: Trigger Portainer stack redeploy
env:
PORTAINER_URL: ${{ secrets.PORTAINER_URL }}
PORTAINER_API_KEY: ${{ secrets.PORTAINER_API_KEY }}
PORTAINER_STACK_ID: ${{ secrets.PORTAINER_STACK_ID }}
PORTAINER_ENDPOINT_ID: ${{ secrets.PORTAINER_ENDPOINT_ID }}
PORTAINER_INSECURE_TLS: ${{ secrets.PORTAINER_INSECURE_TLS }}
run: |
set -eu
for required in PORTAINER_URL PORTAINER_API_KEY PORTAINER_STACK_ID PORTAINER_ENDPOINT_ID; do
if [ -z "$(eval "printf '%s' \"\${$required:-}\"")" ]; then
echo "Missing required secret: $required" >&2
exit 1
fi
done
CURL_ARGS=(--fail --show-error --silent --retry 3 --retry-all-errors)
if [ "${PORTAINER_INSECURE_TLS:-false}" = "true" ]; then
CURL_ARGS+=(--insecure)
fi
STACK_BASE="${PORTAINER_URL%/}/api/stacks/${PORTAINER_STACK_ID}"
QUERY="endpointId=${PORTAINER_ENDPOINT_ID}"
# Git-based stacks can be redeployed directly.
if curl "${CURL_ARGS[@]}" \
--header "X-API-Key: ${PORTAINER_API_KEY}" \
--request POST \
"${STACK_BASE}/git/redeploy?${QUERY}" >/dev/null; then
echo "Portainer deploy: git stack redeploy triggered."
exit 0
fi
# Non-git stacks: fetch current stack file and redeploy with pullImage=true.
STACK_FILE_CONTENT="$(
curl "${CURL_ARGS[@]}" \
--header "X-API-Key: ${PORTAINER_API_KEY}" \
"${STACK_BASE}/file" \
| jq -r '.StackFileContent'
)"
PAYLOAD_LOWER="$(jq -cn --arg stackFileContent "${STACK_FILE_CONTENT}" \
'{stackFileContent: $stackFileContent, prune: false, pullImage: true}')"
if curl "${CURL_ARGS[@]}" \
--header "X-API-Key: ${PORTAINER_API_KEY}" \
--header "Content-Type: application/json" \
--request PUT \
--data "${PAYLOAD_LOWER}" \
"${STACK_BASE}?${QUERY}" >/dev/null; then
echo "Portainer deploy: stack updated with lower-camel payload."
exit 0
fi
PAYLOAD_UPPER="$(jq -cn --arg StackFileContent "${STACK_FILE_CONTENT}" \
'{StackFileContent: $StackFileContent, Prune: false, PullImage: true}')"
curl "${CURL_ARGS[@]}" \
--header "X-API-Key: ${PORTAINER_API_KEY}" \
--header "Content-Type: application/json" \
--request PUT \
--data "${PAYLOAD_UPPER}" \
"${STACK_BASE}?${QUERY}" >/dev/null
echo "Portainer deploy: stack updated with upper-camel payload."

3
HELLOWORLD.md Normal file
View File

@@ -0,0 +1,3 @@
Hello developers!
Welcome to the trips project. We are glad you are here and look forward to your contributions.

View File

@@ -79,7 +79,7 @@ The container stores SQLite data at `/data/trips.db` (mounted as the `trips-data
Workflows live in `.gitea/workflows`:
- `pr-checks.yml`: runs lint, tests, and Docker build on pull requests.
- `main-image.yml`: builds and pushes a Docker image on push to `main`.
- `main-image.yml`: builds and pushes a Docker image on push to `main`, then calls the Portainer API to redeploy. It can also be run manually from the Actions UI.
### Registry secrets for image publish
@@ -87,10 +87,62 @@ Configure these repository secrets in Gitea:
- `REGISTRY_USERNAME`
- `REGISTRY_PASSWORD`
- `PORTAINER_URL` (for example, `https://portainer.example.com`)
- `PORTAINER_API_KEY` (Portainer API key for a user with access to the stack)
- `PORTAINER_STACK_ID` (numeric stack ID in Portainer)
- `PORTAINER_ENDPOINT_ID` (numeric environment/endpoint ID in Portainer)
- `PORTAINER_INSECURE_TLS` (optional: set to `true` only if Portainer uses self-signed TLS)
By default, the publish workflow pushes to:
- `git.campbellwireless.net/<owner>/<repo>:latest`
- `git.campbellwireless.net/<owner>/<repo>:<short-sha>`
- `registry.campbellwireless.net/<owner>/<repo>:latest`
- `registry.campbellwireless.net/<owner>/<repo>:<short-sha>`
If your registry host differs, edit `REGISTRY_HOST` in `.gitea/workflows/main-image.yml`.
## Deploy with Portainer API
The `main-image.yml` workflow now calls the Portainer API after pushing `latest`.
In Portainer, create/update your stack to use a published image (not `build`), for example:
```yaml
services:
trips:
image: registry.campbellwireless.net/<owner>/<repo>:latest
container_name: trips
restart: unless-stopped
ports:
- '3000:3000'
volumes:
- trips-data:/data
env_file:
- .env
environment:
NODE_ENV: production
DATABASE_URL: file:/data/trips.db
PORT: '3000'
volumes:
trips-data:
```
Then in Portainer:
1. Create an API key from your user profile (`My account` -> `API keys`).
2. Open the stack details page and note the stack ID.
3. Open `Environments` and note the endpoint/environment ID where the stack runs.
4. Save these values in your Gitea repository secrets:
- `PORTAINER_URL`
- `PORTAINER_API_KEY`
- `PORTAINER_STACK_ID`
- `PORTAINER_ENDPOINT_ID`
- optional `PORTAINER_INSECURE_TLS=true`
Flow on each push to `main` (or manual run of `main-image.yml`):
1. Build image.
2. Push `:latest` and `:<short-sha>`.
3. Run Portainer preflight checks (auth + stack/endpoint IDs).
4. Call Portainer API.
5. Portainer redeploys the stack and pulls the updated image.