Scaffold blackroad-infra: Terraform modules, Docker configs, CI templates

- Terraform environments (production, staging) with R2 backend
- Terraform modules: cloudflare-pages, cloudflare-worker, railway-service, digitalocean-droplet
- Docker: multi-stage Dockerfiles for core, web, agents, operator + compose
- CI templates: node-ci, terraform-ci, docker-ci reusable workflows
- Composite actions: brand-compliance, deploy-cloudflare
- Operational scripts: bootstrap, health-check, rotate-keys
- GitHub Actions: terraform-plan, terraform-apply, docker-build

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexa Amundson
2026-02-20 16:53:12 -06:00
parent 0b2ae58240
commit 63cf488432
41 changed files with 966 additions and 1 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
* @alexa-amundson

15
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,15 @@
## Summary
<!-- What does this PR change? -->
## Type
- [ ] Terraform module
- [ ] Docker config
- [ ] CI/CD workflow
- [ ] Script
- [ ] Documentation
## Checklist
- [ ] `terraform validate` passes (if applicable)
- [ ] Docker builds successfully (if applicable)
- [ ] No secrets committed
- [ ] Documentation updated

27
.github/workflows/docker-build.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
name: Docker Build
on:
push:
branches: [main]
paths: ['docker/**']
pull_request:
branches: [main]
paths: ['docker/**']
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
service: [core, web, agents, operator]
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: docker/${{ matrix.service }}
push: false
tags: blackroad/${{ matrix.service }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

31
.github/workflows/terraform-apply.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
name: Terraform Apply
on:
push:
branches: [main]
paths: ['terraform/**']
jobs:
apply:
runs-on: ubuntu-latest
environment: production
defaults:
run:
working-directory: terraform/environments/production
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.7"
- run: terraform init
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
- run: terraform apply -auto-approve
env:
TF_VAR_cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
TF_VAR_cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_VAR_digitalocean_token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}

25
.github/workflows/terraform-plan.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
name: Terraform Plan
on:
pull_request:
branches: [main]
paths: ['terraform/**']
jobs:
plan:
runs-on: ubuntu-latest
strategy:
matrix:
environment: [staging, production]
defaults:
run:
working-directory: terraform/environments/${{ matrix.environment }}
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.7"
- run: terraform init -backend=false
- run: terraform validate
- run: terraform fmt -check

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.terraform/
*.tfstate
*.tfstate.backup
*.tfvars
!terraform.tfvars.example
.env
*.pem
*.key

5
.prettierrc Normal file
View File

@@ -0,0 +1,5 @@
{
"singleQuote": true,
"trailingComma": "all",
"semi": false
}

18
CLAUDE.md Normal file
View File

@@ -0,0 +1,18 @@
# CLAUDE.md — blackroad-infra
Infrastructure-as-Code for BlackRoad OS. Terraform modules, Docker configs, CI templates, and operational scripts.
## Stack
- Terraform (Cloudflare, DigitalOcean, Railway modules)
- Docker (multi-service compose)
- GitHub Actions (reusable workflows and composite actions)
## Key Directories
- `terraform/environments/` — Production and staging configs
- `terraform/modules/` — Reusable Terraform modules
- `docker/` — Dockerfiles and docker-compose
- `ci/` — Reusable workflow templates and composite actions
- `scripts/` — Operational shell scripts
## Copyright
All files are proprietary to BlackRoad OS, Inc.

6
LICENSE Normal file
View File

@@ -0,0 +1,6 @@
Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
PROPRIETARY AND CONFIDENTIAL
This software and related documentation are proprietary to BlackRoad OS, Inc.
Unauthorized copying, distribution, or use is strictly prohibited.

View File

