diff --git a/config/config.example.yml b/config/config.example.yml
index 9311514e..827a2c49 100644
--- a/config/config.example.yml
+++ b/config/config.example.yml
@@ -21,8 +21,9 @@ traefik:
gerbil:
start_port: 51820
base_endpoint: localhost
- block_size: 16
- subnet_group: 10.0.0.0/8
+ block_size: 24
+ site_block_size: 30
+ subnet_group: 100.89.137.0/20
use_subdomain: true
rate_limits:
diff --git a/install/Makefile b/install/Makefile
index df531e44..acc663ae 100644
--- a/install/Makefile
+++ b/install/Makefile
@@ -2,11 +2,13 @@
all: build
build:
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o installer
+ CGO_ENABLED=0 go build -o bin/installer
-build-release:
+release:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/installer_linux_amd64
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o bin/installer_linux_arm64
clean:
- rm installer
+ rm bin/installer
+ rm bin/installer_linux_amd64
+ rm bin/installer_linux_arm64
diff --git a/install/fs/config.yml b/install/fs/config.yml
index 2ad323f0..d3809433 100644
--- a/install/fs/config.yml
+++ b/install/fs/config.yml
@@ -1,6 +1,6 @@
app:
- dashboard_url: https://{{.Domain}}
- base_domain: {{.Domain}}
+ dashboard_url: https://{{.DashboardDomain}}
+ base_domain: {{.BaseDomain}}
log_level: info
save_logs: false
@@ -23,8 +23,9 @@ gerbil:
start_port: 51820
base_endpoint: {{.Domain}}
use_subdomain: false
- block_size: 16
- subnet_group: 10.0.0.0/8
+ block_size: 24
+ site_block_size: 30
+ subnet_group: 100.89.137.0/20
rate_limits:
global:
diff --git a/install/main.go b/install/main.go
index 897b4741..e1a363a9 100644
--- a/install/main.go
+++ b/install/main.go
@@ -18,7 +18,8 @@ import (
var configFiles embed.FS
type Config struct {
- Domain string `yaml:"domain"`
+ BaseDomain string `yaml:"baseDomain"`
+ DashboardDomain string `yaml:"dashboardUrl"`
LetsEncryptEmail string `yaml:"letsEncryptEmail"`
AdminUserEmail string `yaml:"adminUserEmail"`
AdminUserPassword string `yaml:"adminUserPassword"`
@@ -102,12 +103,13 @@ func collectUserInput(reader *bufio.Reader) Config {
// Basic configuration
fmt.Println("\n=== Basic Configuration ===")
- config.Domain = readString(reader, "Enter your domain name", "")
+ config.BaseDomain = readString(reader, "Enter your base domain (no subdomain e.g. example.com)", "")
+ config.DashboardDomain = readString(reader, "Enter the domain for the Pangolin dashboard (e.g. example.com OR proxy.example.com)", config.BaseDomain)
config.LetsEncryptEmail = readString(reader, "Enter email for Let's Encrypt certificates", "")
// Admin user configuration
fmt.Println("\n=== Admin User Configuration ===")
- config.AdminUserEmail = readString(reader, "Enter admin user email", "admin@"+config.Domain)
+ config.AdminUserEmail = readString(reader, "Enter admin user email", "admin@"+config.BaseDomain)
for {
config.AdminUserPassword = readString(reader, "Enter admin user password", "")
if valid, message := validatePassword(config.AdminUserPassword); valid {
@@ -140,10 +142,14 @@ func collectUserInput(reader *bufio.Reader) Config {
}
// Validate required fields
- if config.Domain == "" {
+ if config.BaseDomain == "" {
fmt.Println("Error: Domain name is required")
os.Exit(1)
}
+ if config.DashboardDomain == "" {
+ fmt.Println("Error: Dashboard Domain name is required")
+ os.Exit(1)
+ }
if config.LetsEncryptEmail == "" {
fmt.Println("Error: Let's Encrypt email is required")
os.Exit(1)
diff --git a/server/lib/config.ts b/server/lib/config.ts
index 6642e7d3..3da4ea36 100644
--- a/server/lib/config.ts
+++ b/server/lib/config.ts
@@ -3,11 +3,7 @@ import yaml from "js-yaml";
import path from "path";
import { z } from "zod";
import { fromError } from "zod-validation-error";
-import {
- __DIRNAME,
- configFilePath1,
- configFilePath2
-} from "@server/lib/consts";
+import { __DIRNAME, APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts";
import { loadAppVersion } from "@server/lib/loadAppVersion";
import { passwordSchema } from "@server/auth/passwordSchema";
@@ -15,9 +11,9 @@ const portSchema = z.number().positive().gt(0).lte(65535);
const hostnameSchema = z
.string()
.regex(
- /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/
- )
- .or(z.literal("localhost"));
+ /^(?!-)[a-zA-Z0-9-]{1,63}(? url.toLowerCase()),
use_subdomain: z.boolean(),
subnet_group: z.string(),
- block_size: z.number().positive().gt(0)
+ block_size: z.number().positive().gt(0),
+ site_block_size: z.number().positive().gt(0)
}),
rate_limits: z.object({
global: z.object({
diff --git a/server/routers/site/pickSiteDefaults.ts b/server/routers/site/pickSiteDefaults.ts
index 5a111faa..56d072e0 100644
--- a/server/routers/site/pickSiteDefaults.ts
+++ b/server/routers/site/pickSiteDefaults.ts
@@ -8,6 +8,7 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { findNextAvailableCidr } from "@server/lib/ip";
import { generateId } from "@server/auth/sessions/app";
+import config from "@server/lib/config";
export type PickSiteDefaultsResponse = {
exitNodeId: number;
@@ -51,9 +52,9 @@ export async function pickSiteDefaults(
// TODO: we need to lock this subnet for some time so someone else does not take it
let subnets = sitesQuery.map((site) => site.subnet);
- // exclude the exit node address by replacing after the / with a /28
- subnets.push(exitNode.address.replace(/\/\d+$/, "/29"));
- const newSubnet = findNextAvailableCidr(subnets, 29, exitNode.address);
+ // exclude the exit node address by replacing after the / with a site block size
+ subnets.push(exitNode.address.replace(/\/\d+$/, `/${config.getRawConfig().gerbil.site_block_size}`));
+ const newSubnet = findNextAvailableCidr(subnets, config.getRawConfig().gerbil.site_block_size, exitNode.address);
if (!newSubnet) {
return next(
createHttpError(
diff --git a/server/routers/user/acceptInvite.ts b/server/routers/user/acceptInvite.ts
index b7f225a4..ade89e58 100644
--- a/server/routers/user/acceptInvite.ts
+++ b/server/routers/user/acceptInvite.ts
@@ -72,6 +72,16 @@ export async function acceptInvite(
const { user, session } = await verifySession(req);
+ // at this point we know the user exists
+ if (!user) {
+ return next(
+ createHttpError(
+ HttpCode.UNAUTHORIZED,
+ "You must be logged in to accept an invite"
+ )
+ );
+ }
+
if (user && user.email !== existingInvite.email) {
return next(
createHttpError(
diff --git a/server/setup/migrations.ts b/server/setup/migrations.ts
index 7b1ad8ce..c0fe6216 100644
--- a/server/setup/migrations.ts
+++ b/server/setup/migrations.ts
@@ -8,6 +8,7 @@ import { __DIRNAME } from "@server/lib/consts";
import { loadAppVersion } from "@server/lib/loadAppVersion";
import m1 from "./scripts/1.0.0-beta1";
import m2 from "./scripts/1.0.0-beta2";
+import m3 from "./scripts/1.0.0-beta3";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA
@@ -15,7 +16,8 @@ import m2 from "./scripts/1.0.0-beta2";
// Define the migration list with versions and their corresponding functions
const migrations = [
{ version: "1.0.0-beta.1", run: m1 },
- { version: "1.0.0-beta.2", run: m2 }
+ { version: "1.0.0-beta.2", run: m2 },
+ { version: "1.0.0-beta.3", run: m3 }
// Add new migrations here as they are created
] as const;
diff --git a/server/setup/scripts/1.0.0-beta3.ts b/server/setup/scripts/1.0.0-beta3.ts
new file mode 100644
index 00000000..3bbaae81
--- /dev/null
+++ b/server/setup/scripts/1.0.0-beta3.ts
@@ -0,0 +1,42 @@
+import { configFilePath1, configFilePath2 } from "@server/lib/consts";
+import fs from "fs";
+import yaml from "js-yaml";
+
+export default async function migration() {
+ console.log("Running setup script 1.0.0-beta.3...");
+
+ // Determine which config file exists
+ const filePaths = [configFilePath1, configFilePath2];
+ let filePath = "";
+ for (const path of filePaths) {
+ if (fs.existsSync(path)) {
+ filePath = path;
+ break;
+ }
+ }
+
+ if (!filePath) {
+ throw new Error(
+ `No config file found (expected config.yml or config.yaml).`
+ );
+ }
+
+ // Read and parse the YAML file
+ let rawConfig: any;
+ const fileContents = fs.readFileSync(filePath, "utf8");
+ rawConfig = yaml.load(fileContents);
+
+ // Validate the structure
+ if (!rawConfig.gerbil) {
+ throw new Error(`Invalid config file: gerbil is missing.`);
+ }
+
+ // Update the config
+ rawConfig.gerbil.site_block_size = 29;
+
+ // Write the updated YAML back to the file
+ const updatedYaml = yaml.dump(rawConfig);
+ fs.writeFileSync(filePath, updatedYaml, "utf8");
+
+ console.log("Done.");
+}
\ No newline at end of file
diff --git a/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx
index fdecf9ff..30a645f0 100644
--- a/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx
+++ b/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx
@@ -352,7 +352,7 @@ export default function ReverseProxyTargets(props: {
},
{
accessorKey: "ip",
- header: "IP Address",
+ header: "IP / Hostname",
cell: ({ row }) => (