fix: install sh
build-and-release / release (push) Successful in 2m32s

This commit is contained in:
2026-06-25 20:42:05 +02:00
parent 22e6812d4b
commit cad6c1f421
4 changed files with 142 additions and 233 deletions
+136 -8
View File
@@ -1,6 +1,7 @@
package main
import (
"bufio"
"flag"
"fmt"
"io"
@@ -8,8 +9,11 @@ import (
"os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/mattn/go-isatty"
"nadir/internal/auth"
"nadir/internal/config"
)
@@ -137,6 +141,125 @@ func installService(args []string) error {
isUnsecure := *unsecureOpt || optCount == 0
isTrustProxy := *trustProxyOpt
cfgPath, err := resolveConfigPath()
if err != nil {
return err
}
shouldWriteConfig := false
if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
shouldWriteConfig = true
}
username := getUsername()
var logFiles map[string][]string
if fs.NFlag() == 0 && (isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())) {
reader := bufio.NewReader(os.Stdin)
if !shouldWriteConfig {
fmt.Printf("Configuration file already exists at %s. Overwrite? [y/N] (default n): ", cfgPath)
overwriteInput, _ := reader.ReadString('\n')
overwriteInput = strings.ToLower(strings.TrimSpace(overwriteInput))
if overwriteInput != "y" && overwriteInput != "yes" {
fmt.Println("Keeping existing configuration. Proceeding with installation...")
if existingCfg, loadErr := config.Load(cfgPath); loadErr == nil {
*hostnameOpt = existingCfg.Server.Hostname
if p, err := strconv.Atoi(existingCfg.Server.Port); err == nil {
*portOpt = p
}
isTLS = existingCfg.Server.TLSCert != "" && existingCfg.Server.TLSKey != ""
isTrustProxy = existingCfg.Server.TrustProxy
isUnsecure = !isTLS && !isTrustProxy
}
goto skipConfigPrompt
}
shouldWriteConfig = true
}
fmt.Println("Configuring Nadir installation:")
fmt.Println(" 1) Serve plaintext HTTP directly (unsecure) [default]")
fmt.Println(" 2) Generate persistent self-signed TLS cert/key and enable HTTPS (tls)")
fmt.Println(" 3) Serve plaintext HTTP behind a trusted TLS-terminating reverse proxy (trust-proxy)")
fmt.Print("Enter choice [1-3] (default 1): ")
choice, _ := reader.ReadString('\n')
choice = strings.TrimSpace(choice)
if choice == "" || choice == "1" {
isUnsecure = true
isTLS = false
isTrustProxy = false
} else if choice == "2" {
isTLS = true
isUnsecure = false
isTrustProxy = false
} else if choice == "3" {
isTrustProxy = true
isTLS = false
isUnsecure = false
} else {
return fmt.Errorf("invalid choice: %q", choice)
}
fmt.Printf("Enter hostname to bind to (default %s): ", *hostnameOpt)
hostChoice, _ := reader.ReadString('\n')
hostChoice = strings.TrimSpace(hostChoice)
if hostChoice != "" {
*hostnameOpt = hostChoice
}
fmt.Printf("Enter port to bind to (default %d): ", *portOpt)
portChoice, _ := reader.ReadString('\n')
portChoice = strings.TrimSpace(portChoice)
if portChoice != "" {
p, err := strconv.Atoi(portChoice)
if err != nil || p <= 0 || p > 65535 {
return fmt.Errorf("invalid port: %q", portChoice)
}
*portOpt = p
}
fmt.Printf("Enter main admin username (default %s): ", username)
userChoice, _ := reader.ReadString('\n')
userChoice = strings.TrimSpace(userChoice)
if userChoice != "" {
username = userChoice
}
fmt.Print("Would you like to expose any log files to the Nadir UI? [y/N] (default n): ")
logInput, _ := reader.ReadString('\n')
logInput = strings.ToLower(strings.TrimSpace(logInput))
if logInput == "y" || logInput == "yes" {
logFiles = make(map[string][]string)
for {
fmt.Print(" Enter service/unit name (e.g. nginx): ")
unit, _ := reader.ReadString('\n')
unit = strings.TrimSpace(unit)
if unit == "" {
fmt.Println(" Service name cannot be empty. Skipping.")
continue
}
fmt.Printf(" Enter absolute path to log file for %s: ", unit)
path, _ := reader.ReadString('\n')
path = strings.TrimSpace(path)
if path == "" {
fmt.Println(" Path cannot be empty. Skipping.")
continue
}
logFiles[unit] = append(logFiles[unit], path)
fmt.Print(" Add another log file? [y/N] (default n): ")
another, _ := reader.ReadString('\n')
another = strings.ToLower(strings.TrimSpace(another))
if another != "y" && another != "yes" {
break
}
}
}
}
skipConfigPrompt:
// Provision the PAM service the server authenticates against, so it exists
// before the unit starts rather than appearing on first login. Idempotent:
// EnsurePAMService leaves an existing /etc/pam.d/nadir untouched. runServer
@@ -181,11 +304,6 @@ func installService(args []string) error {
}
}
cfgPath, err := resolveConfigPath()
if err != nil {
return err
}
// Construct configuration template content based on installation options
secureTLSVal := "true"
trustProxyLine := "# trust_proxy: false"
@@ -204,11 +322,21 @@ func installService(args []string) error {
keyLine = "# tls_key: /var/lib/nadir/tls/key.pem"
}
username := getUsername()
configContent := fmt.Sprintf(configTemplateBase, secureTLSVal, trustProxyLine, certLine, keyLine, *hostnameOpt, *portOpt, username)
if len(logFiles) > 0 {
var logFilesSection strings.Builder
logFilesSection.WriteString("\nlog_files:\n")
for unit, paths := range logFiles {
logFilesSection.WriteString(fmt.Sprintf(" %s:\n", unit))
for _, path := range paths {
logFilesSection.WriteString(fmt.Sprintf(" - %s\n", path))
}
}
configContent += logFilesSection.String()
}
// Ensure default config file exists
if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
// Ensure default config file exists or we explicitly overwrote it
if shouldWriteConfig {
if err := saveDefaultConfig(cfgPath, configContent); err != nil {
return err
}
+1 -1
View File
@@ -16,7 +16,7 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jedisct1/go-minisign v0.0.0-20260527172527-a09352b57a22
github.com/mattn/go-isatty v0.0.21 // indirect
github.com/mattn/go-isatty v0.0.21
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/crypto v0.52.0 // indirect
-223
View File
@@ -1,223 +0,0 @@
#!/bin/sh
# ──────────────────────────────────────────────────────────────────────
# Nadir interactive setup — generates config.yaml
#
# Run this script on a fresh box after placing the nadir binary, before
# `nadir install`. It asks about server settings, TLS mode, and log-file
# tracking, then writes the configuration file and prints the next steps.
#
# Everything in here is a question with a default answer in brackets.
# Press Enter to accept the default.
# ──────────────────────────────────────────────────────────────────────
set -e
# ── helpers ──────────────────────────────────────────────────────────
prompt() {
printf "%s [%s]: " "$1" "$2"
read -r val
echo "${val:-$2}"
}
prompt_yn() {
while :; do
printf "%s (y/n) [%s]: " "$1" "$2"
read -r val
val=$(printf "%s" "${val:-$2}" | tr '[:upper:]' '[:lower:]')
case "$val" in
y|yes) return 0 ;;
n|no) return 1 ;;
*) printf " please answer y or n.\n" >&2 ;;
esac
done
}
prompt_multi() {
desc="$1"
label="$2"
result=""
printf "%s\n" "$desc"
printf " Enter one %s at a time, then press Enter on an empty line when done.\n" "$label"
while :; do
printf " > "
read -r val
[ -z "$val" ] && break
result="${result} - ${val}
"
done
echo "$result"
}
# ── preamble ─────────────────────────────────────────────────────────
if [ "$(id -u)" -ne 0 ]; then
echo "This script should usually be run as root so the config file ends"
echo "up owned by root (the server runs as root). Continue anyway? (y/n) [y]"
if ! prompt_yn "Continue without root?" "y"; then
echo "Aborted."
exit 1
fi
fi
echo
echo "╔══════════════════════════════════════════════════════════╗"
echo "║ Nadir — interactive setup ║"
echo "╚══════════════════════════════════════════════════════════╝"
echo
# ── 1. admin username ───────────────────────────────────────────────
SUGGESTED_USER="${SUDO_USER:-$USER}"
if [ -z "$SUGGESTED_USER" ] || [ "$SUGGESTED_USER" = "root" ]; then
SUGGESTED_USER="admin"
fi
ADMIN_USER=$(prompt "Admin username (system user assigned the admin role)" "$SUGGESTED_USER")
# ── 2. config path ───────────────────────────────────────────────────
DEFAULT_CONFIG="${CONFIG_PATH:-$HOME/.config/nadir/config.yaml}"
CONFIG_PATH=$(prompt "Config file path" "$DEFAULT_CONFIG")
# ── 3. hostname ──────────────────────────────────────────────────────
HOSTNAME=$(prompt "Bind address (use 127.0.0.1 for local-only, or a Tailscale/Netbird IP)" "127.0.0.1")
# ── 4. port ──────────────────────────────────────────────────────────
PORT=$(prompt "Port" "9999")
# ── 5. TLS mode ──────────────────────────────────────────────────────
echo
echo "How should clients connect to nadir?"
echo " 1) Plain HTTP (unsecure — local-only or dev)"
echo " 2) HTTPS with a self-signed certificate (nadir generates cert + key)"
echo " 3) Behind a TLS-terminating reverse proxy (trust_proxy mode)"
printf "Choose [1-3] [1]: "
read -r tls_mode
tls_mode="${tls_mode:-1}"
SECURE_TLS="false"
TRUST_PROXY_LINE="# trust_proxy: false"
CERT_LINE="# tls_cert: /var/lib/nadir/tls/cert.pem"
KEY_LINE="# tls_key: /var/lib/nadir/tls/key.pem"
case "$tls_mode" in
2)
echo " → HTTPS with self-signed certificate"
SECURE_TLS="true"
CERT_LINE="tls_cert: /var/lib/nadir/tls/cert.pem"
KEY_LINE="tls_key: /var/lib/nadir/tls/key.pem"
;;
3)
echo " → Behind reverse proxy (trust_proxy)"
SECURE_TLS="true"
TRUST_PROXY_LINE="trust_proxy: true"
;;
*)
echo " → Plain HTTP"
SECURE_TLS="false"
;;
esac
# ── 6. release repo ──────────────────────────────────────────────────
RELEASE_REPO=$(prompt "Gitea release repo (for auto-updates, or leave blank to disable)" "")
# ── 7. log files ─────────────────────────────────────────────────────
echo
if prompt_yn "Track log files (add entries to log_files allowlist)?" "n"; then
echo
echo "For each service (e.g. nginx, ssh, auth), you'll list the paths"
echo "nadir is allowed to serve. Add as many services as you like."
echo
LOG_FILES_SECTION="log_files:"
LOG_FILES_BODY=""
while :; do
service=$(prompt " Service name (e.g. nginx, sshd) — empty line to stop" "")
[ -z "$service" ] && break
paths=$(prompt_multi "" "log path for $service")
LOG_FILES_BODY="${LOG_FILES_BODY}
${service}:"
# indentation inside prompt_multi already includes leading spaces
IFS='
'
for line in $paths; do
LOG_FILES_BODY="${LOG_FILES_BODY}
${line}"
done
echo
done
if [ -n "$LOG_FILES_BODY" ]; then
LOG_FILES_SECTION="${LOG_FILES_SECTION}${LOG_FILES_BODY}"
else
LOG_FILES_SECTION="# log_files:"
fi
else
LOG_FILES_SECTION="# log_files:"
fi
# ── write config ─────────────────────────────────────────────────────
CONFIG_DIR=$(dirname "$CONFIG_PATH")
mkdir -p "$CONFIG_DIR"
cat > "$CONFIG_PATH" <<CONFIGEOF
# Nadir configuration - config.yaml
# Generated by install.sh
server:
secure_tls: ${SECURE_TLS}
${TRUST_PROXY_LINE}
${CERT_LINE}
${KEY_LINE}
hostname: ${HOSTNAME}
port: ${PORT}
release_repo: ${RELEASE_REPO}
roles:
admin:
"*": ["*"]
auditor:
"*": ["read"]
assignments:
${ADMIN_USER}: [admin]
dashboard: [auditor]
${LOG_FILES_SECTION}
CONFIGEOF
chmod 600 "$CONFIG_PATH"
echo
echo "╔══════════════════════════════════════════════════════════╗"
echo "║ Configuration written to: ║"
echo "$CONFIG_PATH"
echo "╚══════════════════════════════════════════════════════════╝"
echo
echo "Next steps:"
echo
echo " 1. Make sure the nadir binary is installed:"
echo " sudo cp ./nadir /usr/local/bin/nadir"
echo " sudo chmod +x /usr/local/bin/nadir"
echo
echo " 2. Run the service installer:"
echo " sudo nadir install"
echo
echo " 3. If you chose TLS mode, nadir will generate a self-signed"
echo " certificate automatically during install."
echo
echo " 4. Check status:"
echo " nadir status"
echo
# If a SUDO_USER exists, chown the config directory so the user can
# read it without root.
if [ -n "${SUDO_USER}" ] && [ "${SUDO_USER}" != "root" ]; then
chown "${SUDO_USER}:${SUDO_USER}" "$CONFIG_DIR" 2>/dev/null || true
chown "${SUDO_USER}:${SUDO_USER}" "$CONFIG_PATH" 2>/dev/null || true
fi
+5 -1
View File
@@ -99,7 +99,11 @@ do_install() {
echo "binary installed at /usr/local/bin/nadir"
echo "installing as a systemd service ..."
/usr/local/bin/nadir install
if [ -c /dev/tty ]; then
/usr/local/bin/nadir install < /dev/tty
else
/usr/local/bin/nadir install
fi
echo
echo "done. check status with: nadir status"