li / docker /entrypoint.sh
hf-deploy
Deploy Grok2API v3.0.7 to Hugging Face Space
0208fd2
Raw
History Blame Contribute Delete
9.39 kB
#!/bin/sh
set -eu
umask 077
CONFIG_SOURCE="${GROK2API_CONFIG_SOURCE:-/run/grok2api/config.yaml}"
APP_CONFIG="/app/config.yaml"
SECRETS_FILE_NAME=".grok2api-secrets.env"
log() {
printf '%s\n' "$*" >&2
}
die() {
log "error: $*"
exit 1
}
yaml_quote() {
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
}
is_truthy() {
case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in
1|true|yes|on) return 0 ;;
*) return 1 ;;
esac
}
has_writable_dir() {
dir="$1"
[ -d "$dir" ] || return 1
[ -w "$dir" ] || return 1
return 0
}
resolve_data_root() {
if [ -n "${GROK2API_DATA_DIR:-}" ]; then
printf '%s' "$GROK2API_DATA_DIR"
return
fi
if has_writable_dir /data; then
printf '%s' /data
return
fi
printf '%s' /app/data
}
random_hex() {
dd if=/dev/urandom bs="$1" count=1 2>/dev/null | od -An -tx1 | tr -d ' \n'
}
random_base64_32() {
dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d '\n'
}
shell_single_quote() {
printf "%s" "$1" | sed "s/'/'\\\\''/g"
}
persist_secrets() {
secrets_file="$1"
jwt_secret="$2"
credential_key="$3"
tmp="${secrets_file}.tmp"
{
printf "export JWT_SECRET='%s'\n" "$(shell_single_quote "$jwt_secret")"
printf "export CREDENTIAL_ENCRYPTION_KEY='%s'\n" "$(shell_single_quote "$credential_key")"
} >"$tmp"
mv "$tmp" "$secrets_file"
chmod 0600 "$secrets_file" 2>/dev/null || true
}
resolve_listen() {
if [ -n "${GROK2API_LISTEN:-}" ]; then
printf '%s' "$GROK2API_LISTEN"
return
fi
if [ -n "${PORT:-}" ]; then
printf '0.0.0.0:%s' "$PORT"
return
fi
if [ -n "${SPACE_ID:-}" ] || [ -n "${SPACE_HOST:-}" ] || is_truthy "${HF_SPACE:-}" || is_truthy "${GROK2API_HF:-}"; then
printf '%s' "0.0.0.0:7860"
return
fi
printf '%s' "0.0.0.0:8000"
}
resolve_public_api_base_url() {
if [ -n "${PUBLIC_API_BASE_URL:-}" ]; then
printf '%s' "${PUBLIC_API_BASE_URL%/}"
return
fi
if [ -n "${GROK2API_PUBLIC_API_BASE_URL:-}" ]; then
printf '%s' "${GROK2API_PUBLIC_API_BASE_URL%/}"
return
fi
if [ -n "${SPACE_HOST:-}" ]; then
printf 'https://%s' "$SPACE_HOST"
return
fi
printf '%s' ""
}
resolve_secure_cookies() {
if [ -n "${SECURE_COOKIES:-}" ]; then
if is_truthy "$SECURE_COOKIES"; then
printf '%s' "true"
else
printf '%s' "false"
fi
return
fi
if [ -n "${SPACE_ID:-}" ] || [ -n "${SPACE_HOST:-}" ] || is_truthy "${HF_SPACE:-}" || is_truthy "${GROK2API_HF:-}"; then
printf '%s' "true"
return
fi
printf '%s' "false"
}
prepare_data_dirs() {
data_root="$1"
mkdir -p "$data_root" "$data_root/media"
if [ "$(id -u)" -eq 0 ]; then
chown -R grok2api:grok2api "$data_root" 2>/dev/null || true
fi
}
resolve_secret_value() {
env_primary="$1"
env_alt="$2"
file_value="$3"
if [ -n "$env_primary" ]; then
printf '%s' "$env_primary"
return
fi
if [ -n "$env_alt" ]; then
printf '%s' "$env_alt"
return
fi
printf '%s' "$file_value"
}
generate_config_from_env() {
data_root="$1"
secrets_file="$data_root/$SECRETS_FILE_NAME"
env_jwt="${JWT_SECRET:-}"
env_jwt_alt="${GROK2API_JWT_SECRET:-}"
env_key="${CREDENTIAL_ENCRYPTION_KEY:-}"
env_key_alt="${GROK2API_CREDENTIAL_ENCRYPTION_KEY:-}"
file_jwt=""
file_key=""
if [ -f "$secrets_file" ]; then
# shellcheck disable=SC1090
. "$secrets_file"
file_jwt="${JWT_SECRET:-}"
file_key="${CREDENTIAL_ENCRYPTION_KEY:-}"
fi
jwt_secret="$(resolve_secret_value "$env_jwt" "$env_jwt_alt" "$file_jwt")"
credential_key="$(resolve_secret_value "$env_key" "$env_key_alt" "$file_key")"
generated_secrets=0
if [ -z "$jwt_secret" ]; then
jwt_secret="$(random_hex 32)"
generated_secrets=1
fi
if [ -z "$credential_key" ]; then
credential_key="$(random_base64_32)"
generated_secrets=1
fi
if [ "${#jwt_secret}" -lt 32 ]; then
die "JWT_SECRET must be at least 32 characters"
fi
admin_user="${BOOTSTRAP_ADMIN_USERNAME:-${GROK2API_BOOTSTRAP_ADMIN_USERNAME:-admin}}"
admin_pass="${BOOTSTRAP_ADMIN_PASSWORD:-${GROK2API_BOOTSTRAP_ADMIN_PASSWORD:-}}"
if [ -z "$admin_pass" ]; then
die "BOOTSTRAP_ADMIN_PASSWORD is required when generating config from environment"
fi
case "$admin_pass" in
replace-with-a-strong-password)
die "BOOTSTRAP_ADMIN_PASSWORD must not use the example placeholder"
;;
esac
db_driver="${DATABASE_DRIVER:-${GROK2API_DATABASE_DRIVER:-sqlite}}"
runtime_driver="${RUNTIME_STORE_DRIVER:-${GROK2API_RUNTIME_STORE_DRIVER:-memory}}"
sqlite_path="${SQLITE_PATH:-${GROK2API_SQLITE_PATH:-$data_root/backend.db}}"
media_path="${MEDIA_PATH:-${GROK2API_MEDIA_PATH:-$data_root/media}}"
static_path="${FRONTEND_STATIC_PATH:-/app/frontend/dist}"
public_api_base_url="$(resolve_public_api_base_url)"
secure_cookies="$(resolve_secure_cookies)"
swagger_enabled="false"
if is_truthy "${SWAGGER_ENABLED:-}"; then
swagger_enabled="true"
fi
postgres_dsn="${POSTGRES_DSN:-${GROK2API_POSTGRES_DSN:-}}"
redis_address="${REDIS_ADDRESS:-${GROK2API_REDIS_ADDRESS:-127.0.0.1:6379}}"
redis_username="${REDIS_USERNAME:-${GROK2API_REDIS_USERNAME:-}}"
redis_password="${REDIS_PASSWORD:-${GROK2API_REDIS_PASSWORD:-}}"
redis_database="${REDIS_DATABASE:-${GROK2API_REDIS_DATABASE:-0}}"
redis_prefix="${REDIS_KEY_PREFIX:-${GROK2API_REDIS_KEY_PREFIX:-grok2api:}}"
redis_tls="false"
if is_truthy "${REDIS_TLS:-${GROK2API_REDIS_TLS:-}}"; then
redis_tls="true"
fi
if [ "$db_driver" = "postgres" ] && [ -z "$postgres_dsn" ]; then
die "POSTGRES_DSN is required when DATABASE_DRIVER=postgres"
fi
jwt_q="$(yaml_quote "$jwt_secret")"
key_q="$(yaml_quote "$credential_key")"
admin_user_q="$(yaml_quote "$admin_user")"
admin_pass_q="$(yaml_quote "$admin_pass")"
sqlite_path_q="$(yaml_quote "$sqlite_path")"
media_path_q="$(yaml_quote "$media_path")"
static_path_q="$(yaml_quote "$static_path")"
public_api_q="$(yaml_quote "$public_api_base_url")"
postgres_dsn_q="$(yaml_quote "$postgres_dsn")"
redis_address_q="$(yaml_quote "$redis_address")"
redis_username_q="$(yaml_quote "$redis_username")"
redis_password_q="$(yaml_quote "$redis_password")"
redis_prefix_q="$(yaml_quote "$redis_prefix")"
cat >"$APP_CONFIG" <<EOF
server:
listen: "0.0.0.0:8000"
maxBodyBytes: 33554432
readTimeout: 15m
requestTimeout: 2h
swaggerEnabled: ${swagger_enabled}
auth:
accessTokenTTL: 15m
refreshTokenTTL: 720h
secureCookies: ${secure_cookies}
secrets:
jwtSecret: "${jwt_q}"
credentialEncryptionKey: "${key_q}"
bootstrapAdmin:
username: "${admin_user_q}"
password: "${admin_pass_q}"
frontend:
publicApiBaseURL: "${public_api_q}"
staticPath: "${static_path_q}"
database:
driver: ${db_driver}
sqlite:
path: "${sqlite_path_q}"
postgres:
dsn: "${postgres_dsn_q}"
maxOpenConns: 50
maxIdleConns: 10
runtimeStore:
driver: ${runtime_driver}
redis:
address: "${redis_address_q}"
username: "${redis_username_q}"
password: "${redis_password_q}"
database: ${redis_database}
keyPrefix: "${redis_prefix_q}"
tls: ${redis_tls}
media:
driver: local
local:
path: "${media_path_q}"
routing:
reasoningReplayEnabled: true
reasoningReplayTTL: 1h
reasoningReplayMaxEntries: 10240
EOF
if [ "$(id -u)" -eq 0 ]; then
chown grok2api:grok2api "$APP_CONFIG"
fi
chmod 0600 "$APP_CONFIG"
persist_secrets "$secrets_file" "$jwt_secret" "$credential_key"
if [ "$(id -u)" -eq 0 ]; then
chown grok2api:grok2api "$secrets_file" 2>/dev/null || true
fi
if [ "$generated_secrets" -eq 1 ]; then
log "generated JWT/credential secrets and saved them under ${secrets_file}"
log "set JWT_SECRET and CREDENTIAL_ENCRYPTION_KEY as Space Secrets for stable personal use"
fi
if [ -z "$public_api_base_url" ]; then
log "PUBLIC_API_BASE_URL is empty; docs may show http://127.0.0.1:8000"
log "on Hugging Face, SPACE_HOST is usually injected; or set PUBLIC_API_BASE_URL"
fi
}
install_config_from_source() {
src="$1"
[ -f "$src" ] || die "missing config: ${src}"
cp "$src" "$APP_CONFIG"
if [ "$(id -u)" -eq 0 ]; then
chown grok2api:grok2api "$APP_CONFIG"
fi
chmod 0600 "$APP_CONFIG"
}
run_as_app() {
if [ "$(id -u)" -eq 0 ]; then
exec su-exec grok2api:grok2api "$@"
fi
exec "$@"
}
# ---- main ----
DATA_ROOT="$(resolve_data_root)"
prepare_data_dirs "$DATA_ROOT"
LISTEN="$(resolve_listen)"
if [ -f "$CONFIG_SOURCE" ]; then
log "using mounted config: $CONFIG_SOURCE"
install_config_from_source "$CONFIG_SOURCE"
elif [ -n "${BOOTSTRAP_ADMIN_PASSWORD:-}" ] || [ -n "${GROK2API_BOOTSTRAP_ADMIN_PASSWORD:-}" ] || [ -n "${JWT_SECRET:-}" ] || [ -n "${GROK2API_JWT_SECRET:-}" ] || [ -n "${CREDENTIAL_ENCRYPTION_KEY:-}" ] || [ -n "${GROK2API_CREDENTIAL_ENCRYPTION_KEY:-}" ]; then
log "generating config from environment (data root: $DATA_ROOT, listen: $LISTEN)"
generate_config_from_env "$DATA_ROOT"
else
die "missing config: mount ${CONFIG_SOURCE} or set BOOTSTRAP_ADMIN_PASSWORD (and optional JWT_SECRET / CREDENTIAL_ENCRYPTION_KEY) for Hugging Face / env deploy"
fi
# CLI --listen overrides YAML. Append resolved listen last so HF PORT/7860 wins over image CMD.
if [ "$#" -eq 0 ]; then
set -- /app/grok2api --config "$APP_CONFIG" --listen "$LISTEN"
else
set -- "$@" --listen "$LISTEN"
fi
log "starting grok2api on $LISTEN"
run_as_app "$@"