2023-01-27 21:37:20 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2021-08-23 04:12:27 +00:00
|
|
|
|
|
|
|
//go:build linux && !android
|
|
|
|
|
|
|
|
package hostinfo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2021-08-23 04:15:30 +00:00
|
|
|
"os"
|
2021-08-23 04:12:27 +00:00
|
|
|
"strings"
|
|
|
|
|
2022-06-01 13:03:00 +00:00
|
|
|
"golang.org/x/sys/unix"
|
2022-12-01 01:33:29 +00:00
|
|
|
"tailscale.com/types/ptr"
|
2021-08-23 04:12:27 +00:00
|
|
|
"tailscale.com/util/lineread"
|
|
|
|
"tailscale.com/version/distro"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2022-09-11 17:57:18 +00:00
|
|
|
osVersion = lazyOSVersion.Get
|
2022-02-22 18:01:36 +00:00
|
|
|
packageType = packageTypeLinux
|
2022-09-11 17:57:18 +00:00
|
|
|
distroName = distroNameLinux
|
|
|
|
distroVersion = distroVersionLinux
|
|
|
|
distroCodeName = distroCodeNameLinux
|
2024-04-01 20:26:20 +00:00
|
|
|
deviceModel = deviceModelLinux
|
2021-08-23 04:12:27 +00:00
|
|
|
}
|
|
|
|
|
2022-09-11 17:57:18 +00:00
|
|
|
var (
|
2022-12-01 01:33:29 +00:00
|
|
|
lazyVersionMeta = &lazyAtomicValue[versionMeta]{f: ptr.To(linuxVersionMeta)}
|
|
|
|
lazyOSVersion = &lazyAtomicValue[string]{f: ptr.To(osVersionLinux)}
|
2022-09-11 17:57:18 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type versionMeta struct {
|
|
|
|
DistroName string
|
|
|
|
DistroVersion string
|
|
|
|
DistroCodeName string // "jammy", etc (VERSION_CODENAME from /etc/os-release)
|
|
|
|
}
|
|
|
|
|
|
|
|
func distroNameLinux() string {
|
|
|
|
return lazyVersionMeta.Get().DistroName
|
|
|
|
}
|
|
|
|
|
|
|
|
func distroVersionLinux() string {
|
|
|
|
return lazyVersionMeta.Get().DistroVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
func distroCodeNameLinux() string {
|
|
|
|
return lazyVersionMeta.Get().DistroCodeName
|
|
|
|
}
|
|
|
|
|
2024-04-01 20:26:20 +00:00
|
|
|
func deviceModelLinux() string {
|
2021-10-04 16:52:25 +00:00
|
|
|
for _, path := range []string{
|
|
|
|
// First try the Synology-specific location.
|
|
|
|
// Example: "DS916+-j"
|
|
|
|
"/proc/sys/kernel/syno_hw_version",
|
|
|
|
|
|
|
|
// Otherwise, try the Devicetree model, usually set on
|
|
|
|
// ARM SBCs, etc.
|
|
|
|
// Example: "Raspberry Pi 4 Model B Rev 1.2"
|
2022-06-05 04:34:06 +00:00
|
|
|
// Example: "WD My Cloud Gen2: Marvell Armada 375"
|
2021-10-04 16:52:25 +00:00
|
|
|
"/sys/firmware/devicetree/base/model", // Raspberry Pi 4 Model B Rev 1.2"
|
|
|
|
} {
|
|
|
|
b, _ := os.ReadFile(path)
|
|
|
|
if s := strings.Trim(string(b), "\x00\r\n\t "); s != "" {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2022-07-17 18:43:39 +00:00
|
|
|
func getQnapQtsVersion(versionInfo string) string {
|
|
|
|
for _, field := range strings.Fields(versionInfo) {
|
2023-02-01 21:43:06 +00:00
|
|
|
if suffix, ok := strings.CutPrefix(field, "QTSFW_"); ok {
|
2022-09-11 17:57:18 +00:00
|
|
|
return suffix
|
2022-07-17 18:43:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2021-08-23 04:12:27 +00:00
|
|
|
func osVersionLinux() string {
|
2022-09-11 17:57:18 +00:00
|
|
|
var un unix.Utsname
|
|
|
|
unix.Uname(&un)
|
|
|
|
return unix.ByteSliceToString(un.Release[:])
|
|
|
|
}
|
|
|
|
|
|
|
|
func linuxVersionMeta() (meta versionMeta) {
|
2021-08-23 04:12:27 +00:00
|
|
|
dist := distro.Get()
|
2022-09-11 17:57:18 +00:00
|
|
|
meta.DistroName = string(dist)
|
|
|
|
|
2021-08-23 04:12:27 +00:00
|
|
|
propFile := "/etc/os-release"
|
|
|
|
switch dist {
|
|
|
|
case distro.Synology:
|
|
|
|
propFile = "/etc.defaults/VERSION"
|
|
|
|
case distro.OpenWrt:
|
|
|
|
propFile = "/etc/openwrt_release"
|
2023-05-03 03:57:52 +00:00
|
|
|
case distro.Unraid:
|
|
|
|
propFile = "/etc/unraid-version"
|
2022-06-05 04:34:06 +00:00
|
|
|
case distro.WDMyCloud:
|
2022-09-15 12:06:59 +00:00
|
|
|
slurp, _ := os.ReadFile("/etc/version")
|
2022-09-11 17:57:18 +00:00
|
|
|
meta.DistroVersion = string(bytes.TrimSpace(slurp))
|
|
|
|
return
|
2022-07-17 18:43:39 +00:00
|
|
|
case distro.QNAP:
|
2022-09-15 12:06:59 +00:00
|
|
|
slurp, _ := os.ReadFile("/etc/version_info")
|
2022-09-11 17:57:18 +00:00
|
|
|
meta.DistroVersion = getQnapQtsVersion(string(slurp))
|
|
|
|
return
|
2021-08-23 04:12:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
m := map[string]string{}
|
|
|
|
lineread.File(propFile, func(line []byte) error {
|
|
|
|
eq := bytes.IndexByte(line, '=')
|
|
|
|
if eq == -1 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
k, v := string(line[:eq]), strings.Trim(string(line[eq+1:]), `"'`)
|
|
|
|
m[k] = v
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2022-09-11 17:57:18 +00:00
|
|
|
if v := m["VERSION_CODENAME"]; v != "" {
|
|
|
|
meta.DistroCodeName = v
|
2021-08-23 04:12:27 +00:00
|
|
|
}
|
2022-09-11 17:57:18 +00:00
|
|
|
if v := m["VERSION_ID"]; v != "" {
|
|
|
|
meta.DistroVersion = v
|
2021-08-23 04:12:27 +00:00
|
|
|
}
|
|
|
|
id := m["ID"]
|
2022-09-11 17:57:18 +00:00
|
|
|
if id != "" {
|
|
|
|
meta.DistroName = id
|
|
|
|
}
|
2021-08-23 04:12:27 +00:00
|
|
|
switch id {
|
|
|
|
case "debian":
|
2022-09-11 17:57:18 +00:00
|
|
|
// Debian's VERSION_ID is just like "11". But /etc/debian_version has "11.5" normally.
|
|
|
|
// Or "bookworm/sid" on sid/testing.
|
2022-09-15 12:06:59 +00:00
|
|
|
slurp, _ := os.ReadFile("/etc/debian_version")
|
2022-09-11 17:57:18 +00:00
|
|
|
if v := string(bytes.TrimSpace(slurp)); v != "" {
|
|
|
|
if '0' <= v[0] && v[0] <= '9' {
|
|
|
|
meta.DistroVersion = v
|
|
|
|
} else if meta.DistroCodeName == "" {
|
|
|
|
meta.DistroCodeName = v
|
|
|
|
}
|
2021-08-23 04:12:27 +00:00
|
|
|
}
|
2022-09-11 17:57:18 +00:00
|
|
|
case "", "centos": // CentOS 6 has no /etc/os-release, so its id is ""
|
|
|
|
if meta.DistroVersion == "" {
|
2022-09-15 12:06:59 +00:00
|
|
|
if cr, _ := os.ReadFile("/etc/centos-release"); len(cr) > 0 { // "CentOS release 6.10 (Final)
|
2022-09-11 17:57:18 +00:00
|
|
|
meta.DistroVersion = string(bytes.TrimSpace(cr))
|
|
|
|
}
|
2021-08-23 04:12:27 +00:00
|
|
|
}
|
|
|
|
}
|
2022-09-11 17:57:18 +00:00
|
|
|
if v := m["PRETTY_NAME"]; v != "" && meta.DistroVersion == "" && !strings.HasSuffix(v, "/sid") {
|
|
|
|
meta.DistroVersion = v
|
|
|
|
}
|
2021-08-23 04:12:27 +00:00
|
|
|
switch dist {
|
|
|
|
case distro.Synology:
|
2022-09-11 17:57:18 +00:00
|
|
|
meta.DistroVersion = m["productversion"]
|
2021-08-23 04:12:27 +00:00
|
|
|
case distro.OpenWrt:
|
2022-09-11 17:57:18 +00:00
|
|
|
meta.DistroVersion = m["DISTRIB_RELEASE"]
|
2023-05-03 03:57:52 +00:00
|
|
|
case distro.Unraid:
|
|
|
|
meta.DistroVersion = m["version"]
|
2021-08-23 04:12:27 +00:00
|
|
|
}
|
2022-09-11 17:57:18 +00:00
|
|
|
return
|
2021-08-23 04:12:27 +00:00
|
|
|
}
|
2022-02-22 18:01:36 +00:00
|
|
|
|
|
|
|
func packageTypeLinux() string {
|
|
|
|
// Report whether this is in a snap.
|
|
|
|
// See https://snapcraft.io/docs/environment-variables
|
|
|
|
// We just look at two somewhat arbitrarily.
|
|
|
|
if os.Getenv("SNAP_NAME") != "" && os.Getenv("SNAP") != "" {
|
|
|
|
return "snap"
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|