--- /dev/null
+PORT=3000
+
+CLOUDFLARE_API_TOKEN=...
+CLOUDFLARE_ACCOUNT_ID=...
+CLOUDFLARE_ZONE_ID=... # zone ID for crimata.com
+BASE_DOMAIN=crimata.com
--- /dev/null
+FROM node:20-alpine AS builder
+WORKDIR /app
+COPY package*.json ./
+RUN npm ci
+COPY . .
+RUN npm run build
+
+FROM node:20-alpine
+WORKDIR /app
+COPY package*.json ./
+RUN npm ci --omit=dev
+COPY --from=builder /app/dist ./dist
+EXPOSE 3000
+CMD ["node", "dist/index.js"]
--- /dev/null
+{
+ "name": "crimata-dns",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "tsx watch src/index.ts",
+ "build": "tsc",
+ "start": "node dist/index.js"
+ },
+ "dependencies": {
+ "cloudflare": "^3.3.0",
+ "fastify": "^4.26.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20.0.0",
+ "tsx": "^4.7.0",
+ "typescript": "^5.4.0"
+ }
+}
--- /dev/null
+import Cloudflare from "cloudflare"
+import type { DomainCheckResult, DomainRecord } from "./types"
+
+const cf = new Cloudflare({ apiToken: process.env.CLOUDFLARE_API_TOKEN })
+
+const ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID!
+const ZONE_ID = process.env.CLOUDFLARE_ZONE_ID! // crimata.com zone
+const BASE_DOMAIN = process.env.BASE_DOMAIN ?? "crimata.com"
+
+export async function checkDomain(domain: string): Promise<DomainCheckResult> {
+ const result = await cf.registrar.domains.get(domain, { account_id: ACCOUNT_ID })
+ return {
+ domain,
+ available: !result.registered_at,
+ }
+}
+
+export async function registerDomain(domain: string): Promise<string> {
+ const result = await cf.registrar.domains.update(domain, {
+ account_id: ACCOUNT_ID,
+ auto_renew: true,
+ locked: false,
+ privacy: true,
+ })
+ return result.id ?? domain
+}
+
+export async function createDNSRecord(domain: string, target: string): Promise<string> {
+ // Point the custom domain at the ALB/target
+ const record = await cf.dns.records.create({
+ zone_id: ZONE_ID,
+ type: "CNAME",
+ name: domain,
+ content: target,
+ ttl: 1, // auto
+ proxied: true,
+ })
+ return record.id!
+}
+
+export async function createSubdomain(slug: string, target: string): Promise<string> {
+ // Free subdomain: slug.crimata.com
+ const record = await cf.dns.records.create({
+ zone_id: ZONE_ID,
+ type: "CNAME",
+ name: `${slug}.${BASE_DOMAIN}`,
+ content: target,
+ ttl: 1,
+ proxied: true,
+ })
+ return record.id!
+}
+
+export async function deleteDNSRecord(recordId: string): Promise<void> {
+ await cf.dns.records.delete(recordId, { zone_id: ZONE_ID })
+}
--- /dev/null
+import Fastify from "fastify"
+import { registerRoutes } from "./routes"
+
+const app = Fastify({ logger: true })
+
+registerRoutes(app)
+
+app.listen({ port: Number(process.env.PORT ?? 3000), host: "0.0.0.0" }, (err) => {
+ if (err) {
+ app.log.error(err)
+ process.exit(1)
+ }
+})
--- /dev/null
+import type { FastifyInstance } from "fastify"
+import type { DomainCheckResult, ProvisionDomainRequest, ApiError } from "./types"
+import * as cf from "./cloudflare"
+
+export async function registerRoutes(app: FastifyInstance) {
+
+ // Check domain availability
+ app.get<{ Querystring: { domain: string } }>("/domains/check", async (req, reply) => {
+ const { domain } = req.query
+ if (!domain) return reply.status(400).send({ error: "domain is required" })
+
+ const result = await cf.checkDomain(domain)
+ return reply.send(result)
+ })
+
+ // Provision a domain (register + point DNS) and assign free subdomain
+ app.post<{ Body: ProvisionDomainRequest }>("/domains", async (req, reply) => {
+ const { domain, customerSlug, targetIP } = req.body
+ if (!customerSlug || !targetIP) {
+ return reply.status(400).send({ error: "customerSlug and targetIP are required" })
+ }
+
+ // Always create the free subdomain
+ await cf.createSubdomain(customerSlug, targetIP)
+
+ // Register and point custom domain if provided
+ if (domain) {
+ await cf.registerDomain(domain)
+ await cf.createDNSRecord(domain, targetIP)
+ }
+
+ return reply.status(201).send({ customerSlug, domain: domain ?? null, status: "provisioning" })
+ })
+
+ // Delete DNS records for a customer
+ app.delete<{ Params: { recordId: string } }>("/domains/:recordId", async (req, reply) => {
+ const { recordId } = req.params
+ await cf.deleteDNSRecord(recordId)
+ return reply.status(204).send()
+ })
+
+}
--- /dev/null
+export interface DomainCheckResult {
+ domain: string
+ available: boolean
+ price?: number
+ currency?: string
+}
+
+export interface ProvisionDomainRequest {
+ domain: string
+ customerSlug: string
+ targetIP: string // IP or hostname to point DNS at
+}
+
+export interface DomainRecord {
+ domain: string
+ customerSlug: string
+ cloudflareZoneId: string
+ status: DomainStatus
+ createdAt: string
+}
+
+export type DomainStatus = "provisioning" | "active" | "failed" | "released"
+
+export interface ApiError {
+ error: string
+}
--- /dev/null
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "commonjs",
+ "lib": ["ES2022"],
+ "outDir": "dist",
+ "rootDir": "src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "resolveJsonModule": true
+ },
+ "include": ["src"]
+}