mirror of
git://f0xx.org/ac/ac-deploy
synced 2026-07-29 00:58:20 +03:00
initial
This commit is contained in:
18
.env.example
Normal file
18
.env.example
Normal file
@@ -0,0 +1,18 @@
|
||||
COMPOSE_PROJECT_NAME=androidcast_local
|
||||
|
||||
# Public entry (landing FE)
|
||||
LANDING_HTTP_PORT=8080
|
||||
|
||||
# Optional direct debug ports
|
||||
GITEA_FE_PORT=8081
|
||||
CRASHES_FE_PORT=8082
|
||||
BUILD_FE_PORT=8083
|
||||
|
||||
# Internal app creds (local dev defaults)
|
||||
MARIADB_ROOT_PASSWORD=androidcast_root
|
||||
MARIADB_DATABASE=androidcast_crashes
|
||||
MARIADB_USER=androidcast
|
||||
MARIADB_PASSWORD=androidcast
|
||||
|
||||
# Used for generated crash backend config + gitea ROOT_URL
|
||||
PUBLIC_BASE_URL=http://localhost:8080
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.env
|
||||
runtime/*
|
||||
!runtime/db-init/
|
||||
!runtime/db-init/.gitkeep
|
||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "ac-scripts"]
|
||||
path = ac-scripts
|
||||
url = git://f0xx.org/ac/ac-scripts
|
||||
branch = next
|
||||
6
README.md
Normal file
6
README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# ac-deploy
|
||||
|
||||
Orchestration, cluster0 lab, Docker images, nginx seeds.
|
||||
|
||||
- **Remote:** git://f0xx.org/ac/ac-deploy
|
||||
- **Submodules:** ac-scripts
|
||||
1
ac-scripts
Submodule
1
ac-scripts
Submodule
Submodule ac-scripts added at 3c197ed9dd
67
cleanup.sh
Executable file
67
cleanup.sh
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COMPOSE_FILE="$ROOT_DIR/docker-compose.yml"
|
||||
ENV_FILE="$ROOT_DIR/.env"
|
||||
ENV_EXAMPLE="$ROOT_DIR/.env.example"
|
||||
RUNTIME_DIR="$ROOT_DIR/runtime"
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "docker not found in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker compose version >/dev/null 2>&1; then
|
||||
echo "docker compose plugin is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$ENV_FILE" && -f "$ENV_EXAMPLE" ]]; then
|
||||
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
||||
fi
|
||||
|
||||
set -a
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$ENV_FILE"
|
||||
fi
|
||||
set +a
|
||||
|
||||
PROJECT_NAME="${COMPOSE_PROJECT_NAME:-androidcast_local}"
|
||||
|
||||
echo "Stopping and removing orchestration stack..."
|
||||
docker compose -f "$COMPOSE_FILE" down --remove-orphans --volumes --rmi local || true
|
||||
|
||||
echo "Removing leftover containers with compose project label..."
|
||||
docker ps -aq --filter "label=com.docker.compose.project=${PROJECT_NAME}" | while read -r cid; do
|
||||
[[ -n "$cid" ]] && docker rm -f "$cid" || true
|
||||
done
|
||||
|
||||
echo "Removing leftover compose networks..."
|
||||
docker network ls -q --filter "label=com.docker.compose.project=${PROJECT_NAME}" | while read -r nid; do
|
||||
[[ -n "$nid" ]] && docker network rm "$nid" || true
|
||||
done
|
||||
|
||||
echo "Removing leftover compose volumes..."
|
||||
docker volume ls -q --filter "label=com.docker.compose.project=${PROJECT_NAME}" | while read -r vid; do
|
||||
[[ -n "$vid" ]] && docker volume rm "$vid" || true
|
||||
done
|
||||
|
||||
echo "Removing orchestration screen sessions if present..."
|
||||
if command -v screen >/dev/null 2>&1; then
|
||||
screen_output="$(screen -ls 2>/dev/null || true)"
|
||||
printf '%s\n' "$screen_output" | awk '/androidcast|orchestration/ {print $1}' | while read -r session; do
|
||||
[[ -n "$session" ]] && screen -S "$session" -X quit || true
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Cleaning generated runtime artifacts..."
|
||||
rm -f "$RUNTIME_DIR/crash-config.php"
|
||||
rm -f "$RUNTIME_DIR/db-init/010_schema.sql" \
|
||||
"$RUNTIME_DIR/db-init/040_ticket_workflow.sql" \
|
||||
"$RUNTIME_DIR/db-init/050_ticket_attachments.sql"
|
||||
mkdir -p "$RUNTIME_DIR/db-init"
|
||||
touch "$RUNTIME_DIR/db-init/.gitkeep"
|
||||
|
||||
echo "Orchestration cleanup complete."
|
||||
150
deploy.sh
Executable file
150
deploy.sh
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ENV_FILE="$ROOT_DIR/.env"
|
||||
ENV_EXAMPLE="$ROOT_DIR/.env.example"
|
||||
RUNTIME_DIR="$ROOT_DIR/runtime"
|
||||
DB_INIT_DIR="$RUNTIME_DIR/db-init"
|
||||
CRASH_CONFIG="$RUNTIME_DIR/crash-config.php"
|
||||
BUILD_CONFIG="$RUNTIME_DIR/build-config.php"
|
||||
OTA_DIR="$RUNTIME_DIR/ota-artifacts"
|
||||
|
||||
SRC_SCHEMA="$ROOT_DIR/../examples/crash_reporter/backend/sql/schema.mariadb.sql"
|
||||
SRC_MIG_004="$ROOT_DIR/../examples/crash_reporter/backend/sql/migrations/004_ticket_workflow.sql"
|
||||
SRC_MIG_005="$ROOT_DIR/../examples/crash_reporter/backend/sql/migrations/005_ticket_attachments.sql"
|
||||
SRC_MIG_060="$ROOT_DIR/runtime/db-init/060_build_ecosystem.sql"
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "docker not found in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker compose version >/dev/null 2>&1; then
|
||||
echo "docker compose plugin is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
||||
echo "Created .env from .env.example"
|
||||
fi
|
||||
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
|
||||
mkdir -p "$DB_INIT_DIR" "$OTA_DIR"
|
||||
|
||||
cat >"$CRASH_CONFIG" <<EOF
|
||||
<?php
|
||||
return [
|
||||
'app_name' => 'Android Cast Crashes',
|
||||
'base_path' => '/app/androidcast_project/crashes',
|
||||
'session_name' => 'ac_crash_sess',
|
||||
'session_cookie_path' => '/app/androidcast_project',
|
||||
'db' => [
|
||||
'driver' => 'mysql',
|
||||
'sqlite_path' => __DIR__ . '/../data/crashes.sqlite',
|
||||
'mysql' => [
|
||||
'host' => 'mariadb',
|
||||
'port' => 3306,
|
||||
'socket' => '',
|
||||
'database' => '${MARIADB_DATABASE}',
|
||||
'username' => '${MARIADB_USER}',
|
||||
'password' => '${MARIADB_PASSWORD}',
|
||||
'charset' => 'utf8mb4',
|
||||
],
|
||||
],
|
||||
'debug' => true,
|
||||
'rbac' => [
|
||||
'default_company_slug' => 'default',
|
||||
'default_company_id' => 1,
|
||||
],
|
||||
];
|
||||
EOF
|
||||
|
||||
cat >"$BUILD_CONFIG" <<EOF
|
||||
<?php
|
||||
return [
|
||||
'app_name' => 'Android Cast Builder',
|
||||
'base_path' => '/app/androidcast_project/build',
|
||||
'session_name' => 'ac_crash_sess',
|
||||
'session_cookie_path' => '/app/androidcast_project',
|
||||
'db' => [
|
||||
'driver' => 'mysql',
|
||||
'sqlite_path' => __DIR__ . '/../../crash_reporter/backend/data/crashes.sqlite',
|
||||
'mysql' => [
|
||||
'host' => 'mariadb',
|
||||
'port' => 3306,
|
||||
'socket' => '',
|
||||
'database' => '${MARIADB_DATABASE}',
|
||||
'username' => '${MARIADB_USER}',
|
||||
'password' => '${MARIADB_PASSWORD}',
|
||||
'charset' => 'utf8mb4',
|
||||
],
|
||||
],
|
||||
'debug' => true,
|
||||
'rbac' => [
|
||||
'default_company_slug' => 'default',
|
||||
'default_company_id' => 1,
|
||||
],
|
||||
'build' => [
|
||||
'docker_image' => 'androidcast-ci:latest',
|
||||
'ci_version' => '00.01.00.1000',
|
||||
'repo_root' => '/workspace',
|
||||
'artifacts_root' => '/workspace/out/builds',
|
||||
'ota_mount' => '/workspace/orchestration/runtime/ota-artifacts',
|
||||
'ota_base_url' => '${PUBLIC_BASE_URL}',
|
||||
'runner_script' => '/workspace/scripts/docker-build-runner.sh',
|
||||
'pipeline_config' => '/workspace/build.config.yml',
|
||||
'default_channels' => ['stable', 'staging', 'dev', 'nightly'],
|
||||
],
|
||||
'links' => [
|
||||
'hub' => '/app/androidcast_project/',
|
||||
'crashes' => '/app/androidcast_project/crashes/',
|
||||
'git' => '/app/androidcast_project/git/',
|
||||
'tickets' => '/app/androidcast_project/crashes/?view=tickets',
|
||||
],
|
||||
];
|
||||
EOF
|
||||
|
||||
SRC_MIG_070="$ROOT_DIR/runtime/db-init/070_graph_ecosystem.sql"
|
||||
SRC_MIG_007="$ROOT_DIR/runtime/db-init/007_remote_access.sql"
|
||||
|
||||
write_migration_with_db() {
|
||||
local src="$1"
|
||||
local dest="$2"
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
{
|
||||
echo "USE \`${MARIADB_DATABASE}\`;"
|
||||
cat "$src"
|
||||
} >"$tmp"
|
||||
mv "$tmp" "$dest"
|
||||
}
|
||||
|
||||
cp "$SRC_SCHEMA" "$DB_INIT_DIR/010_schema.sql"
|
||||
write_migration_with_db "$SRC_MIG_004" "$DB_INIT_DIR/040_ticket_workflow.sql"
|
||||
write_migration_with_db "$SRC_MIG_005" "$DB_INIT_DIR/050_ticket_attachments.sql"
|
||||
write_migration_with_db "$SRC_MIG_060" "$DB_INIT_DIR/060_build_ecosystem.sql"
|
||||
write_migration_with_db "$SRC_MIG_070" "$DB_INIT_DIR/070_graph_ecosystem.sql"
|
||||
write_migration_with_db "$SRC_MIG_007" "$DB_INIT_DIR/007_remote_access.sql"
|
||||
|
||||
echo "Pulling images..."
|
||||
docker compose -f "$ROOT_DIR/docker-compose.yml" pull landing-fe gitea-fe crashes-fe build-fe mariadb gitea || true
|
||||
|
||||
echo "Starting/updating stack..."
|
||||
docker compose -f "$ROOT_DIR/docker-compose.yml" up -d --build --remove-orphans --force-recreate
|
||||
|
||||
echo
|
||||
echo "Stack is up."
|
||||
echo "Landing: ${PUBLIC_BASE_URL}/app/androidcast_project/"
|
||||
echo "Gitea (direct FE): http://localhost:${GITEA_FE_PORT}/app/androidcast_project/git/"
|
||||
echo "Crashes (direct FE): http://localhost:${CRASHES_FE_PORT}/app/androidcast_project/crashes/"
|
||||
echo "Graphs (via landing): ${PUBLIC_BASE_URL}/app/androidcast_project/graphs/"
|
||||
echo "Builder (direct FE): http://localhost:${BUILD_FE_PORT:-8083}/app/androidcast_project/build/"
|
||||
echo "Shared login: same user/password as Crashes (session cookie path /app/androidcast_project)"
|
||||
echo
|
||||
docker compose -f "$ROOT_DIR/docker-compose.yml" ps
|
||||
125
docker-compose.yml
Normal file
125
docker-compose.yml
Normal file
@@ -0,0 +1,125 @@
|
||||
services:
|
||||
landing-fe:
|
||||
image: nginx:1.27-alpine
|
||||
depends_on:
|
||||
- gitea-fe
|
||||
- crashes-fe
|
||||
- build-fe
|
||||
ports:
|
||||
- "${LANDING_HTTP_PORT}:8080"
|
||||
volumes:
|
||||
- ../:/workspace:ro
|
||||
- ./nginx/landing.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
networks:
|
||||
- appnet
|
||||
restart: unless-stopped
|
||||
|
||||
gitea-fe:
|
||||
image: nginx:1.27-alpine
|
||||
depends_on:
|
||||
- gitea
|
||||
ports:
|
||||
- "${GITEA_FE_PORT}:8080"
|
||||
volumes:
|
||||
- ./nginx/gitea-fe.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
networks:
|
||||
- appnet
|
||||
restart: unless-stopped
|
||||
|
||||
crashes-fe:
|
||||
image: nginx:1.27-alpine
|
||||
depends_on:
|
||||
- crash-php
|
||||
ports:
|
||||
- "${CRASHES_FE_PORT}:8080"
|
||||
volumes:
|
||||
- ../:/workspace
|
||||
- ./nginx/crashes-fe.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
networks:
|
||||
- appnet
|
||||
restart: unless-stopped
|
||||
|
||||
build-fe:
|
||||
image: nginx:1.27-alpine
|
||||
depends_on:
|
||||
- build-php
|
||||
ports:
|
||||
- "${BUILD_FE_PORT}:8080"
|
||||
volumes:
|
||||
- ../:/workspace
|
||||
- ./nginx/build-fe.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
networks:
|
||||
- appnet
|
||||
restart: unless-stopped
|
||||
|
||||
build-php:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: images/build-php-fpm/Dockerfile
|
||||
depends_on:
|
||||
- mariadb
|
||||
volumes:
|
||||
- ../:/workspace
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./runtime/build-config.php:/workspace/examples/build_console/backend/config/config.php
|
||||
- ./runtime/ota-artifacts:/workspace/orchestration/runtime/ota-artifacts
|
||||
networks:
|
||||
- appnet
|
||||
restart: unless-stopped
|
||||
|
||||
crash-php:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: images/crash-php-fpm/Dockerfile
|
||||
depends_on:
|
||||
- mariadb
|
||||
volumes:
|
||||
- ../:/workspace
|
||||
- ./runtime/crash-config.php:/workspace/examples/crash_reporter/backend/config/config.php
|
||||
networks:
|
||||
- appnet
|
||||
restart: unless-stopped
|
||||
|
||||
mariadb:
|
||||
image: mariadb:11.4
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: "${MARIADB_ROOT_PASSWORD}"
|
||||
MARIADB_DATABASE: "${MARIADB_DATABASE}"
|
||||
MARIADB_USER: "${MARIADB_USER}"
|
||||
MARIADB_PASSWORD: "${MARIADB_PASSWORD}"
|
||||
command: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"]
|
||||
volumes:
|
||||
- mariadb-data:/var/lib/mysql
|
||||
- ./runtime/db-init:/docker-entrypoint-initdb.d:ro
|
||||
networks:
|
||||
- appnet
|
||||
restart: unless-stopped
|
||||
|
||||
gitea:
|
||||
image: gitea/gitea:1.22
|
||||
environment:
|
||||
USER_UID: "1000"
|
||||
USER_GID: "1000"
|
||||
GITEA__server__ROOT_URL: "http://localhost:${GITEA_FE_PORT}/"
|
||||
GITEA__server__DOMAIN: "localhost"
|
||||
GITEA__server__HTTP_PORT: "3000"
|
||||
GITEA__server__PROTOCOL: "http"
|
||||
GITEA__security__INSTALL_LOCK: "true"
|
||||
GITEA__service__DISABLE_REGISTRATION: "true"
|
||||
GITEA__database__DB_TYPE: "sqlite3"
|
||||
volumes:
|
||||
- gitea-data:/data
|
||||
networks:
|
||||
- appnet
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
appnet:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 10.203.0.0/24
|
||||
|
||||
volumes:
|
||||
mariadb-data:
|
||||
gitea-data:
|
||||
7
images/build-php-fpm/Dockerfile
Normal file
7
images/build-php-fpm/Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
||||
FROM php:8.2-fpm-alpine
|
||||
|
||||
RUN apk add --no-cache bash docker-cli icu-dev libzip-dev oniguruma-dev mariadb-client sqlite-dev \
|
||||
&& docker-php-ext-install pdo pdo_mysql pdo_sqlite \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
WORKDIR /workspace/examples/build_console/backend
|
||||
7
images/crash-php-fpm/Dockerfile
Normal file
7
images/crash-php-fpm/Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
||||
FROM php:8.2-fpm-alpine
|
||||
|
||||
RUN apk add --no-cache bash icu-dev libzip-dev oniguruma-dev mariadb-client sqlite-dev \
|
||||
&& docker-php-ext-install pdo pdo_mysql pdo_sqlite \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
WORKDIR /workspace/examples/crash_reporter/backend
|
||||
3
nginx-seeds/README.md
Normal file
3
nginx-seeds/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# nginx-seeds
|
||||
|
||||
Nginx fragments from monolith examples/crash_reporter/backend/.
|
||||
17
nginx-seeds/nginx.apps-builder.frag
Normal file
17
nginx-seeds/nginx.apps-builder.frag
Normal file
@@ -0,0 +1,17 @@
|
||||
location = /app/androidcast_project/build {
|
||||
return 301 /app/androidcast_project/build/;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/build/assets/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/build_console/backend/public/assets/;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/build/ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/build_console/backend/public/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/build/index.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 512m;
|
||||
fastcgi_read_timeout 1800s;
|
||||
}
|
||||
134
nginx-seeds/nginx.apps-port80.fragment
Normal file
134
nginx-seeds/nginx.apps-port80.fragment
Normal file
@@ -0,0 +1,134 @@
|
||||
# Full androidcast vhost for Alpine BE — listen 80 only.
|
||||
# FE (apps.f0xx.org) proxies here: proxy_pass http://artc0.intra.raptor.org:80;
|
||||
# Port 8089 is a different service (e.g. Janus) — not this vhost.
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
# OTA v0 static tree — devices fetch https://apps.f0xx.org/v0/ota/channel/stable.json
|
||||
# Populate: builder auto_deploy, or rsync out/ota/v0/ → .../ota-artifacts/v0/
|
||||
location ^~ /v0/ota/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/ota-artifacts/v0/ota/;
|
||||
add_header Cache-Control "public, max-age=60";
|
||||
}
|
||||
|
||||
location = /app/androidcast_project {
|
||||
return 301 /app/androidcast_project/;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/index.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/index.php;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/ {
|
||||
rewrite ^ /app/androidcast_project/index.php last;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/;
|
||||
index index.php index.html;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/issues {
|
||||
return 301 /app/androidcast_project/issues/;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/crashes {
|
||||
return 301 /app/androidcast_project/issues/;
|
||||
}
|
||||
|
||||
location ~ ^/app/androidcast_project/crashes(/.*)?$ {
|
||||
return 301 /app/androidcast_project/issues$1;
|
||||
}
|
||||
|
||||
location ~ ^/app/androidcast_project/(login|logout|register|two-factor|verify-email)(/|$) {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/issues/index.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/issues/assets/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/assets/;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/issues/api/upload.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/upload.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/issues/api/upload.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 4m;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/issues/ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/issues/index.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 4m;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/graphs {
|
||||
return 301 /app/androidcast_project/graphs/;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/graphs/assets/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/assets/;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/graphs/api/graphs.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/graphs.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/graphs/api/graphs.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/graphs/api/graph_upload.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/graph_upload.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/graphs/api/graph_upload.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 4m;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/graphs/ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/graphs/index.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 4m;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/build {
|
||||
return 301 /app/androidcast_project/build/;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/build/assets/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/build_console/backend/public/assets/;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/build/ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/build_console/backend/public/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/build/index.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 512m;
|
||||
fastcgi_read_timeout 1800s;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
include /etc/nginx/fastcgi.conf;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
}
|
||||
}
|
||||
117
nginx-seeds/nginx.apps.conf.fragment
Normal file
117
nginx-seeds/nginx.apps.conf.fragment
Normal file
@@ -0,0 +1,117 @@
|
||||
# Paste into the listen 443 (and 80) server {} in /etc/nginx/conf.d/apps.conf
|
||||
# Replaces old per-URL blocks + any backend symlink paths.
|
||||
|
||||
location ^~ /v0/ota/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/ota-artifacts/v0/ota/;
|
||||
add_header Cache-Control "public, max-age=60";
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/crashes {
|
||||
return 301 /app/androidcast_project/crashes/;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/graphs {
|
||||
return 301 /app/androidcast_project/graphs/;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project {
|
||||
return 301 /app/androidcast_project/;
|
||||
}
|
||||
|
||||
# Hub — see nginx.apps-port80.fragment (index.php + shared footer).
|
||||
location = /app/androidcast_project/index.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/index.php;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/ {
|
||||
rewrite ^ /app/androidcast_project/index.php last;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/;
|
||||
index index.php index.html;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/crashes/assets/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/assets/;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/crashes/api/upload.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/upload.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/crashes/api/upload.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 4m;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/crashes/api/diag.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/diag.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/crashes/api/diag.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/crashes/api/reports.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/reports.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/crashes/api/reports.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/crashes/api/report_viewed.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/report_viewed.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/crashes/api/report_viewed.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/crashes/api/report_tags.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/report_tags.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/crashes/api/report_tags.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/crashes/ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/crashes/index.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 4m;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/graphs/api/graphs.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/graphs.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/graphs/api/graphs.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/graphs/api/graph_upload.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/graph_upload.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/graphs/api/graph_upload.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 4m;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/graphs/ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php-fpm.socket;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/graphs/index.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 4m;
|
||||
}
|
||||
30
nginx-seeds/nginx.fe-apps.f0xx.org.snippet
Normal file
30
nginx-seeds/nginx.fe-apps.f0xx.org.snippet
Normal file
@@ -0,0 +1,30 @@
|
||||
# FE apps.f0xx.org — replace hub/crashes upstream blocks (use BE port 80, preserve URI).
|
||||
# OTA at /v0/ota/ must proxy to BE :80 even when hub uses :443 — see nginx.fe-ota.snippet
|
||||
# and scripts/install-fe-ota-nginx.sh (public URL 400 until this is applied on FE).
|
||||
#
|
||||
# File: tmp/FE_gentoo/etc/nginx/nginx.conf (inside server { listen 443; server_name apps.f0xx.org; })
|
||||
#
|
||||
# WRONG (causes browser to download index.php as application/octet-stream):
|
||||
# proxy_pass https://artc0.intra.raptor.org/app/androidcast_project/;
|
||||
# BE :443 serves static files; hub must hit BE :80 (PHP-FPM) like location / already does.
|
||||
|
||||
location /app/androidcast_project/ {
|
||||
proxy_pass http://artc0.intra.raptor.org:80;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/crashes/ {
|
||||
proxy_pass http://artc0.intra.raptor.org:80;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# apps.conf build block must stay:
|
||||
# proxy_pass http://artc0.intra.raptor.org:80; (no trailing slash)
|
||||
20
nginx-seeds/rssh-bastion-stream.conf.example
Normal file
20
nginx-seeds/rssh-bastion-stream.conf.example
Normal file
@@ -0,0 +1,20 @@
|
||||
# FE nginx stream — TCP proxy to BE sshd for RSSH bastion (ra.apps.f0xx.org:443).
|
||||
# Install on FE (Gentoo); BE runs openssh with Match User ra-* (see sshd_config.d/ra.conf).
|
||||
#
|
||||
# *** DO NOT install under BE /etc/nginx/stream.d/ ***
|
||||
# BE already uses listen 443 ssl in conf.d/apps.conf (http). Stream listen 443 here
|
||||
# causes: nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address in use)
|
||||
#
|
||||
# Include from nginx.conf:
|
||||
# include /etc/nginx/stream.d/rssh-bastion.conf;
|
||||
|
||||
upstream rssh_bastion_be {
|
||||
server 10.7.16.128:22; # artc0 / alpine-be — adjust for cluster/lab
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
proxy_pass rssh_bastion_be;
|
||||
proxy_connect_timeout 30s;
|
||||
proxy_timeout 24h;
|
||||
}
|
||||
39
nginx/build-fe.conf
Normal file
39
nginx/build-fe.conf
Normal file
@@ -0,0 +1,39 @@
|
||||
server {
|
||||
listen 8080;
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 64m;
|
||||
index index.php;
|
||||
|
||||
location = / {
|
||||
return 302 /app/androidcast_project/build/;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/build/ {
|
||||
alias /workspace/examples/build_console/backend/public/;
|
||||
index index.php;
|
||||
try_files $uri $uri/ @build_front;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/crashes/assets/ {
|
||||
alias /workspace/examples/crash_reporter/backend/public/assets/;
|
||||
}
|
||||
|
||||
location @build_front {
|
||||
rewrite ^ /app/androidcast_project/build/index.php?$query_string last;
|
||||
}
|
||||
|
||||
location ~ ^/app/androidcast_project/build/(.+\.php)$ {
|
||||
alias /workspace/examples/build_console/backend/public/$1;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /workspace/examples/build_console/backend/public/$1;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/build/$1;
|
||||
fastcgi_param DOCUMENT_ROOT /workspace/examples/build_console/backend/public;
|
||||
fastcgi_pass build-php:9000;
|
||||
fastcgi_read_timeout 300s;
|
||||
}
|
||||
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
72
nginx/crashes-fe.conf
Normal file
72
nginx/crashes-fe.conf
Normal file
@@ -0,0 +1,72 @@
|
||||
server {
|
||||
listen 8080;
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 16m;
|
||||
index index.php;
|
||||
|
||||
location = / {
|
||||
return 302 /app/androidcast_project/crashes/;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/graphs {
|
||||
return 302 /app/androidcast_project/graphs/;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/graphs/assets/ {
|
||||
alias /workspace/examples/crash_reporter/backend/public/assets/;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/graphs/api/graphs.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /workspace/examples/crash_reporter/backend/public/api/graphs.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/graphs/api/graphs.php;
|
||||
fastcgi_param DOCUMENT_ROOT /workspace/examples/crash_reporter/backend/public;
|
||||
fastcgi_pass crash-php:9000;
|
||||
fastcgi_read_timeout 120s;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/graphs/api/graph_upload.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /workspace/examples/crash_reporter/backend/public/api/graph_upload.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/graphs/api/graph_upload.php;
|
||||
fastcgi_param DOCUMENT_ROOT /workspace/examples/crash_reporter/backend/public;
|
||||
fastcgi_pass crash-php:9000;
|
||||
client_max_body_size 4m;
|
||||
fastcgi_read_timeout 120s;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/graphs/ {
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /workspace/examples/crash_reporter/backend/public/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/graphs/index.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
fastcgi_param DOCUMENT_ROOT /workspace/examples/crash_reporter/backend/public;
|
||||
fastcgi_pass crash-php:9000;
|
||||
fastcgi_read_timeout 120s;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/crashes/ {
|
||||
alias /workspace/examples/crash_reporter/backend/public/;
|
||||
index index.php;
|
||||
try_files $uri $uri/ @crashes_front;
|
||||
}
|
||||
|
||||
location @crashes_front {
|
||||
rewrite ^ /app/androidcast_project/crashes/index.php?$query_string last;
|
||||
}
|
||||
|
||||
location ~ ^/app/androidcast_project/crashes/(.+\.php)$ {
|
||||
alias /workspace/examples/crash_reporter/backend/public/$1;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /workspace/examples/crash_reporter/backend/public/$1;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/crashes/$1;
|
||||
fastcgi_param DOCUMENT_ROOT /workspace/examples/crash_reporter/backend/public;
|
||||
fastcgi_pass crash-php:9000;
|
||||
fastcgi_read_timeout 120s;
|
||||
}
|
||||
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
33
nginx/gitea-fe.conf
Normal file
33
nginx/gitea-fe.conf
Normal file
@@ -0,0 +1,33 @@
|
||||
server {
|
||||
listen 8080;
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 64m;
|
||||
|
||||
location /app/androidcast_project/git/ {
|
||||
rewrite ^/app/androidcast_project/git/?(.*)$ /$1 break;
|
||||
proxy_pass http://gitea:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://gitea:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
63
nginx/landing.conf
Normal file
63
nginx/landing.conf
Normal file
@@ -0,0 +1,63 @@
|
||||
server {
|
||||
listen 8080;
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 16m;
|
||||
|
||||
location = / {
|
||||
return 302 /app/androidcast_project/;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/git/ {
|
||||
return 302 http://localhost:8081/;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/crashes/ {
|
||||
proxy_pass http://crashes-fe:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/graphs/ {
|
||||
proxy_pass http://crashes-fe:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/build/ {
|
||||
proxy_pass http://build-fe:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/v0/ota/ {
|
||||
alias /workspace/orchestration/runtime/ota-artifacts/v0/;
|
||||
add_header Cache-Control "public, max-age=60";
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/index.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /workspace/examples/app_hub/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/index.php;
|
||||
fastcgi_pass crash-php:9000;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/ {
|
||||
rewrite ^ /app/androidcast_project/index.php last;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/ {
|
||||
alias /workspace/examples/app_hub/;
|
||||
index index.php index.html;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
}
|
||||
1
runtime/db-init/.gitkeep
Normal file
1
runtime/db-init/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
56
runtime/db-init/007_remote_access.sql
Normal file
56
runtime/db-init/007_remote_access.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
-- Remote access control plane (WireGuard v1; reverse SSH reserved).
|
||||
-- MariaDB / production: mysql -u root -p androidcast_crashes < sql/migrations/007_remote_access.sql
|
||||
|
||||
CREATE TABLE IF NOT EXISTS remote_access_devices (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
device_id VARCHAR(128) NOT NULL,
|
||||
company_id INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
whitelisted TINYINT(1) NOT NULL DEFAULT 0,
|
||||
opt_in_mode ENUM('none','wireguard','rssh') NOT NULL DEFAULT 'none',
|
||||
last_random VARCHAR(96) NULL,
|
||||
app_version VARCHAR(64) NULL,
|
||||
last_seen_at TIMESTAMP NULL,
|
||||
notes TEXT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_ra_device (device_id),
|
||||
KEY idx_ra_device_company (company_id),
|
||||
KEY idx_ra_device_whitelist (whitelisted, opt_in_mode)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS remote_access_sessions (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
session_id VARCHAR(36) NOT NULL,
|
||||
device_id VARCHAR(128) NOT NULL,
|
||||
company_id INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
operator_user_id INT UNSIGNED NULL,
|
||||
status ENUM('pending','active','inactive','closed','closed_timeout') NOT NULL DEFAULT 'pending',
|
||||
tunnel ENUM('wireguard','ssh_reverse') NOT NULL DEFAULT 'wireguard',
|
||||
endpoint VARCHAR(255) NULL,
|
||||
wg_client_private_key VARCHAR(64) NULL,
|
||||
wg_client_address VARCHAR(64) NULL,
|
||||
wg_server_public_key VARCHAR(64) NULL,
|
||||
wg_peer_allowed_ips VARCHAR(255) NULL,
|
||||
expires_at INT UNSIGNED NULL,
|
||||
last_activity_at TIMESTAMP NULL,
|
||||
close_reason VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
closed_at TIMESTAMP NULL,
|
||||
UNIQUE KEY uq_ra_session (session_id),
|
||||
KEY idx_ra_sess_device (device_id, status),
|
||||
KEY idx_ra_sess_status (status, last_activity_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS remote_access_events (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
session_id VARCHAR(36) NULL,
|
||||
device_id VARCHAR(128) NOT NULL,
|
||||
actor_user_id INT UNSIGNED NULL,
|
||||
action VARCHAR(64) NOT NULL,
|
||||
reason VARCHAR(255) NULL,
|
||||
meta_json LONGTEXT NULL,
|
||||
client_ip VARCHAR(64) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY idx_ra_evt_device (device_id, created_at),
|
||||
KEY idx_ra_evt_session (session_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
50
runtime/db-init/007_remote_access.sqlite.sql
Normal file
50
runtime/db-init/007_remote_access.sqlite.sql
Normal file
@@ -0,0 +1,50 @@
|
||||
-- SQLite dev mirror for remote access tables.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS remote_access_devices (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
device_id TEXT NOT NULL UNIQUE,
|
||||
company_id INTEGER NOT NULL DEFAULT 1,
|
||||
whitelisted INTEGER NOT NULL DEFAULT 0,
|
||||
opt_in_mode TEXT NOT NULL DEFAULT 'none',
|
||||
last_random TEXT NULL,
|
||||
app_version TEXT NULL,
|
||||
last_seen_at TEXT NULL,
|
||||
notes TEXT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS remote_access_sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL UNIQUE,
|
||||
device_id TEXT NOT NULL,
|
||||
company_id INTEGER NOT NULL DEFAULT 1,
|
||||
operator_user_id INTEGER NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
tunnel TEXT NOT NULL DEFAULT 'wireguard',
|
||||
endpoint TEXT NULL,
|
||||
wg_client_private_key TEXT NULL,
|
||||
wg_client_address TEXT NULL,
|
||||
wg_server_public_key TEXT NULL,
|
||||
wg_peer_allowed_ips TEXT NULL,
|
||||
expires_at INTEGER NULL,
|
||||
last_activity_at TEXT NULL,
|
||||
close_reason TEXT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
closed_at TEXT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS remote_access_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NULL,
|
||||
device_id TEXT NOT NULL,
|
||||
actor_user_id INTEGER NULL,
|
||||
action TEXT NOT NULL,
|
||||
reason TEXT NULL,
|
||||
meta_json TEXT NULL,
|
||||
client_ip TEXT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ra_evt_device ON remote_access_events(device_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_ra_sess_device ON remote_access_sessions(device_id, status);
|
||||
127
runtime/db-init/010_schema.sql
Normal file
127
runtime/db-init/010_schema.sql
Normal file
@@ -0,0 +1,127 @@
|
||||
-- Fresh MariaDB install for Android Cast crash reporter.
|
||||
-- Run as root: mysql -u root -p < sql/schema.mariadb.sql
|
||||
-- App user needs: SELECT, INSERT, UPDATE, DELETE on androidcast_crashes.*
|
||||
|
||||
CREATE DATABASE IF NOT EXISTS androidcast_crashes
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE androidcast_crashes;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS companies (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
slug VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_companies_slug (slug)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(64) NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
role ENUM('root', 'platform_admin', 'admin', 'viewer') NOT NULL DEFAULT 'viewer',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_users_username (username)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS company_memberships (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT UNSIGNED NOT NULL,
|
||||
company_id INT UNSIGNED NOT NULL,
|
||||
role ENUM('owner', 'admin', 'member') NOT NULL DEFAULT 'member',
|
||||
permissions_json LONGTEXT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_membership_user_company (user_id, company_id),
|
||||
KEY idx_membership_company (company_id),
|
||||
CONSTRAINT fk_membership_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_membership_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS devices (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
company_id INT UNSIGNED NOT NULL,
|
||||
external_id VARCHAR(128) NOT NULL,
|
||||
display_name VARCHAR(256) NULL,
|
||||
manufacturer VARCHAR(128) NULL,
|
||||
model VARCHAR(128) NULL,
|
||||
source ENUM('auto', 'manual', 'barcode') NOT NULL DEFAULT 'auto',
|
||||
registered_at_ms BIGINT NOT NULL,
|
||||
last_seen_at_ms BIGINT NOT NULL,
|
||||
meta_json LONGTEXT NULL,
|
||||
UNIQUE KEY uq_devices_company_external (company_id, external_id),
|
||||
KEY idx_devices_company (company_id),
|
||||
CONSTRAINT fk_devices_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS reports (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
company_id INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
device_id BIGINT UNSIGNED NULL,
|
||||
report_id VARCHAR(128) NOT NULL,
|
||||
fingerprint VARCHAR(64) NOT NULL,
|
||||
crash_type VARCHAR(32) NOT NULL,
|
||||
generated_at_ms BIGINT NOT NULL,
|
||||
received_at_ms BIGINT NOT NULL,
|
||||
device_model VARCHAR(128) NULL,
|
||||
app_version VARCHAR(64) NULL,
|
||||
payload_json LONGTEXT NOT NULL,
|
||||
tags_json LONGTEXT NOT NULL,
|
||||
UNIQUE KEY uq_reports_report_id (report_id),
|
||||
KEY idx_reports_company (company_id),
|
||||
KEY idx_reports_device (device_id),
|
||||
KEY idx_reports_generated (generated_at_ms),
|
||||
KEY idx_reports_received (received_at_ms),
|
||||
KEY idx_reports_fingerprint (fingerprint),
|
||||
KEY idx_reports_fp_received (fingerprint, received_at_ms),
|
||||
CONSTRAINT fk_reports_company FOREIGN KEY (company_id) REFERENCES companies (id),
|
||||
CONSTRAINT fk_reports_device FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS report_views (
|
||||
user_id INT UNSIGNED NOT NULL,
|
||||
report_id BIGINT UNSIGNED NOT NULL,
|
||||
viewed_at_ms BIGINT NOT NULL,
|
||||
PRIMARY KEY (user_id, report_id),
|
||||
CONSTRAINT fk_report_views_report
|
||||
FOREIGN KEY (report_id) REFERENCES reports (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tickets (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
company_id INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
device_id BIGINT UNSIGNED NULL,
|
||||
ticket_id VARCHAR(128) NOT NULL,
|
||||
title VARCHAR(256) NOT NULL,
|
||||
brief TEXT NULL,
|
||||
body LONGTEXT NOT NULL,
|
||||
opened_at_ms BIGINT NOT NULL,
|
||||
created_at_ms BIGINT NOT NULL,
|
||||
updated_at_ms BIGINT NOT NULL,
|
||||
owner_username VARCHAR(64) NOT NULL DEFAULT '',
|
||||
verified_by_username VARCHAR(64) NULL,
|
||||
device_model VARCHAR(128) NULL,
|
||||
app_package VARCHAR(128) NULL,
|
||||
app_version VARCHAR(64) NULL,
|
||||
os_name VARCHAR(64) NULL,
|
||||
os_version VARCHAR(128) NULL,
|
||||
rating TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
tags_json LONGTEXT NOT NULL,
|
||||
payload_json LONGTEXT NOT NULL,
|
||||
UNIQUE KEY uq_tickets_ticket_id (ticket_id),
|
||||
KEY idx_tickets_company (company_id),
|
||||
KEY idx_tickets_opened (opened_at_ms),
|
||||
KEY idx_tickets_updated (updated_at_ms),
|
||||
KEY idx_tickets_owner (owner_username),
|
||||
CONSTRAINT fk_tickets_company FOREIGN KEY (company_id) REFERENCES companies (id),
|
||||
CONSTRAINT fk_tickets_device FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
INSERT IGNORE INTO companies (id, slug, name) VALUES (1, 'default', 'Default');
|
||||
|
||||
INSERT IGNORE INTO users (id, username, password_hash, role)
|
||||
VALUES (1, 'admin', '$2y$10$t2JyYCyjBwRxAHfmLwsnG.APoj/in0i6nxpKcQggdl.AkhFJR13o.', 'root');
|
||||
-- default password: admin — change in production
|
||||
|
||||
INSERT IGNORE INTO company_memberships (user_id, company_id, role)
|
||||
VALUES (1, 1, 'owner');
|
||||
19
runtime/db-init/040_ticket_workflow.sql
Normal file
19
runtime/db-init/040_ticket_workflow.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
USE `androidcast_crashes`;
|
||||
-- Ticket workflow, assignees, comments. Run as MySQL root on existing DBs.
|
||||
-- mysql -u root -p androidcast_crashes < sql/migrations/004_ticket_workflow.sql
|
||||
|
||||
ALTER TABLE tickets
|
||||
ADD COLUMN workflow_state VARCHAR(32) NOT NULL DEFAULT 'triage' AFTER tags_json,
|
||||
ADD COLUMN assignees_json LONGTEXT NOT NULL DEFAULT '[]' AFTER workflow_state;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ticket_comments (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
ticket_id BIGINT UNSIGNED NOT NULL,
|
||||
author_username VARCHAR(64) NOT NULL,
|
||||
body LONGTEXT NOT NULL,
|
||||
created_at_ms BIGINT NOT NULL,
|
||||
updated_at_ms BIGINT NOT NULL,
|
||||
KEY idx_ticket_comments_ticket (ticket_id),
|
||||
KEY idx_ticket_comments_created (created_at_ms),
|
||||
CONSTRAINT fk_ticket_comments_ticket FOREIGN KEY (ticket_id) REFERENCES tickets (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
21
runtime/db-init/050_ticket_attachments.sql
Normal file
21
runtime/db-init/050_ticket_attachments.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
USE `androidcast_crashes`;
|
||||
-- Ticket attachments: uploaded files + external links (incl. Google URLs).
|
||||
-- mysql -u root -p androidcast_crashes < sql/migrations/005_ticket_attachments.sql
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ticket_attachments (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
ticket_id BIGINT UNSIGNED NOT NULL,
|
||||
kind ENUM('file', 'link') NOT NULL,
|
||||
title VARCHAR(256) NOT NULL DEFAULT '',
|
||||
external_url TEXT NULL,
|
||||
provider VARCHAR(32) NOT NULL DEFAULT 'generic',
|
||||
storage_name VARCHAR(128) NULL,
|
||||
original_name VARCHAR(256) NULL,
|
||||
mime_type VARCHAR(128) NULL,
|
||||
size_bytes BIGINT UNSIGNED NULL,
|
||||
created_by VARCHAR(64) NOT NULL,
|
||||
created_at_ms BIGINT NOT NULL,
|
||||
KEY idx_ticket_attachments_ticket (ticket_id),
|
||||
KEY idx_ticket_attachments_created (created_at_ms),
|
||||
CONSTRAINT fk_ticket_attachments_ticket FOREIGN KEY (ticket_id) REFERENCES tickets (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
31
runtime/db-init/060_build_ecosystem.sql
Normal file
31
runtime/db-init/060_build_ecosystem.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
USE `androidcast_crashes`;
|
||||
CREATE TABLE IF NOT EXISTS builds (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
build_code VARCHAR(64) NOT NULL,
|
||||
status ENUM('queued','running','passed','failed','cancelled') NOT NULL DEFAULT 'queued',
|
||||
phase VARCHAR(64) NOT NULL DEFAULT 'init',
|
||||
git_ref VARCHAR(256) NULL,
|
||||
git_sha VARCHAR(64) NULL,
|
||||
branch VARCHAR(128) NULL,
|
||||
pipeline_yaml LONGTEXT NULL,
|
||||
params_json LONGTEXT NOT NULL,
|
||||
dockerfile_version VARCHAR(32) NOT NULL DEFAULT '00.01.00.1000',
|
||||
builder_id VARCHAR(128) NOT NULL DEFAULT 'local-docker',
|
||||
version_app VARCHAR(32) NULL,
|
||||
version_code INT NULL,
|
||||
target_os VARCHAR(32) NOT NULL DEFAULT 'android',
|
||||
min_api_level INT NOT NULL DEFAULT 24,
|
||||
ota_channel VARCHAR(64) NULL,
|
||||
auto_ota TINYINT(1) NOT NULL DEFAULT 0,
|
||||
auto_deploy TINYINT(1) NOT NULL DEFAULT 0,
|
||||
log_path VARCHAR(512) NULL,
|
||||
artifacts_json LONGTEXT NULL,
|
||||
error_message VARCHAR(512) NULL,
|
||||
created_by_user_id INT UNSIGNED NULL,
|
||||
started_at TIMESTAMP NULL,
|
||||
finished_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_builds_code (build_code),
|
||||
KEY idx_builds_status (status),
|
||||
KEY idx_builds_created (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
28
runtime/db-init/070_graph_ecosystem.sql
Normal file
28
runtime/db-init/070_graph_ecosystem.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
USE `androidcast_crashes`;
|
||||
CREATE TABLE IF NOT EXISTS graph_sessions (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
session_id VARCHAR(96) NOT NULL,
|
||||
company_id INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
user_id INT UNSIGNED NULL,
|
||||
device_id VARCHAR(128) NULL,
|
||||
app_version VARCHAR(64) NULL,
|
||||
sdk_int INT NULL,
|
||||
started_at_ms BIGINT NOT NULL,
|
||||
ended_at_ms BIGINT NOT NULL,
|
||||
duration_s INT NOT NULL,
|
||||
completed TINYINT(1) NOT NULL DEFAULT 1,
|
||||
direction ENUM('sender','receiver') NOT NULL DEFAULT 'sender',
|
||||
transport VARCHAR(24) NOT NULL DEFAULT 'wifi',
|
||||
avg_kbps INT NOT NULL DEFAULT 0,
|
||||
recv_kbps INT NOT NULL DEFAULT 0,
|
||||
packet_loss_pct DECIMAL(6,3) NOT NULL DEFAULT 0.0,
|
||||
ntp_source VARCHAR(32) NOT NULL DEFAULT 'device',
|
||||
ntp_correction_s INT NULL,
|
||||
install_source VARCHAR(32) NOT NULL DEFAULT 'direct',
|
||||
meta_json LONGTEXT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_graph_session_id (session_id),
|
||||
KEY idx_graph_company_time (company_id, started_at_ms),
|
||||
KEY idx_graph_device (device_id),
|
||||
KEY idx_graph_app_version (app_version)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
34
scripts/apply-hosts.sh
Executable file
34
scripts/apply-hosts.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
MODE="${1:-add}"
|
||||
HOSTS_FILE="/etc/hosts"
|
||||
BEGIN_MARK="# >>> androidcast-local >>>"
|
||||
END_MARK="# <<< androidcast-local <<<"
|
||||
|
||||
BLOCK=$(cat <<'EOF'
|
||||
# >>> androidcast-local >>>
|
||||
127.0.0.1 apps.androidcast.local
|
||||
127.0.0.1 git.androidcast.local
|
||||
127.0.0.1 crashes.androidcast.local
|
||||
# <<< androidcast-local <<<
|
||||
EOF
|
||||
)
|
||||
|
||||
if [[ "$MODE" == "remove" ]]; then
|
||||
sudo awk -v begin="$BEGIN_MARK" -v end="$END_MARK" '
|
||||
$0==begin {skip=1; next}
|
||||
$0==end {skip=0; next}
|
||||
!skip {print}
|
||||
' "$HOSTS_FILE" | sudo tee "$HOSTS_FILE" >/dev/null
|
||||
echo "Removed androidcast-local hosts entries."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if grep -Fq "$BEGIN_MARK" "$HOSTS_FILE"; then
|
||||
echo "Entries already exist in $HOSTS_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf "\n%s\n" "$BLOCK" | sudo tee -a "$HOSTS_FILE" >/dev/null
|
||||
echo "Added androidcast-local hosts entries."
|
||||
77
sim/cluster0/ARCHITECTURE.md
Normal file
77
sim/cluster0/ARCHITECTURE.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# cast01–cast03 lab architecture
|
||||
|
||||
## Goals
|
||||
|
||||
Simulate production patterns on three isolated Alpine VMs with **shared config only** on NFS (`/shared/cluster/`). No database files on NFS — each node owns `/var/lib/mysql` locally.
|
||||
|
||||
## Topology
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────┐
|
||||
│ FE 10.7.0.10 (NFS export /shared) │
|
||||
└──────────────────┬──────────────────┘
|
||||
│ NFS (configs/scripts only)
|
||||
┌─────────────────────────────┼─────────────────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌───────────┐ ┌───────────┐ ┌───────────┐
|
||||
│ cast01 │ async GTID │ cast02 │ async GTID │ cast03 │
|
||||
│ .16.236 │◄──────────────│ .16.237 │◄──────────────│ .16.238 │
|
||||
│ PRIMARY │ replica │ REPLICA │ replica │ REPLICA │
|
||||
│ MariaDB │ │ MariaDB │ │ MariaDB │
|
||||
│ nginx/php │ │ nginx/php │ │ nginx/php │
|
||||
└───────────┘ └───────────┘ └───────────┘
|
||||
▲ │ │
|
||||
└──────── writes + default reads ────────────────────────────┘
|
||||
(replicas: read-only, failover drill)
|
||||
```
|
||||
|
||||
| Node | IP | MariaDB | App role (planned) |
|
||||
|------|-----|---------|-------------------|
|
||||
| cast01 | 10.7.16.236 | **Primary** (read/write) | Crashes console primary target |
|
||||
| cast02 | 10.7.16.237 | Replica (read_only) | App worker / read replica |
|
||||
| cast03 | 10.7.16.238 | Replica (read_only) | App worker / read replica |
|
||||
|
||||
## MariaDB
|
||||
|
||||
- **Engine:** MariaDB 11.8 (Alpine 3.24 packages — no edge required)
|
||||
- **Replication:** GTID async primary → two replicas (`server-id` 1/2/3)
|
||||
- **Bootstrap:** app DB dump over TCP (`--skip-ssl`); GTID position copied from primary; `replicate-do-db` filters ongoing binlog to app schemas only (avoids `mysql.help_topic` dump failures)
|
||||
- **Databases:** `androidcast_crashes`, `url_shortener` (schemas from project SQL in `/shared/cluster/sql/`)
|
||||
- **Credentials:** see `credentials.txt` (same lab password on all nodes)
|
||||
|
||||
## PHP
|
||||
|
||||
- **Version:** 8.3 (`php83-fpm`) on all nodes — matches modern Alpine; prod BE remains 8.1 until upgraded separately
|
||||
- **Socket:** `127.0.0.1:9000`
|
||||
|
||||
## DNS
|
||||
|
||||
Pending external records. Until then: `/etc/hosts` block maintained by `baseline.sh`.
|
||||
|
||||
## Service split (selected for lab)
|
||||
|
||||
| Service | Write DB host | Read DB host (lab default) | Future app path (local disk) |
|
||||
|---------|---------------|----------------------------|------------------------------|
|
||||
| Crashes / tickets / RA | cast01 | cast01 (cast02/03 for drill) | `/var/www/androidcast/` per node |
|
||||
| URL shortener API | cast01 | cast01 | `/var/www/url-shortener/` on cast03 |
|
||||
| Graphs ingest | cast01 | cast01 | shared crashes backend |
|
||||
|
||||
App deploys stay on **local VM disk**, not NFS.
|
||||
|
||||
## Operations
|
||||
|
||||
```sh
|
||||
sudo sh /shared/cluster/scripts/baseline.sh # per-node baseline
|
||||
sudo sh /shared/cluster/scripts/mariadb-primary.sh # cast01 only
|
||||
sudo sh /shared/cluster/scripts/load-schemas.sh # cast01 only
|
||||
sudo sh /shared/cluster/scripts/mariadb-replica.sh # cast02 + cast03
|
||||
sudo sh /shared/cluster/scripts/verify-cluster.sh # any node
|
||||
```
|
||||
|
||||
NFS mount may be `noexec` — always invoke scripts with `sh`.
|
||||
|
||||
## Not in scope yet
|
||||
|
||||
- ProxySQL / MaxScale (add if read splitting becomes necessary)
|
||||
- Galera multi-primary (overkill for 3-node lab; async matches prod BE migration path)
|
||||
- TLS between nodes (intra VLAN)
|
||||
108
sim/cluster0/DEPLOY.md
Normal file
108
sim/cluster0/DEPLOY.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# cast cluster — unattended populate & verify
|
||||
|
||||
This directory is the **single source of truth** for lab/staging cluster deployment.
|
||||
Store scripts, configs, SQL, and credentials here only — **never** MariaDB datadirs or dumps.
|
||||
|
||||
## Fresh cluster workflow (cluster0 → clusterN)
|
||||
|
||||
### 0. FE: create NFS export + seed this tree
|
||||
|
||||
Copy or rsync this entire `cluster/` tree to the FE export, e.g.:
|
||||
|
||||
`10.7.0.10:/mnt/raid0/xendomains/domU_cast_cluster_0/shared/cluster/`
|
||||
|
||||
Edit `cluster.env` for the new cluster (IPs, export path, `CLUSTER_NAME`, `PUBLIC_ORIGIN`).
|
||||
|
||||
### 1. Create 3 empty Alpine VMs
|
||||
|
||||
Each VM needs hostname `cast01` / `cast02` / `cast03` and static IP from `cluster.env`.
|
||||
|
||||
Copy bootstrap env once (before NFS is mounted):
|
||||
|
||||
```sh
|
||||
sudo install -m 644 bootstrap/cluster0-bootstrap.env /etc/cast-cluster.env
|
||||
```
|
||||
|
||||
### 2. Populate (unattended)
|
||||
|
||||
**Option A — coordinator** (from a host with passwordless SSH to all cast nodes — typically your workstation, not cast01):
|
||||
|
||||
```sh
|
||||
ssh ai@cast01 # or run from laptop with SSH config for cast01–03
|
||||
sudo sh /shared/cluster/populate_lab_setup.sh --coordinator
|
||||
```
|
||||
|
||||
If inter-node SSH is not configured, run **Option B** on each VM instead; cluster verify uses HTTP checks to peer IPs.
|
||||
|
||||
**Option B — per node** (parallel OK; replicas wait for primary):
|
||||
|
||||
```sh
|
||||
sudo sh /shared/cluster/populate_lab_setup.sh
|
||||
```
|
||||
|
||||
Phases:
|
||||
|
||||
| Phase | What |
|
||||
|-------|------|
|
||||
| `baseline` | NFS, hosts, `apk upgrade`, nginx/php83, packages |
|
||||
| `db` | cast01: GTID primary + schemas; cast02/03: replicas |
|
||||
| `app` | git/bundle deploy, nginx vhost, lab `config.php` |
|
||||
| `all` | baseline → db → app → `verify_lab_setup.sh --local` |
|
||||
|
||||
Always invoke with `sh` — NFS may be mounted `noexec`.
|
||||
|
||||
### 3. DNS + FE redirect
|
||||
|
||||
Update external DNS and FE nginx upstream to point at the new cluster (`FE_PROXY_TARGET` in `cluster.env`).
|
||||
|
||||
### 4. Global verify
|
||||
|
||||
```sh
|
||||
sudo sh /shared/cluster/verify_global_setup.sh
|
||||
```
|
||||
|
||||
Checks `PUBLIC_ORIGIN` paths (crashes UI, assets, diag, short links).
|
||||
|
||||
## Staging / cloud
|
||||
|
||||
Same scripts; change only `cluster.env`:
|
||||
|
||||
- `FE_NFS_*` → object storage or config bucket mount
|
||||
- `APP_SOURCE=bundle` + `release/current/` tarball from CI
|
||||
- `PUBLIC_ORIGIN` → staging URL
|
||||
- IPs / hostnames → cloud private network
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `cluster.env` | Cluster-specific variables (IPs, NFS, URLs, git) |
|
||||
| `credentials.txt` | Shared lab passwords |
|
||||
| `populate_lab_setup.sh` | Main unattended installer |
|
||||
| `verify_lab_setup.sh` | Internal cluster health |
|
||||
| `verify_global_setup.sh` | Post-DNS public checks |
|
||||
| `scripts/` | Baseline, MariaDB, deploy, nginx |
|
||||
| `sql/` | Schema + migrations |
|
||||
| `nginx/apps-port80.conf` | php83-fpm TCP vhost |
|
||||
| `bootstrap/` | Minimal `/etc/cast-cluster.env` for first boot |
|
||||
|
||||
See `ARCHITECTURE.md` for topology.
|
||||
|
||||
## Journal (read from FE if VMs fail)
|
||||
|
||||
Every populate/verify step appends to **`/shared/journal/{CLUSTER_NAME}.journal.tsv`** with status **OK**, **NOK**, **WARN**, or **START**.
|
||||
|
||||
Human summary (regenerated automatically):
|
||||
|
||||
```sh
|
||||
less /shared/journal/cluster0.summary.md
|
||||
grep NOK /shared/journal/cluster0.journal.tsv
|
||||
```
|
||||
|
||||
On FE host:
|
||||
|
||||
```sh
|
||||
less /mnt/raid0/xendomains/domU_cast_cluster_0/shared/journal/cluster0.summary.md
|
||||
```
|
||||
|
||||
See `/shared/journal/README.md`. VM disk snapshots are out-of-band (ask sysop) — not stored on NFS.
|
||||
17
sim/cluster0/README.md
Normal file
17
sim/cluster0/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# cast01–cast03 lab cluster
|
||||
|
||||
**Runbook:** [DEPLOY.md](DEPLOY.md) — unattended populate & verify.
|
||||
|
||||
Quick start on a fresh node:
|
||||
|
||||
```sh
|
||||
sudo install -m 644 bootstrap/cluster0-bootstrap.env /etc/cast-cluster.env # once, before NFS
|
||||
sudo sh /shared/cluster/populate_lab_setup.sh # or --coordinator
|
||||
sudo sh /shared/cluster/verify_lab_setup.sh --local
|
||||
# after DNS:
|
||||
sudo sh /shared/cluster/verify_global_setup.sh
|
||||
```
|
||||
|
||||
See `ARCHITECTURE.md` for topology and `cluster.env` for cluster-specific settings.
|
||||
|
||||
**Journal:** every setup action is logged under `/shared/journal/` with **OK** / **NOK** — readable from FE if VMs are down. See `/shared/journal/README.md`.
|
||||
5
sim/cluster0/bootstrap/cluster0-bootstrap.env
Normal file
5
sim/cluster0/bootstrap/cluster0-bootstrap.env
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copy to /etc/cast-cluster.env on each fresh VM before first populate run.
|
||||
# Only needed to mount NFS when /shared/cluster/cluster.env is not reachable yet.
|
||||
FE_NFS_SERVER=10.7.0.10
|
||||
FE_NFS_EXPORT_PATH=/mnt/raid0/xendomains/domU_cast_cluster_0/shared
|
||||
SHARED_MOUNT=/shared
|
||||
48
sim/cluster0/cluster.env
Normal file
48
sim/cluster0/cluster.env
Normal file
@@ -0,0 +1,48 @@
|
||||
# cluster0 — cast01–cast03 lab (edit for new clusters: cluster1, staging, etc.)
|
||||
CLUSTER_NAME=cluster0
|
||||
CLUSTER_ID=0
|
||||
|
||||
FE_NFS_SERVER=10.7.0.10
|
||||
FE_NFS_EXPORT_PATH=/mnt/raid0/xendomains/domU_cast_cluster_0/shared
|
||||
SHARED_MOUNT=/shared
|
||||
|
||||
CAST01_HOST=cast01
|
||||
CAST01_IP=10.7.16.236
|
||||
CAST02_HOST=cast02
|
||||
CAST02_IP=10.7.16.237
|
||||
CAST03_HOST=cast03
|
||||
CAST03_IP=10.7.16.238
|
||||
CAST_DOMAIN=intra.raptor.org
|
||||
|
||||
PRIMARY_DB_HOST=cast01
|
||||
PRIMARY_DB_PORT=3306
|
||||
|
||||
ARTC0_HOST=artc0
|
||||
ARTC0_IP=10.7.16.128
|
||||
|
||||
SSH_USER=ai
|
||||
|
||||
GIT_ORIGIN=git://f0xx.org/android_cast
|
||||
GIT_BRANCH=next
|
||||
# git | bundle — bundle uses SHARED_MOUNT/cluster/release/current/
|
||||
APP_SOURCE=git
|
||||
|
||||
APP_ROOT=/var/www/localhost/htdocs/apps/app/androidcast_project
|
||||
APP_BASE_PATH=/app/androidcast_project/crashes
|
||||
APP_HUB_PATH=/app/androidcast_project
|
||||
|
||||
# Set after DNS + FE redirect, then run verify_global_setup.sh
|
||||
PUBLIC_ORIGIN=https://apps.f0xx.org
|
||||
SHORT_LINKS_PUBLIC_BASE=https://s.f0xx.org
|
||||
FE_PROXY_TARGET=cast01.intra.raptor.org:80
|
||||
|
||||
# Lab DNS (2026-06): acl0.f0xx.org + c1–c3.acl0.f0xx.org → FE (cast01–03 cluster)
|
||||
ACL0_CNAME=acl0.f0xx.org
|
||||
CAST01_PUBLIC=c1.acl0.f0xx.org
|
||||
CAST02_PUBLIC=c2.acl0.f0xx.org
|
||||
CAST03_PUBLIC=c3.acl0.f0xx.org
|
||||
|
||||
EXPECTED_CRASHES_TABLES=16
|
||||
EXPECTED_URL_SHORTENER_TABLES=4
|
||||
|
||||
APK_PACKAGES_BASE="nginx php83 php83-fpm php83-cli php83-session php83-pdo php83-pdo_mysql php83-pdo_sqlite php83-sqlite3 php83-mbstring php83-json php83-curl php83-openssl php83-xml php83-zip php83-phar php83-opcache mariadb-client git rsync curl bash ca-certificates sqlite wireguard-tools nfs-utils openssh-client"
|
||||
22
sim/cluster0/credentials.txt
Normal file
22
sim/cluster0/credentials.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
# cast cluster lab credentials (NOT production — shared intentionally for lab safety)
|
||||
# Same values on cast01, cast02, cast03 unless noted.
|
||||
|
||||
[mariadb_root]
|
||||
# Alpine default: unix socket auth as root, no password
|
||||
|
||||
[mariadb_app]
|
||||
user=androidcast
|
||||
password=cast-cluster-dev
|
||||
host_write=cast01
|
||||
host_read=cast01
|
||||
port=3306
|
||||
databases=androidcast_crashes,url_shortener
|
||||
|
||||
[mariadb_replication]
|
||||
user=repl
|
||||
password=cast-cluster-dev
|
||||
allowed_from=10.7.16.%
|
||||
|
||||
[ssh]
|
||||
user=ai
|
||||
sudo=passwordless
|
||||
55
sim/cluster0/journal/README.md
Normal file
55
sim/cluster0/journal/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Cluster setup journal (`/shared/journal/`)
|
||||
|
||||
Human-readable audit trail for populate / verify runs. **Read from FE** when VMs are down or after reboot failure — no SSH to cast nodes required.
|
||||
|
||||
## Files (per cluster)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `{CLUSTER_NAME}.journal.tsv` | Append-only machine log (tab-separated) |
|
||||
| `{CLUSTER_NAME}.summary.md` | Regenerated table with **OK** / **NOK** / **WARN** |
|
||||
| `README.md` | This file |
|
||||
|
||||
Example for cluster0:
|
||||
|
||||
```
|
||||
/shared/journal/cluster0.journal.tsv
|
||||
/shared/journal/cluster0.summary.md
|
||||
```
|
||||
|
||||
## TSV columns
|
||||
|
||||
```
|
||||
timestamp_utc host actor phase action status detail
|
||||
```
|
||||
|
||||
**status:** `START` → work begun; `OK` / `NOK` / `WARN` → outcome.
|
||||
|
||||
**actor:** script name (`populate`, `verify_lab`, `verify_global`, `baseline`, …).
|
||||
|
||||
## Who writes entries
|
||||
|
||||
All cluster scripts call `journal_*` helpers in `scripts/lib/common.sh`:
|
||||
|
||||
- `populate_lab_setup.sh` — each phase and sub-script
|
||||
- `verify_lab_setup.sh` / `verify_global_setup.sh` — pass/fail
|
||||
- `run_script` — every `scripts/*.sh` invocation
|
||||
|
||||
## Read from FE (10.7.0.10)
|
||||
|
||||
```sh
|
||||
less /mnt/raid0/xendomains/domU_cast_cluster_0/shared/journal/cluster0.summary.md
|
||||
grep NOK /mnt/raid0/xendomains/domU_cast_cluster_0/shared/journal/cluster0.journal.tsv
|
||||
```
|
||||
|
||||
## Rebuild summary manually
|
||||
|
||||
```sh
|
||||
sudo sh /shared/cluster/scripts/journal-rebuild-summary.sh
|
||||
```
|
||||
|
||||
## Snapshots
|
||||
|
||||
If reboot breaks a node and journal shows last **OK** before power-loss, ask sysop for **cluster snapshot restore** (XEN/HVM snapshot — out of band, not stored on NFS).
|
||||
|
||||
Do **not** store VM disk images or MariaDB datadirs in `/shared/`.
|
||||
10
sim/cluster0/mariadb/primary.cnf
Normal file
10
sim/cluster0/mariadb/primary.cnf
Normal file
@@ -0,0 +1,10 @@
|
||||
[mysqld]
|
||||
# cast01 — MariaDB primary (GTID async replication)
|
||||
server-id=1
|
||||
log_bin=mysql-bin
|
||||
binlog_format=ROW
|
||||
gtid_domain_id=1
|
||||
log_slave_updates=1
|
||||
expire_logs_days=7
|
||||
bind-address=0.0.0.0
|
||||
read_only=0
|
||||
11
sim/cluster0/mariadb/replica.cnf
Normal file
11
sim/cluster0/mariadb/replica.cnf
Normal file
@@ -0,0 +1,11 @@
|
||||
[mysqld]
|
||||
# cast02/cast03 — MariaDB replica (server-id set by mariadb-replica.sh)
|
||||
log_bin=mysql-bin
|
||||
binlog_format=ROW
|
||||
gtid_domain_id=1
|
||||
log_slave_updates=1
|
||||
bind-address=0.0.0.0
|
||||
read_only=1
|
||||
relay_log=relay-bin
|
||||
replicate-do-db=androidcast_crashes
|
||||
replicate-do-db=url_shortener
|
||||
119
sim/cluster0/nginx/apps-port80.conf
Normal file
119
sim/cluster0/nginx/apps-port80.conf
Normal file
@@ -0,0 +1,119 @@
|
||||
# Full androidcast vhost for Alpine BE — listen 80 only.
|
||||
# FE (apps.f0xx.org) proxies here: proxy_pass http://artc0.intra.raptor.org:80;
|
||||
# Port 8089 is a different service (e.g. Janus) — not this vhost.
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
|
||||
# OTA v0 static tree — devices fetch https://apps.f0xx.org/v0/ota/channel/stable.json
|
||||
# Populate: builder auto_deploy, or rsync out/ota/v0/ → .../ota-artifacts/v0/
|
||||
location ^~ /v0/ota/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/ota-artifacts/v0/ota/;
|
||||
add_header Cache-Control "public, max-age=60";
|
||||
}
|
||||
|
||||
location = /app/androidcast_project {
|
||||
return 301 /app/androidcast_project/;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/index.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/index.php;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/ {
|
||||
rewrite ^ /app/androidcast_project/index.php last;
|
||||
}
|
||||
|
||||
location /app/androidcast_project/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/;
|
||||
index index.php index.html;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/crashes {
|
||||
return 301 /app/androidcast_project/crashes/;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/graphs {
|
||||
return 301 /app/androidcast_project/graphs/;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/crashes/assets/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/assets/;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/graphs/assets/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/assets/;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/crashes/api/upload.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/upload.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/crashes/api/upload.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 4m;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/crashes/ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/crashes/index.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 4m;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/graphs/api/graphs.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/graphs.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/graphs/api/graphs.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/graphs/api/graph_upload.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/graph_upload.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/graphs/api/graph_upload.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 4m;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/graphs/ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/graphs/index.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 4m;
|
||||
}
|
||||
|
||||
location = /app/androidcast_project/build {
|
||||
return 301 /app/androidcast_project/build/;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/build/assets/ {
|
||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/build_console/backend/public/assets/;
|
||||
}
|
||||
|
||||
location ^~ /app/androidcast_project/build/ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/build_console/backend/public/index.php;
|
||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/build/index.php;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
client_max_body_size 512m;
|
||||
fastcgi_read_timeout 1800s;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
include /etc/nginx/fastcgi.conf;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
}
|
||||
}
|
||||
136
sim/cluster0/populate_lab_setup.sh
Normal file
136
sim/cluster0/populate_lab_setup.sh
Normal file
@@ -0,0 +1,136 @@
|
||||
#!/bin/sh
|
||||
# Unattended cast cluster population — dev lab and staging template.
|
||||
# Usage:
|
||||
# sudo sh populate_lab_setup.sh # full setup on this node
|
||||
# sudo sh populate_lab_setup.sh --coordinator # from jump host: all nodes in order
|
||||
# sudo sh populate_lab_setup.sh --phase baseline|db|app|all
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||
export CAST_CLUSTER_ROOT="$ROOT"
|
||||
# shellcheck source=/dev/null
|
||||
. "$ROOT/scripts/lib/common.sh"
|
||||
export JOURNAL_ACTOR=populate
|
||||
|
||||
PHASE=all
|
||||
COORDINATOR=0
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--coordinator) COORDINATOR=1; shift ;;
|
||||
--phase) PHASE="${2:-all}"; shift 2 ;;
|
||||
--help|-h)
|
||||
echo "Usage: sudo sh populate_lab_setup.sh [--coordinator] [--phase baseline|db|app|all]"
|
||||
exit 0
|
||||
;;
|
||||
*) die "unknown arg: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
run_phase_baseline() {
|
||||
JOURNAL_PHASE=baseline
|
||||
journal_start phase_baseline "$(host_short)"
|
||||
log "phase baseline on $(host_short)"
|
||||
journal_start apk_upgrade
|
||||
apk update
|
||||
apk upgrade -U -a || apk upgrade -U || true
|
||||
journal_ok apk_upgrade
|
||||
run_script "$ROOT/scripts/baseline.sh"
|
||||
journal_ok phase_baseline
|
||||
}
|
||||
|
||||
run_phase_db() {
|
||||
JOURNAL_PHASE=db
|
||||
journal_start phase_db "$(host_short)"
|
||||
if is_primary_node; then
|
||||
log "phase db primary on $(host_short)"
|
||||
run_script "$ROOT/scripts/mariadb-primary.sh"
|
||||
run_script "$ROOT/scripts/load-schemas.sh"
|
||||
journal_ok phase_db "primary"
|
||||
return
|
||||
fi
|
||||
if is_replica_node; then
|
||||
log "phase db replica on $(host_short) — waiting for primary"
|
||||
wait_for_primary_db
|
||||
wait_for_tcp "$PRIMARY_DB_HOST" "$PRIMARY_DB_PORT" 300
|
||||
run_script "$ROOT/scripts/mariadb-replica.sh"
|
||||
journal_ok phase_db "replica"
|
||||
return
|
||||
fi
|
||||
journal_nok phase_db "unknown host"
|
||||
die "unknown host for db phase: $(host_short)"
|
||||
}
|
||||
|
||||
run_phase_app() {
|
||||
JOURNAL_PHASE=app
|
||||
journal_start phase_app "$(host_short)"
|
||||
log "phase app on $(host_short)"
|
||||
if is_replica_node; then
|
||||
wait_for_primary_db
|
||||
fi
|
||||
run_script "$ROOT/scripts/deploy-app.sh"
|
||||
run_script "$ROOT/scripts/configure-nginx.sh"
|
||||
rc-service php-fpm83 restart 2>/dev/null || true
|
||||
rc-service nginx reload 2>/dev/null || rc-service nginx restart 2>/dev/null || true
|
||||
if is_primary_node; then
|
||||
rc-service mariadb restart 2>/dev/null || true
|
||||
elif is_replica_node; then
|
||||
rc-service mariadb restart 2>/dev/null || true
|
||||
fi
|
||||
journal_ok phase_app
|
||||
}
|
||||
|
||||
run_local() {
|
||||
ensure_shared_mounted
|
||||
journal_start populate_local "phase=${PHASE}"
|
||||
case "$PHASE" in
|
||||
baseline) run_phase_baseline ;;
|
||||
db) run_phase_baseline; run_phase_db ;;
|
||||
app) run_phase_app ;;
|
||||
all)
|
||||
run_phase_baseline || exit 1
|
||||
run_phase_db || exit 1
|
||||
run_phase_app || exit 1
|
||||
run_script "$ROOT/verify_lab_setup.sh" --local || exit 1
|
||||
;;
|
||||
*) journal_nok populate_local "unknown phase"; die "unknown phase: $PHASE" ;;
|
||||
esac
|
||||
journal_ok populate_local "phase=${PHASE}"
|
||||
}
|
||||
|
||||
run_coordinator() {
|
||||
ensure_shared_mounted
|
||||
journal_start populate_coordinator "nodes=${ALL_CAST_HOSTS}"
|
||||
log "coordinator: ${CLUSTER_NAME} nodes=${ALL_CAST_HOSTS}"
|
||||
|
||||
for H in $CAST01_HOST $CAST02_HOST $CAST03_HOST; do
|
||||
ssh_node "$H" "sudo sh ${SHARED_MOUNT}/cluster/populate_lab_setup.sh --phase baseline" &
|
||||
done
|
||||
wait || die "baseline failed on one or more nodes"
|
||||
|
||||
ssh_node "$CAST01_HOST" "sudo sh ${SHARED_MOUNT}/cluster/populate_lab_setup.sh --phase db"
|
||||
|
||||
for H in $CAST02_HOST $CAST03_HOST; do
|
||||
ssh_node "$H" "sudo sh ${SHARED_MOUNT}/cluster/populate_lab_setup.sh --phase db" &
|
||||
done
|
||||
wait
|
||||
|
||||
for H in $CAST01_HOST $CAST02_HOST $CAST03_HOST; do
|
||||
ssh_node "$H" "sudo sh ${SHARED_MOUNT}/cluster/populate_lab_setup.sh --phase app" &
|
||||
done
|
||||
wait
|
||||
|
||||
sh "$ROOT/verify_lab_setup.sh" --cluster
|
||||
journal_ok populate_coordinator
|
||||
log "populate_coordinator_ok ${CLUSTER_NAME}"
|
||||
}
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
die "run as root (sudo sh populate_lab_setup.sh)"
|
||||
fi
|
||||
|
||||
if [ "$COORDINATOR" -eq 1 ]; then
|
||||
run_coordinator
|
||||
else
|
||||
run_local
|
||||
fi
|
||||
67
sim/cluster0/scripts/baseline.sh
Normal file
67
sim/cluster0/scripts/baseline.sh
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/bin/sh
|
||||
# cast cluster baseline — packages, NFS, hosts, nginx/php smoke vhost.
|
||||
set -eu
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
. "$ROOT/scripts/lib/common.sh"
|
||||
ensure_shared_mounted
|
||||
|
||||
HOST="$(host_short)"
|
||||
STAGE="$(mktemp -d /var/tmp/cast-cluster.XXXXXX)"
|
||||
trap 'rm -rf "$STAGE"' EXIT
|
||||
|
||||
log "baseline start on $HOST"
|
||||
|
||||
grep -v "${FE_NFS_EXPORT_PATH}" /etc/fstab > "$STAGE/fstab.new" || true
|
||||
echo "$FSTAB_NFS_LINE" >> "$STAGE/fstab.new"
|
||||
mv "$STAGE/fstab.new" /etc/fstab
|
||||
mkdir -p "$SHARED_MOUNT"
|
||||
rc-update add nfsmount boot 2>/dev/null || true
|
||||
mount "$SHARED_MOUNT" 2>/dev/null || mount -a 2>/dev/null || true
|
||||
|
||||
grep -v '# cast-cluster' /etc/hosts | grep -v 'cast0[123]\.' | grep -v 'artc0\.' > "$STAGE/hosts.new" || cp /etc/hosts "$STAGE/hosts.new"
|
||||
cat >> "$STAGE/hosts.new" <<EOF
|
||||
# cast-cluster ${CLUSTER_NAME}
|
||||
${CAST01_IP} ${CAST01_HOST} ${CAST01_HOST}.${CAST_DOMAIN}
|
||||
${CAST02_IP} ${CAST02_HOST} ${CAST02_HOST}.${CAST_DOMAIN}
|
||||
${CAST03_IP} ${CAST03_HOST} ${CAST03_HOST}.${CAST_DOMAIN}
|
||||
${ARTC0_IP} ${ARTC0_HOST} ${ARTC0_HOST}.${CAST_DOMAIN} alpine-be
|
||||
EOF
|
||||
mv "$STAGE/hosts.new" /etc/hosts
|
||||
|
||||
apk update
|
||||
PKGS="$APK_PACKAGES_BASE"
|
||||
if is_primary_node; then
|
||||
PKGS="$PKGS mariadb"
|
||||
fi
|
||||
# shellcheck disable=SC2086
|
||||
apk add --no-cache $PKGS
|
||||
|
||||
rc-update add nginx default 2>/dev/null || true
|
||||
rc-update add php-fpm83 default 2>/dev/null || true
|
||||
run_script "$ROOT/scripts/configure-php-fpm.sh"
|
||||
rc-service php-fpm83 restart 2>/dev/null || rc-service php-fpm83 start 2>/dev/null || true
|
||||
rc-service nginx start 2>/dev/null || true
|
||||
|
||||
if is_primary_node; then
|
||||
grep -q '^skip-networking' /etc/my.cnf.d/mariadb-server.cnf 2>/dev/null && \
|
||||
sed -i 's/^skip-networking/#skip-networking/' /etc/my.cnf.d/mariadb-server.cnf || true
|
||||
grep -q '^bind-address' /etc/my.cnf.d/mariadb-server.cnf 2>/dev/null || \
|
||||
printf '\n[mysqld]\nbind-address = 0.0.0.0\n' >> /etc/my.cnf.d/mariadb-server.cnf
|
||||
rc-update add mariadb default 2>/dev/null || true
|
||||
rc-service mariadb restart 2>/dev/null || rc-service mariadb start 2>/dev/null || true
|
||||
fi
|
||||
|
||||
mkdir -p /var/www/localhost/htdocs
|
||||
echo "<html><body><h1>${HOST}</h1><p>${CLUSTER_NAME} node</p></body></html>" > /var/www/localhost/htdocs/index.html
|
||||
chown -R nginx:nginx /var/www/localhost/htdocs 2>/dev/null || true
|
||||
|
||||
mkdir -p "$ROOT"
|
||||
IP="$(ip -4 -o addr show eth0 2>/dev/null | awk '{print $4}' | cut -d/ -f1)"
|
||||
echo "$HOST $(date -Iseconds) ip=$IP cluster=$CLUSTER_NAME" >> "$ROOT/seen.log"
|
||||
printf '{"host":"%s","ip":"%s","cluster":"%s","configured_at":"%s"}\n' \
|
||||
"$HOST" "$IP" "$CLUSTER_NAME" "$(date -Iseconds)" > "$ROOT/${HOST}.json"
|
||||
|
||||
mount | grep -q " on ${SHARED_MOUNT} " || die "NFS not mounted"
|
||||
curl -sS -o /dev/null -w "nginx_http=%{http_code}\n" http://127.0.0.1/
|
||||
log "baseline_ok $HOST"
|
||||
12
sim/cluster0/scripts/configure-nginx.sh
Normal file
12
sim/cluster0/scripts/configure-nginx.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
. "$ROOT/scripts/lib/common.sh"
|
||||
ensure_shared_mounted
|
||||
|
||||
install -D -m 644 "$ROOT/nginx/apps-port80.conf" /etc/nginx/http.d/androidcast.conf
|
||||
rm -f /etc/nginx/http.d/default.conf /etc/nginx/http.d/cluster.conf 2>/dev/null || true
|
||||
nginx -t
|
||||
rc-service nginx reload
|
||||
log "nginx androidcast vhost installed"
|
||||
13
sim/cluster0/scripts/configure-php-fpm.sh
Normal file
13
sim/cluster0/scripts/configure-php-fpm.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
. "$ROOT/scripts/lib/common.sh"
|
||||
ensure_shared_mounted
|
||||
|
||||
CONF=/etc/php83/php-fpm.d/www.conf
|
||||
grep -q '^listen = 127.0.0.1:9000' "$CONF" 2>/dev/null || \
|
||||
sed -i 's|^listen = .*|listen = 127.0.0.1:9000|' "$CONF"
|
||||
grep -q '^listen.allowed_clients' "$CONF" 2>/dev/null || \
|
||||
printf '\nlisten.allowed_clients = 127.0.0.1\n' >> "$CONF"
|
||||
log "php-fpm83 listen 127.0.0.1:9000"
|
||||
109
sim/cluster0/scripts/deploy-app.sh
Normal file
109
sim/cluster0/scripts/deploy-app.sh
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/bin/sh
|
||||
# Deploy androidcast project tree + lab config.php (local disk, not NFS).
|
||||
set -eu
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
. "$ROOT/scripts/lib/common.sh"
|
||||
ensure_shared_mounted
|
||||
|
||||
APP_PASS="$(read_cred mariadb_app password)"
|
||||
DB_HOST="$PRIMARY_DB_HOST"
|
||||
if is_primary_node; then
|
||||
DB_HOST=127.0.0.1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$APP_ROOT")"
|
||||
GIT_ROOT="${APP_ROOT}/android_cast"
|
||||
BACKEND="${GIT_ROOT}/examples/crash_reporter/backend"
|
||||
CONFIG="${BACKEND}/config/config.php"
|
||||
|
||||
case "$APP_SOURCE" in
|
||||
git)
|
||||
if [ -d "${GIT_ROOT}/.git" ]; then
|
||||
log "git pull ${GIT_BRANCH} in ${GIT_ROOT}"
|
||||
git config --global --add safe.directory "$GIT_ROOT" 2>/dev/null || true
|
||||
git -C "$GIT_ROOT" fetch origin
|
||||
git -C "$GIT_ROOT" checkout "$GIT_BRANCH"
|
||||
git -C "$GIT_ROOT" pull --ff-only origin "$GIT_BRANCH"
|
||||
else
|
||||
log "git clone ${GIT_ORIGIN} → ${GIT_ROOT}"
|
||||
rm -rf "$GIT_ROOT"
|
||||
git clone --branch "$GIT_BRANCH" --depth 1 "$GIT_ORIGIN" "$GIT_ROOT"
|
||||
git config --global --add safe.directory "$GIT_ROOT" 2>/dev/null || true
|
||||
fi
|
||||
;;
|
||||
bundle)
|
||||
BUNDLE="${ROOT}/release/current"
|
||||
[ -d "$BUNDLE" ] || die "missing bundle dir $BUNDLE (APP_SOURCE=bundle)"
|
||||
log "rsync bundle $BUNDLE → ${APP_ROOT}"
|
||||
mkdir -p "$APP_ROOT"
|
||||
rsync -a --delete "$BUNDLE/" "$APP_ROOT/"
|
||||
;;
|
||||
*)
|
||||
die "unknown APP_SOURCE=$APP_SOURCE"
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p "${BACKEND}/config" "${BACKEND}/data" "${BACKEND}/storage"
|
||||
chown -R nginx:nginx "$APP_ROOT" 2>/dev/null || true
|
||||
|
||||
if [ ! -f "$CONFIG" ] || [ "${FORCE_CONFIG:-1}" = "1" ]; then
|
||||
log "writing lab config.php"
|
||||
cat > "$CONFIG" <<PHP
|
||||
<?php
|
||||
return [
|
||||
'app_name' => 'Android Cast Issues',
|
||||
'base_path' => '${APP_BASE_PATH}',
|
||||
'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_server_public_key' => '',
|
||||
'provision_peers' => false,
|
||||
'require_wg_tools' => false,
|
||||
],
|
||||
'auth' => [
|
||||
'encryption_key' => 'lab-cluster-dev-32-char-min-secret!!',
|
||||
'max_attempts' => 8,
|
||||
'lockout_window_minutes' => 15,
|
||||
],
|
||||
'url_shortener' => [
|
||||
'enabled' => true,
|
||||
'public_base' => '${SHORT_LINKS_PUBLIC_BASE}',
|
||||
'db' => [
|
||||
'mysql' => [
|
||||
'host' => '${DB_HOST}',
|
||||
'port' => ${PRIMARY_DB_PORT},
|
||||
'socket' => '',
|
||||
'database' => 'url_shortener',
|
||||
'username' => 'androidcast',
|
||||
'password' => '${APP_PASS}',
|
||||
'charset' => 'utf8mb4',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
PHP
|
||||
chown nginx:nginx "$CONFIG" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log "deploy-app_ok $(host_short)"
|
||||
57
sim/cluster0/scripts/journal-backfill-cluster0.sh
Normal file
57
sim/cluster0/scripts/journal-backfill-cluster0.sh
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
# One-time backfill of cluster0 journal from known-good state (2026-06-13 session).
|
||||
# Safe to re-run: appends only if journal is empty.
|
||||
set -eu
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
. "$ROOT/scripts/lib/common.sh"
|
||||
ensure_shared_mounted
|
||||
journal_init
|
||||
|
||||
if [ -f "$JOURNAL_TSV" ] && [ "$(wc -l < "$JOURNAL_TSV")" -gt 1 ]; then
|
||||
log "journal already has entries — skip backfill (see $JOURNAL_TSV)"
|
||||
sh "$ROOT/scripts/journal-rebuild-summary.sh"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
export JOURNAL_ACTOR=backfill
|
||||
TS="2026-06-13T12:00:00+00:00"
|
||||
|
||||
append() {
|
||||
_host="$1"
|
||||
_phase="$2"
|
||||
_action="$3"
|
||||
_status="$4"
|
||||
_detail="$5"
|
||||
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||
"$TS" "$_host" "$JOURNAL_ACTOR" "$_phase" "$_action" "$_status" "$_detail" >> "$JOURNAL_TSV"
|
||||
}
|
||||
|
||||
# Infrastructure (all nodes)
|
||||
for H in cast01 cast02 cast03; do
|
||||
append "$H" baseline nfs_mount OK "/shared from FE export"
|
||||
append "$H" baseline packages OK "nginx php83-fpm mariadb-client nfs-utils"
|
||||
append "$H" baseline hosts OK "cast-cluster /etc/hosts block"
|
||||
done
|
||||
|
||||
# MariaDB
|
||||
append cast01 db mariadb-primary OK "GTID primary server-id=1"
|
||||
append cast01 db load-schemas OK "16 crashes + 4 url_shortener tables"
|
||||
append cast02 db mariadb-replica OK "GTID replica IO+SQL running"
|
||||
append cast03 db mariadb-replica OK "GTID replica IO+SQL running"
|
||||
|
||||
# App + nginx
|
||||
for H in cast01 cast02 cast03; do
|
||||
append "$H" app deploy-app OK "git next → android_cast/"
|
||||
append "$H" app configure-nginx OK "apps-port80.conf php83:9000"
|
||||
done
|
||||
|
||||
# Verification
|
||||
append cast01 verify verify_lab OK "mode=local"
|
||||
append cast02 verify verify_lab OK "mode=local"
|
||||
append cast03 verify verify_lab OK "mode=local"
|
||||
append cast01 verify verify_lab OK "mode=cluster HTTP peers"
|
||||
append cast01 verify verify_global START "pending DNS/FE cutover"
|
||||
|
||||
sh "$ROOT/scripts/journal-rebuild-summary.sh"
|
||||
log "backfill_ok → $JOURNAL_TSV"
|
||||
35
sim/cluster0/scripts/journal-rebuild-summary.sh
Normal file
35
sim/cluster0/scripts/journal-rebuild-summary.sh
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
# Regenerate human-readable OK/NOK summary from journal TSV (safe on FE via NFS read).
|
||||
set -eu
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
. "$ROOT/scripts/lib/common.sh"
|
||||
load_cluster_env
|
||||
journal_init
|
||||
|
||||
{
|
||||
echo "# ${CLUSTER_NAME} — setup journal summary"
|
||||
echo
|
||||
echo "Read raw log on FE: \`${JOURNAL_TSV}\`"
|
||||
echo
|
||||
echo "Last rebuilt: $(date -u -Iseconds 2>/dev/null || date -u)"
|
||||
echo
|
||||
echo "| UTC time | Host | Phase | Action | Status | Detail |"
|
||||
echo "|----------|------|-------|--------|--------|--------|"
|
||||
tail -n +2 "$JOURNAL_TSV" 2>/dev/null | while IFS=' ' read -r ts host actor phase action status detail; do
|
||||
printf '| %s | %s | %s | %s | **%s** | %s |\n' \
|
||||
"$ts" "$host" "$phase" "$action" "$status" "$detail"
|
||||
done
|
||||
echo
|
||||
echo "## Latest status per action/host"
|
||||
echo
|
||||
tail -n +2 "$JOURNAL_TSV" 2>/dev/null | awk -F' ' '
|
||||
{ key=$2 SUBSEP $5 SUBSEP $4; line=$0; status=$6; if (status != "START") last[key]=line }
|
||||
END { for (k in last) print last[k] }' | sort -t' ' -k1,1 | while IFS=' ' read -r ts host actor phase action status detail; do
|
||||
printf -- '- **%s** `%s` / `%s`' "$status" "$host" "$action"
|
||||
[ -n "$detail" ] && printf ' — %s' "$detail"
|
||||
printf '\n'
|
||||
done
|
||||
} > "$JOURNAL_SUMMARY"
|
||||
|
||||
echo "summary → $JOURNAL_SUMMARY"
|
||||
192
sim/cluster0/scripts/lib/common.sh
Normal file
192
sim/cluster0/scripts/lib/common.sh
Normal file
@@ -0,0 +1,192 @@
|
||||
# shellcheck shell=sh
|
||||
# Shared helpers for cast cluster scripts.
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(date -Iseconds 2>/dev/null || date)" "$*"
|
||||
}
|
||||
|
||||
die() {
|
||||
log "ERROR: $*"
|
||||
if [ -n "${JOURNAL_LAST_ACTION:-}" ]; then
|
||||
journal_record NOK "$JOURNAL_LAST_ACTION" "$*"
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
cluster_root() {
|
||||
if [ -n "${CAST_CLUSTER_ROOT:-}" ]; then
|
||||
printf '%s\n' "$CAST_CLUSTER_ROOT"
|
||||
return
|
||||
fi
|
||||
if [ -f /shared/cluster/cluster.env ]; then
|
||||
printf '/shared/cluster\n'
|
||||
return
|
||||
fi
|
||||
if [ -f "$(dirname "$0")/../cluster.env" ]; then
|
||||
cd "$(dirname "$0")/.." && pwd
|
||||
return
|
||||
fi
|
||||
die "cannot locate cluster root (is /shared mounted?)"
|
||||
}
|
||||
|
||||
load_cluster_env() {
|
||||
ROOT="$(cluster_root)"
|
||||
ENV_FILE="${1:-$ROOT/cluster.env}"
|
||||
[ -f "$ENV_FILE" ] || die "missing $ENV_FILE"
|
||||
# shellcheck disable=SC1090
|
||||
. "$ENV_FILE"
|
||||
CAST_CLUSTER_ROOT="$ROOT"
|
||||
FE_NFS="${FE_NFS_SERVER}:${FE_NFS_EXPORT_PATH}"
|
||||
FSTAB_NFS_LINE="${FE_NFS} ${SHARED_MOUNT} nfs rw,_netdev,nofail,noatime,nodiratime 0 0"
|
||||
ALL_CAST_HOSTS="${CAST01_HOST} ${CAST02_HOST} ${CAST03_HOST}"
|
||||
JOURNAL_DIR="${SHARED_MOUNT}/journal"
|
||||
JOURNAL_TSV="${JOURNAL_DIR}/${CLUSTER_NAME}.journal.tsv"
|
||||
JOURNAL_SUMMARY="${JOURNAL_DIR}/${CLUSTER_NAME}.summary.md"
|
||||
}
|
||||
|
||||
journal_init() {
|
||||
load_cluster_env 2>/dev/null || return 0
|
||||
mkdir -p "$JOURNAL_DIR"
|
||||
if [ ! -f "$JOURNAL_TSV" ]; then
|
||||
printf 'timestamp_utc\thost\tactor\tphase\taction\tstatus\tdetail\n' > "$JOURNAL_TSV"
|
||||
fi
|
||||
}
|
||||
|
||||
journal_record() {
|
||||
_status="$1"
|
||||
_action="$2"
|
||||
_detail="${3:-}"
|
||||
_phase="${JOURNAL_PHASE:-}"
|
||||
journal_init
|
||||
_ts="$(date -u -Iseconds 2>/dev/null || date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
_host="$(host_short 2>/dev/null || echo unknown)"
|
||||
_actor="${JOURNAL_ACTOR:-script}"
|
||||
_line="${_ts} ${_host} ${_actor} ${_phase} ${_action} ${_status} ${_detail}"
|
||||
printf '%s\n' "$_line" >> "$JOURNAL_TSV"
|
||||
log "journal ${_status} ${_action}${_detail:+ — ${_detail}}"
|
||||
if [ -x "${CAST_CLUSTER_ROOT}/scripts/journal-rebuild-summary.sh" ] 2>/dev/null; then
|
||||
sh "${CAST_CLUSTER_ROOT}/scripts/journal-rebuild-summary.sh" 2>/dev/null || true
|
||||
elif [ -f "${CAST_CLUSTER_ROOT}/scripts/journal-rebuild-summary.sh" ]; then
|
||||
sh "${CAST_CLUSTER_ROOT}/scripts/journal-rebuild-summary.sh" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
journal_start() {
|
||||
JOURNAL_LAST_ACTION="$1"
|
||||
journal_record START "$1" "${2:-}"
|
||||
}
|
||||
|
||||
journal_ok() {
|
||||
journal_record OK "$1" "${2:-}"
|
||||
JOURNAL_LAST_ACTION=""
|
||||
}
|
||||
|
||||
journal_nok() {
|
||||
journal_record NOK "$1" "${2:-}"
|
||||
JOURNAL_LAST_ACTION=""
|
||||
}
|
||||
|
||||
journal_warn() {
|
||||
journal_record WARN "$1" "${2:-}"
|
||||
}
|
||||
|
||||
ensure_shared_mounted() {
|
||||
load_cluster_env
|
||||
if mount | grep -q " on ${SHARED_MOUNT} "; then
|
||||
journal_init
|
||||
return 0
|
||||
fi
|
||||
if [ -f /etc/cast-cluster.env ]; then
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/cast-cluster.env
|
||||
FE_NFS="${FE_NFS_SERVER}:${FE_NFS_EXPORT_PATH}"
|
||||
FSTAB_NFS_LINE="${FE_NFS} ${SHARED_MOUNT} nfs rw,_netdev,nofail,noatime,nodiratime 0 0"
|
||||
fi
|
||||
log "mounting ${SHARED_MOUNT} from ${FE_NFS}"
|
||||
journal_start nfs_mount "from ${FE_NFS}"
|
||||
mkdir -p "$SHARED_MOUNT"
|
||||
grep -v "${FE_NFS_EXPORT_PATH}" /etc/fstab > /var/tmp/cast-fstab.new 2>/dev/null || cp /etc/fstab /var/tmp/cast-fstab.new
|
||||
echo "$FSTAB_NFS_LINE" >> /var/tmp/cast-fstab.new
|
||||
mv /var/tmp/cast-fstab.new /etc/fstab
|
||||
rc-update add nfsmount boot 2>/dev/null || true
|
||||
mount "$SHARED_MOUNT" 2>/dev/null || mount -a
|
||||
if mount | grep -q " on ${SHARED_MOUNT} "; then
|
||||
load_cluster_env
|
||||
journal_ok nfs_mount "${SHARED_MOUNT}"
|
||||
else
|
||||
journal_nok nfs_mount "mount failed"
|
||||
die "NFS mount failed: ${SHARED_MOUNT}"
|
||||
fi
|
||||
}
|
||||
|
||||
run_script() {
|
||||
_script="$1"
|
||||
_action="$(basename "$_script" .sh)"
|
||||
journal_start "$_action" "$_script"
|
||||
if sh "$_script"; then
|
||||
journal_ok "$_action" "$_script"
|
||||
return 0
|
||||
fi
|
||||
journal_nok "$_action" "$_script"
|
||||
return 1
|
||||
}
|
||||
|
||||
host_short() {
|
||||
hostname -s 2>/dev/null || hostname
|
||||
}
|
||||
|
||||
is_primary_node() {
|
||||
[ "$(host_short)" = "$CAST01_HOST" ]
|
||||
}
|
||||
|
||||
is_replica_node() {
|
||||
H="$(host_short)"
|
||||
[ "$H" = "$CAST02_HOST" ] || [ "$H" = "$CAST03_HOST" ]
|
||||
}
|
||||
|
||||
wait_for_tcp() {
|
||||
_host="$1"
|
||||
_port="$2"
|
||||
_timeout="${3:-300}"
|
||||
_i=0
|
||||
while [ "$_i" -lt "$_timeout" ]; do
|
||||
if nc -z "$_host" "$_port" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
_i=$((_i + 1))
|
||||
sleep 1
|
||||
done
|
||||
die "timeout waiting for ${_host}:${_port}"
|
||||
}
|
||||
|
||||
wait_for_primary_db() {
|
||||
load_cluster_env
|
||||
CREDS="${CAST_CLUSTER_ROOT}/credentials.txt"
|
||||
APP_PASS="$(awk '/^\[mariadb_app\]/{f=1;next} /^\[/{f=0} f&&/^password=/{print substr($0,10); exit}' "$CREDS")"
|
||||
_i=0
|
||||
while [ "$_i" -lt 300 ]; do
|
||||
if mariadb -u androidcast -p"$APP_PASS" -h"$PRIMARY_DB_HOST" -N -e 'SELECT 1' >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
_i=$((_i + 1))
|
||||
sleep 2
|
||||
done
|
||||
die "primary DB not reachable at ${PRIMARY_DB_HOST}"
|
||||
}
|
||||
|
||||
read_cred() {
|
||||
_section="$1"
|
||||
_key="$2"
|
||||
_file="${CAST_CLUSTER_ROOT}/credentials.txt"
|
||||
awk -v s="[$_section]" -v k="$_key=" '
|
||||
$0 == s { f=1; next }
|
||||
/^\[/ { f=0 }
|
||||
f && index($0, k) == 1 { print substr($0, length(k)+1); exit }
|
||||
' "$_file"
|
||||
}
|
||||
|
||||
ssh_node() {
|
||||
_host="$1"
|
||||
shift
|
||||
ssh -o BatchMode=yes -o StrictHostKeyChecking=no "${SSH_USER}@${_host}" "$@"
|
||||
}
|
||||
39
sim/cluster0/scripts/load-schemas.sh
Normal file
39
sim/cluster0/scripts/load-schemas.sh
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
# Load project schemas on cast01 primary only.
|
||||
set -eu
|
||||
SHARED=/shared/cluster/sql
|
||||
[ "$(hostname -s)" = cast01 ] || { echo "run on cast01 only"; exit 1; }
|
||||
|
||||
apply() {
|
||||
f="$1"
|
||||
[ -f "$f" ] || { echo "missing $f"; exit 1; }
|
||||
echo "==> $f"
|
||||
mariadb -u root < "$f"
|
||||
}
|
||||
|
||||
apply_db() {
|
||||
db="$1"
|
||||
f="$2"
|
||||
echo "==> $db < $f"
|
||||
mariadb -u root "$db" < "$f"
|
||||
}
|
||||
|
||||
apply "$SHARED/crashes/schema.mariadb.sql"
|
||||
|
||||
# Migrations after base schema (003 is for legacy; base schema already includes tickets)
|
||||
for m in \
|
||||
004_ticket_workflow.sql \
|
||||
005_ticket_attachments.sql \
|
||||
006_graph_sessions.sql \
|
||||
007_remote_access.sql \
|
||||
008_graph_sessions_indexes.sql \
|
||||
008_auth_email_2fa.sql \
|
||||
009_rssh_sessions.sql
|
||||
do
|
||||
apply_db androidcast_crashes "$SHARED/crashes/migrations/$m"
|
||||
done
|
||||
|
||||
apply "$SHARED/url_shortener/schema.mariadb.sql"
|
||||
|
||||
mariadb -u root -e "SHOW DATABASES; SELECT COUNT(*) AS crash_tables FROM information_schema.tables WHERE table_schema='androidcast_crashes'; SELECT COUNT(*) AS url_tables FROM information_schema.tables WHERE table_schema='url_shortener';"
|
||||
echo "schemas_loaded_ok"
|
||||
33
sim/cluster0/scripts/mariadb-primary.sh
Normal file
33
sim/cluster0/scripts/mariadb-primary.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
# Configure cast01 as MariaDB GTID primary. Idempotent-ish.
|
||||
set -eu
|
||||
SHARED=/shared/cluster
|
||||
CREDS="$SHARED/credentials.txt"
|
||||
REPL_USER="$(awk '/^\[mariadb_replication\]/{f=1;next} /^\[/{f=0} f&&/^user=/{print substr($0,6); exit}' "$CREDS")"
|
||||
REPL_PASS="$(awk '/^\[mariadb_replication\]/{f=1;next} /^\[/{f=0} f&&/^password=/{print substr($0,10); exit}' "$CREDS")"
|
||||
APP_USER="$(awk '/^\[mariadb_app\]/{f=1;next} /^\[/{f=0} f&&/^user=/{print substr($0,6); exit}' "$CREDS")"
|
||||
APP_PASS="$(awk '/^\[mariadb_app\]/{f=1;next} /^\[/{f=0} f&&/^password=/{print substr($0,10); exit}' "$CREDS")"
|
||||
|
||||
[ "$(hostname -s)" = cast01 ] || { echo "run on cast01 only"; exit 1; }
|
||||
|
||||
grep -q '^skip-networking' /etc/my.cnf.d/mariadb-server.cnf && \
|
||||
sed -i 's/^skip-networking/#skip-networking/' /etc/my.cnf.d/mariadb-server.cnf
|
||||
|
||||
install -D -m 644 "$SHARED/mariadb/primary.cnf" /etc/my.cnf.d/lab-cluster-primary.cnf
|
||||
rc-update add mariadb default 2>/dev/null || true
|
||||
rc-service mariadb restart
|
||||
|
||||
mariadb -u root <<SQL
|
||||
CREATE USER IF NOT EXISTS '${REPL_USER}'@'10.7.16.%' IDENTIFIED BY '${REPL_PASS}';
|
||||
GRANT REPLICATION SLAVE, REPLICATION CLIENT, RELOAD, LOCK TABLES, SELECT, SHOW VIEW, EVENT, TRIGGER ON *.* TO '${REPL_USER}'@'10.7.16.%';
|
||||
CREATE USER IF NOT EXISTS '${APP_USER}'@'10.7.16.%' IDENTIFIED BY '${APP_PASS}';
|
||||
CREATE USER IF NOT EXISTS '${APP_USER}'@'localhost' IDENTIFIED BY '${APP_PASS}';
|
||||
GRANT ALL PRIVILEGES ON androidcast_crashes.* TO '${APP_USER}'@'10.7.16.%';
|
||||
GRANT ALL PRIVILEGES ON androidcast_crashes.* TO '${APP_USER}'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON url_shortener.* TO '${APP_USER}'@'10.7.16.%';
|
||||
GRANT ALL PRIVILEGES ON url_shortener.* TO '${APP_USER}'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
|
||||
echo "primary_ok server_id=$(mariadb -u root -N -e 'SELECT @@server_id')"
|
||||
echo "gtid=$(mariadb -u root -N -e 'SELECT @@gtid_current_pos')"
|
||||
73
sim/cluster0/scripts/mariadb-replica.sh
Normal file
73
sim/cluster0/scripts/mariadb-replica.sh
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/bin/sh
|
||||
# Bootstrap cast02 or cast03 as GTID replica of cast01.
|
||||
set -eu
|
||||
set -o pipefail
|
||||
SHARED=/shared/cluster
|
||||
CREDS="$SHARED/credentials.txt"
|
||||
[ -f "$SHARED/cluster.env" ] && . "$SHARED/cluster.env"
|
||||
HOST="$(hostname -s)"
|
||||
case "$HOST" in
|
||||
cast02) SERVER_ID=2 ;;
|
||||
cast03) SERVER_ID=3 ;;
|
||||
*) echo "run on cast02 or cast03"; exit 1 ;;
|
||||
esac
|
||||
|
||||
REPL_PASS="$(awk '/^\[mariadb_replication\]/{f=1;next} /^\[/{f=0} f&&/^password=/{print substr($0,10); exit}' "$CREDS")"
|
||||
REPL_USER="$(awk '/^\[mariadb_replication\]/{f=1;next} /^\[/{f=0} f&&/^user=/{print substr($0,6); exit}' "$CREDS")"
|
||||
APP_PASS="$(awk '/^\[mariadb_app\]/{f=1;next} /^\[/{f=0} f&&/^password=/{print substr($0,10); exit}' "$CREDS")"
|
||||
APP_USER="$(awk '/^\[mariadb_app\]/{f=1;next} /^\[/{f=0} f&&/^user=/{print substr($0,6); exit}' "$CREDS")"
|
||||
|
||||
grep -q '^skip-networking' /etc/my.cnf.d/mariadb-server.cnf && \
|
||||
sed -i 's/^skip-networking/#skip-networking/' /etc/my.cnf.d/mariadb-server.cnf
|
||||
|
||||
apk add --no-cache mariadb
|
||||
install -D -m 644 "$SHARED/mariadb/replica.cnf" /etc/my.cnf.d/lab-cluster-replica.cnf
|
||||
grep -q '^server-id=' /etc/my.cnf.d/lab-cluster-replica.cnf && \
|
||||
sed -i "s/^server-id=.*/server-id=${SERVER_ID}/" /etc/my.cnf.d/lab-cluster-replica.cnf || \
|
||||
echo "server-id=${SERVER_ID}" >> /etc/my.cnf.d/lab-cluster-replica.cnf
|
||||
|
||||
rc-service mariadb stop 2>/dev/null || true
|
||||
rm -rf /var/lib/mysql/*
|
||||
/etc/init.d/mariadb setup
|
||||
|
||||
rc-update add mariadb default
|
||||
rc-service mariadb restart
|
||||
sleep 2
|
||||
|
||||
echo "=== app DB sync from cast01 (skip mysql.* — avoids help_topic dump failures) ==="
|
||||
mariadb-dump --skip-ssl -h cast01 -u "${REPL_USER}" -p"${REPL_PASS}" \
|
||||
--databases androidcast_crashes url_shortener \
|
||||
--single-transaction --gtid --routines --events \
|
||||
| mariadb -u root
|
||||
|
||||
GTID_POS="$(mariadb --skip-ssl -h cast01 -u "${REPL_USER}" -p"${REPL_PASS}" -N -e 'SELECT @@gtid_current_pos')"
|
||||
[ -n "$GTID_POS" ] || { echo "FAIL: could not read gtid from cast01"; exit 1; }
|
||||
mariadb -u root -e "SET GLOBAL gtid_slave_pos='${GTID_POS}'"
|
||||
echo "gtid_slave_pos set to: ${GTID_POS}"
|
||||
|
||||
mariadb -u root <<SQL
|
||||
CREATE USER IF NOT EXISTS '${APP_USER}'@'10.7.16.%' IDENTIFIED BY '${APP_PASS}';
|
||||
CREATE USER IF NOT EXISTS '${APP_USER}'@'localhost' IDENTIFIED BY '${APP_PASS}';
|
||||
GRANT ALL PRIVILEGES ON androidcast_crashes.* TO '${APP_USER}'@'10.7.16.%';
|
||||
GRANT ALL PRIVILEGES ON androidcast_crashes.* TO '${APP_USER}'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON url_shortener.* TO '${APP_USER}'@'10.7.16.%';
|
||||
GRANT ALL PRIVILEGES ON url_shortener.* TO '${APP_USER}'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
CHANGE MASTER TO
|
||||
MASTER_HOST='cast01',
|
||||
MASTER_USER='${REPL_USER}',
|
||||
MASTER_PASSWORD='${REPL_PASS}',
|
||||
MASTER_USE_GTID=slave_pos;
|
||||
START REPLICA;
|
||||
SQL
|
||||
|
||||
sleep 3
|
||||
mariadb -u root -e "SHOW REPLICA STATUS\G" | grep -E 'Slave_IO_Running|Slave_SQL_Running|Last_Error|Last_SQL_Error|Seconds_Behind_Master'
|
||||
mariadb -u root -e "SELECT table_schema, COUNT(*) AS tables FROM information_schema.tables WHERE table_schema IN ('androidcast_crashes','url_shortener') GROUP BY table_schema;"
|
||||
URL_N="$(mariadb -u root -N -e 'SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='"'"'url_shortener'"'"'')"
|
||||
CRASH_N="$(mariadb -u root -N -e 'SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='"'"'androidcast_crashes'"'"'')"
|
||||
EXP_CRASH="${EXPECTED_CRASHES_TABLES:-16}"
|
||||
EXP_URL="${EXPECTED_URL_SHORTENER_TABLES:-4}"
|
||||
[ "$CRASH_N" = "$EXP_CRASH" ] && [ "$URL_N" = "$EXP_URL" ] || { echo "FAIL: expected ${EXP_CRASH}+${EXP_URL} tables, got crashes=$CRASH_N url=$URL_N"; exit 1; }
|
||||
mariadb -u root -e "SHOW REPLICA STATUS\G" | grep -E 'Slave_SQL_Running: Yes' >/dev/null || { echo "FAIL: SQL thread not running"; mariadb -u root -e "SHOW REPLICA STATUS\G" | grep -E 'Last_SQL_Error'; exit 1; }
|
||||
echo "replica_ok $HOST server_id=$SERVER_ID"
|
||||
34
sim/cluster0/scripts/verify-cluster.sh
Normal file
34
sim/cluster0/scripts/verify-cluster.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
# Quick health check for cast cluster.
|
||||
set -eu
|
||||
HOST="$(hostname -s)"
|
||||
FAIL=0
|
||||
echo "=== $HOST ==="
|
||||
mount | grep ' /shared ' || { echo "WARN: /shared not mounted"; FAIL=1; }
|
||||
curl -sS -o /dev/null -w "nginx=%{http_code}\n" http://127.0.0.1/ || true
|
||||
php83 -v | head -1
|
||||
|
||||
case "$HOST" in
|
||||
cast01)
|
||||
mariadb -u root -e "SELECT @@server_id AS server_id, @@read_only AS read_only, @@gtid_current_pos AS gtid;"
|
||||
mariadb -u androidcast -pcast-cluster-dev -h127.0.0.1 -e \
|
||||
"SELECT table_schema, COUNT(*) AS tables FROM information_schema.tables WHERE table_schema IN ('androidcast_crashes','url_shortener') GROUP BY table_schema;"
|
||||
;;
|
||||
cast02|cast03)
|
||||
mariadb -u root -e "SELECT @@server_id AS server_id, @@read_only AS read_only;" 2>/dev/null || { echo "mariadb not running"; FAIL=1; }
|
||||
RS="$(mariadb -u root -e 'SHOW REPLICA STATUS\G' 2>/dev/null || true)"
|
||||
echo "$RS" | grep -E 'Master_Host|Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master|Last_SQL_Error' || true
|
||||
echo "$RS" | grep -E 'Slave_IO_Running: Yes' >/dev/null || { echo "FAIL: IO thread not running"; FAIL=1; }
|
||||
echo "$RS" | grep -E 'Slave_SQL_Running: Yes' >/dev/null || { echo "FAIL: SQL thread not running"; FAIL=1; }
|
||||
mariadb -u root -e \
|
||||
"SELECT table_schema, COUNT(*) AS tables FROM information_schema.tables WHERE table_schema IN ('androidcast_crashes','url_shortener') GROUP BY table_schema;"
|
||||
mariadb -u androidcast -pcast-cluster-dev -h127.0.0.1 -e "SELECT 1 AS local_read_ok;" 2>/dev/null || echo "WARN: local app user read failed"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo OK
|
||||
else
|
||||
echo FAIL
|
||||
exit 1
|
||||
fi
|
||||
64
sim/cluster0/verify_global_setup.sh
Normal file
64
sim/cluster0/verify_global_setup.sh
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/bin/sh
|
||||
# Post-DNS / post-FE verification — run after PUBLIC_ORIGIN points at this cluster.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||
export CAST_CLUSTER_ROOT="$ROOT"
|
||||
# shellcheck source=/dev/null
|
||||
. "$ROOT/scripts/lib/common.sh"
|
||||
export JOURNAL_ACTOR=verify_global
|
||||
export JOURNAL_PHASE=verify_global
|
||||
ensure_shared_mounted
|
||||
|
||||
FAIL=0
|
||||
ORIGIN="${PUBLIC_ORIGIN%/}"
|
||||
|
||||
check_url() {
|
||||
_path="$1"
|
||||
_expect="${2:-200}"
|
||||
_url="${ORIGIN}${_path}"
|
||||
_action="curl ${_path}"
|
||||
journal_start "$_action" "$_url"
|
||||
_code="$(curl -sS -o /dev/null -w '%{http_code}' -L --max-time 30 "$_url" 2>/dev/null || echo 000)"
|
||||
if [ "$_code" = "$_expect" ]; then
|
||||
journal_ok "$_action" "http=${_code}"
|
||||
log "OK $_code $_url"
|
||||
else
|
||||
journal_nok "$_action" "got=${_code} want=${_expect}"
|
||||
log "FAIL got $_code want $_expect $_url"
|
||||
FAIL=1
|
||||
fi
|
||||
}
|
||||
|
||||
journal_start verify_global "origin=${ORIGIN}"
|
||||
log "verify_global_setup cluster=${CLUSTER_NAME} origin=${ORIGIN}"
|
||||
[ -n "$ORIGIN" ] || die "set PUBLIC_ORIGIN in cluster.env"
|
||||
|
||||
check_url "${APP_HUB_PATH}/" "200"
|
||||
check_url "${APP_BASE_PATH}/" "200"
|
||||
check_url "${APP_BASE_PATH}/assets/js/app.js" "200"
|
||||
check_url "${APP_BASE_PATH}/api/diag.php" "200"
|
||||
|
||||
# Short links UI (may redirect to login)
|
||||
_code="$(curl -sS -o /dev/null -w '%{http_code}' -L --max-time 30 "${ORIGIN}${APP_BASE_PATH}/?view=short_links" 2>/dev/null || echo 000)"
|
||||
case "$_code" in
|
||||
200|302) log "OK $_code short_links" ;;
|
||||
*) log "FAIL short_links http=$_code"; FAIL=1 ;;
|
||||
esac
|
||||
|
||||
if [ -n "${FE_PROXY_TARGET:-}" ]; then
|
||||
_fe_code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 15 "http://${FE_PROXY_TARGET}${APP_BASE_PATH}/" 2>/dev/null || echo 000)"
|
||||
case "$_fe_code" in
|
||||
200|302) log "OK upstream $_fe_code http://${FE_PROXY_TARGET}${APP_BASE_PATH}/" ;;
|
||||
*) log "WARN upstream $_fe_code (check FE nginx after DNS cutover)" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
journal_ok verify_global
|
||||
log "verify_global_setup_ok ${CLUSTER_NAME}"
|
||||
exit 0
|
||||
fi
|
||||
journal_nok verify_global
|
||||
log "verify_global_setup_FAIL ${CLUSTER_NAME}"
|
||||
exit 1
|
||||
105
sim/cluster0/verify_lab_setup.sh
Normal file
105
sim/cluster0/verify_lab_setup.sh
Normal file
@@ -0,0 +1,105 @@
|
||||
#!/bin/sh
|
||||
# Internal cluster verification (before or after DNS). No external network required for --local.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||
export CAST_CLUSTER_ROOT="$ROOT"
|
||||
# shellcheck source=/dev/null
|
||||
. "$ROOT/scripts/lib/common.sh"
|
||||
export JOURNAL_ACTOR=verify_lab
|
||||
export JOURNAL_PHASE=verify
|
||||
|
||||
MODE=local
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--local|--local-only) MODE=local; shift ;;
|
||||
--cluster) MODE=cluster; shift ;;
|
||||
*) die "unknown arg: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
FAIL=0
|
||||
check_local_node() {
|
||||
H="$(host_short)"
|
||||
log "verify local $H"
|
||||
run_script "$ROOT/scripts/verify-cluster.sh" || FAIL=1
|
||||
|
||||
nginx -t >/dev/null 2>&1 || { log "FAIL nginx -t"; FAIL=1; }
|
||||
rc-status nginx php-fpm83 2>/dev/null | grep -E 'nginx|php-fpm83' || true
|
||||
if is_primary_node || is_replica_node; then
|
||||
rc-service mariadb status 2>/dev/null | grep -q started || { log "FAIL mariadb not running"; FAIL=1; }
|
||||
fi
|
||||
|
||||
CODE="$(curl -sS -o /dev/null -w '%{http_code}' "http://127.0.0.1${APP_BASE_PATH}/" 2>/dev/null || echo 000)"
|
||||
case "$CODE" in
|
||||
200|302) log "app_http=${CODE}" ;;
|
||||
*) log "FAIL app_http=${CODE} for ${APP_BASE_PATH}/"; FAIL=1 ;;
|
||||
esac
|
||||
|
||||
ASSET="${APP_BASE_PATH}/assets/js/app.js"
|
||||
ACODE="$(curl -sS -o /dev/null -w '%{http_code}' "http://127.0.0.1${ASSET}" 2>/dev/null || echo 000)"
|
||||
case "$ACODE" in
|
||||
200) log "asset_http=${ACODE}" ;;
|
||||
*) log "WARN asset_http=${ACODE} (${ASSET})" ;;
|
||||
esac
|
||||
|
||||
DIAG="$(curl -sS "http://127.0.0.1${APP_BASE_PATH}/api/diag.php" 2>/dev/null || true)"
|
||||
echo "$DIAG" | grep -q '"ok":true' || { log "FAIL diag.php"; FAIL=1; }
|
||||
|
||||
CRASH_N="$(mariadb -u androidcast -p"$(read_cred mariadb_app password)" -h127.0.0.1 -N -e \
|
||||
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='androidcast_crashes'" 2>/dev/null || echo 0)"
|
||||
[ "$CRASH_N" = "$EXPECTED_CRASHES_TABLES" ] || { log "FAIL crashes tables=$CRASH_N want $EXPECTED_CRASHES_TABLES"; FAIL=1; }
|
||||
}
|
||||
|
||||
check_remote_node_http() {
|
||||
_host="$1"
|
||||
_ip=""
|
||||
case "$_host" in
|
||||
"$CAST01_HOST") _ip="$CAST01_IP" ;;
|
||||
"$CAST02_HOST") _ip="$CAST02_IP" ;;
|
||||
"$CAST03_HOST") _ip="$CAST03_IP" ;;
|
||||
esac
|
||||
[ -n "$_ip" ] || { log "FAIL unknown host $_host"; FAIL=1; return; }
|
||||
log "verify http $_host ($_ip)"
|
||||
_code="$(curl -sS -o /dev/null -w '%{http_code}' "http://${_ip}${APP_BASE_PATH}/" 2>/dev/null || echo 000)"
|
||||
case "$_code" in
|
||||
200|302) log "OK $_host app_http=$_code" ;;
|
||||
*) log "FAIL $_host app_http=$_code"; FAIL=1 ;;
|
||||
esac
|
||||
_diag="$(curl -sS "http://${_ip}${APP_BASE_PATH}/api/diag.php" 2>/dev/null || true)"
|
||||
echo "$_diag" | grep -q '"ok":true' || { log "FAIL $_host diag.php"; FAIL=1; }
|
||||
}
|
||||
|
||||
check_remote_node_ssh() {
|
||||
_host="$1"
|
||||
log "verify ssh $_host"
|
||||
ssh_node "$_host" "sudo sh ${SHARED_MOUNT}/cluster/verify_lab_setup.sh --local" || FAIL=1
|
||||
}
|
||||
|
||||
ensure_shared_mounted
|
||||
journal_start verify_lab "mode=${MODE}"
|
||||
|
||||
if [ "$MODE" = cluster ]; then
|
||||
for H in $CAST01_HOST $CAST02_HOST $CAST03_HOST; do
|
||||
if [ "$(host_short)" = "$H" ]; then
|
||||
check_local_node
|
||||
else
|
||||
if ssh -o BatchMode=yes -o ConnectTimeout=3 "${SSH_USER}@${H}" true 2>/dev/null; then
|
||||
check_remote_node_ssh "$H"
|
||||
else
|
||||
check_remote_node_http "$H"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
check_local_node
|
||||
fi
|
||||
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
journal_ok verify_lab "mode=${MODE}"
|
||||
log "verify_lab_setup_ok ${CLUSTER_NAME} mode=$MODE"
|
||||
exit 0
|
||||
fi
|
||||
journal_nok verify_lab "mode=${MODE}"
|
||||
log "verify_lab_setup_FAIL ${CLUSTER_NAME} mode=$MODE"
|
||||
exit 1
|
||||
Reference in New Issue
Block a user