simplify installer and remove parsing from read config

This commit is contained in:
miloschwartz 2025-06-21 16:35:22 -04:00
parent 9f38ad9b4d
commit ead5df0a8c
No known key found for this signature in database
9 changed files with 51 additions and 70 deletions

View file

@ -21,4 +21,4 @@ update-versions:
echo "Updated main.go with latest versions" echo "Updated main.go with latest versions"
put-back: put-back:
mv main.go.bak main.go mv main.go.bak main.go

View file

@ -4,7 +4,6 @@
app: app:
dashboard_url: "https://{{.DashboardDomain}}" dashboard_url: "https://{{.DashboardDomain}}"
log_level: "info" log_level: "info"
save_logs: false
domains: domains:
domain1: domain1:
@ -12,40 +11,17 @@ domains:
cert_resolver: "letsencrypt" cert_resolver: "letsencrypt"
server: server:
external_port: 3000 secret: "{{.Secret}}"
internal_port: 3001
next_port: 3002
internal_hostname: "pangolin"
session_cookie_name: "p_session_token"
resource_access_token_param: "p_token"
resource_access_token_headers:
id: "P-Access-Token-Id"
token: "P-Access-Token"
resource_session_request_param: "p_session_request"
secret: {{.Secret}}
cors: cors:
origins: ["https://{{.DashboardDomain}}"] origins: ["https://{{.DashboardDomain}}"]
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"] methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
allowed_headers: ["X-CSRF-Token", "Content-Type"] allowed_headers: ["X-CSRF-Token", "Content-Type"]
credentials: false credentials: false
traefik:
cert_resolver: "letsencrypt"
http_entrypoint: "web"
https_entrypoint: "websecure"
gerbil: gerbil:
start_port: 51820 start_port: 51820
base_endpoint: "{{.DashboardDomain}}" base_endpoint: "{{.DashboardDomain}}"
use_subdomain: false
block_size: 24
site_block_size: 30
subnet_group: 100.89.137.0/20
rate_limits:
global:
window_minutes: 1
max_requests: 500
{{if .EnableEmail}} {{if .EnableEmail}}
email: email:
smtp_host: "{{.EmailSMTPHost}}" smtp_host: "{{.EmailSMTPHost}}"
@ -57,7 +33,7 @@ email:
flags: flags:
require_email_verification: {{.EnableEmail}} require_email_verification: {{.EnableEmail}}
disable_signup_without_invite: {{.DisableSignupWithoutInvite}} disable_signup_without_invite: true
disable_user_create_org: {{.DisableUserCreateOrg}} disable_user_create_org: false
allow_raw_resources: true allow_raw_resources: true
allow_base_domain_resources: true allow_base_domain_resources: true

View file

