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 }) => (