| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package buildinfo |
| 4 | |
| 5 | import ( |
| 6 | "fmt" |
| 7 | "path/filepath" |
| 8 | "runtime" |
| 9 | "strings" |
| 10 | |
| 11 | "github.com/netdata/netdata/go/plugins/pkg/executable" |
| 12 | ) |
| 13 | |
| 14 | // The following variables are set at build time using linker flags. |
| 15 | |
| 16 | // Version is the Netdata Agent version. |
| 17 | var Version = "v0.0.0" |
| 18 | |
| 19 | // UserConfigDir is the path to the user configuration directory. |
| 20 | var UserConfigDir = "" |
| 21 | |
| 22 | // StockConfigDir is the path to the stock (default) configuration directory. |
| 23 | var StockConfigDir = "" |
| 24 | |
| 25 | // PluginsDir is the path to the installed plugins directory. |
| 26 | var PluginsDir = "/usr/libexec/netdata/plugins.d" |
| 27 | |
| 28 | // NetdataBinDir is the path to the installed executables directory. |
| 29 | var NetdataBinDir = "/usr/sbin" |
| 30 | |
| 31 | // CacheDir is the path to the Netdata cache directory. |
| 32 | var CacheDir = "/var/cache/netdata" |
| 33 | |
| 34 | // Info returns all build information as a single line of space-delimited |
| 35 | // key=value pairs using snake_case keys. |
| 36 | // |
| 37 | // This output is intended for external consumption. Callers must not assume a |
| 38 | // fixed set, order, or count of keys. New keys may be added over time as more |
| 39 | // build metadata becomes available, so parsers should ignore unknown keys to |
| 40 | // remain forward-compatible. |
| 41 | func Info() string { |
| 42 | return fmt.Sprintf( |
| 43 | "version=%s go_version=%s user_config_dir=%s stock_config_dir=%s plugins_dir=%s netdata_bin_dir=%s cache_dir=%s", |
| 44 | Version, |
| 45 | runtime.Version(), |
| 46 | UserConfigDir, |
| 47 | StockConfigDir, |
| 48 | PluginsDir, |
| 49 | NetdataBinDir, |
| 50 | CacheDir, |
| 51 | ) |
| 52 | } |
| 53 | |
| 54 | func init() { |
| 55 | if runtime.GOOS != "windows" { |
| 56 | return |
| 57 | } |
| 58 | |
| 59 | execDir := executable.Directory |
| 60 | if execDir == "" || PluginsDir == "" { |
| 61 | return |
| 62 | } |
| 63 | |
| 64 | // ---------------------------------------------------------------------------- |
| 65 | // 1. Detect install prefix on Windows |
| 66 | // |
| 67 | // We assume that on Windows the *running binary* lives inside PluginsDir. |
| 68 | // |
| 69 | // Example: |
| 70 | // execDir = "C:/Program Files/Netdata/usr/libexec/netdata/plugins.d" |
| 71 | // PluginsDir = "/usr/libexec/netdata/plugins.d" |
| 72 | // |
| 73 | // By normalizing both paths to forward-slash format, we can test: |
| 74 | // |
| 75 | // strings.HasSuffix(execDir, PluginsDir) → true |
| 76 | // |
| 77 | // From that, we strip the suffix and recover the actual installation prefix: |
| 78 | // |
| 79 | // prefix = "C:/Program Files/Netdata" |
| 80 | // |
| 81 | // If execDir does *not* end with PluginsDir, we simply do nothing — this keeps |
| 82 | // development/testing environments safe where binaries are run outside the |
| 83 | // expected layout. |
| 84 | // ---------------------------------------------------------------------------- |
| 85 | normalized := filepath.ToSlash(execDir) |
| 86 | suffix := filepath.ToSlash(PluginsDir) |
| 87 | |
| 88 | if !strings.HasSuffix(normalized, suffix) { |
| 89 | return |
| 90 | } |
| 91 | |
| 92 | // Extract the prefix by removing the suffix. |
| 93 | // |
| 94 | // Example: |
| 95 | // normalized = "C:/Program Files/Netdata/usr/libexec/netdata/plugins.d" |
| 96 | // suffix = "/usr/libexec/netdata/plugins.d" |
| 97 | // |
| 98 | // → prefix = "C:/Program Files/Netdata" |
| 99 | prefix := strings.TrimSuffix(normalized, suffix) |
| 100 | prefix = strings.TrimSuffix(prefix, "/") |
| 101 | |
| 102 | installPrefix := filepath.FromSlash(prefix) |
| 103 | |
| 104 | // ---------------------------------------------------------------------------- |
| 105 | // 2. Rewrite all buildinfo paths as: |
| 106 | // <installPrefix> + <original buildinfo path as relative suffix> |
| 107 | // |
| 108 | // Example: |
| 109 | // NetdataBinDir build-time: "/usr/sbin" |
| 110 | // After rewrite: |
| 111 | // "C:\Program Files\Netdata\usr\sbin" |
| 112 | // |
| 113 | // Notes: |
| 114 | // • We must remove the *leading slash* from the build-time path, otherwise |
| 115 | // filepath.Join would treat it as absolute and ignore the prefix. |
| 116 | // |
| 117 | // Example of what we avoid: |
| 118 | // filepath.Join("C:\\Program Files\\Netdata", "/usr/sbin") → |
| 119 | // "\usr\sbin" (WRONG — prefix lost!) |
| 120 | // |
| 121 | // • Paths that were empty at build time should remain empty. |
| 122 | // ---------------------------------------------------------------------------- |
| 123 | rebuild := func(p string) string { |
| 124 | if p == "" { |
| 125 | return "" |
| 126 | } |
| 127 | |
| 128 | // Convert to slash form and trim the leading '/' so it becomes relative. |
| 129 | // |
| 130 | // Example: |
| 131 | // p = "/usr/sbin" |
| 132 | // → s = "usr/sbin" |
| 133 | // |
| 134 | s := filepath.ToSlash(p) |
| 135 | s = strings.TrimPrefix(s, "/") |
| 136 | |
| 137 | // Now prefix + relative suffix works reliably on Windows. |
| 138 | // |
| 139 | // Example: |
| 140 | // installPrefix = "C:\\Program Files\\Netdata" |
| 141 | // s = "usr/sbin" |
| 142 | // → result = "C:\\Program Files\\Netdata\\usr\\sbin" |
| 143 | return filepath.Join(installPrefix, s) |
| 144 | } |
| 145 | |
| 146 | UserConfigDir = rebuild(UserConfigDir) |
| 147 | StockConfigDir = rebuild(StockConfigDir) |
| 148 | PluginsDir = rebuild(PluginsDir) |
| 149 | NetdataBinDir = rebuild(NetdataBinDir) |
| 150 | CacheDir = rebuild(CacheDir) |
| 151 | } |