2 Commits

Author SHA1 Message Date
hermes
d7aa7c2013 docs(ci): rewrite setup guide for Gitea Webhook deploy trigger
The previous doc assumed a Gitea Actions workflow (which requires a
self-hosted act_runner that we don't have, hence the 'No matching
online runner with label: ubuntu-latest' error).

The actual setup is much simpler: a Gitea Webhook pointing at the
deploy endpoint. No runner, no workflow file. Setup is one-time in
Settings -> Webhooks -> Add Webhook.

Documents:
- The push -> webhook -> deploy -> EasyPanel flow
- Exact payload URL, method, content type, events
- Test Delivery verification step
- Why Gitea Actions doesn't work without act_runner
- Troubleshooting: push not triggering, build failure modes
  (nixpacks 'No start command', node 20 vs 22), and a curl recipe
  for redeploying without a code change.
2026-06-09 14:54:14 +07:00
hermes
4820289252 ci: remove Gitea Actions workflows (rely on EasyPanel auto-deploy)
The workflows were never able to run — Gitea Actions has no managed
runners the way GitHub does, and 'ubuntu-latest' isn't a label this
self-hosted Gitea instance can match. Every push to main showed the
job as 'No matching online runner with label: ubuntu-latest' in
the Actions tab.

EasyPanel already watches the 'main' branch and rebuilds on push
(Dockerfile-based, git source). The CI step was duplicative and
produced noise in the Actions tab without running anything.

If a real CI step is needed later (lint, build artifact, test), a
Gitea act_runner has to be installed on a server, registered with
labels like 'self-hosted:host:linux', and the workflow has to use
those labels. Skipping that for now.

Files removed:
  .gitea/workflows/build-and-deploy.yml
  .gitea/workflows/lint.yml
  .gitea/  (empty after removal)
2026-06-09 14:36:47 +07:00
3 changed files with 85 additions and 194 deletions

View File

@@ -1,97 +0,0 @@
name: Build & Deploy to EasyPanel
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci --no-audit --no-fund
- name: Build Astro site
run: npm run build
- name: Trigger EasyPanel redeploy
if: success()
env:
EASYPANEL_TOKEN: ${{ secrets.EASYPANEL_TOKEN }}
EASYPANEL_PROJECT_NAME: ${{ secrets.EASYPANEL_PROJECT_NAME }}
EASYPANEL_SERVICE_NAME: ${{ secrets.EASYPANEL_SERVICE_NAME }}
run: |
# EasyPanel tRPC endpoint for app service redeploy
EASYPANEL_API="https://panelwebsite.moreminimore.com/api"
DEPLOY_URL="${EASYPANEL_API}/trpc/services.app.deployService"
# Guard: required secrets
if [ -z "$EASYPANEL_TOKEN" ] || [ -z "$EASYPANEL_PROJECT_NAME" ] || [ -z "$EASYPANEL_SERVICE_NAME" ]; then
echo "::warning::One or more required secrets are empty:"
[ -z "$EASYPANEL_TOKEN" ] && echo " - EASYPANEL_TOKEN"
[ -z "$EASYPANEL_PROJECT_NAME" ] && echo " - EASYPANEL_PROJECT_NAME"
[ -z "$EASYPANEL_SERVICE_NAME" ] && echo " - EASYPANEL_SERVICE_NAME"
echo "Skipping deploy trigger. Set these in repo settings to enable auto-deploy."
exit 0
fi
# tRPC mutation payload: {"json":{"projectName":"...","serviceName":"..."}}
PAYLOAD=$(jq -nc \
--arg pj "$EASYPANEL_PROJECT_NAME" \
--arg sv "$EASYPANEL_SERVICE_NAME" \
'{json:{projectName:$pj, serviceName:$sv}}')
echo "Triggering EasyPanel redeploy"
echo " Endpoint: $DEPLOY_URL"
echo " Project: $EASYPANEL_PROJECT_NAME"
echo " Service: $EASYPANEL_SERVICE_NAME"
echo " Payload: $PAYLOAD"
echo ""
HTTP_CODE=$(curl -sS -o /tmp/easypanel-response.json -w "%{http_code}" \
-X POST "$DEPLOY_URL" \
-H "Authorization: Bearer *** \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
RESPONSE=$(cat /tmp/easypanel-response.json)
echo " HTTP $HTTP_CODE"
echo " Response: $RESPONSE"
# 2xx = success
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo ""
echo "EasyPanel redeploy triggered successfully"
exit 0
fi
# 4xx with explicit field errors = workflow is right, payload is wrong
if [ "$HTTP_CODE" -ge 400 ] && [ "$HTTP_CODE" -lt 500 ]; then
echo "::error::EasyPanel rejected the deploy request (HTTP $HTTP_CODE). See response above."
echo "::error::Likely the payload shape does not match what the panel expects."
echo "::error::Check https://panelwebsite.moreminimore.com/api for the procedure spec."
exit 1
fi
# 5xx = server error
echo "::error::EasyPanel API error (HTTP $HTTP_CODE). Response: $RESPONSE"
exit 1
- name: Upload build artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 7

View File

@@ -1,22 +0,0 @@
name: Lint & Test
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci --no-audit --no-fund
- name: Build (catches syntax + missing-image errors before deploy)
run: npm run build

View File

@@ -1,99 +1,109 @@
# CI/CD Setup — EasyPanel Deploy # CI/CD Setup — Deploy via Gitea Webhook
Push to `main` triggers `build-and-deploy.yml`: The site auto-rebuilds on every push to `main` via a Gitea **webhook**
1. Builds the Astro static site into `dist/` (no Actions runner, no `.gitea/workflows/`, no act_runner required).
2. Uploads `dist/` as a 7-day artifact
3. Calls EasyPanel tRPC endpoint to trigger a redeploy
## Required Gitea repo secrets ## How it works
Go to **Settings → Actions → Secrets** and add three secrets: ```
git push origin main
Gitea Webhook (Gitea built-in, not Gitea Actions)
│ POST Content-Type: application/json
http://110.164.146.47:3000/api/deploy/<token>
│ HTTP 200 "Deploying..."
EasyPanel pulls repo, builds with Dockerfile, redeploys
```
| Name | Value (for this site) | Where to get it | ## One-time webhook setup
|---|---|---|
| `EASYPANEL_TOKEN` | `cmq61xwrv000407qn9e2hhfuw` | EasyPanel → profile/settings → API tokens |
| `EASYPANEL_PROJECT_NAME` | `customerwebsite` | EasyPanel → project name in the dashboard |
| `EASYPANEL_SERVICE_NAME` | `dealplustech-astro` | EasyPanel → service name inside the project |
> ⚠️ Project name ≠ repo name. The site lives in the `customerwebsite` In `https://git.moreminimore.com/kunthawat/dealplustech-astroreal/settings/hooks`:
> project on the panel; the repo is `dealplustech-astroreal`.
If any of these are empty, the workflow logs a warning and skips the deploy 1. Click **Add Webhook → Gitea**
trigger. The build still runs and the artifact is still uploaded. 2. Fill in:
- **Payload URL**: `http://110.164.146.47:3000/api/deploy/772d2c3a4a7d8671657c947c059bc1cdc64bd816efb7fbe2`
- **HTTP Method**: `POST`
- **Content Type**: `application/json`
- **Events**: **Push events**
- **Active**: ✓
- **Branch filter**: leave empty (or `main` to restrict)
3. Click **Add Webhook**
4. Test: click the webhook row → **Test Delivery****Push events** → confirm
**Last Response** is HTTP 200.
Done. From now on every push to main redeploys automatically.
## EasyPanel service requirements ## EasyPanel service requirements
The service on the panel side must be: The service on the panel side (`project=customerwebsite`,
`service=dealplustech-astro`) must be:
- **Type: `app`** - **Type: `app`**
- **Source: Git**, pointing at this repo (`kunthawat/dealplustech-astroreal`) - **Source: Git**, branch `main`
on branch **`main`** (not `source-code`). - **Build type: `dockerfile`**, file `Dockerfile` at repo root
- **Build type: `dockerfile`**, **file: `Dockerfile`** at the repo root.
> The workflow used to trigger nixpacks builds, but nixpacks expects a
> `start` script in `package.json` and an Astro static site doesn't
> have one. Switching to a Dockerfile + nginx fixes that.
- **Port: `80`** - **Port: `80`**
If you need a different service type later, swap the endpoint in The Dockerfile is two-stage:
`.gitea/workflows/build-and-deploy.yml` to the matching procedure:
- `services.app.deployService` (Dockerfile / app)
- `services.box.rebuildDockerImage` (low-level)
- `services.compose.deployService` (docker-compose)
## Why Dockerfile and not nixpacks 1. `node:22-alpine` — runs `npm ci` and `npm run build` to produce
`dist/`
2. `nginx:1.27-alpine` — copies `dist/` to nginx's web root and serves
it (with gzip, security headers, 1-year cache for hashed assets,
and a `try_files` fallback for Astro's UTF-8 slugs)
Astro builds to static files in `dist/` — there is no Node server to run. ## Why not Gitea Actions?
Nixpacks tries to find a `start` command and fails. The Dockerfile in
this repo:
1. **Stage 1** (`node:20-alpine`): runs `npm ci` then `npm run build` to Gitea Actions does **not** ship with managed runners (unlike GitHub
produce `dist/`. Actions' `ubuntu-latest`). Without a self-hosted `act_runner` registered
2. **Stage 2** (`nginx:1.27-alpine`): copies `dist/` to nginx's web root with matching labels, any workflow run will fail with
and serves it. **"No matching online runner with label: ubuntu-latest"**.
`nginx.conf` ships gzip, security headers, 1-year cache for hashed The Gitea Webhook mechanism is built into Gitea itself — no runner
assets, and a `try_files` fallback for client-side routes (Astro's required. It fires the same events but POSTs to a URL you control,
file-based routing produces UTF-8 slugs). which is the simplest possible deploy trigger.
## Verifying the trigger payload If a real CI step is ever needed (lint, test, build artifact), install
`act_runner` on a box, register it, and write a workflow using
`runs-on: self-hosted`. See `act_runner` docs for setup.
If the deploy runs but the panel rejects it, the response in the workflow ## Troubleshooting
log will show the exact `zodErrors` field telling you which field name
or shape is wrong. Update the `PAYLOAD` JSON in the workflow accordingly.
To test the payload shape from your local machine: ### Push happened, but site didn't update
1. Open the webhook row in Settings → Webhooks
2. Open **Recent Deliveries**
3. Check the most recent one:
- Status 200 → deploy endpoint received it, check EasyPanel logs
- Status != 200 → check the response body
- **No recent delivery** → webhook is not bound to this branch/event
or the push target branch doesn't match the filter
### Build fails inside EasyPanel
1. EasyPanel UI → service `dealplustech-astro` → Logs tab
2. Look for the `nixpacks` or `docker build` step
3. Common failures:
- **"No start command could be found"**: you're on `nixpacks`. Switch
build type to `dockerfile` and point at `Dockerfile` at repo root.
- **"Node.js v20.x is not supported by Astro"**: the Dockerfile is
using `node:20-alpine`. It should be `node:22-alpine` (Astro 6
requires `>=22.12.0`).
- **"Failed to sync changes"** (HTTP 500 from the deploy endpoint):
usually a panel-side state issue. Retry, or open the service in
the panel UI and check container state.
### Want to redeploy without a code change
The webhook URL itself is idempotent. You can hit it directly with:
```bash ```bash
curl -sS -X POST \ curl -X POST \
"https://panelwebsite.moreminimore.com/api/trpc/services.app.deployService" \ "http://110.164.146.47:3000/api/deploy/772d2c3a4a7d8671657c947c059bc1cdc64bd816efb7fbe2"
-H "Authorization: Bearer *** -H "Content-Type: application/json" \
-d '{"json":{"projectName":"customerwebsite","serviceName":"dealplustech-astro"}}'
``` ```
A 2xx response = the panel accepted the trigger. The service will start A `200` response with body `Deploying...` means the panel accepted the
rebuilding/redploying in the EasyPanel dashboard. trigger. The actual rebuild happens in the background — check EasyPanel
UI for the progress.
## Common failure: "No start command could be found" (nixpacks)
Astro builds to static files in `dist/` — there is no Node server to run.
Nixpacks tries to find a `start` command and fails. The Dockerfile in
this repo handles this with a two-stage build (node:22-alpine build +
nginx:1.27-alpine serve).
## Common failure: "Node.js v20.x is not supported by Astro"
Astro 6 requires Node `>=22.12.0`. The Dockerfile uses `node:22-alpine`.
If you see this error, the panel is probably reading a stale `node:20`
cached image. Push an empty commit to force a rebuild, or clear the
panel's image cache from the EasyPanel UI.
## Common failure: "Failed to sync changes" (HTTP 500)
This is a **server-side** error, not a payload problem. It usually means:
- The EasyPanel service is currently in a state that can't be redeployed
(e.g. the previous container is stuck, or a build is in progress).
- The Docker daemon on the panel host is busy.
Try again in a few seconds. If it persists, open the EasyPanel dashboard
and check the service's container state.