Refactor project to transition from Rust backend to Node.js with Fastify; update Dockerfile and Docker configurations for new build process; enhance local development instructions in CONTRIBUTING.md; implement health checks in Docker Compose; update pnpm-lock.yaml with new dependencies for API and shared packages; revise README.md to reflect new stack and development setup.
Build, Test, and Push CFDM Docker Image / test (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / create-release (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / test (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / create-release (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been cancelled
This commit is contained in:
Vendored
+377
@@ -0,0 +1,377 @@
|
||||
// src/constants.ts
|
||||
var SYNC_SYNCED = "synced";
|
||||
var SYNC_PENDING_PUSH = "pending_push";
|
||||
var SYNC_PENDING_DELETE = "pending_delete";
|
||||
var SYNC_CONFLICT = "conflict";
|
||||
var SYNC_ERROR = "error";
|
||||
var CERT_OK = "ok";
|
||||
var CERT_WARNING = "warning";
|
||||
var CERT_EXPIRED = "expired";
|
||||
var CERT_ERROR = "error";
|
||||
var CERT_UNKNOWN = "unknown";
|
||||
|
||||
// src/validators.ts
|
||||
var NAME_RE = /^(@|\*|[a-zA-Z0-9_]([a-zA-Z0-9_-]*[a-zA-Z0-9_])?(\.[a-zA-Z0-9_]([a-zA-Z0-9_-]*[a-zA-Z0-9_])?)*)$/;
|
||||
var IPV4_RE = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
|
||||
var IPV6_RE = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
||||
var ALLOWED_TYPES = ["A", "AAAA", "CNAME", "TXT", "MX", "NS", "SRV", "CAA"];
|
||||
var ValidationError = class extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "ValidationError";
|
||||
}
|
||||
};
|
||||
function validateDnsRecord(recordType, name, content, ttl, proxied) {
|
||||
const rt = recordType.toUpperCase();
|
||||
if (!ALLOWED_TYPES.includes(rt)) {
|
||||
throw new ValidationError(`unsupported record type: ${recordType}`);
|
||||
}
|
||||
if (!NAME_RE.test(name)) {
|
||||
throw new ValidationError(`invalid record name: ${name}`);
|
||||
}
|
||||
if (ttl !== 1 && (ttl < 60 || ttl > 86400)) {
|
||||
throw new ValidationError("ttl must be 1 (auto) or 60-86400");
|
||||
}
|
||||
if (proxied && !["A", "AAAA", "CNAME"].includes(rt)) {
|
||||
throw new ValidationError("proxied only allowed for A, AAAA, CNAME");
|
||||
}
|
||||
switch (rt) {
|
||||
case "A":
|
||||
if (!IPV4_RE.test(content)) {
|
||||
throw new ValidationError("A record requires valid IPv4");
|
||||
}
|
||||
break;
|
||||
case "AAAA":
|
||||
if (!IPV6_RE.test(content)) {
|
||||
throw new ValidationError("AAAA record requires valid IPv6");
|
||||
}
|
||||
break;
|
||||
case "CNAME":
|
||||
case "NS":
|
||||
if (!content || content.includes(" ")) {
|
||||
throw new ValidationError("CNAME/NS requires valid hostname");
|
||||
}
|
||||
break;
|
||||
case "TXT":
|
||||
if (!content || content.length > 2048) {
|
||||
throw new ValidationError("TXT content length 1-2048");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
function certStatusFromExpiry(daysLeft) {
|
||||
if (daysLeft < 0) return CERT_EXPIRED;
|
||||
if (daysLeft <= 30) return CERT_WARNING;
|
||||
return CERT_OK;
|
||||
}
|
||||
function isValidIpv4(ip) {
|
||||
const parts = ip.split(".");
|
||||
if (parts.length !== 4) return false;
|
||||
return parts.every((p) => {
|
||||
const n = Number(p);
|
||||
return Number.isInteger(n) && n >= 0 && n <= 255;
|
||||
});
|
||||
}
|
||||
|
||||
// src/subdomain.ts
|
||||
function dnsNameToSubdomainLabel(recordName, zoneName) {
|
||||
const rn = recordName.trim().replace(/\.+$/, "");
|
||||
const zn = zoneName.trim().replace(/\.+$/, "");
|
||||
if (!rn || !zn) return null;
|
||||
if (rn === "*") return "*";
|
||||
const wildcardFqdn = `*.${zn}`;
|
||||
if (rn.toLowerCase() === wildcardFqdn.toLowerCase()) return "*";
|
||||
if (rn.toLowerCase() === zn.toLowerCase()) return "@";
|
||||
const zoneSuffix = `.${zn}`;
|
||||
if (rn.toLowerCase().endsWith(zoneSuffix.toLowerCase())) {
|
||||
const prefix = rn.slice(0, rn.length - zoneSuffix.length);
|
||||
return prefix || "@";
|
||||
}
|
||||
if (!rn.includes(".")) return rn;
|
||||
return null;
|
||||
}
|
||||
function subdomainLabelToFqdn(label, zoneName) {
|
||||
return label === "@" ? zoneName : `${label}.${zoneName}`;
|
||||
}
|
||||
|
||||
// src/parse-fqdn.ts
|
||||
function fqdnToDisplay(hostname, zoneName) {
|
||||
if (hostname === "@") {
|
||||
return zoneName;
|
||||
}
|
||||
return `${hostname}.${zoneName}`;
|
||||
}
|
||||
function parseFqdn(fqdn, knownZones) {
|
||||
const normalized = fqdn.trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
const zones = [...knownZones].sort((a, b) => b.length - a.length);
|
||||
for (const zone of zones) {
|
||||
const zoneLower = zone.toLowerCase();
|
||||
if (normalized === zoneLower) {
|
||||
return {
|
||||
zoneName: zone,
|
||||
hostname: "@",
|
||||
fqdn: fqdnToDisplay("@", zone)
|
||||
};
|
||||
}
|
||||
const suffix = `.${zoneLower}`;
|
||||
if (normalized.endsWith(suffix)) {
|
||||
const prefix = normalized.slice(0, -suffix.length);
|
||||
if (prefix) {
|
||||
return {
|
||||
zoneName: zone,
|
||||
hostname: prefix,
|
||||
fqdn: fqdnToDisplay(prefix, zone)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function bindingToFqdn(binding) {
|
||||
return binding.fqdn ?? fqdnToDisplay(binding.hostname, binding.zone_name);
|
||||
}
|
||||
|
||||
// src/schemas.ts
|
||||
import { z } from "zod";
|
||||
var groupSchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string()
|
||||
});
|
||||
var groupWithStatsSchema = groupSchema.extend({
|
||||
domain_count: z.number()
|
||||
});
|
||||
var serviceGroupTypeSchema = z.enum([
|
||||
"vpn",
|
||||
"network",
|
||||
"internet",
|
||||
"bgp",
|
||||
"custom"
|
||||
]);
|
||||
var serviceGroupSchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
type: serviceGroupTypeSchema.catch("custom"),
|
||||
icon: z.string().nullable(),
|
||||
domain: z.string().nullable(),
|
||||
enabled: z.boolean(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string()
|
||||
});
|
||||
var serviceSchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
service_group_id: z.number().nullable().optional(),
|
||||
subdomain: z.string().optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
computed_fqdn: z.string().nullable().optional(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string()
|
||||
});
|
||||
var serviceDomainBindingSchema = z.object({
|
||||
binding_id: z.number(),
|
||||
domain_id: z.number(),
|
||||
zone_name: z.string(),
|
||||
hostname: z.string(),
|
||||
fqdn: z.string(),
|
||||
target_ips: z.array(z.string()).optional(),
|
||||
target_ip: z.string().nullable().optional(),
|
||||
sync_status: z.string().nullable()
|
||||
}).transform((binding) => ({
|
||||
...binding,
|
||||
target_ips: binding.target_ips && binding.target_ips.length > 0 ? binding.target_ips : binding.target_ip ? [binding.target_ip] : []
|
||||
}));
|
||||
var serviceViewSchema = serviceSchema.extend({
|
||||
subdomain: z.string().default(""),
|
||||
enabled: z.boolean().default(false),
|
||||
ips: z.array(z.string()).default([]),
|
||||
domains: z.array(serviceDomainBindingSchema).default([])
|
||||
});
|
||||
var serviceGroupViewSchema = serviceGroupSchema.extend({
|
||||
services: z.array(serviceViewSchema).default([])
|
||||
});
|
||||
var serviceGroupsResponseSchema = z.object({
|
||||
groups: z.array(serviceGroupViewSchema).default([]),
|
||||
ungrouped: z.array(serviceViewSchema).default([])
|
||||
});
|
||||
var domainSchema = z.object({
|
||||
id: z.number(),
|
||||
group_id: z.number().nullable(),
|
||||
zone_name: z.string(),
|
||||
cf_zone_id: z.string(),
|
||||
status: z.string(),
|
||||
last_synced_at: z.string().nullable(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string()
|
||||
});
|
||||
var domainListItemSchema = domainSchema.extend({
|
||||
group_name: z.string().nullable(),
|
||||
service_count: z.number()
|
||||
});
|
||||
var serviceBindingSchema = z.object({
|
||||
id: z.number(),
|
||||
domain_id: z.number(),
|
||||
service_id: z.number(),
|
||||
hostname: z.string(),
|
||||
dns_record_id: z.number().nullable(),
|
||||
zone_name: z.string(),
|
||||
group_id: z.number().nullable(),
|
||||
group_name: z.string().nullable(),
|
||||
service_name: z.string(),
|
||||
service_slug: z.string(),
|
||||
target_ip: z.string().nullable(),
|
||||
sync_status: z.string().nullable(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string()
|
||||
});
|
||||
var dnsRecordSchema = z.object({
|
||||
id: z.number(),
|
||||
domain_id: z.number(),
|
||||
cf_record_id: z.string().nullable(),
|
||||
record_type: z.string(),
|
||||
name: z.string(),
|
||||
content: z.string(),
|
||||
ttl: z.number(),
|
||||
proxied: z.boolean(),
|
||||
priority: z.number().nullable(),
|
||||
sync_status: z.string(),
|
||||
origin: z.string(),
|
||||
last_error: z.string().nullable(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string()
|
||||
});
|
||||
var certificateSchema = z.object({
|
||||
id: z.number(),
|
||||
domain_id: z.number(),
|
||||
subdomain_id: z.number().nullable(),
|
||||
hostname: z.string(),
|
||||
expires_at: z.string().nullable(),
|
||||
last_checked_at: z.string().nullable(),
|
||||
last_error: z.string().nullable(),
|
||||
status: z.string(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string()
|
||||
});
|
||||
var createGroupSchema = z.object({
|
||||
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435"),
|
||||
slug: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 slug")
|
||||
});
|
||||
var ipv4Schema = z.string().regex(
|
||||
/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/,
|
||||
"\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 IPv4"
|
||||
);
|
||||
var serviceDomainInputSchema = z.object({
|
||||
fqdn: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 FQDN"),
|
||||
target_ips: z.array(ipv4Schema).min(1, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u0438\u043D IP")
|
||||
});
|
||||
var createServiceSchema = z.object({
|
||||
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435"),
|
||||
slug: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 slug")
|
||||
});
|
||||
var createServiceWithConfigSchema = createServiceSchema.extend({
|
||||
service_group_id: z.number().nullable().optional(),
|
||||
ips: z.array(ipv4Schema).default([]),
|
||||
domains: z.array(serviceDomainInputSchema).default([])
|
||||
});
|
||||
var createServiceBindingSchema = z.object({
|
||||
domain_id: z.string().min(1, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043E\u043C\u0435\u043D"),
|
||||
service_id: z.string().min(1, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0435\u0440\u0432\u0438\u0441"),
|
||||
hostname: z.string().optional(),
|
||||
target_ip: z.string().optional()
|
||||
});
|
||||
var updateDomainGroupSchema = z.object({
|
||||
group_id: z.number().nullable(),
|
||||
status: z.string().optional()
|
||||
});
|
||||
var createDomainSchema = z.object({
|
||||
zone_name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F \u0437\u043E\u043D\u044B"),
|
||||
group_id: z.string()
|
||||
});
|
||||
var loginSchema = z.object({
|
||||
username: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F"),
|
||||
password: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043F\u0430\u0440\u043E\u043B\u044C")
|
||||
});
|
||||
var createDnsRecordSchema = z.object({
|
||||
record_type: z.enum(["A", "AAAA", "CNAME", "TXT", "MX"]),
|
||||
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F"),
|
||||
content: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435"),
|
||||
ttl: z.number().int().min(1),
|
||||
proxied: z.boolean()
|
||||
});
|
||||
var subdomainSchema = z.object({
|
||||
id: z.number(),
|
||||
domain_id: z.number(),
|
||||
name: z.string(),
|
||||
fqdn: z.string(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string()
|
||||
});
|
||||
var updateServiceConfigSchema = z.object({
|
||||
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435").optional(),
|
||||
slug: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 slug").optional(),
|
||||
service_group_id: z.number().nullable().optional(),
|
||||
ips: z.array(ipv4Schema).optional(),
|
||||
domains: z.array(serviceDomainInputSchema).optional()
|
||||
});
|
||||
var createServiceGroupSchema = z.object({
|
||||
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435"),
|
||||
type: serviceGroupTypeSchema.default("custom"),
|
||||
icon: z.string().nullable().optional(),
|
||||
domain: z.string().nullable().optional()
|
||||
});
|
||||
var toggleEnabledSchema = z.object({
|
||||
enabled: z.boolean()
|
||||
});
|
||||
export {
|
||||
CERT_ERROR,
|
||||
CERT_EXPIRED,
|
||||
CERT_OK,
|
||||
CERT_UNKNOWN,
|
||||
CERT_WARNING,
|
||||
SYNC_CONFLICT,
|
||||
SYNC_ERROR,
|
||||
SYNC_PENDING_DELETE,
|
||||
SYNC_PENDING_PUSH,
|
||||
SYNC_SYNCED,
|
||||
ValidationError,
|
||||
bindingToFqdn,
|
||||
certStatusFromExpiry,
|
||||
certificateSchema,
|
||||
createDnsRecordSchema,
|
||||
createDomainSchema,
|
||||
createGroupSchema,
|
||||
createServiceBindingSchema,
|
||||
createServiceGroupSchema,
|
||||
createServiceSchema,
|
||||
createServiceWithConfigSchema,
|
||||
dnsNameToSubdomainLabel,
|
||||
dnsRecordSchema,
|
||||
domainListItemSchema,
|
||||
domainSchema,
|
||||
fqdnToDisplay,
|
||||
groupSchema,
|
||||
groupWithStatsSchema,
|
||||
isValidIpv4,
|
||||
loginSchema,
|
||||
parseFqdn,
|
||||
serviceBindingSchema,
|
||||
serviceDomainBindingSchema,
|
||||
serviceGroupSchema,
|
||||
serviceGroupTypeSchema,
|
||||
serviceGroupViewSchema,
|
||||
serviceGroupsResponseSchema,
|
||||
serviceSchema,
|
||||
serviceViewSchema,
|
||||
subdomainLabelToFqdn,
|
||||
subdomainSchema,
|
||||
toggleEnabledSchema,
|
||||
updateDomainGroupSchema,
|
||||
updateServiceConfigSchema,
|
||||
validateDnsRecord
|
||||
};
|
||||
Reference in New Issue
Block a user