@@ -1,2 +1,44 @@
# blackroad-infra # blackroad-infra
Infrastructure-as-code, CI/CD workflows, deployment configurations, and environment orchestration.
Infrastructure-as-Code, Docker configs, CI templates, and operational scripts for BlackRoad OS.
## Structure
```
terraform/
environments/ # Production and staging configs
modules/ # Reusable Terraform modules
docker/ # Dockerfiles and docker-compose
ci/
templates/ # Reusable GitHub Actions workflows
actions/ # Composite GitHub Actions
scripts/ # Operational shell scripts
```
## Prerequisites
- Terraform >= 1.7
- Docker & Docker Compose
- Node.js >= 22
## Quick Start
```bash
./scripts/bootstrap.sh # Check tooling
cd docker && docker compose up # Run all services
cd terraform/environments/staging
terraform init -backend=false && terraform validate
```
## Terraform Modules
| Module | Purpose |
|--------|---------|
| `cloudflare-pages` | Pages project deployment |
| `cloudflare-worker` | Worker script + route |
| `railway-service` | Railway CLI deployment |
| `digitalocean-droplet` | Droplet + firewall |
## License
Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.

View File

@@ -0,0 +1,29 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
name: Brand Compliance Check
description: Verify CSS and UI files use approved BlackRoad brand colors
inputs:
scan-path:
description: Directory to scan
default: "."
runs:
using: composite
steps:
- name: Check for forbidden colors
shell: bash
run: |
echo "Checking brand compliance in ${{ inputs.scan-path }}..."
FORBIDDEN=("#FF9D00" "#FF6B00" "#FF0066" "#FF006B" "#D600AA" "#7700FF" "#0066FF")
FOUND=0
for color in "${FORBIDDEN[@]}"; do
if grep -ri "$color" "${{ inputs.scan-path }}" --include="*.css" --include="*.tsx" --include="*.ts" 2>/dev/null; then
echo "FORBIDDEN COLOR FOUND: $color"
FOUND=1
fi
done
if [ $FOUND -eq 1 ]; then
echo "Brand compliance check FAILED"
exit 1
fi
echo "Brand compliance check PASSED"

View File

@@ -0,0 +1,30 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
name: Deploy to Cloudflare Pages
description: Build and deploy a project to Cloudflare Pages
inputs:
project-name:
description: Cloudflare Pages project name
required: true
directory:
description: Build output directory to deploy
required: true
api-token:
description: Cloudflare API token
required: true
account-id:
description: Cloudflare account ID
required: true
runs:
using: composite
steps:
- name: Deploy to Cloudflare Pages
shell: bash
run: |
npx wrangler pages deploy "${{ inputs.directory }}" \
--project-name="${{ inputs.project-name }}" \
--branch="${GITHUB_REF_NAME}"
env:
CLOUDFLARE_API_TOKEN: ${{ inputs.api-token }}
CLOUDFLARE_ACCOUNT_ID: ${{ inputs.account-id }}

View File

@@ -0,0 +1,29 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
name: Docker CI
on:
workflow_call:
inputs:
context:
type: string
required: true
image-name:
type: string
required: true
secrets:
registry-token:
required: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: ${{ inputs.context }}
push: false
tags: ${{ inputs.image-name }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

30
ci/templates/node-ci.yml Normal file
View File

@@ -0,0 +1,30 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
name: Node.js CI
on:
workflow_call:
inputs:
node-version:
type: string
default: "22"
working-directory:
type: string
default: "."
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: npm
cache-dependency-path: ${{ inputs.working-directory }}/package-lock.json
- run: npm ci
- run: npm run lint --if-present
- run: npm run typecheck --if-present
- run: npm test --if-present

View File

@@ -0,0 +1,27 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
name: Terraform CI
on:
workflow_call:
inputs:
working-directory:
type: string
required: true
terraform-version:
type: string
default: "1.7"
jobs:
validate:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ inputs.terraform-version }}
- run: terraform fmt -check -recursive
- run: terraform init -backend=false
- run: terraform validate

15
docker/agents/Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 8788
CMD ["node", "dist/index.js"]

15
docker/core/Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 8787
CMD ["node", "dist/index.js"]

63
docker/docker-compose.yml Normal file
View File

