mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-18 06:34:00 -05:00
add railway secrets automation
This commit is contained in:
48
.github/workflows/railway-automation.yml
vendored
Normal file
48
.github/workflows/railway-automation.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name: Railway Secrets & Automation Audit
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main", "claude/**"]
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
schedule:
|
||||||
|
- cron: '0 6 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
name: Validate Railway configuration
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Run Railway validation script
|
||||||
|
run: |
|
||||||
|
python scripts/railway/validate_env_template.py
|
||||||
|
|
||||||
|
summary:
|
||||||
|
name: Automation summary
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: validate
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Summarize results
|
||||||
|
run: |
|
||||||
|
echo ""
|
||||||
|
echo "╔════════════════════════════════════════════════════════╗"
|
||||||
|
if [ "${{ needs.validate.result }}" = "success" ]; then
|
||||||
|
echo "║ ✅ Railway secrets automation checks succeeded ║"
|
||||||
|
else
|
||||||
|
echo "║ ❌ Railway automation detected configuration drift ║"
|
||||||
|
fi
|
||||||
|
echo "║ scripts/railway/validate_env_template.py ║"
|
||||||
|
echo "║ keeps .env.example and Railway config synced ║"
|
||||||
|
echo "╚════════════════════════════════════════════════════════╝"
|
||||||
14
README.md
14
README.md
@@ -82,6 +82,20 @@ The GitHub Pages workflow publishes the canonical frontend from
|
|||||||
`backend/static/index.html` (and any supporting assets in that directory) so
|
`backend/static/index.html` (and any supporting assets in that directory) so
|
||||||
the validation and deploy jobs keep pointing at the same file.
|
the validation and deploy jobs keep pointing at the same file.
|
||||||
|
|
||||||
|
### Railway Secrets & Automation
|
||||||
|
|
||||||
|
- **Vercel-free deploys** – the backend ships with a Railway-native workflow
|
||||||
|
(`Deploy to Railway`) so you can ignore Vercel entirely.
|
||||||
|
- **Single source of truth** – `backend/.env.example` enumerates every runtime
|
||||||
|
variable and uses obvious placeholders so you can paste the file into the
|
||||||
|
Railway variables dashboard without leaking credentials.
|
||||||
|
- **CI enforcement** – the GitHub Action `Railway Secrets & Automation Audit`
|
||||||
|
runs `scripts/railway/validate_env_template.py` on every PR + nightly to make
|
||||||
|
sure the template, `railway.toml`, and `railway.json` never drift from the
|
||||||
|
FastAPI settings.
|
||||||
|
- **Manual check** – run `python scripts/railway/validate_env_template.py`
|
||||||
|
locally to get the same assurance before pushing.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Single-Page Application
|
### Single-Page Application
|
||||||
|
|||||||
@@ -1,83 +1,76 @@
|
|||||||
# Database
|
# BlackRoad OS Backend - Railway Secrets Template
|
||||||
DATABASE_URL=postgresql://blackroad:password@localhost:5432/blackroad_db
|
# Copy this file to .env for local development and keep the actual
|
||||||
DATABASE_ASYNC_URL=postgresql+asyncpg://blackroad:password@localhost:5432/blackroad_db
|
# values in Railway's Variables dashboard. The GitHub workflow
|
||||||
|
# scripts/railway/validate_env_template.py ensures this template stays
|
||||||
|
# aligned with app/app.config.Settings.
|
||||||
|
|
||||||
# Redis
|
# Application metadata
|
||||||
REDIS_URL=redis://localhost:6379/0
|
APP_NAME=BlackRoad Operating System
|
||||||
|
APP_VERSION=1.0.0
|
||||||
|
DEBUG=False
|
||||||
|
ENVIRONMENT=production
|
||||||
|
|
||||||
# JWT
|
# Database connectivity
|
||||||
SECRET_KEY=your-secret-key-change-this-in-production
|
DATABASE_URL=postgresql://YOUR_DB_USER:YOUR_DB_PASSWORD@YOUR_DB_HOST:5432/blackroad
|
||||||
|
DATABASE_ASYNC_URL=postgresql+asyncpg://YOUR_DB_USER:YOUR_DB_PASSWORD@YOUR_DB_HOST:5432/blackroad
|
||||||
|
REDIS_URL=redis://YOUR_REDIS_HOST:6379/0
|
||||||
|
|
||||||
|
# Security / auth
|
||||||
|
SECRET_KEY=changeme-super-secret-key
|
||||||
ALGORITHM=HS256
|
ALGORITHM=HS256
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||||
REFRESH_TOKEN_EXPIRE_DAYS=7
|
REFRESH_TOKEN_EXPIRE_DAYS=7
|
||||||
WALLET_MASTER_KEY=replace-with-strong-unique-master-key
|
WALLET_MASTER_KEY=changeme-wallet-master-key
|
||||||
|
ALLOWED_ORIGINS=https://blackroad.systems,https://your-frontend.example
|
||||||
|
|
||||||
# AWS S3 (for file storage)
|
# Object storage
|
||||||
AWS_ACCESS_KEY_ID=your-access-key
|
AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY_ID
|
||||||
AWS_SECRET_ACCESS_KEY=your-secret-key
|
AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY
|
||||||
AWS_REGION=us-east-1
|
AWS_REGION=us-east-1
|
||||||
S3_BUCKET_NAME=blackroad-files
|
S3_BUCKET_NAME=blackroad-files
|
||||||
|
|
||||||
# Email (SMTP)
|
# Email / SMTP
|
||||||
SMTP_HOST=smtp.gmail.com
|
SMTP_HOST=smtp.gmail.com
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_USER=your-email@gmail.com
|
SMTP_USER=road@example.com
|
||||||
SMTP_PASSWORD=your-app-password
|
SMTP_PASSWORD=changeme-smtp-password
|
||||||
EMAIL_FROM=noreply@blackroad.com
|
EMAIL_FROM=blackroad@example.com
|
||||||
|
|
||||||
# Application
|
# AI integrations
|
||||||
APP_NAME=BlackRoad Operating System
|
OPENAI_API_KEY=sk-your-openai-key
|
||||||
APP_VERSION=1.0.0
|
|
||||||
DEBUG=True
|
|
||||||
ENVIRONMENT=development
|
|
||||||
|
|
||||||
# CORS (add your production domains here)
|
# Blockchain tuning
|
||||||
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8000,https://blackboxprogramming.github.io,https://www.blackroad.systems
|
BLOCKCHAIN_DIFFICULTY=4
|
||||||
|
MINING_REWARD=50.0
|
||||||
|
|
||||||
# API Keys - Existing Integrations
|
# Railway deployment + alerting
|
||||||
OPENAI_API_KEY=your-openai-key-for-ai-chat
|
RAILWAY_TOKEN=railway-token-placeholder
|
||||||
|
RAILWAY_PROJECT_ID=00000000-0000-0000-0000-000000000000
|
||||||
|
RAILWAY_ENVIRONMENT_ID=00000000-0000-0000-0000-000000000000
|
||||||
|
RAILWAY_DOMAIN=your-service.up.railway.app
|
||||||
|
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ
|
||||||
|
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/XXX/YYY
|
||||||
|
|
||||||
|
# Optional cloud/API integrations
|
||||||
DIGITALOCEAN_TOKEN=your-digitalocean-token
|
DIGITALOCEAN_TOKEN=your-digitalocean-token
|
||||||
GITHUB_TOKEN=your-github-personal-access-token
|
GITHUB_TOKEN=your-github-personal-access-token
|
||||||
HUGGINGFACE_TOKEN=your-huggingface-api-token
|
HUGGINGFACE_TOKEN=your-huggingface-token
|
||||||
|
VERCEL_TOKEN=vercel-token-placeholder
|
||||||
# API Keys - New Deployment Platform Integrations
|
VERCEL_TEAM_ID=your-vercel-team-id
|
||||||
RAILWAY_TOKEN=your-railway-api-token
|
|
||||||
VERCEL_TOKEN=your-vercel-api-token
|
|
||||||
VERCEL_TEAM_ID=your-vercel-team-id-optional
|
|
||||||
|
|
||||||
# API Keys - Payment Processing
|
|
||||||
STRIPE_SECRET_KEY=sk_test_your-stripe-secret-key
|
STRIPE_SECRET_KEY=sk_test_your-stripe-secret-key
|
||||||
STRIPE_PUBLISHABLE_KEY=pk_test_your-stripe-publishable-key
|
STRIPE_PUBLISHABLE_KEY=pk_test_your-stripe-publishable-key
|
||||||
|
TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||||
# API Keys - Communications (SMS/WhatsApp)
|
|
||||||
TWILIO_ACCOUNT_SID=your-twilio-account-sid
|
|
||||||
TWILIO_AUTH_TOKEN=your-twilio-auth-token
|
TWILIO_AUTH_TOKEN=your-twilio-auth-token
|
||||||
TWILIO_PHONE_NUMBER=+1234567890
|
TWILIO_PHONE_NUMBER=+10000000000
|
||||||
|
|
||||||
# API Keys - Team Collaboration & Notifications
|
|
||||||
SLACK_BOT_TOKEN=xoxb-your-slack-bot-token
|
SLACK_BOT_TOKEN=xoxb-your-slack-bot-token
|
||||||
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
|
DISCORD_BOT_TOKEN=discord-bot-token-placeholder
|
||||||
DISCORD_BOT_TOKEN=your-discord-bot-token
|
SENTRY_DSN=https://example.ingest.sentry.io/project-id
|
||||||
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR/WEBHOOK/URL
|
|
||||||
|
|
||||||
# API Keys - Error Tracking & Monitoring
|
|
||||||
SENTRY_DSN=https://your-sentry-dsn@sentry.io/project-id
|
|
||||||
SENTRY_AUTH_TOKEN=your-sentry-auth-token
|
SENTRY_AUTH_TOKEN=your-sentry-auth-token
|
||||||
SENTRY_ORG=your-sentry-organization-slug
|
SENTRY_ORG=your-sentry-org
|
||||||
|
ROADCHAIN_RPC_URL=https://chain.example-rpc.net
|
||||||
# Blockchain & Mining
|
ROADCOIN_POOL_URL=pool.example.roadcoin:3333
|
||||||
BLOCKCHAIN_DIFFICULTY=4
|
ROADCOIN_WALLET_ADDRESS=your-roadcoin-wallet
|
||||||
MINING_REWARD=50
|
MQTT_BROKER_URL=mqtt://broker.example.internal:1883
|
||||||
ROADCHAIN_RPC_URL=http://localhost:8545
|
|
||||||
ROADCOIN_POOL_URL=pool.roadcoin.network:3333
|
|
||||||
ROADCOIN_WALLET_ADDRESS=auto-generated-per-user
|
|
||||||
|
|
||||||
# Device Management (IoT/Raspberry Pi)
|
|
||||||
MQTT_BROKER_URL=mqtt://localhost:1883
|
|
||||||
MQTT_USERNAME=blackroad
|
MQTT_USERNAME=blackroad
|
||||||
MQTT_PASSWORD=your-mqtt-password
|
MQTT_PASSWORD=your-mqtt-password
|
||||||
DEVICE_HEARTBEAT_TIMEOUT_SECONDS=300
|
DEVICE_HEARTBEAT_TIMEOUT_SECONDS=300
|
||||||
|
|
||||||
# Deployment
|
|
||||||
# Railway will automatically set PORT - use this for local development
|
|
||||||
PORT=8000
|
|
||||||
|
|||||||
@@ -26,6 +26,28 @@ SECRET_KEY=your-secret-key
|
|||||||
ALLOWED_ORIGINS=https://yourdomain.com
|
ALLOWED_ORIGINS=https://yourdomain.com
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> 💡 **Railway-first secrets**: copy `backend/.env.example` to `.env` for local
|
||||||
|
> runs, but store the real values inside your Railway project. The template is
|
||||||
|
> kept in sync with `app.config.Settings` by the
|
||||||
|
> [`scripts/railway/validate_env_template.py`](../scripts/railway/validate_env_template.py)
|
||||||
|
> audit script, which also runs automatically in CI.
|
||||||
|
|
||||||
|
### Railway Secrets Checklist
|
||||||
|
|
||||||
|
1. Install the Railway CLI and authenticate: `curl -fsSL https://railway.app/install.sh | sh`
|
||||||
|
2. Link the project locally: `railway link <project-id>`
|
||||||
|
3. Use the template to populate secrets:
|
||||||
|
```bash
|
||||||
|
while IFS='=' read -r key value; do
|
||||||
|
if [[ -n "$key" && $key != \#* ]]; then
|
||||||
|
railway variables set "$key" "$value"
|
||||||
|
fi
|
||||||
|
done < backend/.env.example
|
||||||
|
```
|
||||||
|
4. Override placeholder values using the Railway dashboard or `railway variables set`.
|
||||||
|
5. Verify everything with `railway variables list` or by running the GitHub
|
||||||
|
Action **Railway Secrets & Automation Audit** from the Actions tab.
|
||||||
|
|
||||||
## Local Development
|
## Local Development
|
||||||
|
|
||||||
### 1. Setup Virtual Environment
|
### 1. Setup Virtual Environment
|
||||||
|
|||||||
229
scripts/railway/validate_env_template.py
Normal file
229
scripts/railway/validate_env_template.py
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
"""Validate Railway env template and deployment config."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import ast
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Set, Tuple
|
||||||
|
|
||||||
|
import tomllib
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
BACKEND_DIR = REPO_ROOT / "backend"
|
||||||
|
CONFIG_PATH = BACKEND_DIR / "app" / "config.py"
|
||||||
|
ENV_TEMPLATE_PATH = BACKEND_DIR / ".env.example"
|
||||||
|
RAILWAY_TOML_PATH = REPO_ROOT / "railway.toml"
|
||||||
|
RAILWAY_JSON_PATH = REPO_ROOT / "railway.json"
|
||||||
|
|
||||||
|
# Keys that live outside app.config.Settings but should exist for automation
|
||||||
|
EXTRA_REQUIRED_KEYS: Set[str] = {
|
||||||
|
"RAILWAY_TOKEN",
|
||||||
|
"RAILWAY_PROJECT_ID",
|
||||||
|
"RAILWAY_ENVIRONMENT_ID",
|
||||||
|
"RAILWAY_DOMAIN",
|
||||||
|
"SLACK_WEBHOOK_URL",
|
||||||
|
"DISCORD_WEBHOOK_URL",
|
||||||
|
"DIGITALOCEAN_TOKEN",
|
||||||
|
"GITHUB_TOKEN",
|
||||||
|
"HUGGINGFACE_TOKEN",
|
||||||
|
"VERCEL_TOKEN",
|
||||||
|
"VERCEL_TEAM_ID",
|
||||||
|
"STRIPE_SECRET_KEY",
|
||||||
|
"STRIPE_PUBLISHABLE_KEY",
|
||||||
|
"TWILIO_ACCOUNT_SID",
|
||||||
|
"TWILIO_AUTH_TOKEN",
|
||||||
|
"TWILIO_PHONE_NUMBER",
|
||||||
|
"SLACK_BOT_TOKEN",
|
||||||
|
"DISCORD_BOT_TOKEN",
|
||||||
|
"SENTRY_DSN",
|
||||||
|
"SENTRY_AUTH_TOKEN",
|
||||||
|
"SENTRY_ORG",
|
||||||
|
"ROADCHAIN_RPC_URL",
|
||||||
|
"ROADCOIN_POOL_URL",
|
||||||
|
"ROADCOIN_WALLET_ADDRESS",
|
||||||
|
"MQTT_BROKER_URL",
|
||||||
|
"MQTT_USERNAME",
|
||||||
|
"MQTT_PASSWORD",
|
||||||
|
"DEVICE_HEARTBEAT_TIMEOUT_SECONDS",
|
||||||
|
}
|
||||||
|
|
||||||
|
SENSITIVE_KEYS: Set[str] = {
|
||||||
|
"DATABASE_URL",
|
||||||
|
"DATABASE_ASYNC_URL",
|
||||||
|
"REDIS_URL",
|
||||||
|
"SECRET_KEY",
|
||||||
|
"WALLET_MASTER_KEY",
|
||||||
|
"AWS_ACCESS_KEY_ID",
|
||||||
|
"AWS_SECRET_ACCESS_KEY",
|
||||||
|
"SMTP_PASSWORD",
|
||||||
|
"OPENAI_API_KEY",
|
||||||
|
"RAILWAY_TOKEN",
|
||||||
|
"RAILWAY_PROJECT_ID",
|
||||||
|
"RAILWAY_ENVIRONMENT_ID",
|
||||||
|
"RAILWAY_DOMAIN",
|
||||||
|
"SLACK_WEBHOOK_URL",
|
||||||
|
"DISCORD_WEBHOOK_URL",
|
||||||
|
"DIGITALOCEAN_TOKEN",
|
||||||
|
"GITHUB_TOKEN",
|
||||||
|
"HUGGINGFACE_TOKEN",
|
||||||
|
"VERCEL_TOKEN",
|
||||||
|
"STRIPE_SECRET_KEY",
|
||||||
|
"STRIPE_PUBLISHABLE_KEY",
|
||||||
|
"TWILIO_ACCOUNT_SID",
|
||||||
|
"TWILIO_AUTH_TOKEN",
|
||||||
|
"TWILIO_PHONE_NUMBER",
|
||||||
|
"SLACK_BOT_TOKEN",
|
||||||
|
"DISCORD_BOT_TOKEN",
|
||||||
|
"SENTRY_DSN",
|
||||||
|
"SENTRY_AUTH_TOKEN",
|
||||||
|
"ROADCHAIN_RPC_URL",
|
||||||
|
"ROADCOIN_POOL_URL",
|
||||||
|
"ROADCOIN_WALLET_ADDRESS",
|
||||||
|
"MQTT_BROKER_URL",
|
||||||
|
"MQTT_PASSWORD",
|
||||||
|
}
|
||||||
|
|
||||||
|
PLACEHOLDER_MARKERS: Tuple[str, ...] = (
|
||||||
|
"changeme",
|
||||||
|
"your_",
|
||||||
|
"your-",
|
||||||
|
"placeholder",
|
||||||
|
"example",
|
||||||
|
"dummy",
|
||||||
|
"xxxx",
|
||||||
|
"xxx",
|
||||||
|
"yyy",
|
||||||
|
"zzz",
|
||||||
|
"0000",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_env_template(path: Path) -> Dict[str, str]:
|
||||||
|
env: Dict[str, str] = {}
|
||||||
|
for raw_line in path.read_text().splitlines():
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
if "=" not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
env[key.strip()] = value.strip()
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
def extract_settings_fields(config_path: Path) -> List[str]:
|
||||||
|
tree = ast.parse(config_path.read_text())
|
||||||
|
fields: Set[str] = set()
|
||||||
|
|
||||||
|
class SettingsVisitor(ast.NodeVisitor):
|
||||||
|
def visit_ClassDef(self, node: ast.ClassDef) -> None:
|
||||||
|
if node.name != "Settings":
|
||||||
|
return
|
||||||
|
for stmt in node.body:
|
||||||
|
if isinstance(stmt, ast.ClassDef):
|
||||||
|
continue
|
||||||
|
if isinstance(stmt, ast.Assign):
|
||||||
|
for target in stmt.targets:
|
||||||
|
if isinstance(target, ast.Name):
|
||||||
|
fields.add(target.id)
|
||||||
|
elif isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name):
|
||||||
|
fields.add(stmt.target.id)
|
||||||
|
|
||||||
|
SettingsVisitor().visit(tree)
|
||||||
|
return sorted(fields)
|
||||||
|
|
||||||
|
|
||||||
|
def is_placeholder(value: str) -> bool:
|
||||||
|
lower_value = value.lower()
|
||||||
|
return any(marker in lower_value for marker in PLACEHOLDER_MARKERS)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_env_template() -> None:
|
||||||
|
if not ENV_TEMPLATE_PATH.exists():
|
||||||
|
raise SystemExit(".env.example template is missing")
|
||||||
|
|
||||||
|
env_values = parse_env_template(ENV_TEMPLATE_PATH)
|
||||||
|
settings_fields = extract_settings_fields(CONFIG_PATH)
|
||||||
|
required_keys = set(settings_fields) | EXTRA_REQUIRED_KEYS
|
||||||
|
|
||||||
|
missing = sorted(key for key in required_keys if key not in env_values)
|
||||||
|
if missing:
|
||||||
|
raise SystemExit(
|
||||||
|
"Missing keys in .env.example: " + ", ".join(missing)
|
||||||
|
)
|
||||||
|
|
||||||
|
unexpected = sorted(
|
||||||
|
key for key in env_values.keys() if key not in required_keys
|
||||||
|
)
|
||||||
|
if unexpected:
|
||||||
|
print("⚠️ Found extra keys in .env.example:", ", ".join(unexpected))
|
||||||
|
|
||||||
|
insecure = sorted(
|
||||||
|
key
|
||||||
|
for key in SENSITIVE_KEYS
|
||||||
|
if key in env_values and not is_placeholder(env_values[key])
|
||||||
|
)
|
||||||
|
if insecure:
|
||||||
|
raise SystemExit(
|
||||||
|
"Sensitive keys must use placeholders: " + ", ".join(insecure)
|
||||||
|
)
|
||||||
|
|
||||||
|
print("✅ .env.example matches app.config.Settings")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_railway_configs() -> None:
|
||||||
|
toml_data = tomllib.loads(RAILWAY_TOML_PATH.read_text())
|
||||||
|
json_data = json.loads(RAILWAY_JSON_PATH.read_text()) if RAILWAY_JSON_PATH.exists() else {}
|
||||||
|
|
||||||
|
build = toml_data.get("build", {})
|
||||||
|
if build.get("builder") != "DOCKERFILE":
|
||||||
|
raise SystemExit("railway.toml must use the Dockerfile builder")
|
||||||
|
if build.get("dockerfilePath") != "backend/Dockerfile":
|
||||||
|
raise SystemExit("railway.toml dockerfilePath must be backend/Dockerfile")
|
||||||
|
|
||||||
|
deploy = toml_data.get("deploy", {})
|
||||||
|
start_command = deploy.get("startCommand", "")
|
||||||
|
if "$PORT" not in start_command:
|
||||||
|
raise SystemExit("Railway start command must forward the $PORT value")
|
||||||
|
|
||||||
|
services = toml_data.get("services", [])
|
||||||
|
env_names = {
|
||||||
|
env_entry.get("name")
|
||||||
|
for service in services
|
||||||
|
if isinstance(service, dict)
|
||||||
|
for env_entry in service.get("env", [])
|
||||||
|
if isinstance(env_entry, dict)
|
||||||
|
}
|
||||||
|
|
||||||
|
for required in ("ENVIRONMENT", "DEBUG"):
|
||||||
|
if required not in env_names:
|
||||||
|
raise SystemExit(f"Railway services must set {required}")
|
||||||
|
|
||||||
|
json_build = json_data.get("build", {})
|
||||||
|
if json_build.get("builder") and json_build.get("builder") != "DOCKERFILE":
|
||||||
|
raise SystemExit("railway.json builder must remain DOCKERFILE")
|
||||||
|
json_path = json_build.get("dockerfilePath")
|
||||||
|
if json_path and json_path != "backend/Dockerfile":
|
||||||
|
raise SystemExit("railway.json dockerfilePath must match backend/Dockerfile")
|
||||||
|
|
||||||
|
print("✅ Railway deployment descriptors look consistent")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description="Validate Railway secret automation")
|
||||||
|
parser.add_argument("--skip-env", action="store_true", help="Skip env template validation")
|
||||||
|
parser.add_argument("--skip-config", action="store_true", help="Skip Railway config validation")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.skip_env:
|
||||||
|
validate_env_template()
|
||||||
|
|
||||||
|
if not args.skip_config:
|
||||||
|
validate_railway_configs()
|
||||||
|
|
||||||
|
print("\nAll Railway automation checks passed ✅")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user