Fixing various things

This commit is contained in:
Owen
2025-10-27 17:52:39 -07:00
parent 15d63ddffa
commit bd5cc790d6
14 changed files with 117 additions and 47 deletions

View File

@@ -14,7 +14,6 @@ app:
domains:
domain1:
base_domain: "{{.BaseDomain}}"
cert_resolver: "letsencrypt"
server:
secret: "{{.Secret}}"

View File

@@ -27,7 +27,7 @@ export const domains = pgTable("domains", {
export const dnsRecords = pgTable("dnsRecords", {
id: varchar("id").primaryKey(),
id: serial("id").primaryKey(),
domainId: varchar("domainId")
.notNull()
.references(() => domains.domainId, { onDelete: "cascade" }),

View File

@@ -18,7 +18,7 @@ export const domains = sqliteTable("domains", {
});
export const dnsRecords = sqliteTable("dnsRecords", {
id: text("id").primaryKey(),
id: integer("id").primaryKey({ autoIncrement: true }),
domainId: text("domainId")
.notNull()
.references(() => domains.domainId, { onDelete: "cascade" }),

View File

@@ -2,7 +2,7 @@ import path from "path";
import { fileURLToPath } from "url";
// This is a placeholder value replaced by the build process
export const APP_VERSION = "1.11.0";
export const APP_VERSION = "1.12.0";
export const __FILENAME = fileURLToPath(import.meta.url);
export const __DIRNAME = path.dirname(__FILENAME);

View File

@@ -50,7 +50,7 @@ export const configSchema = z
.string()
.nonempty("base_domain must not be empty")
.transform((url) => url.toLowerCase()),
cert_resolver: z.string().optional().default("letsencrypt"),
cert_resolver: z.string().optional(), // null falls back to traefik.cert_resolver
prefer_wildcard_cert: z.boolean().optional().default(false)
})
)

View File

@@ -3,9 +3,9 @@ import axios from "axios";
let serverIp: string | null = null;
const services = [
"https://checkip.amazonaws.com",
"https://ifconfig.io/ip",
"https://api.ipify.org",
"https://checkip.amazonaws.com"
];
export async function fetchServerIp() {

View File

@@ -75,6 +75,7 @@ import { validateResourceSessionToken } from "@server/auth/sessions/resource";
import { checkExitNodeOrg, resolveExitNodes } from "#private/lib/exitNodes";
import { maxmindLookup } from "@server/db/maxmind";
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
import semver from "semver";
// Zod schemas for request validation
const getResourceByDomainParamsSchema = z
@@ -1070,11 +1071,20 @@ hybridRouter.get(
);
}
const rules = await db
let rules = await db
.select()
.from(resourceRules)
.where(eq(resourceRules.resourceId, resourceId));
// backward compatibility: COUNTRY -> GEOIP
if ((remoteExitNode.version && semver.lt(remoteExitNode.version, "1.1.0")) || !remoteExitNode.version) {
for (const rule of rules) {
if (rule.match == "COUNTRY") {
rule.match = "GEOIP";
}
}
}
return response<(typeof resourceRules.$inferSelect)[]>(res, {
data: rules,
success: true,

View File

@@ -286,7 +286,6 @@ export async function createOrgDomain(
// Save NS records to database
for (const nsValue of nsRecords) {
recordsToInsert.push({
id: generateId(15),
domainId,
recordType: "NS",
baseDomain: baseDomain,
@@ -309,7 +308,6 @@ export async function createOrgDomain(
// Save CNAME records to database
for (const cnameRecord of cnameRecords) {
recordsToInsert.push({
id: generateId(15),
domainId,
recordType: "CNAME",
baseDomain: cnameRecord.baseDomain,
@@ -332,7 +330,6 @@ export async function createOrgDomain(
// Save A records to database
for (const aRecord of aRecords) {
recordsToInsert.push({
id: generateId(15),
domainId,
recordType: "A",
baseDomain: aRecord.baseDomain,

View File

@@ -98,15 +98,16 @@ export async function updateOrg(
parsedBody.data.passwordExpiryDays = undefined;
}
const { tier } = await getOrgTierData(orgId);
if (
!isLicensed &&
tier != TierId.STANDARD &&
parsedBody.data.settingsLogRetentionDaysRequest &&
parsedBody.data.settingsLogRetentionDaysRequest > 30
) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"You are not allowed to set log retention days greater than 30 because you are not subscribed to the Standard tier"
"You are not allowed to set log retention days greater than 30 with your current subscription"
)
);
}

View File

@@ -1,4 +1,4 @@
import { db } from "@server/db";
import { db, dnsRecords } from "@server/db";
import { domains, exitNodes, orgDomains, orgs, resources } from "@server/db";
import config from "@server/lib/config";
import { eq, ne } from "drizzle-orm";
@@ -8,7 +8,10 @@ export async function copyInConfig() {
const endpoint = config.getRawConfig().gerbil.base_endpoint;
const listenPort = config.getRawConfig().gerbil.start_port;
if (!config.getRawConfig().flags?.disable_config_managed_domains && config.getRawConfig().domains) {
if (
!config.getRawConfig().flags?.disable_config_managed_domains &&
config.getRawConfig().domains
) {
await copyInDomains();
}
@@ -39,7 +42,7 @@ async function copyInDomains() {
domainId: key,
baseDomain: value.base_domain.toLowerCase(),
certResolver: value.cert_resolver || null,
preferWildcardCert: value.prefer_wildcard_cert || null,
preferWildcardCert: value.prefer_wildcard_cert || null
})
);
@@ -56,22 +59,54 @@ async function copyInDomains() {
if (!configDomainKeys.has(existingDomain.domainId)) {
await trx
.delete(domains)
.where(eq(domains.domainId, existingDomain.domainId))
.execute();
.where(eq(domains.domainId, existingDomain.domainId));
await trx
.delete(dnsRecords)
.where(eq(dnsRecords.domainId, existingDomain.domainId));
}
}
for (const { domainId, baseDomain, certResolver, preferWildcardCert } of configDomains) {
for (const {
domainId,
baseDomain,
certResolver,
preferWildcardCert
} of configDomains) {
if (existingDomainKeys.has(domainId)) {
await trx
.update(domains)
.set({ baseDomain, verified: true, type: "wildcard", certResolver, preferWildcardCert })
.where(eq(domains.domainId, domainId))
.execute();
} else {
.set({
baseDomain,
verified: true,
type: "wildcard",
certResolver,
preferWildcardCert
})
.where(eq(domains.domainId, domainId));
// delete the dns records and add them again to ensure they are correct
await trx
.insert(domains)
.values({
.delete(dnsRecords)
.where(eq(dnsRecords.domainId, domainId));
await trx.insert(dnsRecords).values([
{
domainId,
recordType: "A",
baseDomain,
value: "Server IP Address",
verified: true
},
{
domainId,
recordType: "A",
baseDomain,
value: "Server IP Address",
verified: true
}
]);
} else {
await trx.insert(domains).values({
domainId,
baseDomain,
configManaged: true,
@@ -79,8 +114,24 @@ async function copyInDomains() {
verified: true,
certResolver,
preferWildcardCert
})
.execute();
});
await trx.insert(dnsRecords).values([
{
domainId,
recordType: "A",
baseDomain,
value: "Server IP Address",
verified: true
},
{
domainId,
recordType: "A",
baseDomain,
value: "Server IP Address",
verified: true
}
]);
}
}

View File

@@ -44,7 +44,7 @@ export default async function migration() {
await db.execute(sql`
CREATE TABLE "dnsRecords" (
"id" varchar PRIMARY KEY NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"domainId" varchar NOT NULL,
"recordType" varchar NOT NULL,
"baseDomain" varchar,
@@ -108,10 +108,10 @@ export default async function migration() {
await db.execute(sql`ALTER TABLE "orgs" DROP COLUMN "settings";`);
await db.execute(sql`COMMIT`);
console.log(`Updated resource rules match value from GEOIP to COUNTRY`);
console.log("Migrated database");
} catch (e) {
await db.execute(sql`ROLLBACK`);
console.log("Unable to update resource rules match value");
console.log("Unable to migrate database");
console.log(e);
throw e;
}

View File

@@ -72,7 +72,7 @@ export default async function migration() {
db.prepare(
`
CREATE TABLE 'dnsRecords' (
'id' text PRIMARY KEY NOT NULL,
'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
'domainId' text NOT NULL,
'recordType' text NOT NULL,
'baseDomain' text,

View File

@@ -111,7 +111,7 @@ export function DNSRecordsDataTable<TData, TValue>({
<h1 className="font-bold">{t("dnsRecord")}</h1>
<Badge variant="secondary">{t("required")}</Badge>
</div>
<Link href="https://docs.pangolin.net/self-host/dns-and-networking">
<Link href="https://docs.pangolin.net/manage/domains">
<Button variant="outline">
<ExternalLink className="h-4 w-4 mr-1" />
{t("howToAddRecords")}

View File

@@ -15,7 +15,12 @@ import { useTranslations } from "next-intl";
import CreateDomainForm from "@app/components/CreateDomainForm";
import { useToast } from "@app/hooks/useToast";
import { useOrgContext } from "@app/hooks/useOrgContext";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./ui/dropdown-menu";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from "./ui/dropdown-menu";
import Link from "next/link";
export type DomainRow = {
@@ -179,9 +184,15 @@ export default function DomainsTable({ domains, orgId }: Props) {
);
},
cell: ({ row }) => {
const { verified, failed } = row.original;
const { verified, failed, type } = row.original;
if (verified) {
return <Badge variant="green">{t("verified")}</Badge>;
type === "wildcard" ? (
<Badge variant="outlinePrimary">
{t("manual", { fallback: "Manual" })}
</Badge>
) : (
<Badge variant="green">{t("verified")}</Badge>
);
} else if (failed) {
return (
<Badge variant="destructive">
@@ -218,8 +229,13 @@ export default function DomainsTable({ domains, orgId }: Props) {
<div className="flex items-center justify-end gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<Button
variant="ghost"
className="h-8 w-8 p-0"
>
<span className="sr-only">
Open menu
</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
@@ -282,12 +298,8 @@ export default function DomainsTable({ domains, orgId }: Props) {
}}
dialog={
<div>
<p>
{t("domainQuestionRemove")}
</p>
<p>
{t("domainMessageRemove")}
</p>
<p>{t("domainQuestionRemove")}</p>
<p>{t("domainMessageRemove")}</p>
</div>
}
buttonText={t("domainConfirmDelete")}