Compare commits

..

9 Commits
1.7.1 ... 1.7.2

Author SHA1 Message Date
Owen
2e986def78 const 2025-07-17 23:15:16 -07:00
miloschwartz
d16a05959d Merge branch 'main' into dev 2025-07-17 23:14:50 -07:00
Owen
7e58e0b490 Correctly handle ssl on new domains 2025-07-17 22:57:47 -07:00
Owen
9b01aecf3c Add default cert resovler 2025-07-17 22:37:33 -07:00
miloschwartz
86043fd5f8 add defaults for domain cert resolver and prefer wildcard cert 2025-07-17 22:35:07 -07:00
Milo Schwartz
372a1758e9 Update README.md 2025-07-17 19:35:27 -04:00
Owen
0a2b1d9e53 Use a records for the wildcard 2025-07-17 16:17:01 -07:00
Owen
e562946308 Fix logic 2025-07-17 16:03:34 -07:00
Owen
398e15b3c6 Format 2025-07-17 15:59:28 -07:00
6 changed files with 292 additions and 195 deletions

View File

@@ -16,7 +16,7 @@ _Pangolin tunnels your services to the internet so you can access anything from
<div align="center"> <div align="center">
<h5> <h5>
<a href="https://fossorial.io"> <a href="https://digpangolin.com">
Website Website
</a> </a>
<span> | </span> <span> | </span>
@@ -111,7 +111,7 @@ Host the full application on your own server or on the cloud with a VPS. Take a
### Pangolin Cloud ### Pangolin Cloud
Easy to use with simple [pay as you go pricing](https://fossorial.io/pricing). [Check it out here](https://pangolin.fossorial.io/auth/signup). Easy to use with simple [pay as you go pricing](https://digpangolin.io/pricing). [Check it out here](https://pangolin.fossorial.io/auth/signup).
- Everything you get with self hosted Pangolin, but fully managed for you. - Everything you get with self hosted Pangolin, but fully managed for you.

View File

@@ -1266,6 +1266,7 @@
"createDomainName": "Name:", "createDomainName": "Name:",
"createDomainValue": "Value:", "createDomainValue": "Value:",
"createDomainCnameRecords": "CNAME Records", "createDomainCnameRecords": "CNAME Records",
"createDomainARecords": "A Records",
"createDomainRecordNumber": "Record {number}", "createDomainRecordNumber": "Record {number}",
"createDomainTxtRecords": "TXT Records", "createDomainTxtRecords": "TXT Records",
"createDomainSaveTheseRecords": "Save These Records", "createDomainSaveTheseRecords": "Save These Records",

View File

@@ -128,7 +128,9 @@ export const configSchema = z
.object({ .object({
http_entrypoint: z.string().optional().default("web"), http_entrypoint: z.string().optional().default("web"),
https_entrypoint: z.string().optional().default("websecure"), https_entrypoint: z.string().optional().default("websecure"),
additional_middlewares: z.array(z.string()).optional() additional_middlewares: z.array(z.string()).optional(),
cert_resolver: z.string().optional().default("letsencrypt"),
prefer_wildcard_cert: z.boolean().optional().default(false)
}) })
.optional() .optional()
.default({}), .default({}),
@@ -157,13 +159,16 @@ export const configSchema = z
}) })
.optional() .optional()
.default({}), .default({}),
orgs: z.object({ orgs: z
block_size: z.number().positive().gt(0).optional().default(24), .object({
subnet_group: z.string().optional().default("100.90.128.0/24") block_size: z.number().positive().gt(0).optional().default(24),
}).optional().default({ subnet_group: z.string().optional().default("100.90.128.0/24")
block_size: 24, })
subnet_group: "100.90.128.0/24" .optional()
}), .default({
block_size: 24,
subnet_group: "100.90.128.0/24"
}),
rate_limits: z rate_limits: z
.object({ .object({
global: z global: z
@@ -242,7 +247,7 @@ export const configSchema = z
{ {
message: "At least one domain must be defined" message: "At least one domain must be defined"
} }
) );
export function readConfigFile() { export function readConfigFile() {
const loadConfig = (configPath: string) => { const loadConfig = (configPath: string) => {

View File

@@ -29,6 +29,7 @@ export type CreateDomainResponse = {
domainId: string; domainId: string;
nsRecords?: string[]; nsRecords?: string[];
cnameRecords?: { baseDomain: string; value: string }[]; cnameRecords?: { baseDomain: string; value: string }[];
aRecords?: { baseDomain: string; value: string }[];
txtRecords?: { baseDomain: string; value: string }[]; txtRecords?: { baseDomain: string; value: string }[];
}; };
@@ -97,6 +98,7 @@ export async function createOrgDomain(
} }
let numOrgDomains: OrgDomains[] | undefined; let numOrgDomains: OrgDomains[] | undefined;
let aRecords: CreateDomainResponse["aRecords"];
let cnameRecords: CreateDomainResponse["cnameRecords"]; let cnameRecords: CreateDomainResponse["cnameRecords"];
let txtRecords: CreateDomainResponse["txtRecords"]; let txtRecords: CreateDomainResponse["txtRecords"];
let nsRecords: CreateDomainResponse["nsRecords"]; let nsRecords: CreateDomainResponse["nsRecords"];
@@ -239,7 +241,7 @@ export async function createOrgDomain(
} }
]; ];
} else if (type === "wildcard") { } else if (type === "wildcard") {
cnameRecords = [ aRecords = [
{ {
value: `Server IP Address`, value: `Server IP Address`,
baseDomain: `*.${baseDomain}` baseDomain: `*.${baseDomain}`
@@ -271,7 +273,8 @@ export async function createOrgDomain(
domainId: returned.domainId, domainId: returned.domainId,
cnameRecords, cnameRecords,
txtRecords, txtRecords,
nsRecords nsRecords,
aRecords
}, },
success: true, success: true,
error: false, error: false,

View File

@@ -214,22 +214,29 @@ export async function traefikConfigProvider(
const configDomain = config.getDomain(resource.domainId); const configDomain = config.getDomain(resource.domainId);
let tls = {}; let certResolver: string, preferWildcardCert: boolean;
if (configDomain) { if (!configDomain) {
tls = { certResolver = config.getRawConfig().traefik.cert_resolver;
certResolver: configDomain.cert_resolver, preferWildcardCert =
...(configDomain.prefer_wildcard_cert config.getRawConfig().traefik.prefer_wildcard_cert;
? { } else {
domains: [ certResolver = configDomain.cert_resolver;
{ preferWildcardCert = configDomain.prefer_wildcard_cert;
main: wildCard
}
]
}
: {})
};
} }
const tls = {
certResolver: certResolver,
...(preferWildcardCert
? {
domains: [
{
main: wildCard
}
]
}
: {})
};
const additionalMiddlewares = const additionalMiddlewares =
config.getRawConfig().traefik.additional_middlewares || []; config.getRawConfig().traefik.additional_middlewares || [];

View File

@@ -205,188 +205,267 @@ export default function CreateDomainForm({
</Alert> </Alert>
<div className="space-y-4"> <div className="space-y-4">
{domainType === "ns" && {createdDomain.nsRecords &&
createdDomain.nsRecords && ( createdDomain.nsRecords.length > 0 && (
<div>
<h3 className="font-medium mb-3">
{t("createDomainNsRecords")}
</h3>
<InfoSections cols={1}>
<InfoSection>
<InfoSectionTitle>
{t("createDomainRecord")}
</InfoSectionTitle>
<InfoSectionContent>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm font-medium">
{t(
"createDomainType"
)}
</span>
<span className="text-sm font-mono">
NS
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm font-medium">
{t(
"createDomainName"
)}
</span>
<span className="text-sm font-mono">
{baseDomain}
</span>
</div>
<span className="text-sm font-medium">
{t(
"createDomainValue"
)}
</span>
{createdDomain.nsRecords.map(
(
nsRecord,
index
) => (
<div
className="flex justify-between items-center"
key={index}
>
<CopyToClipboard
text={
nsRecord
}
/>
</div>
)
)}
</div>
</InfoSectionContent>
</InfoSection>
</InfoSections>
</div>
)}
{createdDomain.cnameRecords &&
createdDomain.cnameRecords.length > 0 && (
<div> <div>
<h3 className="font-medium mb-3"> <h3 className="font-medium mb-3">
{t("createDomainNsRecords")} {t("createDomainCnameRecords")}
</h3> </h3>
<InfoSections cols={1}> <InfoSections cols={1}>
<InfoSection> {createdDomain.cnameRecords.map(
<InfoSectionTitle> (cnameRecord, index) => (
{t("createDomainRecord")} <InfoSection
</InfoSectionTitle> key={index}
<InfoSectionContent> >
<div className="space-y-2"> <InfoSectionTitle>
<div className="flex justify-between items-center"> {t(
<span className="text-sm font-medium"> "createDomainRecordNumber",
{t("createDomainType")} {
</span> number:
<span className="text-sm font-mono"> index +
NS 1
</span> }
</div> )}
<div className="flex justify-between items-center"> </InfoSectionTitle>
<span className="text-sm font-medium"> <InfoSectionContent>
{t("createDomainName")} <div className="space-y-2">
</span> <div className="flex justify-between items-center">
<span className="text-sm font-mono"> <span className="text-sm font-medium">
{baseDomain} {t(
</span> "createDomainType"
</div> )}
<span className="text-sm font-medium"> </span>
{t("createDomainValue")} <span className="text-sm font-mono">
</span> CNAME
{createdDomain.nsRecords.map( </span>
( </div>
nsRecord, <div className="flex justify-between items-center">
index <span className="text-sm font-medium">
) => ( {t(
<div "createDomainName"
className="flex justify-between items-center" )}
key={ </span>
index <span className="text-sm font-mono">
} {
> cnameRecord.baseDomain
}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm font-medium">
{t(
"createDomainValue"
)}
</span>
<CopyToClipboard <CopyToClipboard
text={ text={
nsRecord cnameRecord.value
} }
/> />
</div> </div>
) </div>
)} </InfoSectionContent>
</div> </InfoSection>
</InfoSectionContent> )
</InfoSection> )}
</InfoSections> </InfoSections>
</div> </div>
)} )}
{domainType === "cname" || {createdDomain.aRecords &&
(domainType == "wildcard" && ( createdDomain.aRecords.length > 0 && (
<> <div>
{createdDomain.cnameRecords && <h3 className="font-medium mb-3">
createdDomain.cnameRecords {t("createDomainARecords")}
.length > 0 && ( </h3>
<div> <InfoSections cols={1}>
<h3 className="font-medium mb-3"> {createdDomain.aRecords.map(
{t("createDomainCnameRecords")} (aRecord, index) => (
</h3> <InfoSection
<InfoSections cols={1}> key={index}
{createdDomain.cnameRecords.map( >
( <InfoSectionTitle>
cnameRecord, {t(
index "createDomainRecordNumber",
) => ( {
<InfoSection number:
key={ index +
index 1
} }
> )}
<InfoSectionTitle> </InfoSectionTitle>
{t("createDomainRecordNumber", { number: index + 1 })} <InfoSectionContent>
</InfoSectionTitle> <div className="space-y-2">
<InfoSectionContent> <div className="flex justify-between items-center">
<div className="space-y-2"> <span className="text-sm font-medium">
<div className="flex justify-between items-center"> {t(
<span className="text-sm font-medium"> "createDomainType"
{t("createDomainType")} )}
</span> </span>
<span className="text-sm font-mono"> <span className="text-sm font-mono">
CNAME A
</span> </span>
</div> </div>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<span className="text-sm font-medium"> <span className="text-sm font-medium">
{t("createDomainName")} {t(
</span> "createDomainName"
<span className="text-sm font-mono"> )}
{ </span>
cnameRecord.baseDomain <span className="text-sm font-mono">
} {
</span> aRecord.baseDomain
</div> }
<div className="flex justify-between items-center"> </span>
<span className="text-sm font-medium"> </div>
{t("createDomainValue")} <div className="flex justify-between items-center">
</span> <span className="text-sm font-medium">
<CopyToClipboard {t(
text={ "createDomainValue"
cnameRecord.value )}
} </span>
/> <span className="text-sm font-mono">
</div> {
</div> aRecord.value
</InfoSectionContent> }
</InfoSection> </span>
) </div>
)} </div>
</InfoSections> </InfoSectionContent>
</div> </InfoSection>
)
)} )}
</InfoSections>
{createdDomain.txtRecords && </div>
createdDomain.txtRecords )}
.length > 0 && ( {createdDomain.txtRecords &&
<div> createdDomain.txtRecords.length > 0 && (
<h3 className="font-medium mb-3"> <div>
{t("createDomainTxtRecords")} <h3 className="font-medium mb-3">
</h3> {t("createDomainTxtRecords")}
<InfoSections cols={1}> </h3>
{createdDomain.txtRecords.map( <InfoSections cols={1}>
( {createdDomain.txtRecords.map(
txtRecord, (txtRecord, index) => (
index <InfoSection
) => ( key={index}
<InfoSection >
key={ <InfoSectionTitle>
index {t(
} "createDomainRecordNumber",
> {
<InfoSectionTitle> number:
{t("createDomainRecordNumber", { number: index + 1 })} index +
</InfoSectionTitle> 1
<InfoSectionContent> }
<div className="space-y-2"> )}
<div className="flex justify-between items-center"> </InfoSectionTitle>
<span className="text-sm font-medium"> <InfoSectionContent>
{t("createDomainType")} <div className="space-y-2">
</span> <div className="flex justify-between items-center">
<span className="text-sm font-mono"> <span className="text-sm font-medium">
TXT {t(
</span> "createDomainType"
</div> )}
<div className="flex justify-between items-center"> </span>
<span className="text-sm font-medium"> <span className="text-sm font-mono">
{t("createDomainName")} TXT
</span> </span>
<span className="text-sm font-mono"> </div>
{ <div className="flex justify-between items-center">
txtRecord.baseDomain <span className="text-sm font-medium">
} {t(
</span> "createDomainName"
</div> )}
<div className="flex justify-between items-center"> </span>
<span className="text-sm font-medium"> <span className="text-sm font-mono">
{t("createDomainValue")} {
</span> txtRecord.baseDomain
<CopyToClipboard }
text={ </span>
txtRecord.value </div>
} <div className="flex justify-between items-center">
/> <span className="text-sm font-medium">
</div> {t(
</div> "createDomainValue"
</InfoSectionContent> )}
</InfoSection> </span>
) <CopyToClipboard
)} text={
</InfoSections> txtRecord.value
</div> }
/>
</div>
</div>
</InfoSectionContent>
</InfoSection>
)
)} )}
</> </InfoSections>
))} </div>
)}
</div> </div>
{build == "saas" || {build == "saas" ||
@@ -397,7 +476,9 @@ export default function CreateDomainForm({
{t("createDomainSaveTheseRecords")} {t("createDomainSaveTheseRecords")}
</AlertTitle> </AlertTitle>
<AlertDescription> <AlertDescription>
{t("createDomainSaveTheseRecordsDescription")} {t(
"createDomainSaveTheseRecordsDescription"
)}
</AlertDescription> </AlertDescription>
</Alert> </Alert>
))} ))}