@@ -0,0 +1,63 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
version: "3.9"
services:
core:
build:
context: ./core
dockerfile: Dockerfile
ports:
- "8787:8787"
environment:
- NODE_ENV=production
- BLACKROAD_GATEWAY_PORT=8787
networks:
- blackroad
restart: unless-stopped
web:
build:
context: ./web
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- NEXT_PUBLIC_GATEWAY_URL=http://core:8787
depends_on:
- core
networks:
- blackroad
restart: unless-stopped
agents:
build:
context: ./agents
dockerfile: Dockerfile
ports:
- "8788:8788"
environment:
- NODE_ENV=production
- GATEWAY_URL=http://core:8787
depends_on:
- core
networks:
- blackroad
restart: unless-stopped
operator:
build:
context: ./operator
dockerfile: Dockerfile
environment:
- BLACKROAD_GATEWAY_URL=http://core:8787
depends_on:
- core
networks:
- blackroad
profiles:
- tools
networks:
blackroad:
driver: bridge

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm link
ENTRYPOINT ["br"]

16
docker/web/Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]

50
scripts/bootstrap.sh Executable file
View File

@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
# Bootstrap a new development environment with required tools.
set -euo pipefail
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m'
log() { echo -e "${GREEN}${NC} $1"; }
warn() { echo -e "${YELLOW}${NC} $1"; }
fail() { echo -e "${RED}${NC} $1" >&2; exit 1; }
echo "BlackRoad Infra — Bootstrap"
echo "==========================="
# Check Node.js
if command -v node &>/dev/null; then
log "Node.js $(node -v)"
else
fail "Node.js not found. Install Node.js 22+."
fi
# Check Terraform
if command -v terraform &>/dev/null; then
log "Terraform $(terraform version -json | jq -r .terraform_version)"
else
warn "Terraform not found. Install: brew install terraform"
fi
# Check Docker
if command -v docker &>/dev/null; then
log "Docker $(docker --version | awk '{print $3}')"
else
warn "Docker not found. Install Docker Desktop."
fi
# Check Wrangler
if command -v wrangler &>/dev/null; then
log "Wrangler installed"
else
warn "Wrangler not found. Install: npm i -g wrangler"
fi
# Check GitHub CLI
if command -v gh &>/dev/null; then
log "GitHub CLI $(gh --version | head -1 | awk '{print $3}')"
else
warn "GitHub CLI not found. Install: brew install gh"
fi
echo ""
log "Bootstrap complete."

27
scripts/health-check.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
# Health check all BlackRoad services.
set -euo pipefail
GREEN='\033[0;32m'; RED='\033[0;31m'; NC='\033[0m'
ok() { echo -e "${GREEN}${NC} $1"; }
fail() { echo -e "${RED}${NC} $1"; }
echo "BlackRoad Health Check"
echo "======================"
check_url() {
local name="$1" url="$2"
if curl -sf --max-time 5 "$url" > /dev/null 2>&1; then
ok "$name ($url)"
else
fail "$name ($url)"
fi
}
check_url "Gateway" "${BLACKROAD_GATEWAY_URL:-http://127.0.0.1:8787}/v1/health"
check_url "Web" "${BLACKROAD_WEB_URL:-http://127.0.0.1:3000}"
check_url "Agents" "${BLACKROAD_AGENTS_URL:-http://127.0.0.1:8788}"
echo ""
echo "Done."

20
scripts/rotate-keys.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
# Key rotation helper — generates new secrets and updates .env files.
set -euo pipefail
echo "BlackRoad Key Rotation"
echo "======================"
generate_key() {
openssl rand -hex 32
}
echo "Generating new keys..."
echo ""
echo "GATEWAY_SECRET=$(generate_key)"
echo "JWT_SECRET=$(generate_key)"
echo "SESSION_SECRET=$(generate_key)"
echo ""
echo "Copy the values above into your .env file."
echo "Remember to restart services after updating keys."

View File

