diff --git a/messages/en-US.json b/messages/en-US.json
index 7f00e40d..602754d8 100644
--- a/messages/en-US.json
+++ b/messages/en-US.json
@@ -973,6 +973,7 @@
"logoutError": "Error logging out",
"signingAs": "Signed in as",
"serverAdmin": "Server Admin",
+ "managedSelfhosted": "Managed Self-Hosted",
"otpEnable": "Enable Two-factor",
"otpDisable": "Disable Two-factor",
"logout": "Log Out",
diff --git a/server/lib/config.ts b/server/lib/config.ts
index 6b41df79..82932441 100644
--- a/server/lib/config.ts
+++ b/server/lib/config.ts
@@ -103,9 +103,7 @@ export class Config {
private async checkKeyStatus() {
const licenseStatus = await license.check();
- if (
- !licenseStatus.isHostLicensed
- ) {
+ if (!licenseStatus.isHostLicensed) {
this.checkSupporterKey();
}
}
diff --git a/server/lib/readConfigFile.ts b/server/lib/readConfigFile.ts
index 23db4e52..8107385c 100644
--- a/server/lib/readConfigFile.ts
+++ b/server/lib/readConfigFile.ts
@@ -34,6 +34,7 @@ export const configSchema = z
}),
hybrid: z
.object({
+ name: z.string().optional(),
id: z.string().optional(),
secret: z.string().optional(),
endpoint: z.string().optional(),
diff --git a/server/routers/external.ts b/server/routers/external.ts
index fd7fff50..95add3ed 100644
--- a/server/routers/external.ts
+++ b/server/routers/external.ts
@@ -15,6 +15,7 @@ import * as accessToken from "./accessToken";
import * as idp from "./idp";
import * as license from "./license";
import * as apiKeys from "./apiKeys";
+import * as hybrid from "./hybrid";
import HttpCode from "@server/types/HttpCode";
import {
verifyAccessTokenAccess,
@@ -951,7 +952,8 @@ authRouter.post(
rateLimit({
windowMs: 15 * 60 * 1000,
max: 15,
- keyGenerator: (req) => `requestEmailVerificationCode:${req.body.email || req.ip}`,
+ keyGenerator: (req) =>
+ `requestEmailVerificationCode:${req.body.email || req.ip}`,
handler: (req, res, next) => {
const message = `You can only request an email verification code ${15} times every ${15} minutes. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
@@ -972,7 +974,8 @@ authRouter.post(
rateLimit({
windowMs: 15 * 60 * 1000,
max: 15,
- keyGenerator: (req) => `requestPasswordReset:${req.body.email || req.ip}`,
+ keyGenerator: (req) =>
+ `requestPasswordReset:${req.body.email || req.ip}`,
handler: (req, res, next) => {
const message = `You can only request a password reset ${15} times every ${15} minutes. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
@@ -1066,7 +1069,8 @@ authRouter.post(
rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Allow 5 security key registrations per 15 minutes
- keyGenerator: (req) => `securityKeyRegister:${req.user?.userId || req.ip}`,
+ keyGenerator: (req) =>
+ `securityKeyRegister:${req.user?.userId || req.ip}`,
handler: (req, res, next) => {
const message = `You can only register a security key ${5} times every ${15} minutes. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
diff --git a/src/app/admin/managed/page.tsx b/src/app/admin/managed/page.tsx
new file mode 100644
index 00000000..cb25ba5d
--- /dev/null
+++ b/src/app/admin/managed/page.tsx
@@ -0,0 +1,176 @@
+"use client";
+
+import {
+ SettingsContainer,
+ SettingsSection,
+ SettingsSectionTitle as SectionTitle,
+ SettingsSectionBody,
+ SettingsSectionFooter
+} from "@app/components/Settings";
+import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
+import { Alert } from "@app/components/ui/alert";
+import { Button } from "@app/components/ui/button";
+import {
+ Shield,
+ Zap,
+ RefreshCw,
+ Activity,
+ Wrench,
+ CheckCircle,
+ ExternalLink
+} from "lucide-react";
+import Link from "next/link";
+
+export default async function ManagedPage() {
+ return (
+ <>
+
+ Managed Self-Hosted Pangolin is a
+ deployment option designed for people who want
+ simplicity and extra reliability while still keeping
+ their data private and self-hosted.
+
+ With this option, you still run your own Pangolin
+ node — your tunnels, SSL termination, and traffic
+ all stay on your server. The difference is that
+ management and monitoring are handled through our
+ cloud dashboard, which unlocks a number of benefits:
+
+ No need to run your own mail server
+ or set up complex alerting. You'll
+ get health checks and downtime
+ alerts out of the box.
+
+ The cloud dashboard evolves quickly,
+ so you get new features and bug
+ fixes without having to manually
+ pull new containers every time.
+
+ No database migrations, backups, or
+ extra infrastructure to manage. We
+ handle that in the cloud.
+
+ If your node goes down, your tunnels
+ can temporarily fail over to our
+ cloud points of presence until you
+ bring it back online.
+
+ You can also attach multiple nodes
+ to your account for redundancy and
+ better performance.
+
+ We're planning to add more
+ analytics, alerting, and management
+ tools to make your deployment even
+ more robust.
+
+ Simpler operations
+
+
+ Automatic updates
+
+
+ Less maintenance
+
+
+ Cloud failover
+
+
+ High availability (PoPs)
+
+
+ Future enhancements
+
+