Compare commits
18 Commits
ai-agent/2
...
ci/gitea-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
cbae428e26
|
|||
|
e5c4cbc331
|
|||
|
e28741a4f1
|
|||
|
982c84cc33
|
|||
|
82a242e362
|
|||
|
d464e71654
|
|||
|
6e02e5128a
|
|||
|
02b3d3ca1f
|
|||
|
3455afe396
|
|||
|
5331a05136
|
|||
|
2ff8a71b0c
|
|||
|
dd9d142854
|
|||
|
0e8ac03e07
|
|||
|
8a0094a729
|
|||
|
b7c794db21
|
|||
|
19a62fce8d
|
|||
|
982f0d89be
|
|||
|
0f73be45cb
|
@@ -4,10 +4,9 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY_HOST: registry.campbellwireless.net
|
||||
REGISTRY_HOST: git.campbellwireless.net
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
@@ -51,107 +50,3 @@ 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."
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
Hello and welcome to the Trips project!
|
||||
|
||||
We're glad you're here. This repository is the home for the Trips application and the tools that support it.
|
||||
|
||||
If you're new, start with `README.md` to get the project running, then take a look around `src/` to see how things are organized.
|
||||
|
||||
Happy building!
|
||||
58
README.md
58
README.md
@@ -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`, then calls the Portainer API to redeploy. It can also be run manually from the Actions UI.
|
||||
- `main-image.yml`: builds and pushes a Docker image on push to `main`.
|
||||
|
||||
### Registry secrets for image publish
|
||||
|
||||
@@ -87,62 +87,10 @@ 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:
|
||||
|
||||
- `registry.campbellwireless.net/<owner>/<repo>:latest`
|
||||
- `registry.campbellwireless.net/<owner>/<repo>:<short-sha>`
|
||||
- `git.campbellwireless.net/<owner>/<repo>:latest`
|
||||
- `git.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.
|
||||
|
||||
Reference in New Issue
Block a user