@@ -0,0 +1,17 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
# Using Cloudflare R2 as S3-compatible backend
terraform {
backend "s3" {
bucket = "blackroad-terraform-state"
key = "production/terraform.tfstate"
region = "auto"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_region_validation = true
skip_requesting_account_id = true
skip_s3_checksum = true
endpoints = {
s3 = "https://848cf0b18d51e0170e0d1537aec3505a.r2.cloudflarestorage.com"
}
}
}

View File

@@ -0,0 +1,54 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
terraform {
required_version = ">= 1.7"
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.0"
}
}
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}
provider "digitalocean" {
token = var.digitalocean_token
}
module "gateway_pages" {
source = "../../modules/cloudflare-pages"
project_name = "blackroad-gateway"
build_command = "npm run build"
destination_dir = "dist"
account_id = var.cloudflare_account_id
}
module "web_pages" {
source = "../../modules/cloudflare-pages"
project_name = "blackroad-web"
build_command = "npm run build"
destination_dir = ".next"
account_id = var.cloudflare_account_id
}
module "gateway_worker" {
source = "../../modules/cloudflare-worker"
name = "blackroad-gateway"
script_path = "${path.module}/../../../docker/core/dist/index.js"
account_id = var.cloudflare_account_id
}
module "primary_droplet" {
source = "../../modules/digitalocean-droplet"
name = "blackroad-production"
region = var.do_region
size = var.do_size
image = "ubuntu-24-04-x64"
ssh_keys = var.do_ssh_keys
}

View File

@@ -0,0 +1,35 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
variable "cloudflare_api_token" {
description = "Cloudflare API token"
type = string
sensitive = true
}
variable "cloudflare_account_id" {
description = "Cloudflare account ID"
type = string
}
variable "digitalocean_token" {
description = "DigitalOcean API token"
type = string
sensitive = true
}
variable "do_region" {
description = "DigitalOcean region"
type = string
default = "nyc3"
}
variable "do_size" {
description = "DigitalOcean droplet size"
type = string
default = "s-2vcpu-4gb"
}
variable "do_ssh_keys" {
description = "SSH key fingerprints for droplet access"
type = list(string)
default = []
}

View File

@@ -0,0 +1,16 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
terraform {
backend "s3" {
bucket = "blackroad-terraform-state"
key = "staging/terraform.tfstate"
region = "auto"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_region_validation = true
skip_requesting_account_id = true
skip_s3_checksum = true
endpoints = {
s3 = "https://848cf0b18d51e0170e0d1537aec3505a.r2.cloudflarestorage.com"
}
}
}

View File

@@ -0,0 +1,39 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
terraform {
required_version = ">= 1.7"
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.0"
}
}
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}
provider "digitalocean" {
token = var.digitalocean_token
}
module "gateway_pages" {
source = "../../modules/cloudflare-pages"
project_name = "blackroad-gateway-staging"
build_command = "npm run build"
destination_dir = "dist"
account_id = var.cloudflare_account_id
}
module "staging_droplet" {
source = "../../modules/digitalocean-droplet"
name = "blackroad-staging"
region = var.do_region
size = "s-1vcpu-1gb"
image = "ubuntu-24-04-x64"
ssh_keys = var.do_ssh_keys
}

View File

@@ -0,0 +1,29 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
variable "cloudflare_api_token" {
description = "Cloudflare API token"
type = string
sensitive = true
}
variable "cloudflare_account_id" {
description = "Cloudflare account ID"
type = string
}
variable "digitalocean_token" {
description = "DigitalOcean API token"
type = string
sensitive = true
}
variable "do_region" {
description = "DigitalOcean region"
type = string
default = "nyc3"
}
variable "do_ssh_keys" {
description = "SSH key fingerprints"
type = list(string)
default = []
}

View File

@@ -0,0 +1,17 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
resource "cloudflare_pages_project" "this" {
account_id = var.account_id
name = var.project_name
production_branch = "main"
build_config {
build_command = var.build_command
destination_dir = var.destination_dir
}
deployment_configs {
production {
environment_variables = var.environment_variables
}
}
}

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
output "url" {
description = "Pages project URL"
value = "https://${cloudflare_pages_project.this.name}.pages.dev"
}
output "project_id" {
description = "Pages project ID"
value = cloudflare_pages_project.this.id
}

