diff --git a/README.md b/README.md index f3214b6f..8c94815d 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,6 @@ _Pangolin tunnels your services to the internet so you can access anything from
- - Pangolin is a self-hosted tunneled reverse proxy server with identity and access control, designed to securely expose private resources on distributed networks. Acting as a central hub, it connects isolated networks — even those behind restrictive firewalls — through encrypted tunnels, enabling easy access to remote services without opening ports.
diff --git a/docker-compose.example.yml b/docker-compose.example.yml
index 5a1b0a4e..c7c068f0 100644
--- a/docker-compose.example.yml
+++ b/docker-compose.example.yml
@@ -31,6 +31,7 @@ services:
- SYS_MODULE
ports:
- 51820:51820/udp
+ - 21820:21820/udp
- 443:443 # Port for traefik because of the network_mode
- 80:80 # Port for traefik because of the network_mode
diff --git a/docker-compose.pgr.yml b/docker-compose.pg.yml
similarity index 100%
rename from docker-compose.pgr.yml
rename to docker-compose.pg.yml
diff --git a/install/config/config.yml b/install/config/config.yml
index 5f81c141..00d7c897 100644
--- a/install/config/config.yml
+++ b/install/config/config.yml
@@ -22,10 +22,6 @@ gerbil:
start_port: 51820
base_endpoint: "{{.DashboardDomain}}"
-orgs:
- block_size: 24
- subnet_group: 100.89.138.0/20
-
{{if .EnableEmail}}
email:
smtp_host: "{{.EmailSMTPHost}}"
diff --git a/install/config/docker-compose.yml b/install/config/docker-compose.yml
index 35319dd0..4ce31e41 100644
--- a/install/config/docker-compose.yml
+++ b/install/config/docker-compose.yml
@@ -31,6 +31,7 @@ services:
- SYS_MODULE
ports:
- 51820:51820/udp
+ - 21820:21820/udp
- 443:443 # Port for traefik because of the network_mode
- 80:80 # Port for traefik because of the network_mode
{{end}}
diff --git a/install/main.go b/install/main.go
index 8160f2e9..9bb0c7e1 100644
--- a/install/main.go
+++ b/install/main.go
@@ -60,8 +60,23 @@ const (
)
func main() {
+
+ // print a banner about prerequisites - opening port 80, 443, 51820, and 21820 on the VPS and firewall and pointing your domain to the VPS IP with a records. Docs are at http://localhost:3000/Getting%20Started/dns-networking
+
+ fmt.Println("Welcome to the Pangolin installer!")
+ fmt.Println("This installer will help you set up Pangolin on your server.")
+ fmt.Println("")
+ fmt.Println("Please make sure you have the following prerequisites:")
+ fmt.Println("- Open TCP ports 80 and 443 and UDP ports 51820 and 21820 on your VPS and firewall.")
+ fmt.Println("- Point your domain to the VPS IP with A records.")
+ fmt.Println("")
+ fmt.Println("http://docs.fossorial.io/Getting%20Started/dns-networking")
+ fmt.Println("")
+ fmt.Println("Lets get started!")
+ fmt.Println("")
+
reader := bufio.NewReader(os.Stdin)
- inputContainer := readString(reader, "Would you like to run Pangolin as docker or podman container?", "docker")
+ inputContainer := readString(reader, "Would you like to run Pangolin as Docker or Podman containers?", "docker")
chosenContainer := Docker
if strings.EqualFold(inputContainer, "docker") {
diff --git a/messages/en-US.json b/messages/en-US.json
index ff0ca4e6..e69e2b46 100644
--- a/messages/en-US.json
+++ b/messages/en-US.json
@@ -59,7 +59,6 @@
"siteErrorCreate": "Error creating site",
"siteErrorCreateKeyPair": "Key pair or site defaults not found",
"siteErrorCreateDefaults": "Site defaults not found",
- "siteNameDescription": "This is the display name for the site.",
"method": "Method",
"siteMethodDescription": "This is how you will expose connections.",
"siteLearnNewt": "Learn how to install Newt on your system",
@@ -1094,7 +1093,7 @@
"sidebarAllUsers": "All Users",
"sidebarIdentityProviders": "Identity Providers",
"sidebarLicense": "License",
- "sidebarClients": "Clients",
+ "sidebarClients": "Clients (Beta)",
"sidebarDomains": "Domains",
"enableDockerSocket": "Enable Docker Socket",
"enableDockerSocketDescription": "Enable Docker Socket discovery for populating container information. Socket path must be provided to Newt.",
@@ -1196,7 +1195,7 @@
"sidebarExpand": "Expand",
"newtUpdateAvailable": "Update Available",
"newtUpdateAvailableInfo": "A new version of Newt is available. Please update to the latest version for the best experience.",
- "domainPickerEnterDomain": "Enter your domain",
+ "domainPickerEnterDomain": "Domain",
"domainPickerPlaceholder": "myapp.example.com, api.v1.mydomain.com, or just myapp",
"domainPickerDescription": "Enter the full domain of the resource to see available options.",
"domainPickerDescriptionSaas": "Enter a full domain, subdomain, or just a name to see available options",
@@ -1206,7 +1205,7 @@
"domainPickerSortAsc": "A-Z",
"domainPickerSortDesc": "Z-A",
"domainPickerCheckingAvailability": "Checking availability...",
- "domainPickerNoMatchingDomains": "No matching domains found for \"{userInput}\". Try a different domain or check your organization's domain settings.",
+ "domainPickerNoMatchingDomains": "No matching domains found. Try a different domain or check your organization's domain settings.",
"domainPickerOrganizationDomains": "Organization Domains",
"domainPickerProvidedDomains": "Provided Domains",
"domainPickerSubdomain": "Subdomain: {subdomain}",
@@ -1274,5 +1273,50 @@
"createDomainDnsPropagation": "DNS Propagation",
"createDomainDnsPropagationDescription": "DNS changes may take some time to propagate across the internet. This can take anywhere from a few minutes to 48 hours, depending on your DNS provider and TTL settings.",
"resourcePortRequired": "Port number is required for non-HTTP resources",
- "resourcePortNotAllowed": "Port number should not be set for HTTP resources"
+ "resourcePortNotAllowed": "Port number should not be set for HTTP resources",
+ "signUpTerms": {
+ "IAgreeToThe": "I agree to the",
+ "termsOfService": "terms of service",
+ "and": "and",
+ "privacyPolicy": "privacy policy"
+ },
+ "siteRequired": "Site is required.",
+ "olmTunnel": "Olm Tunnel",
+ "olmTunnelDescription": "Use Olm for client connectivity",
+ "errorCreatingClient": "Error creating client",
+ "clientDefaultsNotFound": "Client defaults not found",
+ "createClient": "Create Client",
+ "createClientDescription": "Create a new client for connecting to your sites",
+ "seeAllClients": "See All Clients",
+ "clientInformation": "Client Information",
+ "clientNamePlaceholder": "Client name",
+ "address": "Address",
+ "subnetPlaceholder": "Subnet",
+ "addressDescription": "The address that this client will use for connectivity",
+ "selectSites": "Select sites",
+ "sitesDescription": "The client will have connectivity to the selected sites",
+ "clientInstallOlm": "Install Olm",
+ "clientInstallOlmDescription": "Get Olm running on your system",
+ "clientOlmCredentials": "Olm Credentials",
+ "clientOlmCredentialsDescription": "This is how Olm will authenticate with the server",
+ "olmEndpoint": "Olm Endpoint",
+ "olmId": "Olm ID",
+ "olmSecretKey": "Olm Secret Key",
+ "clientCredentialsSave": "Save Your Credentials",
+ "clientCredentialsSaveDescription": "You will only be able to see this once. Make sure to copy it to a secure place.",
+ "generalSettingsDescription": "Configure the general settings for this client",
+ "clientUpdated": "Client updated",
+ "clientUpdatedDescription": "The client has been updated.",
+ "clientUpdateFailed": "Failed to update client",
+ "clientUpdateError": "An error occurred while updating the client.",
+ "sitesFetchFailed": "Failed to fetch sites",
+ "sitesFetchError": "An error occurred while fetching sites.",
+ "olmErrorFetchReleases": "An error occurred while fetching Olm releases.",
+ "olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
+ "remoteSubnets": "Remote Subnets",
+ "enterCidrRange": "Enter CIDR range",
+ "remoteSubnetsDescription": "Add CIDR ranges that can access this site remotely. Use format like 10.0.0.0/24 or 192.168.1.0/24.",
+ "resourceEnableProxy": "Enable Public Proxy",
+ "resourceEnableProxyDescription": "Enable public proxying to this resource. This allows access to the resource from outside the network through the cloud on an open port. Requires Traefik config.",
+ "externalProxyEnabled": "External Proxy Enabled"
}
diff --git a/server/db/pg/schema.ts b/server/db/pg/schema.ts
index 39f14598..b9228286 100644
--- a/server/db/pg/schema.ts
+++ b/server/db/pg/schema.ts
@@ -5,7 +5,8 @@ import {
boolean,
integer,
bigint,
- real
+ real,
+ text
} from "drizzle-orm/pg-core";
import { InferSelectModel } from "drizzle-orm";
@@ -58,7 +59,8 @@ export const sites = pgTable("sites", {
publicKey: varchar("publicKey"),
lastHolePunch: bigint("lastHolePunch", { mode: "number" }),
listenPort: integer("listenPort"),
- dockerSocketEnabled: boolean("dockerSocketEnabled").notNull().default(true)
+ dockerSocketEnabled: boolean("dockerSocketEnabled").notNull().default(true),
+ remoteSubnets: text("remoteSubnets") // comma-separated list of subnets that this site can access
});
export const resources = pgTable("resources", {
@@ -92,7 +94,8 @@ export const resources = pgTable("resources", {
enabled: boolean("enabled").notNull().default(true),
stickySession: boolean("stickySession").notNull().default(false),
tlsServerName: varchar("tlsServerName"),
- setHostHeader: varchar("setHostHeader")
+ setHostHeader: varchar("setHostHeader"),
+ enableProxy: boolean("enableProxy").default(true),
});
export const targets = pgTable("targets", {
@@ -135,6 +138,8 @@ export const users = pgTable("user", {
twoFactorSecret: varchar("twoFactorSecret"),
emailVerified: boolean("emailVerified").notNull().default(false),
dateCreated: varchar("dateCreated").notNull(),
+ termsAcceptedTimestamp: varchar("termsAcceptedTimestamp"),
+ termsVersion: varchar("termsVersion"),
serverAdmin: boolean("serverAdmin").notNull().default(false)
});
@@ -504,8 +509,8 @@ export const clients = pgTable("clients", {
name: varchar("name").notNull(),
pubKey: varchar("pubKey"),
subnet: varchar("subnet").notNull(),
- megabytesIn: integer("bytesIn"),
- megabytesOut: integer("bytesOut"),
+ megabytesIn: real("bytesIn"),
+ megabytesOut: real("bytesOut"),
lastBandwidthUpdate: varchar("lastBandwidthUpdate"),
lastPing: varchar("lastPing"),
type: varchar("type").notNull(), // "olm"
@@ -539,7 +544,7 @@ export const olmSessions = pgTable("clientSession", {
olmId: varchar("olmId")
.notNull()
.references(() => olms.olmId, { onDelete: "cascade" }),
- expiresAt: integer("expiresAt").notNull()
+ expiresAt: bigint("expiresAt", { mode: "number" }).notNull()
});
export const userClients = pgTable("userClients", {
@@ -562,9 +567,11 @@ export const roleClients = pgTable("roleClients", {
export const securityKeys = pgTable("webauthnCredentials", {
credentialId: varchar("credentialId").primaryKey(),
- userId: varchar("userId").notNull().references(() => users.userId, {
- onDelete: "cascade"
- }),
+ userId: varchar("userId")
+ .notNull()
+ .references(() => users.userId, {
+ onDelete: "cascade"
+ }),
publicKey: varchar("publicKey").notNull(),
signCount: integer("signCount").notNull(),
transports: varchar("transports"),
diff --git a/server/db/sqlite/schema.ts b/server/db/sqlite/schema.ts
index 3e442d07..974faa67 100644
--- a/server/db/sqlite/schema.ts
+++ b/server/db/sqlite/schema.ts
@@ -65,7 +65,8 @@ export const sites = sqliteTable("sites", {
listenPort: integer("listenPort"),
dockerSocketEnabled: integer("dockerSocketEnabled", { mode: "boolean" })
.notNull()
- .default(true)
+ .default(true),
+ remoteSubnets: text("remoteSubnets"), // comma-separated list of subnets that this site can access
});
export const resources = sqliteTable("resources", {
@@ -105,7 +106,8 @@ export const resources = sqliteTable("resources", {
.notNull()
.default(false),
tlsServerName: text("tlsServerName"),
- setHostHeader: text("setHostHeader")
+ setHostHeader: text("setHostHeader"),
+ enableProxy: integer("enableProxy", { mode: "boolean" }).default(true),
});
export const targets = sqliteTable("targets", {
@@ -154,6 +156,8 @@ export const users = sqliteTable("user", {
.notNull()
.default(false),
dateCreated: text("dateCreated").notNull(),
+ termsAcceptedTimestamp: text("termsAcceptedTimestamp"),
+ termsVersion: text("termsVersion"),
serverAdmin: integer("serverAdmin", { mode: "boolean" })
.notNull()
.default(false)
diff --git a/server/lib/consts.ts b/server/lib/consts.ts
index 70d4404a..cfe45620 100644
--- a/server/lib/consts.ts
+++ b/server/lib/consts.ts
@@ -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.7.3";
+export const APP_VERSION = "1.8.0";
export const __FILENAME = fileURLToPath(import.meta.url);
export const __DIRNAME = path.dirname(__FILENAME);
diff --git a/server/lib/readConfigFile.ts b/server/lib/readConfigFile.ts
index da1e1649..42fcefd3 100644
--- a/server/lib/readConfigFile.ts
+++ b/server/lib/readConfigFile.ts
@@ -229,9 +229,22 @@ export const configSchema = z
disable_local_sites: z.boolean().optional(),
disable_basic_wireguard_sites: z.boolean().optional(),
disable_config_managed_domains: z.boolean().optional(),
- enable_clients: z.boolean().optional()
+ enable_clients: z.boolean().optional().default(true),
+ })
+ .optional(),
+ dns: z
+ .object({
+ nameservers: z
+ .array(z.string().optional().optional())
+ .optional()
+ .default(["ns1.fossorial.io", "ns2.fossorial.io"]),
+ cname_extension: z.string().optional().default("fossorial.io")
})
.optional()
+ .default({
+ nameservers: ["ns1.fossorial.io", "ns2.fossorial.io"],
+ cname_extension: "fossorial.io"
+ })
})
.refine(
(data) => {
diff --git a/server/routers/auth/login.ts b/server/routers/auth/login.ts
index cd51e46a..8dad5a42 100644
--- a/server/routers/auth/login.ts
+++ b/server/routers/auth/login.ts
@@ -106,21 +106,21 @@ export async function login(
);
}
- // Check if user has security keys registered
- const userSecurityKeys = await db
- .select()
- .from(securityKeys)
- .where(eq(securityKeys.userId, existingUser.userId));
-
- if (userSecurityKeys.length > 0) {
- return response+ {error} +
+ ++ {searchQuery + ? `No resources match "${searchQuery}". Try adjusting your search terms or clearing the search to see all resources.` + : "You don't have access to any resources yet. Contact your administrator to get access to resources you need."} +
++ {resource.name} +
++ {t("operatingSystem")} +
++ {["docker", "podman"].includes( + platform + ) + ? t("method") + : t("architecture")} +
++ {t("commands")} +
+{getSubtitle()}
@@ -180,6 +206,54 @@ export default function SignupForm({ )} /> + {build === "saas" && ( +
@@ -382,7 +384,7 @@ export default function DomainPicker({
- {t('otpAuthDescription')} + {t("otpAuthDescription")}
{info}
+ {children || + (info && ( ++ {info} +
+ ))}