@ -24,9 +24,9 @@ import (
// DO NOT EDIT THIS FUNCTION; IT MATCHED BY REGEX IN CICD // DO NOT EDIT THIS FUNCTION; IT MATCHED BY REGEX IN CICD
func loadVersions(config *Config) { func loadVersions(config *Config) {
config.PangolinVersion = "replaceme" config.PangolinVersion = "1.5.1"
config.GerbilVersion = "replaceme" config.GerbilVersion = "1.0.0"
config.BadgerVersion = "replaceme" config.BadgerVersion = "v1.2.0"
} }
//go:embed config/* //go:embed config/*
@ -39,8 +39,6 @@ type Config struct {
BaseDomain string BaseDomain string
DashboardDomain string DashboardDomain string
LetsEncryptEmail string LetsEncryptEmail string
DisableSignupWithoutInvite bool
DisableUserCreateOrg bool
EnableEmail bool EnableEmail bool
EmailSMTPHost string EmailSMTPHost string
EmailSMTPPort int EmailSMTPPort int
@ -72,15 +70,15 @@ func main() {
} }
var config Config var config Config
// check if there is already a config file // check if there is already a config file
if _, err := os.Stat("config/config.yml"); err != nil { if _, err := os.Stat("config/config.yml"); err != nil {
config = collectUserInput(reader) config = collectUserInput(reader)
loadVersions(&config) loadVersions(&config)
config.DoCrowdsecInstall = false config.DoCrowdsecInstall = false
config.Secret = generateRandomSecretKey() config.Secret = generateRandomSecretKey()
if err := createConfigFiles(config); err != nil { if err := createConfigFiles(config); err != nil {
fmt.Printf("Error creating config files: %v\n", err) fmt.Printf("Error creating config files: %v\n", err)
os.Exit(1) os.Exit(1)
@ -234,14 +232,9 @@ func collectUserInput(reader *bufio.Reader) Config {
config.LetsEncryptEmail = readString(reader, "Enter email for Let's Encrypt certificates", "") config.LetsEncryptEmail = readString(reader, "Enter email for Let's Encrypt certificates", "")
config.InstallGerbil = readBool(reader, "Do you want to use Gerbil to allow tunneled connections", true) config.InstallGerbil = readBool(reader, "Do you want to use Gerbil to allow tunneled connections", true)
// Security settings
fmt.Println("\n=== Security Settings ===")
config.DisableSignupWithoutInvite = readBool(reader, "Disable signup without invite", true)
config.DisableUserCreateOrg = readBool(reader, "Disable users from creating organizations", false)
// Email configuration // Email configuration
fmt.Println("\n=== Email Configuration ===") fmt.Println("\n=== Email Configuration ===")
config.EnableEmail = readBool(reader, "Enable email functionality", false) config.EnableEmail = readBool(reader, "Enable email functionality (SMTP)", false)
if config.EnableEmail { if config.EnableEmail {
config.EmailSMTPHost = readString(reader, "Enter SMTP host", "") config.EmailSMTPHost = readString(reader, "Enter SMTP host", "")
@ -353,7 +346,7 @@ func installDocker() error {
return fmt.Errorf("failed to detect Linux distribution: %v", err) return fmt.Errorf("failed to detect Linux distribution: %v", err)
} }
osRelease := string(output) osRelease := string(output)
// Detect system architecture // Detect system architecture
archCmd := exec.Command("uname", "-m") archCmd := exec.Command("uname", "-m")
archOutput, err := archCmd.Output() archOutput, err := archCmd.Output()
@ -361,7 +354,7 @@ func installDocker() error {
return fmt.Errorf("failed to detect system architecture: %v", err) return fmt.Errorf("failed to detect system architecture: %v", err)
} }
arch := strings.TrimSpace(string(archOutput)) arch := strings.TrimSpace(string(archOutput))
// Map architecture to Docker's architecture naming // Map architecture to Docker's architecture naming
var dockerArch string var dockerArch string
switch arch { switch arch {
@ -403,7 +396,7 @@ func installDocker() error {
fedoraVersion = v fedoraVersion = v
} }
} }
// Use appropriate DNF syntax based on version // Use appropriate DNF syntax based on version
var repoCmd string var repoCmd string
if fedoraVersion >= 41 { if fedoraVersion >= 41 {
@ -413,7 +406,7 @@ func installDocker() error {
// DNF 4 syntax for Fedora < 41 // DNF 4 syntax for Fedora < 41
repoCmd = "dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo" repoCmd = "dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo"
} }
installCmd = exec.Command("bash", "-c", fmt.Sprintf(` installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
dnf -y install dnf-plugins-core && dnf -y install dnf-plugins-core &&
%s && %s &&
@ -442,7 +435,7 @@ func installDocker() error {
default: default:
return fmt.Errorf("unsupported Linux distribution") return fmt.Errorf("unsupported Linux distribution")
} }
installCmd.Stdout = os.Stdout installCmd.Stdout = os.Stdout
installCmd.Stderr = os.Stderr installCmd.Stderr = os.Stderr
return installCmd.Run() return installCmd.Run()
@ -527,7 +520,7 @@ func executeDockerComposeCommandWithArgs(args ...string) error {
return fmt.Errorf("neither 'docker compose' nor 'docker-compose' command is available") return fmt.Errorf("neither 'docker compose' nor 'docker-compose' command is available")
} }
} }
if useNewStyle { if useNewStyle {
cmd = exec.Command("docker", append([]string{"compose"}, args...)...) cmd = exec.Command("docker", append([]string{"compose"}, args...)...)
} else { } else {
@ -563,7 +556,7 @@ func startContainers() error {
// stopContainers stops the containers using the appropriate command. // stopContainers stops the containers using the appropriate command.
func stopContainers() error { func stopContainers() error {
fmt.Println("Stopping containers...") fmt.Println("Stopping containers...")
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "down"); err != nil { if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "down"); err != nil {
return fmt.Errorf("failed to stop containers: %v", err) return fmt.Errorf("failed to stop containers: %v", err)
} }
@ -574,7 +567,7 @@ func stopContainers() error {
// restartContainer restarts a specific container using the appropriate command. // restartContainer restarts a specific container using the appropriate command.
func restartContainer(container string) error { func restartContainer(container string) error {
fmt.Println("Restarting containers...") fmt.Println("Restarting containers...")
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "restart", container); err != nil { if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "restart", container); err != nil {
return fmt.Errorf("failed to stop the container \"%s\": %v", container, err) return fmt.Errorf("failed to stop the container \"%s\": %v", container, err)
} }

View file

@ -5,6 +5,12 @@ import { withReplicas } from "drizzle-orm/pg-core";
function createDb() { function createDb() {
const config = readConfigFile(); const config = readConfigFile();
if (!config.postgres) {
throw new Error(
"Postgres configuration is missing in the configuration file."
);
}
const connectionString = config.postgres?.connection_string; const connectionString = config.postgres?.connection_string;
const replicaConnections = config.postgres?.replicas || []; const replicaConnections = config.postgres?.replicas || [];

View file

@ -5,7 +5,6 @@ import path from "path";
import fs from "fs/promises"; import fs from "fs/promises";
import { APP_PATH } from "@server/lib/consts"; import { APP_PATH } from "@server/lib/consts";
import { existsSync, mkdirSync } from "fs"; import { existsSync, mkdirSync } from "fs";
import { readConfigFile } from "@server/lib/readConfigFile";
export const location = path.join(APP_PATH, "db", "db.sqlite"); export const location = path.join(APP_PATH, "db", "db.sqlite");
export const exists = await checkFileExists(location); export const exists = await checkFileExists(location);
@ -13,8 +12,6 @@ export const exists = await checkFileExists(location);
bootstrapVolume(); bootstrapVolume();
function createDb() { function createDb() {
const config = readConfigFile();
const sqlite = new Database(location); const sqlite = new Database(location);
return DrizzleSqlite(sqlite, { schema }); return DrizzleSqlite(sqlite, { schema });
} }

View file

@ -5,6 +5,7 @@ import { SupporterKey, supporterKey } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { license } from "@server/license/license"; import { license } from "@server/license/license";
import { configSchema, readConfigFile } from "./readConfigFile"; import { configSchema, readConfigFile } from "./readConfigFile";
import { fromError } from "zod-validation-error";
export class Config { export class Config {
private rawConfig!: z.infer<typeof configSchema>; private rawConfig!: z.infer<typeof configSchema>;
@ -20,7 +21,29 @@ export class Config {
} }
public load() { public load() {
const parsedConfig = readConfigFile(); const environment = readConfigFile();
const {
data: parsedConfig,
success,
error
} = configSchema.safeParse(environment);
if (!success) {
const errors = fromError(error);
throw new Error(`Invalid configuration file: ${errors}`);
}
if (process.env.APP_BASE_DOMAIN) {
console.log(
"You're using deprecated environment variables. Transition to the configuration file. https://docs.fossorial.io/"
);
}
// @ts-ignore
if (parsedConfig.users) {
console.log("You're admin credentials are still in the config file. This method of setting admin credentials is deprecated. It is recommended to remove them from the config file.");
}
process.env.APP_VERSION = APP_VERSION; process.env.APP_VERSION = APP_VERSION;

View file

@ -2,7 +2,7 @@ import path from "path";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
// This is a placeholder value replaced by the build process // This is a placeholder value replaced by the build process
export const APP_VERSION = "1.5.1"; export const APP_VERSION = "1.6.0";
export const __FILENAME = fileURLToPath(import.meta.url); export const __FILENAME = fileURLToPath(import.meta.url);
export const __DIRNAME = path.dirname(__FILENAME); export const __DIRNAME = path.dirname(__FILENAME);

View file

@ -233,24 +233,11 @@ export function readConfigFile() {
environment = loadConfig(configFilePath2); environment = loadConfig(configFilePath2);
} }
if (process.env.APP_BASE_DOMAIN) {
console.log(
"You're using deprecated environment variables. Transition to the configuration file. https://docs.fossorial.io/"
);
}
if (!environment) { if (!environment) {
throw new Error( throw new Error(
"No configuration file found. Please create one. https://docs.fossorial.io/" "No configuration file found. Please create one. https://docs.fossorial.io/"
); );
} }
const parsedConfig = configSchema.safeParse(environment); return environment;
if (!parsedConfig.success) {
const errors = fromError(parsedConfig.error);
throw new Error(`Invalid configuration file: ${errors}`);
}
return parsedConfig.data;
} }

View file

@ -33,7 +33,6 @@ export default async function Page(props: {
>(`/auth/initial-setup-complete`, await authCookieHeader()); >(`/auth/initial-setup-complete`, await authCookieHeader());
const complete = setupRes.data.data.complete; const complete = setupRes.data.data.complete;
if (!complete) { if (!complete) {
console.log("compelte", complete);
redirect("/auth/initial-setup"); redirect("/auth/initial-setup");
} }