Files
classroomio/setup-railway-stack.sh
T

230 lines
7.1 KiB
Bash
Raw Normal View History

2026-03-10 22:11:47 +01:00
#!/usr/bin/env bash
set -euo pipefail
API_SERVICE_NAME="${API_SERVICE_NAME:-cio-api}"
DASHBOARD_SERVICE_NAME="${DASHBOARD_SERVICE_NAME:-cio-dashboard}"
POSTGRES_SERVICE_NAME="${POSTGRES_SERVICE_NAME:-Postgres}"
REDIS_SERVICE_NAME="${REDIS_SERVICE_NAME:-Redis}"
BUCKET_REGION="${BUCKET_REGION:-iad}"
BUCKET_PREFIX="${BUCKET_PREFIX:-classroomio}"
VIDEOS_BUCKET_NAME="${VIDEOS_BUCKET_NAME:-${BUCKET_PREFIX}-videos}"
DOCUMENTS_BUCKET_NAME="${DOCUMENTS_BUCKET_NAME:-${BUCKET_PREFIX}-documents}"
MEDIA_BUCKET_NAME="${MEDIA_BUCKET_NAME:-${BUCKET_PREFIX}-media}"
require_command() {
local command_name="$1"
if ! command -v "${command_name}" >/dev/null 2>&1; then
echo "Error: ${command_name} is required but not installed."
exit 1
fi
}
json_has_name() {
local json_payload="$1"
local target_name="$2"
printf '%s' "${json_payload}" | jq -e --arg target "${target_name}" '
[
.. | objects | (
.name? //
.serviceName? //
.service?.name? //
empty
)
] | index($target) != null
' >/dev/null 2>&1
}
service_exists() {
local service_name="$1"
local service_json
service_json="$(railway service status --all --json 2>/dev/null || true)"
if [[ -z "${service_json}" ]]; then
return 1
fi
json_has_name "${service_json}" "${service_name}"
}
bucket_exists() {
local bucket_name="$1"
local bucket_json
bucket_json="$(railway bucket list --json 2>/dev/null || true)"
if [[ -z "${bucket_json}" ]]; then
return 1
fi
json_has_name "${bucket_json}" "${bucket_name}"
}
ensure_database_service() {
local database_kind="$1"
local service_name="$2"
local output
if service_exists "${service_name}"; then
echo "Service '${service_name}' already exists."
return
fi
echo "Creating ${database_kind} service '${service_name}'..."
if output="$(railway add --database "${database_kind}" --service "${service_name}" --json 2>&1)"; then
echo "Created '${service_name}'."
return
fi
if service_exists "${service_name}"; then
echo "Service '${service_name}' already exists (detected after create attempt)."
return
fi
echo "${output}"
echo "Error: failed to provision '${service_name}'."
exit 1
}
ensure_bucket() {
local bucket_name="$1"
local output
if bucket_exists "${bucket_name}"; then
echo "Bucket '${bucket_name}' already exists."
return
fi
echo "Creating bucket '${bucket_name}' in region '${BUCKET_REGION}'..."
if output="$(railway bucket create "${bucket_name}" --region "${BUCKET_REGION}" --json 2>&1)"; then
echo "Created bucket '${bucket_name}'."
return
fi
if bucket_exists "${bucket_name}"; then
echo "Bucket '${bucket_name}' already exists (detected after create attempt)."
return
fi
echo "${output}"
echo "Error: failed to create bucket '${bucket_name}'."
exit 1
}
ensure_application_service() {
local service_name="$1"
if service_exists "${service_name}"; then
return
fi
echo "Error: app service '${service_name}' was not found."
echo "Create/link it first, then rerun this script:"
echo " railway add --service \"${service_name}\""
exit 1
}
build_media_public_base_url() {
local endpoint="$1"
local bucket_name="$2"
local force_path_style="$3"
local url
url="${endpoint%/}"
if [[ "${force_path_style}" == "true" ]]; then
printf '%s/%s\n' "${url}" "${bucket_name}"
return
fi
if [[ "${url}" =~ ^(https?)://(.+)$ ]]; then
printf '%s://%s.%s\n' "${BASH_REMATCH[1]}" "${bucket_name}" "${BASH_REMATCH[2]}"
return
fi
printf '%s/%s\n' "${url}" "${bucket_name}"
}
require_command railway
require_command jq
if ! railway status --json >/dev/null 2>&1; then
echo "Error: no linked Railway project in this directory."
echo "Run 'railway link --project <project-name-or-id>' and retry."
exit 1
fi
ensure_application_service "${API_SERVICE_NAME}"
ensure_application_service "${DASHBOARD_SERVICE_NAME}"
ensure_database_service postgres "${POSTGRES_SERVICE_NAME}"
ensure_database_service redis "${REDIS_SERVICE_NAME}"
ensure_bucket "${VIDEOS_BUCKET_NAME}"
ensure_bucket "${DOCUMENTS_BUCKET_NAME}"
ensure_bucket "${MEDIA_BUCKET_NAME}"
echo "Fetching credentials for bucket '${MEDIA_BUCKET_NAME}'..."
MEDIA_BUCKET_CREDS_JSON="$(railway bucket credentials --bucket "${MEDIA_BUCKET_NAME}" --json)"
OBJECT_STORAGE_ENDPOINT="$(printf '%s' "${MEDIA_BUCKET_CREDS_JSON}" | jq -r '.endpoint // empty')"
OBJECT_STORAGE_ACCESS_KEY_ID="$(printf '%s' "${MEDIA_BUCKET_CREDS_JSON}" | jq -r '.accessKeyId // empty')"
OBJECT_STORAGE_SECRET_ACCESS_KEY="$(printf '%s' "${MEDIA_BUCKET_CREDS_JSON}" | jq -r '.secretAccessKey // empty')"
OBJECT_STORAGE_REGION="${BUCKET_REGION}"
OBJECT_STORAGE_URL_STYLE="$(printf '%s' "${MEDIA_BUCKET_CREDS_JSON}" | jq -r '(.urlStyle // "") | ascii_downcase')"
if [[ -z "${OBJECT_STORAGE_ENDPOINT}" || -z "${OBJECT_STORAGE_ACCESS_KEY_ID}" || -z "${OBJECT_STORAGE_SECRET_ACCESS_KEY}" ]]; then
echo "Error: could not parse bucket credentials JSON."
echo "Response:"
echo "${MEDIA_BUCKET_CREDS_JSON}"
exit 1
fi
OBJECT_STORAGE_FORCE_PATH_STYLE="false"
if [[ "${OBJECT_STORAGE_URL_STYLE}" == *path* ]]; then
OBJECT_STORAGE_FORCE_PATH_STYLE="true"
fi
OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL="$(
build_media_public_base_url "${OBJECT_STORAGE_ENDPOINT}" "${MEDIA_BUCKET_NAME}" "${OBJECT_STORAGE_FORCE_PATH_STYLE}"
)"
POSTGRES_DATABASE_URL_REF="\${{${POSTGRES_SERVICE_NAME}.DATABASE_URL}}"
REDIS_URL_REF="\${{${REDIS_SERVICE_NAME}.REDIS_URL}}"
echo "Setting API variables on '${API_SERVICE_NAME}'..."
railway variable set --service "${API_SERVICE_NAME}" \
"SERVICE_TARGET=api" \
"PORT=3081" \
"DATABASE_URL=${POSTGRES_DATABASE_URL_REF}" \
"PRIVATE_DATABASE_URL=${POSTGRES_DATABASE_URL_REF}" \
"REDIS_URL=${REDIS_URL_REF}" \
"OBJECT_STORAGE_ENDPOINT=${OBJECT_STORAGE_ENDPOINT%/}" \
"OBJECT_STORAGE_PUBLIC_ENDPOINT=${OBJECT_STORAGE_ENDPOINT%/}" \
"OBJECT_STORAGE_ACCESS_KEY_ID=${OBJECT_STORAGE_ACCESS_KEY_ID}" \
"OBJECT_STORAGE_SECRET_ACCESS_KEY=${OBJECT_STORAGE_SECRET_ACCESS_KEY}" \
"OBJECT_STORAGE_REGION=${OBJECT_STORAGE_REGION}" \
"OBJECT_STORAGE_FORCE_PATH_STYLE=${OBJECT_STORAGE_FORCE_PATH_STYLE}" \
"OBJECT_STORAGE_BUCKET_VIDEOS=${VIDEOS_BUCKET_NAME}" \
"OBJECT_STORAGE_BUCKET_DOCUMENTS=${DOCUMENTS_BUCKET_NAME}" \
"OBJECT_STORAGE_BUCKET_MEDIA=${MEDIA_BUCKET_NAME}" \
"OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=${OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL}"
echo "Setting dashboard variables on '${DASHBOARD_SERVICE_NAME}'..."
railway variable set --service "${DASHBOARD_SERVICE_NAME}" \
"SERVICE_TARGET=dashboard" \
"PORT=3082" \
"PUBLIC_IS_SELFHOSTED=true"
echo
echo "Railway infrastructure is provisioned and wired:"
echo "- Postgres service: ${POSTGRES_SERVICE_NAME}"
echo "- Redis service: ${REDIS_SERVICE_NAME}"
echo "- Buckets: ${VIDEOS_BUCKET_NAME}, ${DOCUMENTS_BUCKET_NAME}, ${MEDIA_BUCKET_NAME}"
echo "- API service updated: ${API_SERVICE_NAME}"
echo "- Dashboard service updated: ${DASHBOARD_SERVICE_NAME}"
echo
echo "Next: set domain/auth/smtp variables, then deploy with:"
echo " railway up --service \"${API_SERVICE_NAME}\" --detach -m \"deploy api\""
echo " railway up --service \"${DASHBOARD_SERVICE_NAME}\" --detach -m \"deploy dashboard\""