View File

@@ -0,0 +1,28 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
variable "account_id" {
description = "Cloudflare account ID"
type = string
}
variable "project_name" {
description = "Pages project name"
type = string
}
variable "build_command" {
description = "Build command"
type = string
default = "npm run build"
}
variable "destination_dir" {
description = "Build output directory"
type = string
default = "dist"
}
variable "environment_variables" {
description = "Environment variables for production"
type = map(string)
default = {}
}

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
resource "cloudflare_worker_script" "this" {
account_id = var.account_id
name = var.name
content = file(var.script_path)
module = true
}
resource "cloudflare_worker_route" "this" {
count = var.route_pattern != "" ? 1 : 0
zone_id = var.zone_id
pattern = var.route_pattern
script_name = cloudflare_worker_script.this.name
}

View File

@@ -0,0 +1,5 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
output "worker_name" {
description = "Deployed worker name"
value = cloudflare_worker_script.this.name
}

View File

@@ -0,0 +1,27 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
variable "account_id" {
description = "Cloudflare account ID"
type = string
}
variable "name" {
description = "Worker script name"
type = string
}
variable "script_path" {
description = "Path to the worker script file"
type = string
}
variable "zone_id" {
description = "Cloudflare zone ID for route binding"
type = string
default = ""
}
variable "route_pattern" {
description = "URL pattern for worker route"
type = string
default = ""
}

View File

@@ -0,0 +1,45 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
resource "digitalocean_droplet" "this" {
name = var.name
region = var.region
size = var.size
image = var.image
ssh_keys = var.ssh_keys
tags = concat(["blackroad"], var.tags)
}
resource "digitalocean_firewall" "this" {
name = "${var.name}-fw"
droplet_ids = [digitalocean_droplet.this.id]
inbound_rule {
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
}
inbound_rule {
protocol = "tcp"
port_range = "80"
source_addresses = ["0.0.0.0/0", "::/0"]
}
inbound_rule {
protocol = "tcp"
port_range = "443"
source_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "tcp"
port_range = "all"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "udp"
port_range = "all"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
}

View File

@@ -0,0 +1,15 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
output "id" {
description = "Droplet ID"
value = digitalocean_droplet.this.id
}
output "ipv4_address" {
description = "Public IPv4 address"
value = digitalocean_droplet.this.ipv4_address
}
output "name" {
description = "Droplet name"
value = digitalocean_droplet.this.name
}

View File

@@ -0,0 +1,35 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
variable "name" {
description = "Droplet name"
type = string
}
variable "region" {
description = "DigitalOcean region"
type = string
default = "nyc3"
}
variable "size" {
description = "Droplet size slug"
type = string
default = "s-2vcpu-4gb"
}
variable "image" {
description = "Droplet image"
type = string
default = "ubuntu-24-04-x64"
}
variable "ssh_keys" {
description = "SSH key fingerprints"
type = list(string)
default = []
}
variable "tags" {
description = "Additional tags"
type = list(string)
default = []
}

View File

@@ -0,0 +1,20 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
# Railway does not have an official Terraform provider.
# We use null_resource with local-exec to deploy via the Railway CLI.
resource "null_resource" "railway_deploy" {
triggers = {
service_name = var.service_name
project_id = var.project_id
}
provisioner "local-exec" {
command = <<-EOT
railway link ${var.project_id}
railway up --service ${var.service_name}
EOT
environment = {
RAILWAY_TOKEN = var.railway_token
}
}
}

View File

@@ -0,0 +1,5 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
output "service_name" {
description = "Deployed Railway service name"
value = var.service_name
}

View File

@@ -0,0 +1,16 @@
# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.
variable "railway_token" {
description = "Railway API token"
type = string
sensitive = true
}
variable "project_id" {
description = "Railway project ID"
type = string
}
variable "service_name" {
description = "Service name within the project"
type = string
}