diff --git a/README.md b/README.md index 46384d7..2649e18 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The idea of [LLM-maintained knowledge bases](https://gist.github.com/karpathy/44 | The problem | The solution | |---|---| | Desktop apps (Tolaria, Obsidian) need Node.js, Rust, WebKit, or a display server | **Server-first**: one static binary, runs headless, `curl \| bash` to install | -| Knowledge locked in a desktop app only the local user can see | **Agents use MCP, humans use the browser** at `https://mind-map.local` | +| Knowledge locked in a desktop app only the local user can see | **Agents use MCP, humans use the browser** at `http://localhost:4242` | | Can't deploy headless because it needs a GUI even when no human is looking | **Runs anywhere**: laptop, container, cloud VM | | Hand-rolled search scripts that break at scale | **First-class search** powered by [SQLite FTS5](https://www.sqlite.org/fts5.html) with ranked results and snippets | | Knowledge re-discovered via RAG on every query | **Persistent wiki** with wikilinks, backlinks, and plain `.md` files that grow over time | @@ -50,8 +50,6 @@ Invoke-RestMethod https://github.com/aniongithub/mind-map/releases/latest/downlo The installer: - Downloads the binary and adds it to PATH -- Adds `127.0.0.1 mind-map.local` to your hosts file -- Generates a local TLS certificate so `https://mind-map.local` works with no browser warnings - Installs mind-map as a persistent system service Binaries available for **linux-x64**, **linux-arm64**, **darwin-x64**, **darwin-arm64**, **windows-x64**, and **windows-arm64**. @@ -63,8 +61,8 @@ graph TD A[Preact Web App] -->|REST API| B C[AI Agent] -->|MCP stdio| D - subgraph "mind-map serve (HTTPS)" - B[HTTPS Server] --> E[Wiki Engine] + subgraph "mind-map serve (HTTP)" + B[HTTP Server] --> E[Wiki Engine] end subgraph "mind-map (stdio)" @@ -75,14 +73,14 @@ graph TD E -->|read/write| G[Markdown Files] ``` -The web UI is a static Preact app served by `mind-map serve` over HTTPS. It uses a REST API to read and write pages. AI agents use stdio MCP, with each agent launching its own `mind-map` process. +The web UI is a static Preact app served by `mind-map serve` over HTTP. It uses a REST API to read and write pages. AI agents use stdio MCP, with each agent launching its own `mind-map` process. ## Two Modes | Mode | Command | Use case | |------|---------|----------| | **stdio** (default) | `mind-map` | AI agents (Copilot, Claude, Cursor) | -| **HTTPS** | `mind-map serve` | Web UI for humans at `https://mind-map.local` | +| **HTTP** | `mind-map serve` | Web UI for humans at `http://localhost:4242` | Both modes use the same wiki engine and the same wiki directory (`~/.mind-map/wiki` by default). Multiple stdio processes can safely share the same wiki via SQLite page locking. @@ -127,7 +125,7 @@ The installer can set up mind-map as a persistent system service that starts on ```bash # Install and start the service -sudo mind-map service install --addr 127.0.0.1:443 +sudo mind-map service install --addr 127.0.0.1:4242 sudo mind-map service start # Manage @@ -136,23 +134,6 @@ sudo mind-map service stop sudo mind-map service uninstall ``` -## TLS Setup - -The installer automatically generates a local CA and server certificate so `https://mind-map.local` works without browser warnings. You can also manage certificates manually: - -```bash -# Generate certs (as your user) -mind-map tls generate - -# Install CA in system trust store (requires sudo on Linux) -sudo mind-map tls install-ca --tls-dir ~/.mind-map/tls - -# Remove everything -mind-map tls remove -``` - -On Linux, Chrome uses its own NSS certificate store. The installer handles this automatically if `libnss3-tools` is available. - ## MCP Client Configuration The installer automatically configures mind-map for GitHub Copilot, Claude, Cursor, and VS Code, and installs agent skills, so this is usually not needed. If you want to configure a client manually: diff --git a/cmd/mind-map/main.go b/cmd/mind-map/main.go index 761277b..8003ea0 100644 --- a/cmd/mind-map/main.go +++ b/cmd/mind-map/main.go @@ -16,7 +16,6 @@ import ( "github.com/aniongithub/mind-map/internal/config" "github.com/aniongithub/mind-map/internal/logging" - mindtls "github.com/aniongithub/mind-map/internal/tls" "github.com/aniongithub/mind-map/internal/wiki" mindmcp "github.com/aniongithub/mind-map/internal/mcp" mindsync "github.com/aniongithub/mind-map/internal/sync" @@ -78,7 +77,7 @@ var serveCmd = &cobra.Command{ func init() { rootCmd.PersistentFlags().StringP("dir", "d", defaultWikiDir(), "Path to the wiki directory") - serveCmd.Flags().StringP("addr", "a", "127.0.0.1:443", "Address to listen on") + serveCmd.Flags().StringP("addr", "a", "127.0.0.1:4242", "Address to listen on") serveCmd.Flags().String("webui", "", "Path to webui dist directory (overrides embedded webui)") serveCmd.Flags().String("log-file", "", "Path to log file (logs to stderr and file)") serveCmd.Flags().Duration("idle-timeout", 60*time.Second, "Idle timeout for HTTP connections (e.g. 30s, 1m)") @@ -394,49 +393,21 @@ func runHTTPServer(addr, dir, webuiDir string, idleTimeout time.Duration, stopCh IdleTimeout: idleTimeout, } - // Serve HTTPS if TLS certs are available, otherwise HTTP. - // Derive TLS dir from the wiki dir (sibling directory) so it works - // correctly when running as a system service with a different home. - tlsDir := mindtls.DirFromWikiDir(dir) - useTLS := mindtls.HasCerts(tlsDir) - - if useTLS { - certFile, keyFile := mindtls.CertPaths(tlsDir) - slog.Info("mind-map server starting", - slog.String("addr", addr), - slog.String("wiki", w.Root()), - slog.String("url", "https://"+addr), - slog.Bool("tls", true), - ) - - go func() { - <-stopCh - slog.Info("shutting down HTTP server") - server.Close() - }() - - if err := server.ListenAndServeTLS(certFile, keyFile); err != http.ErrServerClosed { - slog.Error("server error", slog.Any("error", err)) - return err - } - } else { - slog.Info("mind-map server starting", - slog.String("addr", addr), - slog.String("wiki", w.Root()), - slog.String("url", "http://"+addr), - slog.Bool("tls", false), - ) - - go func() { - <-stopCh - slog.Info("shutting down HTTP server") - server.Close() - }() - - if err := server.ListenAndServe(); err != http.ErrServerClosed { - slog.Error("server error", slog.Any("error", err)) - return err - } + slog.Info("mind-map server starting", + slog.String("addr", addr), + slog.String("wiki", w.Root()), + slog.String("url", "http://"+addr), + ) + + go func() { + <-stopCh + slog.Info("shutting down HTTP server") + server.Close() + }() + + if err := server.ListenAndServe(); err != http.ErrServerClosed { + slog.Error("server error", slog.Any("error", err)) + return err } slog.Info("server stopped") return nil diff --git a/cmd/mind-map/service.go b/cmd/mind-map/service.go index 58f1502..4694826 100644 --- a/cmd/mind-map/service.go +++ b/cmd/mind-map/service.go @@ -9,7 +9,6 @@ import ( "time" "github.com/aniongithub/mind-map/internal/logging" - mindtls "github.com/aniongithub/mind-map/internal/tls" "github.com/kardianos/service" "github.com/spf13/cobra" ) @@ -114,7 +113,7 @@ var serviceCmd = &cobra.Command{ func init() { // Shared flags for service subcommands that need them for _, cmd := range []*cobra.Command{serviceInstallCmd, serviceStartCmd, serviceStopCmd, serviceUninstallCmd, serviceStatusCmd} { - cmd.Flags().StringP("addr", "a", "127.0.0.1:443", "Address to listen on") + cmd.Flags().StringP("addr", "a", "127.0.0.1:4242", "Address to listen on") cmd.Flags().StringP("dir", "d", defaultWikiDir(), "Path to the wiki directory") cmd.Flags().String("webui", "", "Path to webui dist directory (overrides embedded)") cmd.Flags().Duration("idle-timeout", 60*time.Second, "Idle timeout for HTTP connections (e.g. 30s, 1m)") @@ -171,12 +170,8 @@ var serviceStartCmd = &cobra.Command{ return fmt.Errorf("start service: %w", err) } fmt.Println("Service started.") - scheme := "http" - if mindtls.HasCerts(mindtls.DirFromWikiDir(dir)) { - scheme = "https" - } - fmt.Printf(" Web UI: %s://%s\n", scheme, addr) - fmt.Printf(" MCP endpoint: %s://%s/mcp\n", scheme, addr) + fmt.Printf(" Web UI: http://%s\n", addr) + fmt.Printf(" MCP endpoint: http://%s/mcp\n", addr) return nil }, } diff --git a/cmd/mind-map/tls.go b/cmd/mind-map/tls.go deleted file mode 100644 index bb347e8..0000000 --- a/cmd/mind-map/tls.go +++ /dev/null @@ -1,103 +0,0 @@ -package main - -import ( - "fmt" - - mindtls "github.com/aniongithub/mind-map/internal/tls" - "github.com/spf13/cobra" -) - -var tlsCmd = &cobra.Command{ - Use: "tls", - Short: "Manage TLS certificates for HTTPS", - Long: "Generate and install a local CA so mind-map can serve HTTPS on mind-map.local without browser warnings.", -} - -var tlsSetupCmd = &cobra.Command{ - Use: "setup", - Short: "Generate certs and install CA in system trust store", - Long: "Generates certificates then installs the CA in the system trust store. On Linux, the CA install step requires sudo — use 'tls generate' + 'sudo mind-map tls install-ca' separately if needed.", - RunE: func(cmd *cobra.Command, args []string) error { - dir, _ := cmd.Flags().GetString("tls-dir") - - fmt.Println("==> Generating TLS certificates...") - if err := mindtls.Generate(dir); err != nil { - return fmt.Errorf("generate certs: %w", err) - } - fmt.Printf(" Certificates written to %s\n", dir) - - fmt.Println("==> Installing CA in system trust store...") - if err := mindtls.InstallCA(dir); err != nil { - return fmt.Errorf("install CA: %w", err) - } - fmt.Println(" CA installed. Browsers will trust mind-map.local.") - - fmt.Println() - fmt.Println("Done! Restart the mind-map service to enable HTTPS.") - return nil - }, -} - -var tlsGenerateCmd = &cobra.Command{ - Use: "generate", - Short: "Generate TLS certificates only (no trust store install)", - RunE: func(cmd *cobra.Command, args []string) error { - dir, _ := cmd.Flags().GetString("tls-dir") - - fmt.Println("==> Generating TLS certificates...") - if err := mindtls.Generate(dir); err != nil { - return fmt.Errorf("generate certs: %w", err) - } - fmt.Printf(" Certificates written to %s\n", dir) - fmt.Println() - fmt.Println("Run 'sudo mind-map tls install-ca --tls-dir " + dir + "' to trust the CA.") - return nil - }, -} - -var tlsInstallCACmd = &cobra.Command{ - Use: "install-ca", - Short: "Install the CA certificate in the system trust store", - RunE: func(cmd *cobra.Command, args []string) error { - dir, _ := cmd.Flags().GetString("tls-dir") - - if !mindtls.HasCerts(dir) { - return fmt.Errorf("no certificates found in %s — run 'mind-map tls generate' first", dir) - } - - fmt.Println("==> Installing CA in system trust store...") - if err := mindtls.InstallCA(dir); err != nil { - return fmt.Errorf("install CA: %w", err) - } - fmt.Println(" CA installed. Browsers will trust mind-map.local.") - return nil - }, -} - -var tlsRemoveCmd = &cobra.Command{ - Use: "remove", - Short: "Remove certs and uninstall CA from trust store", - RunE: func(cmd *cobra.Command, args []string) error { - dir, _ := cmd.Flags().GetString("tls-dir") - - fmt.Println("==> Removing CA from system trust store...") - if err := mindtls.UninstallCA(dir); err != nil { - fmt.Printf(" Warning: %v\n", err) - } - - fmt.Println("==> Removing TLS certificates...") - mindtls.Remove(dir) - - fmt.Println("Done! Restart the mind-map service to switch back to HTTP.") - return nil - }, -} - -func init() { - // Add --tls-dir flag to all tls subcommands - for _, cmd := range []*cobra.Command{tlsSetupCmd, tlsGenerateCmd, tlsInstallCACmd, tlsRemoveCmd} { - cmd.Flags().String("tls-dir", mindtls.DefaultDir(), "Path to TLS certificate directory") - } - tlsCmd.AddCommand(tlsSetupCmd, tlsGenerateCmd, tlsInstallCACmd, tlsRemoveCmd) - rootCmd.AddCommand(tlsCmd) -} diff --git a/install.ps1 b/install.ps1 index 83a9a40..04f8967 100644 --- a/install.ps1 +++ b/install.ps1 @@ -98,22 +98,6 @@ Remove-Item $tarballPath -Force -ErrorAction SilentlyContinue Write-Ok "Installed to $BinaryPath" -# Add mind-map.local to hosts file for reliable local name resolution -$hostsFile = "$env:SystemRoot\System32\drivers\etc\hosts" -$hostsContent = Get-Content $hostsFile -Raw -ErrorAction SilentlyContinue -if ($hostsContent -notmatch 'mind-map\.local') { - try { - Add-Content -Path $hostsFile -Value "`n127.0.0.1 mind-map.local" -ErrorAction Stop - Write-Ok "Added mind-map.local to hosts file" - } catch { - Write-Warn "Could not update hosts file. Add '127.0.0.1 mind-map.local' manually." - } -} - -# Set up TLS so https://mind-map.local works without browser warnings -Write-Step "Setting up TLS certificates..." -& $BinaryPath tls setup - # Verify try { & $BinaryPath --help | Out-Null @@ -157,7 +141,7 @@ foreach ($dir in $SkillDirs) { # 6. Interactive: set up as a persistent service # --------------------------------------------------------------------------- -$DefaultPort = "443" +$DefaultPort = "4242" $DefaultWikiDir = "$env:ProgramData\mind-map\wiki" $servicePort = $DefaultPort @@ -232,7 +216,7 @@ if ($installService -match '^[Yy]$') { & $BinaryPath service start @svcFlags Write-Host "" - $webUrl = if ($servicePort -eq "443") { "https://mind-map.local" } else { "https://mind-map.local:$servicePort" } + $webUrl = "http://localhost:$servicePort" Write-Host " Web UI: $webUrl" -ForegroundColor Cyan Write-Host "" Write-Host " Manage with: mind-map service status|stop|start|uninstall" -ForegroundColor DarkGray diff --git a/install.sh b/install.sh index 4389ae1..bff0e5f 100755 --- a/install.sh +++ b/install.sh @@ -98,46 +98,6 @@ if [[ "$(uname -s)" == "Darwin" ]]; then echo "==> Codesigned binary for macOS" || true fi -# Linux: allow binding port 80 without root -if [[ "$(uname -s)" == "Linux" ]] && command -v setcap >/dev/null 2>&1; then - sudo setcap cap_net_bind_service=+ep "${INSTALL_DIR}/mind-map" 2>/dev/null && \ - echo "==> Granted low-port binding capability" || \ - echo " Note: Could not set capability. Port 80 may require root." -fi - -# Add mind-map.local to /etc/hosts for reliable local name resolution -if ! grep -q "mind-map\.local" /etc/hosts 2>/dev/null; then - echo "127.0.0.1 mind-map.local" | sudo tee -a /etc/hosts >/dev/null 2>/dev/null && \ - echo "==> Added mind-map.local to /etc/hosts" || \ - echo " Note: Could not update /etc/hosts. Add '127.0.0.1 mind-map.local' manually." -fi - -# Set up TLS so https://mind-map.local works without browser warnings -TLS_DIR="${HOME}/.mind-map/tls" -echo "==> Setting up TLS certificates..." -# Generate certs as the current user (writes to ~/.mind-map/tls/) -"${INSTALL_DIR}/mind-map" tls generate --tls-dir "${TLS_DIR}" 2>&1 -# Install CA in system trust store (requires elevated privileges on Linux) -if [[ "$(uname -s)" == "Linux" ]]; then - sudo "${INSTALL_DIR}/mind-map" tls install-ca --tls-dir "${TLS_DIR}" 2>&1 || \ - echo " Note: Could not install CA. Run 'sudo mind-map tls install-ca --tls-dir ${TLS_DIR}' manually." - # Also install into Chrome/Chromium NSS database (runs as user, no sudo) - if [ -d "${HOME}/.pki/nssdb" ]; then - if command -v certutil >/dev/null 2>&1; then - certutil -d sql:"${HOME}/.pki/nssdb" -A -t "C,," -n "mind-map Local CA" -i "${TLS_DIR}/ca.crt" 2>/dev/null && \ - echo "==> CA installed in Chrome/Chromium trust store" || \ - echo " Note: Could not install CA in Chrome. Install libnss3-tools and re-run." - else - echo " Note: Install libnss3-tools for Chrome to trust the CA:" - echo " sudo apt install libnss3-tools" - echo " certutil -d sql:${HOME}/.pki/nssdb -A -t 'C,,' -n 'mind-map Local CA' -i ${TLS_DIR}/ca.crt" - fi - fi -else - "${INSTALL_DIR}/mind-map" tls install-ca --tls-dir "${TLS_DIR}" 2>&1 || \ - echo " Note: Could not install CA. Run 'mind-map tls install-ca' manually." -fi - echo "==> Installed mind-map to ${INSTALL_DIR}/mind-map" # Verify @@ -174,7 +134,7 @@ done # Interactive: set up as a persistent service # --------------------------------------------------------------------------- -DEFAULT_PORT="443" +DEFAULT_PORT="4242" DEFAULT_WIKI_DIR="${HOME}/.mind-map/wiki" SERVICE_PORT="$DEFAULT_PORT" @@ -262,11 +222,7 @@ if [[ "$INSTALL_SERVICE" =~ ^[Yy]$ ]]; then fi echo "" - if [ "$SERVICE_PORT" = "443" ]; then - echo " Web UI: https://mind-map.local" - else - echo " Web UI: https://mind-map.local:${SERVICE_PORT}" - fi + echo " Web UI: http://localhost:${SERVICE_PORT}" echo "" echo " Manage with: sudo mind-map service status|stop|start|uninstall" fi diff --git a/internal/tls/tls.go b/internal/tls/tls.go deleted file mode 100644 index d2586c1..0000000 --- a/internal/tls/tls.go +++ /dev/null @@ -1,173 +0,0 @@ -// Package tls generates a local CA and server certificate so that -// mind-map can serve HTTPS on mind-map.local without browser warnings. -// -// Certificates are stored in ~/.mind-map/tls/: -// -// ca.crt, ca.key — local certificate authority -// server.crt, server.key — server cert signed by the CA -// -// The CA cert must be installed in the system trust store (once) so -// browsers accept the server cert. The Setup function handles this. -package tls - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "math/big" - "net" - "os" - "path/filepath" - "time" -) - -// DefaultDir returns the default TLS directory (~/.mind-map/tls). -func DefaultDir() string { - home, err := os.UserHomeDir() - if err != nil { - return filepath.Join(".", ".mind-map", "tls") - } - return filepath.Join(home, ".mind-map", "tls") -} - -// DirFromWikiDir derives the TLS directory from the wiki directory. -// The wiki dir is typically ~/.mind-map/wiki, so TLS is the sibling -// ~/.mind-map/tls. This is more reliable than DefaultDir() when -// running as a system service (where os.UserHomeDir() may differ). -func DirFromWikiDir(wikiDir string) string { - return filepath.Join(filepath.Dir(wikiDir), "tls") -} - -// CertPaths returns the paths to the server cert and key. -func CertPaths(dir string) (certFile, keyFile string) { - return filepath.Join(dir, "server.crt"), filepath.Join(dir, "server.key") -} - -// CACertPath returns the path to the CA certificate. -func CACertPath(dir string) string { - return filepath.Join(dir, "ca.crt") -} - -// HasCerts returns true if the server cert and key exist. -func HasCerts(dir string) bool { - cert, key := CertPaths(dir) - if _, err := os.Stat(cert); err != nil { - return false - } - if _, err := os.Stat(key); err != nil { - return false - } - return true -} - -// Generate creates a local CA and a server certificate for mind-map.local. -// If certs already exist, they are overwritten. -func Generate(dir string) error { - if err := os.MkdirAll(dir, 0o700); err != nil { - return fmt.Errorf("create tls dir: %w", err) - } - - // --- CA --- - caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return fmt.Errorf("generate CA key: %w", err) - } - - caSerial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - caTemplate := &x509.Certificate{ - SerialNumber: caSerial, - Subject: pkix.Name{ - Organization: []string{"mind-map"}, - CommonName: "mind-map Local CA", - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour), // 10 years - KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, - BasicConstraintsValid: true, - IsCA: true, - MaxPathLen: 0, - } - - caCertDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey) - if err != nil { - return fmt.Errorf("create CA cert: %w", err) - } - - caCert, err := x509.ParseCertificate(caCertDER) - if err != nil { - return fmt.Errorf("parse CA cert: %w", err) - } - - if err := writePEM(filepath.Join(dir, "ca.crt"), "CERTIFICATE", caCertDER); err != nil { - return err - } - caKeyDER, err := x509.MarshalECPrivateKey(caKey) - if err != nil { - return fmt.Errorf("marshal CA key: %w", err) - } - if err := writePEM(filepath.Join(dir, "ca.key"), "EC PRIVATE KEY", caKeyDER); err != nil { - return err - } - - // --- Server cert --- - srvKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return fmt.Errorf("generate server key: %w", err) - } - - srvSerial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - srvTemplate := &x509.Certificate{ - SerialNumber: srvSerial, - Subject: pkix.Name{ - Organization: []string{"mind-map"}, - CommonName: "mind-map.local", - }, - DNSNames: []string{"mind-map.local", "localhost"}, - IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, - NotBefore: time.Now(), - NotAfter: time.Now().Add(2 * 365 * 24 * time.Hour), // 2 years - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - } - - srvCertDER, err := x509.CreateCertificate(rand.Reader, srvTemplate, caCert, &srvKey.PublicKey, caKey) - if err != nil { - return fmt.Errorf("create server cert: %w", err) - } - - if err := writePEM(filepath.Join(dir, "server.crt"), "CERTIFICATE", srvCertDER); err != nil { - return err - } - srvKeyDER, err := x509.MarshalECPrivateKey(srvKey) - if err != nil { - return fmt.Errorf("marshal server key: %w", err) - } - if err := writePEM(filepath.Join(dir, "server.key"), "EC PRIVATE KEY", srvKeyDER); err != nil { - return err - } - - return nil -} - -// Remove deletes all generated TLS files. -func Remove(dir string) error { - for _, name := range []string{"ca.crt", "ca.key", "server.crt", "server.key"} { - os.Remove(filepath.Join(dir, name)) - } - // Remove dir if empty - os.Remove(dir) - return nil -} - -func writePEM(path, blockType string, data []byte) error { - f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) - if err != nil { - return fmt.Errorf("write %s: %w", filepath.Base(path), err) - } - defer f.Close() - return pem.Encode(f, &pem.Block{Type: blockType, Bytes: data}) -} diff --git a/internal/tls/trust_darwin.go b/internal/tls/trust_darwin.go deleted file mode 100644 index 9f45aad..0000000 --- a/internal/tls/trust_darwin.go +++ /dev/null @@ -1,32 +0,0 @@ -package tls - -import ( - "fmt" - "os/exec" -) - -// InstallCA installs the CA certificate in the macOS System Keychain. -func InstallCA(dir string) error { - caCert := CACertPath(dir) - out, err := exec.Command("security", "add-trusted-cert", - "-d", "-r", "trustRoot", - "-k", "/Library/Keychains/System.keychain", - caCert, - ).CombinedOutput() - if err != nil { - return fmt.Errorf("security add-trusted-cert: %s: %w", out, err) - } - return nil -} - -// UninstallCA removes the CA certificate from the macOS System Keychain. -func UninstallCA(_ string) error { - out, err := exec.Command("security", "delete-certificate", - "-c", "mind-map Local CA", - "-t", "/Library/Keychains/System.keychain", - ).CombinedOutput() - if err != nil { - return fmt.Errorf("security delete-certificate: %s: %w", out, err) - } - return nil -} diff --git a/internal/tls/trust_linux.go b/internal/tls/trust_linux.go deleted file mode 100644 index 293373e..0000000 --- a/internal/tls/trust_linux.go +++ /dev/null @@ -1,67 +0,0 @@ -package tls - -import ( - "fmt" - "os/exec" - "path/filepath" -) - -// InstallCA installs the CA certificate in the system trust store. -// On Linux this uses update-ca-certificates (Debian/Ubuntu) or -// update-ca-trust (RHEL/Fedora). -func InstallCA(dir string) error { - caCert := CACertPath(dir) - - // Debian/Ubuntu - if dest := "/usr/local/share/ca-certificates/mind-map-ca.crt"; true { - if err := copyFile(caCert, dest); err == nil { - if cmd := exec.Command("update-ca-certificates"); cmd != nil { - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("update-ca-certificates: %s: %w", out, err) - } - return nil - } - } - } - - // RHEL/Fedora - if dest := "/etc/pki/ca-trust/source/anchors/mind-map-ca.crt"; true { - if err := copyFile(caCert, dest); err == nil { - if cmd := exec.Command("update-ca-trust"); cmd != nil { - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("update-ca-trust: %s: %w", out, err) - } - return nil - } - } - } - - return fmt.Errorf("could not install CA cert: no supported trust store found") -} - -// UninstallCA removes the CA certificate from the system trust store. -func UninstallCA(_ string) error { - // Debian/Ubuntu - debPath := "/usr/local/share/ca-certificates/mind-map-ca.crt" - if removeIfExists(debPath) { - exec.Command("update-ca-certificates", "--fresh").CombinedOutput() - return nil - } - - // RHEL/Fedora - rhelPath := "/etc/pki/ca-trust/source/anchors/mind-map-ca.crt" - if removeIfExists(rhelPath) { - exec.Command("update-ca-trust").CombinedOutput() - return nil - } - - return nil -} - -func copyFile(src, dst string) error { - out, err := exec.Command("cp", src, dst).CombinedOutput() - if err != nil { - return fmt.Errorf("cp %s %s: %s: %w", filepath.Base(src), dst, out, err) - } - return nil -} diff --git a/internal/tls/trust_windows.go b/internal/tls/trust_windows.go deleted file mode 100644 index d549120..0000000 --- a/internal/tls/trust_windows.go +++ /dev/null @@ -1,25 +0,0 @@ -package tls - -import ( - "fmt" - "os/exec" -) - -// InstallCA installs the CA certificate in the Windows Root trust store. -func InstallCA(dir string) error { - caCert := CACertPath(dir) - out, err := exec.Command("certutil", "-addstore", "-f", "Root", caCert).CombinedOutput() - if err != nil { - return fmt.Errorf("certutil -addstore: %s: %w", out, err) - } - return nil -} - -// UninstallCA removes the CA certificate from the Windows Root trust store. -func UninstallCA(_ string) error { - out, err := exec.Command("certutil", "-delstore", "Root", "mind-map Local CA").CombinedOutput() - if err != nil { - return fmt.Errorf("certutil -delstore: %s: %w", out, err) - } - return nil -} diff --git a/internal/tls/util.go b/internal/tls/util.go deleted file mode 100644 index 26473d7..0000000 --- a/internal/tls/util.go +++ /dev/null @@ -1,11 +0,0 @@ -package tls - -import "os" - -func removeIfExists(path string) bool { - if _, err := os.Stat(path); err == nil { - os.Remove(path) - return true - } - return false -}