mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-08 03:36:37 +00:00
Fixing various things
This commit is contained in:
@@ -14,7 +14,6 @@ app:
|
|||||||
domains:
|
domains:
|
||||||
domain1:
|
domain1:
|
||||||
base_domain: "{{.BaseDomain}}"
|
base_domain: "{{.BaseDomain}}"
|
||||||
cert_resolver: "letsencrypt"
|
|
||||||
|
|
||||||
server:
|
server:
|
||||||
secret: "{{.Secret}}"
|
secret: "{{.Secret}}"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const domains = pgTable("domains", {
|
|||||||
|
|
||||||
|
|
||||||
export const dnsRecords = pgTable("dnsRecords", {
|
export const dnsRecords = pgTable("dnsRecords", {
|
||||||
id: varchar("id").primaryKey(),
|
id: serial("id").primaryKey(),
|
||||||
domainId: varchar("domainId")
|
domainId: varchar("domainId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => domains.domainId, { onDelete: "cascade" }),
|
.references(() => domains.domainId, { onDelete: "cascade" }),
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const domains = sqliteTable("domains", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const dnsRecords = sqliteTable("dnsRecords", {
|
export const dnsRecords = sqliteTable("dnsRecords", {
|
||||||
id: text("id").primaryKey(),
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||||
domainId: text("domainId")
|
domainId: text("domainId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => domains.domainId, { onDelete: "cascade" }),
|
.references(() => domains.domainId, { onDelete: "cascade" }),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import path from "path";
|
|||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
// This is a placeholder value replaced by the build process
|
// 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 __FILENAME = fileURLToPath(import.meta.url);
|
||||||
export const __DIRNAME = path.dirname(__FILENAME);
|
export const __DIRNAME = path.dirname(__FILENAME);
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const configSchema = z
|
|||||||
.string()
|
.string()
|
||||||
.nonempty("base_domain must not be empty")
|
.nonempty("base_domain must not be empty")
|
||||||
.transform((url) => url.toLowerCase()),
|
.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)
|
prefer_wildcard_cert: z.boolean().optional().default(false)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import axios from "axios";
|
|||||||
let serverIp: string | null = null;
|
let serverIp: string | null = null;
|
||||||
|
|
||||||
const services = [
|
const services = [
|
||||||
|
"https://checkip.amazonaws.com",
|
||||||
"https://ifconfig.io/ip",
|
"https://ifconfig.io/ip",
|
||||||
"https://api.ipify.org",
|
"https://api.ipify.org",
|
||||||
"https://checkip.amazonaws.com"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function fetchServerIp() {
|
export async function fetchServerIp() {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ import { validateResourceSessionToken } from "@server/auth/sessions/resource";
|
|||||||
import { checkExitNodeOrg, resolveExitNodes } from "#private/lib/exitNodes";
|
import { checkExitNodeOrg, resolveExitNodes } from "#private/lib/exitNodes";
|
||||||
import { maxmindLookup } from "@server/db/maxmind";
|
import { maxmindLookup } from "@server/db/maxmind";
|
||||||
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
|
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
|
||||||
|
import semver from "semver";
|
||||||
|
|
||||||
// Zod schemas for request validation
|
// Zod schemas for request validation
|
||||||
const getResourceByDomainParamsSchema = z
|
const getResourceByDomainParamsSchema = z
|
||||||
@@ -1070,11 +1071,20 @@ hybridRouter.get(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rules = await db
|
let rules = await db
|
||||||
.select()
|
.select()
|
||||||
.from(resourceRules)
|
.from(resourceRules)
|
||||||
.where(eq(resourceRules.resourceId, resourceId));
|
.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, {
|
return response<(typeof resourceRules.$inferSelect)[]>(res, {
|
||||||
data: rules,
|
data: rules,
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -286,7 +286,6 @@ export async function createOrgDomain(
|
|||||||
// Save NS records to database
|
// Save NS records to database
|
||||||
for (const nsValue of nsRecords) {
|
for (const nsValue of nsRecords) {
|
||||||
recordsToInsert.push({
|
recordsToInsert.push({
|
||||||
id: generateId(15),
|
|
||||||
domainId,
|
domainId,
|
||||||
recordType: "NS",
|
recordType: "NS",
|
||||||
baseDomain: baseDomain,
|
baseDomain: baseDomain,
|
||||||
@@ -309,7 +308,6 @@ export async function createOrgDomain(
|
|||||||
// Save CNAME records to database
|
// Save CNAME records to database
|
||||||
for (const cnameRecord of cnameRecords) {
|
for (const cnameRecord of cnameRecords) {
|
||||||
recordsToInsert.push({
|
recordsToInsert.push({
|
||||||
id: generateId(15),
|
|
||||||
domainId,
|
domainId,
|
||||||
recordType: "CNAME",
|
recordType: "CNAME",
|
||||||
baseDomain: cnameRecord.baseDomain,
|
baseDomain: cnameRecord.baseDomain,
|
||||||
@@ -332,7 +330,6 @@ export async function createOrgDomain(
|
|||||||
// Save A records to database
|
// Save A records to database
|
||||||
for (const aRecord of aRecords) {
|
for (const aRecord of aRecords) {
|
||||||
recordsToInsert.push({
|
recordsToInsert.push({
|
||||||
id: generateId(15),
|
|
||||||
domainId,
|
domainId,
|
||||||
recordType: "A",
|
recordType: "A",
|
||||||
baseDomain: aRecord.baseDomain,
|
baseDomain: aRecord.baseDomain,
|
||||||
|
|||||||
@@ -98,15 +98,16 @@ export async function updateOrg(
|
|||||||
parsedBody.data.passwordExpiryDays = undefined;
|
parsedBody.data.passwordExpiryDays = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { tier } = await getOrgTierData(orgId);
|
||||||
if (
|
if (
|
||||||
!isLicensed &&
|
tier != TierId.STANDARD &&
|
||||||
parsedBody.data.settingsLogRetentionDaysRequest &&
|
parsedBody.data.settingsLogRetentionDaysRequest &&
|
||||||
parsedBody.data.settingsLogRetentionDaysRequest > 30
|
parsedBody.data.settingsLogRetentionDaysRequest > 30
|
||||||
) {
|
) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.FORBIDDEN,
|
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"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { domains, exitNodes, orgDomains, orgs, resources } from "@server/db";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import { eq, ne } from "drizzle-orm";
|
import { eq, ne } from "drizzle-orm";
|
||||||
@@ -8,7 +8,10 @@ export async function copyInConfig() {
|
|||||||
const endpoint = config.getRawConfig().gerbil.base_endpoint;
|
const endpoint = config.getRawConfig().gerbil.base_endpoint;
|
||||||
const listenPort = config.getRawConfig().gerbil.start_port;
|
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();
|
await copyInDomains();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +42,7 @@ async function copyInDomains() {
|
|||||||
domainId: key,
|
domainId: key,
|
||||||
baseDomain: value.base_domain.toLowerCase(),
|
baseDomain: value.base_domain.toLowerCase(),
|
||||||
certResolver: value.cert_resolver || null,
|
certResolver: value.cert_resolver || null,
|
||||||
preferWildcardCert: value.prefer_wildcard_cert || null,
|
preferWildcardCert: value.prefer_wildcard_cert || null
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -56,31 +59,79 @@ async function copyInDomains() {
|
|||||||
if (!configDomainKeys.has(existingDomain.domainId)) {
|
if (!configDomainKeys.has(existingDomain.domainId)) {
|
||||||
await trx
|
await trx
|
||||||
.delete(domains)
|
.delete(domains)
|
||||||
.where(eq(domains.domainId, existingDomain.domainId))
|
.where(eq(domains.domainId, existingDomain.domainId));
|
||||||
.execute();
|
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)) {
|
if (existingDomainKeys.has(domainId)) {
|
||||||
await trx
|
await trx
|
||||||
.update(domains)
|
.update(domains)
|
||||||
.set({ baseDomain, verified: true, type: "wildcard", certResolver, preferWildcardCert })
|
.set({
|
||||||
.where(eq(domains.domainId, domainId))
|
|
||||||
.execute();
|
|
||||||
} else {
|
|
||||||
await trx
|
|
||||||
.insert(domains)
|
|
||||||
.values({
|
|
||||||
domainId,
|
|
||||||
baseDomain,
|
baseDomain,
|
||||||
configManaged: true,
|
|
||||||
type: "wildcard",
|
|
||||||
verified: true,
|
verified: true,
|
||||||
|
type: "wildcard",
|
||||||
certResolver,
|
certResolver,
|
||||||
preferWildcardCert
|
preferWildcardCert
|
||||||
})
|
})
|
||||||
.execute();
|
.where(eq(domains.domainId, domainId));
|
||||||
|
|
||||||
|
// delete the dns records and add them again to ensure they are correct
|
||||||
|
await trx
|
||||||
|
.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,
|
||||||
|
type: "wildcard",
|
||||||
|
verified: true,
|
||||||
|
certResolver,
|
||||||
|
preferWildcardCert
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default async function migration() {
|
|||||||
|
|
||||||
await db.execute(sql`
|
await db.execute(sql`
|
||||||
CREATE TABLE "dnsRecords" (
|
CREATE TABLE "dnsRecords" (
|
||||||
"id" varchar PRIMARY KEY NOT NULL,
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
"domainId" varchar NOT NULL,
|
"domainId" varchar NOT NULL,
|
||||||
"recordType" varchar NOT NULL,
|
"recordType" varchar NOT NULL,
|
||||||
"baseDomain" varchar,
|
"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`ALTER TABLE "orgs" DROP COLUMN "settings";`);
|
||||||
|
|
||||||
await db.execute(sql`COMMIT`);
|
await db.execute(sql`COMMIT`);
|
||||||
console.log(`Updated resource rules match value from GEOIP to COUNTRY`);
|
console.log("Migrated database");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await db.execute(sql`ROLLBACK`);
|
await db.execute(sql`ROLLBACK`);
|
||||||
console.log("Unable to update resource rules match value");
|
console.log("Unable to migrate database");
|
||||||
console.log(e);
|
console.log(e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default async function migration() {
|
|||||||
db.prepare(
|
db.prepare(
|
||||||
`
|
`
|
||||||
CREATE TABLE 'dnsRecords' (
|
CREATE TABLE 'dnsRecords' (
|
||||||
'id' text PRIMARY KEY NOT NULL,
|
'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
'domainId' text NOT NULL,
|
'domainId' text NOT NULL,
|
||||||
'recordType' text NOT NULL,
|
'recordType' text NOT NULL,
|
||||||
'baseDomain' text,
|
'baseDomain' text,
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export function DNSRecordsDataTable<TData, TValue>({
|
|||||||
<h1 className="font-bold">{t("dnsRecord")}</h1>
|
<h1 className="font-bold">{t("dnsRecord")}</h1>
|
||||||
<Badge variant="secondary">{t("required")}</Badge>
|
<Badge variant="secondary">{t("required")}</Badge>
|
||||||
</div>
|
</div>
|
||||||
<Link href="https://docs.pangolin.net/self-host/dns-and-networking">
|
<Link href="https://docs.pangolin.net/manage/domains">
|
||||||
<Button variant="outline">
|
<Button variant="outline">
|
||||||
<ExternalLink className="h-4 w-4 mr-1" />
|
<ExternalLink className="h-4 w-4 mr-1" />
|
||||||
{t("howToAddRecords")}
|
{t("howToAddRecords")}
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ import { useTranslations } from "next-intl";
|
|||||||
import CreateDomainForm from "@app/components/CreateDomainForm";
|
import CreateDomainForm from "@app/components/CreateDomainForm";
|
||||||
import { useToast } from "@app/hooks/useToast";
|
import { useToast } from "@app/hooks/useToast";
|
||||||
import { useOrgContext } from "@app/hooks/useOrgContext";
|
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";
|
import Link from "next/link";
|
||||||
|
|
||||||
export type DomainRow = {
|
export type DomainRow = {
|
||||||
@@ -179,9 +184,15 @@ export default function DomainsTable({ domains, orgId }: Props) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const { verified, failed } = row.original;
|
const { verified, failed, type } = row.original;
|
||||||
if (verified) {
|
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) {
|
} else if (failed) {
|
||||||
return (
|
return (
|
||||||
<Badge variant="destructive">
|
<Badge variant="destructive">
|
||||||
@@ -210,16 +221,21 @@ export default function DomainsTable({ domains, orgId }: Props) {
|
|||||||
>
|
>
|
||||||
{isRestarting
|
{isRestarting
|
||||||
? t("restarting", {
|
? t("restarting", {
|
||||||
fallback: "Restarting..."
|
fallback: "Restarting..."
|
||||||
})
|
})
|
||||||
: t("restart", { fallback: "Restart" })}
|
: t("restart", { fallback: "Restart" })}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center justify-end gap-2">
|
<div className="flex items-center justify-end gap-2">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
<Button
|
||||||
<span className="sr-only">Open menu</span>
|
variant="ghost"
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
>
|
||||||
|
<span className="sr-only">
|
||||||
|
Open menu
|
||||||
|
</span>
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@@ -282,12 +298,8 @@ export default function DomainsTable({ domains, orgId }: Props) {
|
|||||||
}}
|
}}
|
||||||
dialog={
|
dialog={
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>{t("domainQuestionRemove")}</p>
|
||||||
{t("domainQuestionRemove")}
|
<p>{t("domainMessageRemove")}</p>
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{t("domainMessageRemove")}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
buttonText={t("domainConfirmDelete")}
|
buttonText={t("domainConfirmDelete")}
|
||||||
|
|||||||
Reference in New Issue
Block a user