fix(integrations): разворачивать CNAME до IP при sync в VPS Tracker
Build and Push CFDM Docker Image / build-and-push (push) Successful in 2m16s
Build and Push CFDM Docker Image / create-release (push) Skipped
Build and Push CFDM Docker Image / update-wiki (push) Successful in 6s

CNAME-bindings отдавали пустой ips[]; теперь IP берутся из локальной цепочки bindings, DNS resolve4 или IP сервиса — чтобы vps-tracker мог матчить домен к VPS.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-08-01 00:59:01 +07:00
co-authored by Cursor
parent 19620c0803
commit c01c75116a
7 changed files with 278 additions and 55 deletions
+40 -26
View File
@@ -10,7 +10,8 @@ import {
integer,
primaryKey,
sqliteTable,
text
text,
unique
} from "drizzle-orm/sqlite-core";
var groups = sqliteTable("groups", {
id: integer("id").primaryKey({ autoIncrement: true }),
@@ -94,27 +95,39 @@ var dnsRecords = sqliteTable("dns_records", {
created_at: text("created_at").notNull().default(sql`datetime('now')`),
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
});
var serviceBindings = sqliteTable("service_bindings", {
id: integer("id").primaryKey({ autoIncrement: true }),
domain_id: integer("domain_id").notNull().references(() => domains.id, { onDelete: "cascade" }),
service_id: integer("service_id").notNull().references(() => services.id, { onDelete: "cascade" }),
hostname: text("hostname").notNull().default("@"),
cname_target: text("cname_target"),
dns_record_id: integer("dns_record_id").references(() => dnsRecords.id, {
onDelete: "set null"
}),
lb_mode: text("lb_mode").notNull().default("round_robin"),
health_check_enabled: integer("health_check_enabled", { mode: "boolean" }).notNull().default(false),
health_check_type: text("health_check_type").notNull().default("tcp"),
health_check_port: integer("health_check_port"),
health_check_path: text("health_check_path"),
health_check_expected_status: integer("health_check_expected_status"),
health_check_interval_sec: integer("health_check_interval_sec").notNull().default(30),
health_check_timeout_ms: integer("health_check_timeout_ms").notNull().default(3e3),
health_check_verify_tls: integer("health_check_verify_tls", { mode: "boolean" }).notNull().default(false),
created_at: text("created_at").notNull().default(sql`datetime('now')`),
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
});
var serviceBindings = sqliteTable(
"service_bindings",
{
id: integer("id").primaryKey({ autoIncrement: true }),
domain_id: integer("domain_id").notNull().references(() => domains.id, { onDelete: "cascade" }),
service_id: integer("service_id").notNull().references(() => services.id, { onDelete: "cascade" }),
hostname: text("hostname").notNull().default("@"),
cname_target: text("cname_target"),
dns_record_id: integer("dns_record_id").references(() => dnsRecords.id, {
onDelete: "set null"
}),
lb_mode: text("lb_mode").notNull().default("round_robin"),
health_check_enabled: integer("health_check_enabled", { mode: "boolean" }).notNull().default(false),
health_check_type: text("health_check_type").notNull().default("tcp"),
health_check_port: integer("health_check_port"),
health_check_path: text("health_check_path"),
health_check_expected_status: integer("health_check_expected_status"),
health_check_interval_sec: integer("health_check_interval_sec").notNull().default(30),
health_check_timeout_ms: integer("health_check_timeout_ms").notNull().default(3e3),
health_check_verify_tls: integer("health_check_verify_tls", {
mode: "boolean"
}).notNull().default(false),
created_at: text("created_at").notNull().default(sql`datetime('now')`),
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
},
(table) => [
unique("service_bindings_domain_service_hostname").on(
table.domain_id,
table.service_id,
table.hostname
)
]
);
var serviceIps = sqliteTable("service_ips", {
id: integer("id").primaryKey({ autoIncrement: true }),
service_id: integer("service_id").notNull().references(() => services.id, { onDelete: "cascade" }),
@@ -1189,6 +1202,7 @@ function enrichServiceBindingView(db, row) {
const sync_status = row.sync_status ?? linkedRecords.find((record) => record.sync_status)?.sync_status ?? null;
return {
...row,
cname_target: row.cname_target ?? null,
target_ips,
target_ip: target_ips[0] ?? null,
target_ip_weights,
@@ -1646,14 +1660,14 @@ function listDomainTags(db, domainId) {
}
function setDomainTags(db, domainId, tags) {
db.delete(domainTags).where(eq3(domainTags.domain_id, domainId)).run();
const unique = [...new Set(tags.map((t) => t.trim()).filter(Boolean))];
for (const tag of unique) {
const unique2 = [...new Set(tags.map((t) => t.trim()).filter(Boolean))];
for (const tag of unique2) {
db.insert(domainTags).values({ domain_id: domainId, tag }).run();
}
}
function addDomainTags(db, domainId, tags) {
const unique = [...new Set(tags.map((t) => t.trim()).filter(Boolean))];
for (const tag of unique) {
const unique2 = [...new Set(tags.map((t) => t.trim()).filter(Boolean))];
for (const tag of unique2) {
db.run(sql2`
INSERT INTO domain_tags (domain_id, tag)
VALUES (${domainId}, ${tag})
+1
View File
@@ -1154,6 +1154,7 @@ function enrichServiceBindingView(
return {
...row,
cname_target: row.cname_target ?? null,
target_ips,
target_ip: target_ips[0] ?? null,
target_ip_weights,
+1
View File
@@ -78,6 +78,7 @@ interface ServiceBindingView {
target_ips: string[];
target_ip_weights: Record<string, number>;
target_ip_priorities: Record<string, number>;
cname_target: string | null;
lb_mode: LbMode;
health_check_enabled: boolean;
health_check_type: HealthCheckType;
+1
View File
@@ -140,6 +140,7 @@ export interface ServiceBindingView {
target_ips: string[];
target_ip_weights: Record<string, number>;
target_ip_priorities: Record<string, number>;
cname_target: string | null;
lb_mode: LbMode;
health_check_enabled: boolean;
health_check_type: HealthCheckType;