1
0
mirror of git://f0xx.org/ac/ac-deploy synced 2026-07-29 09:39:50 +03:00
Files
ac-deploy/sim/cluster0/scripts/compose-lab-backend.sh
2026-07-11 17:33:43 +02:00

393 lines
14 KiB
Bash
Executable File

#!/bin/sh
# Assemble runnable PHP backend from ac-workspace submodules (overlay on legacy seed).
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
# shellcheck source=/dev/null
. "$ROOT/scripts/lib/common.sh"
load_cluster_env
WS="${AC_WORKSPACE_ROOT:-/var/www/ac/workspace}"
LAB_SEED="${ROOT}/lab-seeds/backend"
LEGACY="$LAB_SEED"
COMPOSED="${COMPOSE_BACKEND:-/var/www/ac/composed/backend}"
PLATFORM_ROOT="${AC_PLATFORM_ROOT:-/var/www/ac/platform}"
ensure_ac_ms_media_transcode() {
_mt="${WS}/ac-ms-media-transcode"
_origin="${AC_MEDIA_TRANSCODE_ORIGIN:-git://f0xx.org/ac/ac-ms-media-transcode}"
_branch="${AC_GIT_BRANCH:-next}"
_seed="${AC_ROOT:-/mnt/repos/ac}/ac-ms-media-transcode"
if [ -f "${_mt}/assets/pirate_drift.png" ] && [ -x "${_mt}/bin/run.sh" ]; then
log "ac-ms-media-transcode OK at ${_mt}"
chmod +x "${_mt}/bin/"*.sh 2>/dev/null || true
return 0
fi
if [ -f "${_seed}/assets/pirate_drift.png" ] && [ -d "${_seed}/.git" ]; then
log "seed ac-ms-media-transcode from ${_seed}${_mt}"
rm -rf "${_mt}"
cp -a "${_seed}" "${_mt}"
chmod +x "${_mt}/bin/"*.sh 2>/dev/null || true
return 0
fi
if [ -d "${_mt}/.git" ]; then
log "update ac-ms-media-transcode in ${_mt}"
git -C "${_mt}" fetch origin 2>/dev/null || true
git -C "${_mt}" checkout "${_branch}" 2>/dev/null || true
git -C "${_mt}" pull --ff-only origin "${_branch}" 2>/dev/null \
|| git -C "${_mt}" reset --hard "origin/${_branch}" 2>/dev/null || true
else
log "clone ac-ms-media-transcode → ${_mt}"
rm -rf "${_mt}"
git clone --branch "${_branch}" "${_origin}" "${_mt}"
fi
chmod +x "${_mt}/bin/"*.sh 2>/dev/null || true
}
ensure_ac_ms_media_transcode
APP_PASS="$(read_cred mariadb_app password)"
DB_HOST="$PRIMARY_DB_HOST"
if is_primary_node; then
DB_HOST=127.0.0.1
fi
[ -d "$WS/.git" ] || die "missing ac-workspace at $WS (run deploy-ac-workspace.sh first)"
overlay_src() {
_repo="$1"
_dir="${WS}/${_repo}/src"
[ -d "$_dir" ] || { log "skip src ${_repo} (no src/)"; return 0; }
log "overlay src ${_repo}"
cp -a "$_dir"/. "${COMPOSED}/src/"
}
overlay_api() {
_repo="$1"
_dir="${WS}/${_repo}/public/api"
[ -d "$_dir" ] || return 0
log "overlay api ${_repo}"
mkdir -p "${COMPOSED}/public/api"
cp -a "$_dir"/. "${COMPOSED}/public/api/"
}
overlay_views() {
_repo="$1"
_dir="${WS}/${_repo}/views"
[ -d "$_dir" ] || return 0
log "overlay views ${_repo}"
mkdir -p "${COMPOSED}/views"
cp -a "$_dir"/. "${COMPOSED}/views/"
}
if [ ! -f "${COMPOSED}/public/index.php" ] || [ "${FORCE_COMPOSE_SEED:-0}" = "1" ]; then
[ -d "$LEGACY/public" ] || die "backend seed missing at $LEGACY (sync ac-deploy lab-seeds to NFS /shared/cluster)"
log "seed composed backend from ${LEGACY}"
mkdir -p "$(dirname "$COMPOSED")"
rm -rf "$COMPOSED"
mkdir -p "$COMPOSED"
cp -a "${LEGACY}/." "$COMPOSED/"
fi
mkdir -p "$PLATFORM_ROOT"
if [ -d "${WS}/ac-platform-php/platform" ]; then
log "sync platform helpers → ${PLATFORM_ROOT}"
cp -a "${WS}/ac-platform-php/platform/." "$PLATFORM_ROOT/"
fi
if [ -d "${WS}/ac-platform-web/assets" ]; then
log "sync platform-web assets"
mkdir -p "${COMPOSED}/public/assets"
cp -a "${WS}/ac-platform-web/assets/." "${COMPOSED}/public/assets/"
if [ -f "${WS}/ac-platform-web/src/AnalyticsHead.php" ]; then
cp -a "${WS}/ac-platform-web/src/AnalyticsHead.php" "${COMPOSED}/src/"
fi
fi
if [ -f "${WS}/ac-be-auth/public/assets/js/two_factor.js" ]; then
log "sync ac-be-auth two_factor.js"
mkdir -p "${COMPOSED}/public/assets/js"
cp -a "${WS}/ac-be-auth/public/assets/js/two_factor.js" "${COMPOSED}/public/assets/js/"
fi
overlay_src ac-platform-php
overlay_src ac-ms-identity
overlay_src ac-ms-rbac
overlay_src ac-ms-devices
overlay_src ac-ms-issues
overlay_src ac-ms-tickets
overlay_src ac-ms-graphs
overlay_src ac-ms-remote-access
# Lab-seed PHP not yet split into ac-ms-* repos — NFS seed stays authoritative after overlays.
overlay_lab_seed_src() {
_f="$1"
if [ -f "${LEGACY}/src/${_f}" ]; then
log "overlay lab-seed src ${_f}"
cp -a "${LEGACY}/src/${_f}" "${COMPOSED}/src/"
fi
}
overlay_lab_seed_src ShortLinksRepository.php
overlay_lab_seed_src UrlShortenerDatabase.php
overlay_lab_seed_src MediaPipelinesRepository.php
overlay_lab_seed_src MediaPipelineRunner.php
overlay_lab_seed_api() {
_f="$1"
if [ -f "${LEGACY}/public/api/${_f}" ]; then
log "overlay lab-seed api ${_f}"
mkdir -p "${COMPOSED}/public/api"
cp -a "${LEGACY}/public/api/${_f}" "${COMPOSED}/public/api/"
fi
}
overlay_lab_seed_api media_pipelines.php
overlay_lab_seed_public() {
_rel="$1"
if [ -f "${LEGACY}/public/${_rel}" ]; then
log "overlay lab-seed public/${_rel}"
mkdir -p "${COMPOSED}/public/$(dirname "$_rel")"
cp -a "${LEGACY}/public/${_rel}" "${COMPOSED}/public/${_rel}"
fi
}
overlay_lab_seed_public assets/js/media_pipelines.js
overlay_lab_seed_public index.php
overlay_lab_seed_sql() {
_f="$1"
if [ -f "${LEGACY}/sql/migrations/${_f}" ]; then
log "overlay lab-seed sql/migrations/${_f}"
mkdir -p "${COMPOSED}/sql/migrations"
cp -a "${LEGACY}/sql/migrations/${_f}" "${COMPOSED}/sql/migrations/"
fi
}
overlay_lab_seed_sql 081_media_pipelines.sql
overlay_lab_seed_sql 081_media_pipelines.sqlite.sql
overlay_api ac-ms-identity
overlay_api ac-ms-rbac
overlay_api ac-ms-devices
overlay_api ac-ms-issues
overlay_api ac-ms-tickets
overlay_api ac-ms-graphs
overlay_api ac-ms-remote-access
overlay_views ac-be-auth
overlay_views ac-be-tickets
overlay_views ac-be-graphs
overlay_views ac-be-remote-access
overlay_views ac-be-access
# ac-be-issues last — its layout.php is the canonical nav shell (other BE repos ship stubs).
overlay_views ac-be-issues
ISSUES_BASE="${APP_BASE_PATH:-/app/androidcast_project/issues}"
PUBLIC_ORIGIN_CFG="${LAB_PUBLIC_ORIGIN:-https://apps.f0xx.org}"
load_secrets_lab
AUTH_KEY="${AUTH_ENCRYPTION_KEY:-lab-cluster-dev-32-char-min-secret!!}"
_MSMTP_PASS="$(read_secret MSMTP_APP_PASSWORD 2>/dev/null || true)"
[ -n "$_MSMTP_PASS" ] || _MSMTP_PASS="${MSMTP_APP_PASSWORD:-}"
MAIL_TRANSPORT="${MAIL_TRANSPORT:-sendmail}"
_mail_from_secret="$(read_secret MAIL_FROM 2>/dev/null || true)"
[ -n "$_mail_from_secret" ] || _mail_from_secret="${MAIL_FROM:-}"
MAIL_REPLY="${MAIL_REPLY_TO:-bestcastr@gmail.com}"
SMTP_HOST="${SMTP_HOST:-127.0.0.1}"
SMTP_PORT="${SMTP_PORT:-587}"
SMTP_ENC="${SMTP_ENCRYPTION:-tls}"
SMTP_USER="${SMTP_USER:-${MSMTP_USER:-}}"
SMTP_PASS="${SMTP_PASS:-}"
if [ -n "$_MSMTP_PASS" ]; then
MAIL_TRANSPORT=smtp
SMTP_HOST=smtp.gmail.com
SMTP_USER="${MSMTP_USER:-bestcastr@gmail.com}"
SMTP_PASS="$_MSMTP_PASS"
fi
MAIL_FROM="$(normalize_mail_from "$_mail_from_secret" "$SMTP_USER")"
[ -n "$MAIL_FROM" ] || MAIL_FROM="Android Cast Lab <${SMTP_USER:-bestcastr@gmail.com}>"
unset _MSMTP_PASS _mail_from_secret
MAIL_FROM_PHP="$(printf '%s' "$MAIL_FROM" | sed "s/'/\\\\'/g")"
AUTH_BEARER_ID="$(read_secret URL_SHORTENER_AUTH_BEARER_ID 2>/dev/null || true)"
[ -n "$AUTH_BEARER_ID" ] || AUTH_BEARER_ID="${URL_SHORTENER_AUTH_BEARER_ID:-0}"
API_BEARER_TOKEN="$(read_secret URL_SHORTENER_API_BEARER_TOKEN 2>/dev/null || true)"
API_BEARER_PHP=""
if [ -n "$API_BEARER_TOKEN" ]; then
API_BEARER_PHP="$(printf '%s' "$API_BEARER_TOKEN" | sed "s/'/\\\\'/g")"
fi
WG_ENDPOINT_CFG="${WG_ENDPOINT:-}"
WG_SERVER_PUBLIC_KEY_CFG="${WG_SERVER_PUBLIC_KEY:-}"
WG_PROVISION="${WG_PROVISION_PEERS:-0}"
if [ -f /etc/wireguard/keys/server.publickey ] && [ -z "$WG_SERVER_PUBLIC_KEY_CFG" ]; then
WG_SERVER_PUBLIC_KEY_CFG="$(cat /etc/wireguard/keys/server.publickey)"
fi
if [ -z "$WG_ENDPOINT_CFG" ] && [ -n "$WG_SERVER_PUBLIC_KEY_CFG" ]; then
WG_ENDPOINT_CFG="${CAST01_HOST:-cast01}.${CAST_DOMAIN:-intra.raptor.org}:51820"
WG_PROVISION=1
fi
WG_PROVISION_PHP=false
RSSH_PROVISION_PHP=false
[ "$WG_PROVISION" = "1" ] && WG_PROVISION_PHP=true
RSSH_BASTION_HOST_CFG="${RSSH_BASTION_HOST:-}"
RSSH_BASTION_PORT_CFG="${RSSH_BASTION_PORT:-22}"
if [ -z "$RSSH_BASTION_HOST_CFG" ] && [ -n "$WG_SERVER_PUBLIC_KEY_CFG" ]; then
RSSH_BASTION_HOST_CFG="${CAST01_HOST:-cast01}.${CAST_DOMAIN:-intra.raptor.org}"
fi
RSSH_PROVISION="${RSSH_PROVISION_USERS:-0}"
if [ -n "$RSSH_BASTION_HOST_CFG" ]; then
RSSH_PROVISION=1
fi
[ "$RSSH_PROVISION" = "1" ] && RSSH_PROVISION_PHP=true
URL_SHORTENER_API_BASE_CFG="${URL_SHORTENER_API_BASE:-${SHORT_LINKS_PUBLIC_BASE}}"
URL_SHORTENER_API_HOST_CFG="${URL_SHORTENER_API_HOST:-}"
mkdir -p "${COMPOSED}/config"
log "write composed config.php base_path=${ISSUES_BASE}"
cat > "${COMPOSED}/config/config.php" <<PHP
<?php
return [
'app_name' => 'Android Cast Issues',
'base_path' => '${ISSUES_BASE}',
'public_origin' => '${PUBLIC_ORIGIN_CFG}',
'db' => [
'driver' => 'mariadb',
'sqlite_path' => __DIR__ . '/../data/crashes.sqlite',
'mysql' => [
'host' => '${DB_HOST}',
'port' => ${PRIMARY_DB_PORT},
'socket' => '',
'database' => 'androidcast_crashes',
'username' => 'androidcast',
'password' => '${APP_PASS}',
'charset' => 'utf8mb4',
],
],
'session_name' => 'ac_crash_sess',
'session_cookie_path' => '${APP_HUB_PATH}',
'debug' => true,
'rbac' => ['default_company_slug' => 'default', 'default_company_id' => 1],
'analytics' => ['enabled' => false, 'measurement_id' => '', 'debug' => false],
'remote_access' => [
'wg_endpoint' => '${WG_ENDPOINT_CFG}',
'wg_server_public_key' => '${WG_SERVER_PUBLIC_KEY_CFG}',
'wg_interface' => 'wg0',
'wg_server_allowed_ips' => '172.200.2.1/32',
'wg_client_ip_prefix' => '172.200.2.',
'wg_client_ip_min' => 10,
'wg_client_ip_max' => 250,
'wg_set_prefix' => 'sudo -n ',
'provision_peers' => ${WG_PROVISION_PHP:-false},
'require_wg_tools' => ${WG_PROVISION_PHP:-false},
'session_ttl_s' => 3600,
'min_poll_interval_s' => 45,
'rssh' => [
'bastion_host' => '${RSSH_BASTION_HOST_CFG}',
'bastion_port' => ${RSSH_BASTION_PORT_CFG:-22},
'local_forward_port' => 8022,
'remote_port_min' => 18000,
'remote_port_max' => 18999,
'provision_users' => ${RSSH_PROVISION_PHP:-false},
'bastion_user_script' => __DIR__ . '/../scripts/rssh_bastion_user.sh',
],
],
'auth' => [
'encryption_key' => '${AUTH_KEY}',
'max_attempts' => 8,
'lockout_window_minutes' => 15,
],
'mail' => [
'transport' => '${MAIL_TRANSPORT}',
'from' => '${MAIL_FROM_PHP}',
'reply_to' => '${MAIL_REPLY}',
'smtp' => [
'host' => '${SMTP_HOST}',
'port' => ${SMTP_PORT},
'encryption' => '${SMTP_ENC}',
'username' => '${SMTP_USER}',
'password' => '${SMTP_PASS}',
],
],
'url_shortener' => [
'enabled' => true,
'public_base' => '${SHORT_LINKS_PUBLIC_BASE}',
'api_base' => '${URL_SHORTENER_API_BASE_CFG}',
'api_host' => '${URL_SHORTENER_API_HOST_CFG}',
'api_bearer_token' => '${API_BEARER_PHP}',
'auth_bearer_id' => ${AUTH_BEARER_ID:-0},
'db' => [
'mysql' => [
'host' => '${DB_HOST}',
'port' => ${PRIMARY_DB_PORT},
'socket' => '',
'database' => 'url_shortener',
'username' => 'androidcast',
'password' => '${APP_PASS}',
'charset' => 'utf8mb4',
],
],
],
'media_pipelines' => [
'enabled' => true,
'developers_only' => true,
'script_root' => '${WS}/ac-ms-media-transcode',
'storage_root' => '/var/www/ac/media-pipelines',
'hls_url_prefix' => '/app/androidcast_project/media/hls',
'hls_filesystem_root' => '/var/www/ac/media-pipelines/hls',
'logo_path' => '${WS}/ac-ms-media-transcode/assets/pirate_drift.png',
'ytdlp_enabled' => true,
],
];
PHP
MEDIA_ROOT="/var/www/ac/media-pipelines"
MEDIA_HLS="${MEDIA_ROOT}/hls"
MT_ROOT="${WS}/ac-ms-media-transcode"
log "ensure media-pipelines dirs ${MEDIA_ROOT}"
mkdir -p "${MEDIA_HLS}" "${MEDIA_ROOT}/pipelines"
chown nginx:nginx "${MEDIA_ROOT}" "${MEDIA_HLS}" 2>/dev/null || true
if [ -d "${MT_ROOT}/bin" ]; then
chmod +x "${MT_ROOT}"/bin/*.sh 2>/dev/null || true
log "media-transcode scripts at ${MT_ROOT} (logo: ${MT_ROOT}/assets/pirate_drift.png)"
fi
BUILD_CFG="${WS}/ac-be-builder/config/config.php"
if [ -f "${COMPOSED}/config/config.php" ]; then
log "write ac-be-builder config.php (shared cluster session + DB)"
cat > "$BUILD_CFG" <<'PHP'
<?php
$config = require '/var/www/ac/composed/backend/config/config.php';
$config['app_name'] = 'Android Cast Builder';
$config['base_path'] = '/app/androidcast_project/build';
$config['links'] = [
'hub' => '/app/androidcast_project/',
'crashes' => '/app/androidcast_project/issues/',
'git' => '/app/androidcast_project/git/',
'tickets' => '/app/androidcast_project/issues/?view=tickets',
];
$config['build'] = array_merge([
'docker_image' => 'androidcast-ci:latest',
'docker_bin' => '/usr/bin/docker',
'ci_version' => '00.01.00.1000',
'repo_root' => '/var/www/ac/workspace',
'artifacts_root' => '/var/www/ac/broadcast/builds',
'ota_mount' => '/var/www/ac/broadcast/ota-artifacts',
'ota_base_url' => 'https://apps.f0xx.org',
'runner_script' => '/var/www/ac/workspace/ac-scripts/docker-build-runner.sh',
'isolate_source' => true,
'git_clone_depth' => 1,
'pipeline_config' => '/var/www/ac/workspace/ac-scripts/build.config.yml',
'default_channels' => ['stable', 'staging', 'dev', 'nightly'],
], $config['build'] ?? []);
return $config;
PHP
chown nginx:nginx "$BUILD_CFG" 2>/dev/null || true
fi
if command -v composer >/dev/null 2>&1; then
log "composer dump-autoload (optional)"
cat > "${COMPOSED}/composer.json" <<'JSON'
{
"name": "androidcast/lab-composed-backend",
"require": { "php": ">=8.1" },
"autoload": { "classmap": ["src/"] }
}
JSON
(cd "$COMPOSED" && composer dump-autoload -o 2>/dev/null) || true
fi
chown -R nginx:nginx "$(dirname "$COMPOSED")" "$PLATFORM_ROOT" 2>/dev/null || true
log "compose-lab-backend_ok $(host_short)${COMPOSED}"