feat(celld-deploy): add reusable Cloudflare Worker & Durable Objects celld deployment action
Update README Actions Table / update-readme (push) Successful in 6s
Update README Actions Table / update-readme (push) Successful in 6s
This commit is contained in:
@@ -8,6 +8,7 @@ This repository contains reusable composite actions for CI/CD workflows across o
|
|||||||
|
|
||||||
| Action | Description | Reference |
|
| Action | Description | Reference |
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- | :--- |
|
||||||
|
| [Deploy Cloudflare Worker to celld](./celld-deploy) | Bundles a Cloudflare Worker / Durable Objects project (wrangler.json) and deploys it to a self-hosted celld fleet backed by RustFS S3 and TrueNAS Docker Ingress. | `rodydavis/shared-actions/celld-deploy@main` |
|
||||||
| [Enforce Flutter Version Constraint](./enforce-flutter-version) | Scans all pubspec.yaml files to ensure they use the required Flutter SDK constraint. | `rodydavis/shared-actions/enforce-flutter-version@main` |
|
| [Enforce Flutter Version Constraint](./enforce-flutter-version) | Scans all pubspec.yaml files to ensure they use the required Flutter SDK constraint. | `rodydavis/shared-actions/enforce-flutter-version@main` |
|
||||||
| [Flutter Build Android](./flutter-build-android) | Configures Android keystore signing, builds release AAB/APK artifacts, and optionally deploys to Google Play Console and Shorebird. | `rodydavis/shared-actions/flutter-build-android@main` |
|
| [Flutter Build Android](./flutter-build-android) | Configures Android keystore signing, builds release AAB/APK artifacts, and optionally deploys to Google Play Console and Shorebird. | `rodydavis/shared-actions/flutter-build-android@main` |
|
||||||
| [Flutter Build iOS](./flutter-build-ios) | Installs Apple signing certificates and provisioning profiles in an isolated keychain, builds an iOS release IPA, and optionally uploads to TestFlight and Shorebird. | `rodydavis/shared-actions/flutter-build-ios@main` |
|
| [Flutter Build iOS](./flutter-build-ios) | Installs Apple signing certificates and provisioning profiles in an isolated keychain, builds an iOS release IPA, and optionally uploads to TestFlight and Shorebird. | `rodydavis/shared-actions/flutter-build-ios@main` |
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# Deploy Cloudflare Worker to `celld` Action
|
||||||
|
|
||||||
|
Custom Gitea Action for bundling and deploying Cloudflare Workers, Durable Objects, D1, KV, Queues, Workflows, R2, and Static Assets (`wrangler.json`) to a self-hosted [`denoland/celld`](https://github.com/denoland/celld) fleet on TrueNAS SCALE backed by RustFS S3 (`s3.rodydavis.dev`).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Zero-Docker-Build Hot Deploys**: Bundles TypeScript/JS Worker projects via `esbuild` and publishes directly to `s3://celld-<repo-name>`. Running `celld` daemons on TrueNAS adopt new deployments in place with zero container restarts.
|
||||||
|
- **Auto-Bucket Provisioning**: Automatically creates the S3 fleet bucket on RustFS (`s3.rodydavis.dev`) on first deploy.
|
||||||
|
- **Sub-Millisecond Internal S3 Path**: Configures the TrueNAS `celld` daemon to communicate directly with `http://minio-s3:9000` over the internal `apps-net` Docker bridge.
|
||||||
|
- **Automated D1 Migrations**: Optionally applies `.sql` migrations from `./migrations` via `celld d1 migrations apply`.
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
| Name | Description | Default | Required |
|
||||||
|
| :--- | :--- | :--- | :---: |
|
||||||
|
| `ssh_key` | Private SSH key for authenticating to TrueNAS SCALE | — | **Yes** |
|
||||||
|
| `s3_access_key` | S3 Access Key ID (`secrets.CELLD_S3_ACCESS_KEY`) | — | **Yes** |
|
||||||
|
| `s3_secret_key` | S3 Secret Key (`secrets.CELLD_S3_SECRET_KEY`) | — | **Yes** |
|
||||||
|
| `working_directory` | Path to Wrangler project directory | `.` | No |
|
||||||
|
| `app_name` | Application subpath under `/mnt/jupiter/apps/<owner>/<repo>` | `<owner>/<repo>` | No |
|
||||||
|
| `bucket` | S3 bucket name for the `celld` fleet | `celld-<repo-name>` | No |
|
||||||
|
| `domain` | Target domain(s) for Traefik router (comma-separated) | `<repo-name>.rodydavis.dev` | No |
|
||||||
|
| `d1_database` | Optional D1 database name to run `celld d1 migrations apply` | `""` | No |
|
||||||
|
| `env_file_content` | Optional additional `.env` variables for the TrueNAS container | `""` | No |
|
||||||
|
| `idle_evict_s` | Seconds before idle Durable Objects hibernate (`CELLD_IDLE_EVICT_S`) | `60` | No |
|
||||||
|
| `ltx_retention_secs` | Seconds to retain old LTX epochs before GC (`CELLD_LTX_RETENTION_SECS`) | `86400` | No |
|
||||||
|
| `deploy_poll_s` | Seconds between bucket deployment checks (`CELLD_DEPLOY_POLL_S`) | `5` | No |
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Deploy Worker to celld
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Deploy to TrueNAS celld Fleet
|
||||||
|
uses: https://git.rodydavis.dev/rodydavis/shared-actions/celld-deploy@main
|
||||||
|
with:
|
||||||
|
ssh_key: ${{ secrets.TRUENAS_SSH_KEY }}
|
||||||
|
s3_access_key: ${{ secrets.CELLD_S3_ACCESS_KEY }}
|
||||||
|
s3_secret_key: ${{ secrets.CELLD_S3_SECRET_KEY }}
|
||||||
|
domain: my-worker.rodydavis.dev
|
||||||
|
# Optional if using D1 SQLite migrations in ./migrations:
|
||||||
|
# d1_database: main-db
|
||||||
|
```
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
name: 'Deploy Cloudflare Worker to celld'
|
||||||
|
description: 'Bundles a Cloudflare Worker / Durable Objects project (wrangler.json) and deploys it to a self-hosted celld fleet backed by RustFS S3 and TrueNAS Docker Ingress.'
|
||||||
|
inputs:
|
||||||
|
ssh_key:
|
||||||
|
description: 'Private SSH key for authenticating to TrueNAS SCALE'
|
||||||
|
required: true
|
||||||
|
s3_access_key:
|
||||||
|
description: 'S3 Access Key ID for the celld fleet bucket (defaults to CELLD_S3_ACCESS_KEY)'
|
||||||
|
required: true
|
||||||
|
s3_secret_key:
|
||||||
|
description: 'S3 Secret Access Key for the celld fleet bucket (defaults to CELLD_S3_SECRET_KEY)'
|
||||||
|
required: true
|
||||||
|
working_directory:
|
||||||
|
description: 'Path to the Wrangler project directory containing wrangler.json / wrangler.toml'
|
||||||
|
required: false
|
||||||
|
default: '.'
|
||||||
|
app_name:
|
||||||
|
description: 'Application subpath under /mnt/jupiter/apps/<owner>/<repo> (defaults to <owner>/<repo>)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
bucket:
|
||||||
|
description: 'S3 bucket name for the celld fleet (defaults to celld-<repo-name>)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
domain:
|
||||||
|
description: 'Target domain(s) for Traefik router, comma-separated (defaults to <repo-name>.rodydavis.dev)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
s3_endpoint:
|
||||||
|
description: 'External S3 endpoint used by the CI runner during celld deploy'
|
||||||
|
required: false
|
||||||
|
default: 'https://s3.rodydavis.dev'
|
||||||
|
internal_s3_endpoint:
|
||||||
|
description: 'Internal S3 endpoint used by the celld container on TrueNAS over apps-net'
|
||||||
|
required: false
|
||||||
|
default: 'http://minio-s3:9000'
|
||||||
|
s3_region:
|
||||||
|
description: 'S3 bucket region'
|
||||||
|
required: false
|
||||||
|
default: 'us-east-1'
|
||||||
|
d1_database:
|
||||||
|
description: 'Optional D1 database name to automatically apply migrations for (celld d1 migrations apply)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
env_file_content:
|
||||||
|
description: 'Optional additional environment variables to append to the TrueNAS .env file'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
idle_evict_s:
|
||||||
|
description: 'Seconds before idle Durable Object cells hibernate from memory (CELLD_IDLE_EVICT_S)'
|
||||||
|
required: false
|
||||||
|
default: '60'
|
||||||
|
ltx_retention_secs:
|
||||||
|
description: 'Seconds to retain superseded LTX SQLite epoch logs before garbage collection (CELLD_LTX_RETENTION_SECS)'
|
||||||
|
required: false
|
||||||
|
default: '86400'
|
||||||
|
deploy_poll_s:
|
||||||
|
description: 'Seconds between bucket deployment pointer checks on the celld node (CELLD_DEPLOY_POLL_S)'
|
||||||
|
required: false
|
||||||
|
default: '5'
|
||||||
|
host:
|
||||||
|
description: 'TrueNAS SSH host IP or domain'
|
||||||
|
required: false
|
||||||
|
default: '10.0.0.218'
|
||||||
|
user:
|
||||||
|
description: 'TrueNAS SSH user'
|
||||||
|
required: false
|
||||||
|
default: 'root'
|
||||||
|
network:
|
||||||
|
description: 'Docker external network name for Traefik ingress'
|
||||||
|
required: false
|
||||||
|
default: 'apps-net'
|
||||||
|
traefik_entrypoints:
|
||||||
|
description: 'Traefik entrypoint (e.g. web)'
|
||||||
|
required: false
|
||||||
|
default: 'web'
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: 'composite'
|
||||||
|
steps:
|
||||||
|
- name: Deploy Worker Bundle and Ensure celld Fleet on TrueNAS
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
INPUT_SSH_KEY: ${{ inputs.ssh_key }}
|
||||||
|
INPUT_S3_ACCESS_KEY: ${{ inputs.s3_access_key }}
|
||||||
|
INPUT_S3_SECRET_KEY: ${{ inputs.s3_secret_key }}
|
||||||
|
INPUT_WORKING_DIRECTORY: ${{ inputs.working_directory }}
|
||||||
|
INPUT_APP_NAME: ${{ inputs.app_name }}
|
||||||
|
INPUT_BUCKET: ${{ inputs.bucket }}
|
||||||
|
INPUT_DOMAIN: ${{ inputs.domain }}
|
||||||
|
INPUT_S3_ENDPOINT: ${{ inputs.s3_endpoint }}
|
||||||
|
INPUT_INTERNAL_S3_ENDPOINT: ${{ inputs.internal_s3_endpoint }}
|
||||||
|
INPUT_S3_REGION: ${{ inputs.s3_region }}
|
||||||
|
INPUT_D1_DATABASE: ${{ inputs.d1_database }}
|
||||||
|
INPUT_ENV_CONTENT: ${{ inputs.env_file_content }}
|
||||||
|
INPUT_IDLE_EVICT_S: ${{ inputs.idle_evict_s }}
|
||||||
|
INPUT_LTX_RETENTION_SECS: ${{ inputs.ltx_retention_secs }}
|
||||||
|
INPUT_DEPLOY_POLL_S: ${{ inputs.deploy_poll_s }}
|
||||||
|
INPUT_HOST: ${{ inputs.host }}
|
||||||
|
INPUT_USER: ${{ inputs.user }}
|
||||||
|
INPUT_NETWORK: ${{ inputs.network }}
|
||||||
|
INPUT_ENTRYPOINTS: ${{ inputs.traefik_entrypoints }}
|
||||||
|
REPO_FULL_NAME: ${{ github.repository }}
|
||||||
|
REPO_OWNER: ${{ github.repository_owner }}
|
||||||
|
REPO_NAME: ${{ github.event.repository.name }}
|
||||||
|
run: |
|
||||||
|
bash "${{ github.action_path }}/deploy.sh"
|
||||||
Executable
+225
@@ -0,0 +1,225 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "🚀 Starting celld Worker & Durable Objects Deployment..."
|
||||||
|
|
||||||
|
# 1. Resolve repository, owner, and application directory
|
||||||
|
FULL_REPO="${REPO_FULL_NAME:-$GITHUB_REPOSITORY}"
|
||||||
|
OWNER="${REPO_OWNER:-$(dirname "$FULL_REPO")}"
|
||||||
|
if [ -z "${REPO_NAME:-}" ]; then
|
||||||
|
REPO_NAME=$(basename "$FULL_REPO")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${INPUT_APP_NAME:-}" ]; then
|
||||||
|
case "$INPUT_APP_NAME" in
|
||||||
|
*/*) APP_DIR_NAME="$INPUT_APP_NAME" ;;
|
||||||
|
*) APP_DIR_NAME="${OWNER}/${INPUT_APP_NAME}" ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
APP_DIR_NAME="$FULL_REPO"
|
||||||
|
fi
|
||||||
|
|
||||||
|
CLEAN_APP=$(basename "$APP_DIR_NAME" | tr '_' '-' | tr '[:upper:]' '[:lower:]')
|
||||||
|
ROUTER_NAME=$(echo "$CLEAN_APP" | tr -cd '[:alnum:]')
|
||||||
|
|
||||||
|
# 2. Resolve S3 bucket name
|
||||||
|
RAW_BUCKET="${INPUT_BUCKET:-celld-${CLEAN_APP}}"
|
||||||
|
BUCKET="${RAW_BUCKET#s3://}"
|
||||||
|
BUCKET=$(echo "$BUCKET" | tr '_' '-' | tr '[:upper:]' '[:lower:]')
|
||||||
|
|
||||||
|
S3_ENDPOINT="${INPUT_S3_ENDPOINT:-https://s3.rodydavis.dev}"
|
||||||
|
S3_ENDPOINT="${S3_ENDPOINT%/}"
|
||||||
|
INTERNAL_S3_ENDPOINT="${INPUT_INTERNAL_S3_ENDPOINT:-http://minio-s3:9000}"
|
||||||
|
INTERNAL_S3_ENDPOINT="${INTERNAL_S3_ENDPOINT%/}"
|
||||||
|
S3_REGION="${INPUT_S3_REGION:-us-east-1}"
|
||||||
|
|
||||||
|
# 3. Resolve domain(s) and build Traefik Host() rule
|
||||||
|
RAW_DOMAINS="${INPUT_DOMAIN:-${CLEAN_APP}.rodydavis.dev}"
|
||||||
|
IFS=',' read -ra DOMAIN_ARR <<< "$RAW_DOMAINS"
|
||||||
|
TRAEFIK_RULE=""
|
||||||
|
PRIMARY_DOMAIN=""
|
||||||
|
for d in "${DOMAIN_ARR[@]}"; do
|
||||||
|
d_clean=$(echo "$d" | xargs | sed -E 's#^https?://##' | sed 's#/.*$##')
|
||||||
|
if [ -z "$d_clean" ]; then continue; fi
|
||||||
|
if [ -z "$PRIMARY_DOMAIN" ]; then PRIMARY_DOMAIN="$d_clean"; fi
|
||||||
|
if [ -z "$TRAEFIK_RULE" ]; then
|
||||||
|
TRAEFIK_RULE="Host(\`${d_clean}\`)"
|
||||||
|
else
|
||||||
|
TRAEFIK_RULE="${TRAEFIK_RULE} || Host(\`${d_clean}\`)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "📦 Application: ${APP_DIR_NAME}"
|
||||||
|
echo "🪣 Fleet S3 Bucket: s3://${BUCKET} (${S3_ENDPOINT})"
|
||||||
|
echo "🌐 Routing Rule: ${TRAEFIK_RULE}"
|
||||||
|
|
||||||
|
# 4. Ensure celld and esbuild binaries are on PATH
|
||||||
|
if ! command -v celld >/dev/null 2>&1; then
|
||||||
|
echo "⚙️ Installing celld CLI..."
|
||||||
|
if command -v docker >/dev/null 2>&1 && docker image inspect ghcr.io/denoland/celld:latest >/dev/null 2>&1; then
|
||||||
|
CID=$(docker create ghcr.io/denoland/celld:latest)
|
||||||
|
docker cp "${CID}:/usr/local/bin/celld" /usr/local/bin/celld
|
||||||
|
docker rm -f "$CID" >/dev/null
|
||||||
|
chmod +x /usr/local/bin/celld
|
||||||
|
else
|
||||||
|
curl -fsSL https://celld.dev/install.sh | sh
|
||||||
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "✅ Using $(celld --version)"
|
||||||
|
|
||||||
|
WORKDIR="${INPUT_WORKING_DIRECTORY:-.}"
|
||||||
|
cd "$WORKDIR"
|
||||||
|
|
||||||
|
if [ -f "./node_modules/.bin/esbuild" ]; then
|
||||||
|
export PATH="$(pwd)/node_modules/.bin:$PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v esbuild >/dev/null 2>&1; then
|
||||||
|
echo "⚙️ Installing esbuild..."
|
||||||
|
npm install -g esbuild --no-audit --no-fund >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
echo "✅ Using esbuild $(esbuild --version)"
|
||||||
|
|
||||||
|
# 5. Export AWS credentials for S3 and ensure fleet bucket exists
|
||||||
|
export AWS_ACCESS_KEY_ID="$INPUT_S3_ACCESS_KEY"
|
||||||
|
export AWS_SECRET_ACCESS_KEY="$INPUT_S3_SECRET_KEY"
|
||||||
|
export AWS_DEFAULT_REGION="$S3_REGION"
|
||||||
|
|
||||||
|
EMPTY_SHA256="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
--aws-sigv4 "aws:amz:${S3_REGION}:s3" \
|
||||||
|
--user "${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}" \
|
||||||
|
-H "x-amz-content-sha256: ${EMPTY_SHA256}" \
|
||||||
|
-I "${S3_ENDPOINT}/${BUCKET}" || echo "000")
|
||||||
|
|
||||||
|
if [ "$HTTP_STATUS" = "404" ]; then
|
||||||
|
echo "🪣 Creating S3 bucket '${BUCKET}' on ${S3_ENDPOINT}..."
|
||||||
|
CREATE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-X PUT \
|
||||||
|
--aws-sigv4 "aws:amz:${S3_REGION}:s3" \
|
||||||
|
--user "${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}" \
|
||||||
|
-H "x-amz-content-sha256: ${EMPTY_SHA256}" \
|
||||||
|
"${S3_ENDPOINT}/${BUCKET}")
|
||||||
|
if [ "$CREATE_STATUS" != "200" ] && [ "$CREATE_STATUS" != "204" ] && [ "$CREATE_STATUS" != "409" ]; then
|
||||||
|
echo "::error::Failed to create bucket '${BUCKET}' (HTTP ${CREATE_STATUS})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ Created bucket s3://${BUCKET}"
|
||||||
|
elif [ "$HTTP_STATUS" = "200" ]; then
|
||||||
|
echo "✅ Verified existing bucket s3://${BUCKET}"
|
||||||
|
else
|
||||||
|
echo "⚠️ Bucket probe returned HTTP ${HTTP_STATUS}; proceeding with celld deploy..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. Deploy Worker bundle & assets directly to S3 bucket
|
||||||
|
echo "📤 Deploying Worker bundle to s3://${BUCKET}..."
|
||||||
|
celld deploy . \
|
||||||
|
--bucket "s3://${BUCKET}" \
|
||||||
|
--endpoint "${S3_ENDPOINT}" \
|
||||||
|
--region "${S3_REGION}"
|
||||||
|
|
||||||
|
# 7. Ensure celld Fleet Daemon is provisioned and running on TrueNAS
|
||||||
|
HOST="${INPUT_HOST:-10.0.0.218}"
|
||||||
|
USER="${INPUT_USER:-root}"
|
||||||
|
NETWORK="${INPUT_NETWORK:-apps-net}"
|
||||||
|
ENTRYPOINTS="${INPUT_ENTRYPOINTS:-web}"
|
||||||
|
IDLE_EVICT_S="${INPUT_IDLE_EVICT_S:-60}"
|
||||||
|
LTX_RETENTION_SECS="${INPUT_LTX_RETENTION_SECS:-86400}"
|
||||||
|
DEPLOY_POLL_S="${INPUT_DEPLOY_POLL_S:-5}"
|
||||||
|
|
||||||
|
STAGING_DIR=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$STAGING_DIR" ~/.ssh/truenas_deploy_key' EXIT
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "AWS_ACCESS_KEY_ID=${INPUT_S3_ACCESS_KEY}"
|
||||||
|
echo "AWS_SECRET_ACCESS_KEY=${INPUT_S3_SECRET_KEY}"
|
||||||
|
if [ -n "${INPUT_ENV_CONTENT:-}" ]; then
|
||||||
|
printf "%s\n" "$INPUT_ENV_CONTENT"
|
||||||
|
fi
|
||||||
|
} > "${STAGING_DIR}/.env"
|
||||||
|
|
||||||
|
if [ -f "docker-compose.yml" ]; then
|
||||||
|
cp "docker-compose.yml" "${STAGING_DIR}/docker-compose.yml"
|
||||||
|
elif [ -f "docker-compose.yaml" ]; then
|
||||||
|
cp "docker-compose.yaml" "${STAGING_DIR}/docker-compose.yml"
|
||||||
|
else
|
||||||
|
cat <<EOF > "${STAGING_DIR}/docker-compose.yml"
|
||||||
|
services:
|
||||||
|
celld:
|
||||||
|
image: ghcr.io/denoland/celld:latest
|
||||||
|
container_name: ${CLEAN_APP}-celld
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- CELLD_NODE=${CLEAN_APP}-truenas-1
|
||||||
|
- CELLD_WATCH=/var/lib/celld/state
|
||||||
|
- CELLD_IDLE_EVICT_S=${IDLE_EVICT_S}
|
||||||
|
- CELLD_LTX_RETENTION_SECS=${LTX_RETENTION_SECS}
|
||||||
|
- CELLD_DEPLOY_POLL_S=${DEPLOY_POLL_S}
|
||||||
|
volumes:
|
||||||
|
- ./data:/var/lib/celld
|
||||||
|
command:
|
||||||
|
- --bucket=s3://${BUCKET}
|
||||||
|
- --endpoint=${INTERNAL_S3_ENDPOINT}
|
||||||
|
- --region=${S3_REGION}
|
||||||
|
- --listen=0.0.0.0:8080
|
||||||
|
- --internal-listen=127.0.0.1:8081
|
||||||
|
- --advertise=127.0.0.1:8081
|
||||||
|
networks:
|
||||||
|
- ${NETWORK}
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.${ROUTER_NAME}.rule=${TRAEFIK_RULE}"
|
||||||
|
- "traefik.http.routers.${ROUTER_NAME}.entrypoints=${ENTRYPOINTS}"
|
||||||
|
- "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=8080"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
${NETWORK}:
|
||||||
|
external: true
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "$INPUT_SSH_KEY" > ~/.ssh/truenas_deploy_key
|
||||||
|
chmod 600 ~/.ssh/truenas_deploy_key
|
||||||
|
ssh-keyscan -H "$HOST" >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
|
|
||||||
|
REMOTE_DIR="/mnt/jupiter/apps/${APP_DIR_NAME}"
|
||||||
|
ssh -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${USER}@${HOST}" "mkdir -p ${REMOTE_DIR}/data"
|
||||||
|
|
||||||
|
LOCAL_ENV_HASH=$(sha256sum "${STAGING_DIR}/.env" | awk '{print $1}')
|
||||||
|
LOCAL_COMPOSE_HASH=$(sha256sum "${STAGING_DIR}/docker-compose.yml" | awk '{print $1}')
|
||||||
|
|
||||||
|
REMOTE_STATE=$(ssh -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${USER}@${HOST}" "
|
||||||
|
ENV_H=\$(sha256sum ${REMOTE_DIR}/.env 2>/dev/null | awk '{print \$1}' || echo 'none')
|
||||||
|
COMP_H=\$(sha256sum ${REMOTE_DIR}/docker-compose.yml 2>/dev/null | awk '{print \$1}' || echo 'none')
|
||||||
|
RUNNING=\$(docker inspect -f '{{.State.Running}}' ${CLEAN_APP}-celld 2>/dev/null || echo 'false')
|
||||||
|
echo \"\${ENV_H}:\${COMP_H}:\${RUNNING}\"
|
||||||
|
")
|
||||||
|
|
||||||
|
if [ "$REMOTE_STATE" != "${LOCAL_ENV_HASH}:${LOCAL_COMPOSE_HASH}:true" ]; then
|
||||||
|
echo "🚢 Updating TrueNAS celld container stack at ${REMOTE_DIR}..."
|
||||||
|
scp -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${STAGING_DIR}/.env" "${USER}@${HOST}:${REMOTE_DIR}/.env"
|
||||||
|
scp -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${STAGING_DIR}/docker-compose.yml" "${USER}@${HOST}:${REMOTE_DIR}/docker-compose.yml"
|
||||||
|
ssh -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${USER}@${HOST}" "chmod 600 ${REMOTE_DIR}/.env && cd ${REMOTE_DIR} && docker compose up -d --remove-orphans"
|
||||||
|
sleep 3
|
||||||
|
else
|
||||||
|
echo "⚡ TrueNAS container ${CLEAN_APP}-celld is already running with matching config; hot-reloading bundle in place!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 8. Optional D1 SQLite migrations
|
||||||
|
if [ -n "${INPUT_D1_DATABASE:-}" ] && [ -d "migrations" ]; then
|
||||||
|
echo "🗄️ Applying D1 migrations for database '${INPUT_D1_DATABASE}'..."
|
||||||
|
ssh -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${USER}@${HOST}" "rm -rf ${REMOTE_DIR}/data/migrations && mkdir -p ${REMOTE_DIR}/data/migrations"
|
||||||
|
scp -r -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no migrations/. "${USER}@${HOST}:${REMOTE_DIR}/data/migrations/"
|
||||||
|
ssh -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${USER}@${HOST}" "
|
||||||
|
docker exec -w /var/lib/celld ${CLEAN_APP}-celld celld d1 migrations apply '${INPUT_D1_DATABASE}' \
|
||||||
|
--bucket 's3://${BUCKET}' \
|
||||||
|
--endpoint '${INTERNAL_S3_ENDPOINT}' \
|
||||||
|
--region '${S3_REGION}'
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Successfully deployed ${APP_DIR_NAME} to https://${PRIMARY_DOMAIN}"
|
||||||
Reference in New Issue
Block a user