Initial commit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Andrew Gundersen 2026-02-25 15:32:58 -05:00
commit 07b274aa27
9 changed files with 190 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

6
.env.example Normal file
View file

@ -0,0 +1,6 @@
PORT=3000
CLOUDFLARE_API_TOKEN=...
CLOUDFLARE_ACCOUNT_ID=...
CLOUDFLARE_ZONE_ID=... # zone ID for crimata.com
BASE_DOMAIN=crimata.com

14
Dockerfile Normal file
View file

@ -0,0 +1,14 @@
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"]

19
package.json Normal file
View file

@ -0,0 +1,19 @@
{
"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"
}
}

56
src/cloudflare.ts Normal file
View file

@ -0,0 +1,56 @@
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 })
}

13
src/index.ts Normal file
View file

@ -0,0 +1,13 @@
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)
}
})

42
src/routes.ts Normal file
View file

@ -0,0 +1,42 @@
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()
})
}

26
src/types.ts Normal file
View file

@ -0,0 +1,26 @@
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
}

14
tsconfig.json Normal file
View file

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true
},
"include": ["src"]
}