Scaffold hatchet worker service
This commit is contained in:
9
.env.example
Normal file
9
.env.example
Normal file
@@ -0,0 +1,9 @@
|
||||
VAULT_ENABLED=auto
|
||||
VAULT_ADDR=
|
||||
VAULT_TOKEN=
|
||||
VAULT_KV_MOUNT=secret
|
||||
VAULT_SHARED_PATH=
|
||||
VAULT_PROJECT_PATH=
|
||||
|
||||
HATCHET_CLIENT_TOKEN=
|
||||
HATCHET_CLIENT_HOST_PORT=fregat-hatchet-engine:7070
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
.env
|
||||
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM node:22-bookworm-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update -y && apt-get install -y --no-install-recommends curl jq ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY src ./src
|
||||
COPY scripts ./scripts
|
||||
|
||||
CMD ["sh", "-c", ". /app/scripts/load-vault-env.sh && node src/worker.js"]
|
||||
10
README.md
10
README.md
@@ -1,2 +1,10 @@
|
||||
# hatchet-worker
|
||||
# fregat-hatchet-worker
|
||||
|
||||
Separate worker service for Hatchet workflows in the Fregat project.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run start
|
||||
```
|
||||
|
||||
27
package-lock.json
generated
Normal file
27
package-lock.json
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "fregat-hatchet-worker",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "fregat-hatchet-worker",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
|
||||
"integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
package.json
Normal file
13
package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "fregat-hatchet-worker",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node src/worker.js",
|
||||
"dev": "node --watch src/worker.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^17.3.1"
|
||||
}
|
||||
}
|
||||
170
scripts/load-vault-env.sh
Executable file
170
scripts/load-vault-env.sh
Executable file
@@ -0,0 +1,170 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
log() {
|
||||
printf '%s\n' "$*" >&2
|
||||
}
|
||||
|
||||
is_enabled() {
|
||||
case "${1:-}" in
|
||||
1|true|TRUE|yes|YES|on|ON) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
wait_for_http() {
|
||||
name="$1"
|
||||
url="$2"
|
||||
timeout="$3"
|
||||
interval="$4"
|
||||
mode="${5:-strict}"
|
||||
start_ts="$(date +%s)"
|
||||
|
||||
while :; do
|
||||
if [ "$mode" = "connect" ]; then
|
||||
if curl -sS --max-time 3 "$url" >/dev/null 2>&1; then
|
||||
log "$name is reachable at $url."
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
if curl -fsS --max-time 3 "$url" >/dev/null 2>&1; then
|
||||
log "$name is reachable at $url."
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
now_ts="$(date +%s)"
|
||||
if [ $((now_ts - start_ts)) -ge "$timeout" ]; then
|
||||
log "Timeout waiting for $name at $url after ${timeout}s."
|
||||
return 1
|
||||
fi
|
||||
|
||||
sleep "$interval"
|
||||
done
|
||||
}
|
||||
|
||||
wait_for_tcp() {
|
||||
name="$1"
|
||||
host="$2"
|
||||
port="$3"
|
||||
timeout="$4"
|
||||
interval="$5"
|
||||
start_ts="$(date +%s)"
|
||||
|
||||
while :; do
|
||||
if node -e "const net=require('net');const host=process.argv[1];const port=Number(process.argv[2]);const socket=net.connect({host,port});const fail=()=>{socket.destroy();process.exit(1)};socket.setTimeout(2000,fail);socket.on('connect',()=>{socket.end();process.exit(0)});socket.on('error',fail);" "$host" "$port" >/dev/null 2>&1; then
|
||||
log "$name is reachable at ${host}:${port}."
|
||||
return 0
|
||||
fi
|
||||
|
||||
now_ts="$(date +%s)"
|
||||
if [ $((now_ts - start_ts)) -ge "$timeout" ]; then
|
||||
log "Timeout waiting for $name at ${host}:${port} after ${timeout}s."
|
||||
return 1
|
||||
fi
|
||||
|
||||
sleep "$interval"
|
||||
done
|
||||
}
|
||||
|
||||
VAULT_ENABLED="${VAULT_ENABLED:-auto}"
|
||||
if [ "$VAULT_ENABLED" = "false" ] || [ "$VAULT_ENABLED" = "0" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -z "${VAULT_ADDR:-}" ] || [ -z "${VAULT_TOKEN:-}" ]; then
|
||||
if [ "$VAULT_ENABLED" = "true" ] || [ "$VAULT_ENABLED" = "1" ]; then
|
||||
log "Vault bootstrap is required but VAULT_ADDR or VAULT_TOKEN is missing."
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
|
||||
log "Vault bootstrap requires curl and jq."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VAULT_KV_MOUNT="${VAULT_KV_MOUNT:-secret}"
|
||||
VAULT_WAIT_TIMEOUT_SECONDS="${VAULT_WAIT_TIMEOUT_SECONDS:-90}"
|
||||
VAULT_WAIT_INTERVAL_SECONDS="${VAULT_WAIT_INTERVAL_SECONDS:-2}"
|
||||
|
||||
wait_for_http \
|
||||
"Vault" \
|
||||
"${VAULT_ADDR%/}/v1/sys/health?standbyok=true&perfstandbyok=true" \
|
||||
"$VAULT_WAIT_TIMEOUT_SECONDS" \
|
||||
"$VAULT_WAIT_INTERVAL_SECONDS" \
|
||||
"strict"
|
||||
|
||||
load_secret_path() {
|
||||
path="$1"
|
||||
source_name="$2"
|
||||
if [ -z "$path" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
url="${VAULT_ADDR%/}/v1/${VAULT_KV_MOUNT}/data/${path}"
|
||||
response="$(curl -fsS -H "X-Vault-Token: $VAULT_TOKEN" "$url")" || {
|
||||
log "Failed to load Vault path ${VAULT_KV_MOUNT}/${path}."
|
||||
return 1
|
||||
}
|
||||
|
||||
encoded_items="$(printf '%s' "$response" | jq -r '.data.data // {} | to_entries[]? | @base64')"
|
||||
if [ -z "$encoded_items" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
old_ifs="${IFS}"
|
||||
IFS='
|
||||
'
|
||||
for encoded_item in $encoded_items; do
|
||||
key="$(printf '%s' "$encoded_item" | base64 -d | jq -r '.key')"
|
||||
value="$(printf '%s' "$encoded_item" | base64 -d | jq -r '.value | tostring')"
|
||||
export "$key=$value"
|
||||
done
|
||||
IFS="${old_ifs}"
|
||||
|
||||
log "Loaded Vault ${source_name} secrets from ${VAULT_KV_MOUNT}/${path}."
|
||||
}
|
||||
|
||||
load_secret_path "${VAULT_SHARED_PATH:-}" "shared"
|
||||
load_secret_path "${VAULT_PROJECT_PATH:-}" "project"
|
||||
|
||||
if is_enabled "${WAIT_FOR_HATCHET_ENGINE:-0}"; then
|
||||
HATCHET_WAIT_TIMEOUT_SECONDS="${HATCHET_WAIT_TIMEOUT_SECONDS:-120}"
|
||||
HATCHET_WAIT_INTERVAL_SECONDS="${HATCHET_WAIT_INTERVAL_SECONDS:-2}"
|
||||
hatchet_wait_host_port="${HATCHET_WAIT_HOST_PORT:-${HATCHET_CLIENT_HOST_PORT:-}}"
|
||||
hatchet_wait_url="${HATCHET_WAIT_URL:-${HATCHET_CLIENT_API_URL:-}}"
|
||||
|
||||
if [ -n "$hatchet_wait_host_port" ]; then
|
||||
case "$hatchet_wait_host_port" in
|
||||
*:*)
|
||||
hatchet_wait_host="${hatchet_wait_host_port%:*}"
|
||||
hatchet_wait_port="${hatchet_wait_host_port##*:}"
|
||||
;;
|
||||
*)
|
||||
log "Invalid HATCHET wait host:port value: ${hatchet_wait_host_port}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
wait_for_tcp \
|
||||
"Hatchet engine" \
|
||||
"$hatchet_wait_host" \
|
||||
"$hatchet_wait_port" \
|
||||
"$HATCHET_WAIT_TIMEOUT_SECONDS" \
|
||||
"$HATCHET_WAIT_INTERVAL_SECONDS"
|
||||
else
|
||||
if [ -z "$hatchet_wait_url" ]; then
|
||||
log "WAIT_FOR_HATCHET_ENGINE is enabled but no HATCHET wait target is configured."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
wait_for_http \
|
||||
"Hatchet engine" \
|
||||
"${hatchet_wait_url%/}/" \
|
||||
"$HATCHET_WAIT_TIMEOUT_SECONDS" \
|
||||
"$HATCHET_WAIT_INTERVAL_SECONDS" \
|
||||
"connect"
|
||||
fi
|
||||
fi
|
||||
17
src/worker.js
Normal file
17
src/worker.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'dotenv/config';
|
||||
|
||||
const requiredEnv = [
|
||||
'HATCHET_CLIENT_HOST_PORT',
|
||||
];
|
||||
|
||||
const missing = requiredEnv.filter((name) => !process.env[name]);
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Missing required env vars: ${missing.join(', ')}`);
|
||||
}
|
||||
|
||||
console.log('fregat-hatchet-worker started');
|
||||
console.log(`Hatchet endpoint: ${process.env.HATCHET_CLIENT_HOST_PORT}`);
|
||||
|
||||
setInterval(() => {
|
||||
console.log('worker heartbeat');
|
||||
}, 60_000);
|
||||
Reference in New Issue
Block a user