@@ -39,6 +39,7 @@ func replaceRegularFile(path string, data []byte, perm os.FileMode) error {
|
|||||||
if err := os.Rename(tmp, path); err != nil {
|
if err := os.Rename(tmp, path); err != nil {
|
||||||
_ = os.Rename(old, path)
|
_ = os.Rename(old, path)
|
||||||
_ = os.Remove(tmp)
|
_ = os.Remove(tmp)
|
||||||
|
|
||||||
return fmt.Errorf("cannot replace file: %w", err)
|
return fmt.Errorf("cannot replace file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,14 +36,18 @@ func freebsdDiskEncryption(ctx context.Context) Result {
|
|||||||
if !CommandExists("geli") {
|
if !CommandExists("geli") {
|
||||||
return unknown(map[string]any{"note": "geli command not found"})
|
return unknown(map[string]any{"note": "geli command not found"})
|
||||||
}
|
}
|
||||||
|
|
||||||
out := RunCommand(ctx, "geli", "status")
|
out := RunCommand(ctx, "geli", "status")
|
||||||
|
|
||||||
ev := map[string]any{"raw": out.Stdout, "stderr": out.Stderr}
|
ev := map[string]any{"raw": out.Stdout, "stderr": out.Stderr}
|
||||||
if out.Err != nil {
|
if out.Err != nil {
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(out.Stdout, "ACTIVE") {
|
if strings.Contains(out.Stdout, "ACTIVE") {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +58,7 @@ func freebsdScreenLock(ctx context.Context) Result {
|
|||||||
return pass(map[string]any{"raw": out.Stdout})
|
return pass(map[string]any{"raw": out.Stdout})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return notApplicable(
|
return notApplicable(
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"note": "FreeBSD does not have a unified screen lock policy",
|
"note": "FreeBSD does not have a unified screen lock policy",
|
||||||
@@ -65,26 +70,33 @@ func freebsdFirewall(ctx context.Context) Result {
|
|||||||
if !CommandExists("pfctl") {
|
if !CommandExists("pfctl") {
|
||||||
return unknown(map[string]any{"note": "pfctl not found"})
|
return unknown(map[string]any{"note": "pfctl not found"})
|
||||||
}
|
}
|
||||||
|
|
||||||
out := RunCommand(ctx, "pfctl", "-si")
|
out := RunCommand(ctx, "pfctl", "-si")
|
||||||
|
|
||||||
ev := map[string]any{"raw": truncate(out.Stdout, 400)}
|
ev := map[string]any{"raw": truncate(out.Stdout, 400)}
|
||||||
if out.Err != nil {
|
if out.Err != nil {
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(out.Stdout, "Status: Enabled") {
|
if strings.Contains(out.Stdout, "Status: Enabled") {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func freebsdTimeSync(ctx context.Context) Result {
|
func freebsdTimeSync(ctx context.Context) Result {
|
||||||
out := RunCommand(ctx, "service", "ntpd", "status")
|
out := RunCommand(ctx, "service", "ntpd", "status")
|
||||||
|
|
||||||
ev := map[string]any{"raw": out.Stdout, "stderr": out.Stderr}
|
ev := map[string]any{"raw": out.Stdout, "stderr": out.Stderr}
|
||||||
if out.Err != nil {
|
if out.Err != nil {
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(strings.ToLower(out.Stdout), "is running") {
|
if strings.Contains(strings.ToLower(out.Stdout), "is running") {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +105,7 @@ func freebsdOSVersion(ctx context.Context) Result {
|
|||||||
if out.Err != nil {
|
if out.Err != nil {
|
||||||
return unknown(map[string]any{"error": out.Err.Error()})
|
return unknown(map[string]any{"error": out.Err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
return pass(map[string]any{"release": out.Stdout})
|
return pass(map[string]any{"release": out.Stdout})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,15 +122,18 @@ func freebsdPasswordPolicy(ctx context.Context) Result {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return unknown(map[string]any{"error": err.Error()})
|
return unknown(map[string]any{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
body := string(data)
|
body := string(data)
|
||||||
hasPolicy := strings.Contains(body, "minpasswordlen=") ||
|
hasPolicy := strings.Contains(body, "minpasswordlen=") ||
|
||||||
strings.Contains(body, "passwordtime=")
|
strings.Contains(body, "passwordtime=")
|
||||||
|
|
||||||
ev := map[string]any{
|
ev := map[string]any{
|
||||||
"login_conf_snippet": truncate(body, 400),
|
"login_conf_snippet": truncate(body, 400),
|
||||||
}
|
}
|
||||||
if hasPolicy {
|
if hasPolicy {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,25 +145,32 @@ func freebsdMalwareProtection(ctx context.Context) Result {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
out := RunCommand(ctx, "service", "clamav_clamd", "status")
|
out := RunCommand(ctx, "service", "clamav_clamd", "status")
|
||||||
|
|
||||||
ev := map[string]any{"raw": out.Stdout, "stderr": out.Stderr}
|
ev := map[string]any{"raw": out.Stdout, "stderr": out.Stderr}
|
||||||
if out.Err != nil {
|
if out.Err != nil {
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(strings.ToLower(out.Stdout), "is running") {
|
if strings.Contains(strings.ToLower(out.Stdout), "is running") {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func freebsdRemoteLogin(ctx context.Context) Result {
|
func freebsdRemoteLogin(ctx context.Context) Result {
|
||||||
out := RunCommand(ctx, "service", "sshd", "status")
|
out := RunCommand(ctx, "service", "sshd", "status")
|
||||||
|
|
||||||
ev := map[string]any{"raw": out.Stdout, "stderr": out.Stderr}
|
ev := map[string]any{"raw": out.Stdout, "stderr": out.Stderr}
|
||||||
if out.Err != nil {
|
if out.Err != nil {
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(strings.ToLower(out.Stdout), "is running") {
|
if strings.Contains(strings.ToLower(out.Stdout), "is running") {
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ func linuxDiskEncryption(ctx context.Context) Result {
|
|||||||
if data, err := os.ReadFile("/etc/crypttab"); err == nil {
|
if data, err := os.ReadFile("/etc/crypttab"); err == nil {
|
||||||
body := strings.TrimSpace(string(data))
|
body := strings.TrimSpace(string(data))
|
||||||
ev["crypttab_present"] = true
|
ev["crypttab_present"] = true
|
||||||
|
|
||||||
ev["crypttab_lines"] = nonCommentLines(body)
|
ev["crypttab_lines"] = nonCommentLines(body)
|
||||||
if len(nonCommentLines(body)) > 0 {
|
if len(nonCommentLines(body)) > 0 {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
@@ -50,12 +51,14 @@ func linuxDiskEncryption(ctx context.Context) Result {
|
|||||||
lsblk := RunCommand(ctx, "lsblk", "-o", "NAME,TYPE,FSTYPE,MOUNTPOINT", "-r")
|
lsblk := RunCommand(ctx, "lsblk", "-o", "NAME,TYPE,FSTYPE,MOUNTPOINT", "-r")
|
||||||
if lsblk.Err == nil {
|
if lsblk.Err == nil {
|
||||||
ev["lsblk"] = truncate(lsblk.Stdout, 800)
|
ev["lsblk"] = truncate(lsblk.Stdout, 800)
|
||||||
|
|
||||||
lines := strings.SplitSeq(lsblk.Stdout, "\n")
|
lines := strings.SplitSeq(lsblk.Stdout, "\n")
|
||||||
for line := range lines {
|
for line := range lines {
|
||||||
fields := strings.Fields(line)
|
fields := strings.Fields(line)
|
||||||
if len(fields) < 2 {
|
if len(fields) < 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if fields[1] == "crypt" {
|
if fields[1] == "crypt" {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
@@ -67,6 +70,7 @@ func linuxDiskEncryption(ctx context.Context) Result {
|
|||||||
if lsblk.Err != nil {
|
if lsblk.Err != nil {
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +82,7 @@ func linuxScreenLock(ctx context.Context) Result {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
idle := RunCommand(ctx, "gsettings", "get", "org.gnome.desktop.screensaver", "lock-enabled")
|
idle := RunCommand(ctx, "gsettings", "get", "org.gnome.desktop.screensaver", "lock-enabled")
|
||||||
if idle.Err != nil {
|
if idle.Err != nil {
|
||||||
return unknown(
|
return unknown(
|
||||||
@@ -86,11 +91,14 @@ func linuxScreenLock(ctx context.Context) Result {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
on := strings.TrimSpace(idle.Stdout) == "true"
|
on := strings.TrimSpace(idle.Stdout) == "true"
|
||||||
|
|
||||||
ev := map[string]any{"lock_enabled": idle.Stdout}
|
ev := map[string]any{"lock_enabled": idle.Stdout}
|
||||||
if on {
|
if on {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,24 +107,30 @@ func linuxFirewall(ctx context.Context) Result {
|
|||||||
out := RunCommand(ctx, "ufw", "status")
|
out := RunCommand(ctx, "ufw", "status")
|
||||||
if out.Err == nil {
|
if out.Err == nil {
|
||||||
active := strings.Contains(strings.ToLower(out.Stdout), "status: active")
|
active := strings.Contains(strings.ToLower(out.Stdout), "status: active")
|
||||||
|
|
||||||
ev := map[string]any{"backend": "ufw", "raw": out.Stdout}
|
ev := map[string]any{"backend": "ufw", "raw": out.Stdout}
|
||||||
if active {
|
if active {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if CommandExists("firewall-cmd") {
|
if CommandExists("firewall-cmd") {
|
||||||
out := RunCommand(ctx, "firewall-cmd", "--state")
|
out := RunCommand(ctx, "firewall-cmd", "--state")
|
||||||
|
|
||||||
ev := map[string]any{"backend": "firewalld", "raw": out.Stdout}
|
ev := map[string]any{"backend": "firewalld", "raw": out.Stdout}
|
||||||
if out.Err == nil && strings.Contains(strings.ToLower(out.Stdout), "running") {
|
if out.Err == nil && strings.Contains(strings.ToLower(out.Stdout), "running") {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if CommandExists("nft") {
|
if CommandExists("nft") {
|
||||||
out := RunCommand(ctx, "nft", "list", "ruleset")
|
out := RunCommand(ctx, "nft", "list", "ruleset")
|
||||||
|
|
||||||
ev := map[string]any{
|
ev := map[string]any{
|
||||||
"backend": "nftables",
|
"backend": "nftables",
|
||||||
"rules_excerpt": truncate(out.Stdout, 400),
|
"rules_excerpt": truncate(out.Stdout, 400),
|
||||||
@@ -125,21 +139,26 @@ func linuxFirewall(ctx context.Context) Result {
|
|||||||
ev["error"] = out.Err.Error()
|
ev["error"] = out.Err.Error()
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(out.Stdout, "chain ") {
|
if strings.Contains(out.Stdout, "chain ") {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if CommandExists("iptables") {
|
if CommandExists("iptables") {
|
||||||
out := RunCommand(ctx, "iptables", "-S", "INPUT")
|
out := RunCommand(ctx, "iptables", "-S", "INPUT")
|
||||||
|
|
||||||
ev := map[string]any{"backend": "iptables"}
|
ev := map[string]any{"backend": "iptables"}
|
||||||
if out.Err != nil {
|
if out.Err != nil {
|
||||||
ev["error"] = out.Err.Error()
|
ev["error"] = out.Err.Error()
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
policy, rules := parseIptablesInput(out.Stdout)
|
policy, rules := parseIptablesInput(out.Stdout)
|
||||||
ev["input_policy"] = policy
|
ev["input_policy"] = policy
|
||||||
|
|
||||||
ev["input_rules"] = rules
|
ev["input_rules"] = rules
|
||||||
if policy == "DROP" || policy == "REJECT" {
|
if policy == "DROP" || policy == "REJECT" {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
@@ -148,11 +167,13 @@ func linuxFirewall(ctx context.Context) Result {
|
|||||||
if rules == 0 {
|
if rules == 0 {
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACCEPT policy with some rules means the operator is filtering,
|
// ACCEPT policy with some rules means the operator is filtering,
|
||||||
// but we cannot tell from -S whether the rules are restrictive
|
// but we cannot tell from -S whether the rules are restrictive
|
||||||
// or permissive without modelling the chain.
|
// or permissive without modelling the chain.
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return unknown(
|
return unknown(
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"note": "no known firewall tool found",
|
"note": "no known firewall tool found",
|
||||||
@@ -192,14 +213,17 @@ func linuxTimeSync(ctx context.Context) Result {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
out := RunCommand(ctx, "timedatectl", "show")
|
out := RunCommand(ctx, "timedatectl", "show")
|
||||||
if out.Err != nil {
|
if out.Err != nil {
|
||||||
return unknown(map[string]any{"error": out.Err.Error()})
|
return unknown(map[string]any{"error": out.Err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
ev := map[string]any{"raw": truncate(out.Stdout, 400)}
|
ev := map[string]any{"raw": truncate(out.Stdout, 400)}
|
||||||
if strings.Contains(out.Stdout, "NTPSynchronized=yes") {
|
if strings.Contains(out.Stdout, "NTPSynchronized=yes") {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,12 +232,14 @@ func linuxOSVersion(ctx context.Context) Result {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return unknown(map[string]any{"error": err.Error()})
|
return unknown(map[string]any{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
body := string(data)
|
body := string(data)
|
||||||
ev := map[string]any{
|
ev := map[string]any{
|
||||||
"pretty_name": kvLookup(body, "PRETTY_NAME"),
|
"pretty_name": kvLookup(body, "PRETTY_NAME"),
|
||||||
"version_id": kvLookup(body, "VERSION_ID"),
|
"version_id": kvLookup(body, "VERSION_ID"),
|
||||||
"id": kvLookup(body, "ID"),
|
"id": kvLookup(body, "ID"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +247,7 @@ func linuxAutoUpdate(ctx context.Context) Result {
|
|||||||
if _, err := os.Stat("/etc/apt/apt.conf.d/20auto-upgrades"); err == nil {
|
if _, err := os.Stat("/etc/apt/apt.conf.d/20auto-upgrades"); err == nil {
|
||||||
data, _ := os.ReadFile("/etc/apt/apt.conf.d/20auto-upgrades")
|
data, _ := os.ReadFile("/etc/apt/apt.conf.d/20auto-upgrades")
|
||||||
body := string(data)
|
body := string(data)
|
||||||
|
|
||||||
ev := map[string]any{
|
ev := map[string]any{
|
||||||
"backend": "unattended-upgrades",
|
"backend": "unattended-upgrades",
|
||||||
"raw": body,
|
"raw": body,
|
||||||
@@ -228,8 +255,10 @@ func linuxAutoUpdate(ctx context.Context) Result {
|
|||||||
if strings.Contains(body, `"1"`) {
|
if strings.Contains(body, `"1"`) {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if CommandExists("systemctl") {
|
if CommandExists("systemctl") {
|
||||||
out := RunCommand(ctx, "systemctl", "is-enabled", "dnf-automatic.timer")
|
out := RunCommand(ctx, "systemctl", "is-enabled", "dnf-automatic.timer")
|
||||||
if out.Err == nil {
|
if out.Err == nil {
|
||||||
@@ -237,9 +266,11 @@ func linuxAutoUpdate(ctx context.Context) Result {
|
|||||||
if strings.TrimSpace(out.Stdout) == "enabled" {
|
if strings.TrimSpace(out.Stdout) == "enabled" {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return notApplicable(
|
return notApplicable(
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"note": "no known auto-update mechanism",
|
"note": "no known auto-update mechanism",
|
||||||
@@ -252,9 +283,11 @@ func linuxPasswordPolicy(ctx context.Context) Result {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return unknown(map[string]any{"error": err.Error()})
|
return unknown(map[string]any{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
body := string(data)
|
body := string(data)
|
||||||
minLen := loginDefsLookup(body, "PASS_MIN_LEN")
|
minLen := loginDefsLookup(body, "PASS_MIN_LEN")
|
||||||
maxDays := loginDefsLookup(body, "PASS_MAX_DAYS")
|
maxDays := loginDefsLookup(body, "PASS_MAX_DAYS")
|
||||||
|
|
||||||
ev := map[string]any{
|
ev := map[string]any{
|
||||||
"pass_min_len": minLen,
|
"pass_min_len": minLen,
|
||||||
"pass_max_days": maxDays,
|
"pass_max_days": maxDays,
|
||||||
@@ -284,12 +317,15 @@ func linuxRemoteLogin(ctx context.Context) Result {
|
|||||||
if !CommandExists("systemctl") {
|
if !CommandExists("systemctl") {
|
||||||
return unknown(map[string]any{"note": "systemctl unavailable"})
|
return unknown(map[string]any{"note": "systemctl unavailable"})
|
||||||
}
|
}
|
||||||
|
|
||||||
state := RunCommand(ctx, "systemctl", "is-active", "ssh.service")
|
state := RunCommand(ctx, "systemctl", "is-active", "ssh.service")
|
||||||
stateAlt := RunCommand(ctx, "systemctl", "is-active", "sshd.service")
|
stateAlt := RunCommand(ctx, "systemctl", "is-active", "sshd.service")
|
||||||
|
|
||||||
merged := strings.TrimSpace(state.Stdout)
|
merged := strings.TrimSpace(state.Stdout)
|
||||||
if merged == "" {
|
if merged == "" {
|
||||||
merged = strings.TrimSpace(stateAlt.Stdout)
|
merged = strings.TrimSpace(stateAlt.Stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
ev := map[string]any{"is_active": merged}
|
ev := map[string]any{"is_active": merged}
|
||||||
switch merged {
|
switch merged {
|
||||||
case "active":
|
case "active":
|
||||||
@@ -299,6 +335,7 @@ func linuxRemoteLogin(ctx context.Context) Result {
|
|||||||
case "":
|
case "":
|
||||||
return notApplicable(ev)
|
return notApplicable(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +370,7 @@ func linuxMalwareProtection(ctx context.Context) Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var active, installed []string
|
var active, installed []string
|
||||||
|
|
||||||
for _, c := range candidates {
|
for _, c := range candidates {
|
||||||
state := strings.TrimSpace(
|
state := strings.TrimSpace(
|
||||||
RunCommand(ctx, "systemctl", "is-active", c.unit).Stdout)
|
RunCommand(ctx, "systemctl", "is-active", c.unit).Stdout)
|
||||||
@@ -351,21 +389,26 @@ func linuxMalwareProtection(ctx context.Context) Result {
|
|||||||
if len(active) > 0 {
|
if len(active) > 0 {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(installed) > 0 {
|
if len(installed) > 0 {
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func nonCommentLines(s string) []string {
|
func nonCommentLines(s string) []string {
|
||||||
out := []string{}
|
out := []string{}
|
||||||
|
|
||||||
for line := range strings.SplitSeq(s, "\n") {
|
for line := range strings.SplitSeq(s, "\n") {
|
||||||
t := strings.TrimSpace(line)
|
t := strings.TrimSpace(line)
|
||||||
if t == "" || strings.HasPrefix(t, "#") {
|
if t == "" || strings.HasPrefix(t, "#") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
out = append(out, t)
|
out = append(out, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,12 +418,15 @@ func kvLookup(body, key string) string {
|
|||||||
if eq <= 0 {
|
if eq <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(line[:eq]) == key {
|
if strings.TrimSpace(line[:eq]) == key {
|
||||||
v := strings.TrimSpace(line[eq+1:])
|
v := strings.TrimSpace(line[eq+1:])
|
||||||
v = strings.Trim(v, `"`)
|
v = strings.Trim(v, `"`)
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,10 +436,12 @@ func loginDefsLookup(body, key string) string {
|
|||||||
if line == "" || strings.HasPrefix(line, "#") {
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := strings.Fields(line)
|
fields := strings.Fields(line)
|
||||||
if len(fields) >= 2 && fields[0] == key {
|
if len(fields) >= 2 && fields[0] == key {
|
||||||
return fields[1]
|
return fields[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ func windowsDiskEncryption(ctx context.Context) Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
out := RunCommand(ctx, "manage-bde", "-status")
|
out := RunCommand(ctx, "manage-bde", "-status")
|
||||||
|
|
||||||
ev := map[string]any{"raw": truncate(out.Stdout, 600)}
|
ev := map[string]any{"raw": truncate(out.Stdout, 600)}
|
||||||
if out.Err != nil {
|
if out.Err != nil {
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
@@ -77,6 +78,7 @@ func windowsScreenLock(ctx context.Context) Result {
|
|||||||
if v == "1" {
|
if v == "1" {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,6 +109,7 @@ func windowsScreenLock(ctx context.Context) Result {
|
|||||||
"raw": truncate(users.Stdout, 400),
|
"raw": truncate(users.Stdout, 400),
|
||||||
}
|
}
|
||||||
users_, anyDisabled, anyEnabled := parseWindowsUserScreenLock(users.Stdout)
|
users_, anyDisabled, anyEnabled := parseWindowsUserScreenLock(users.Stdout)
|
||||||
|
|
||||||
ev["users"] = users_
|
ev["users"] = users_
|
||||||
if len(users_) == 0 {
|
if len(users_) == 0 {
|
||||||
ev["note"] = "no interactive user hives loaded"
|
ev["note"] = "no interactive user hives loaded"
|
||||||
@@ -125,7 +128,9 @@ func windowsScreenLock(ctx context.Context) Result {
|
|||||||
// saver locking enabled.
|
// saver locking enabled.
|
||||||
func parseWindowsUserScreenLock(s string) (map[string]string, bool, bool) {
|
func parseWindowsUserScreenLock(s string) (map[string]string, bool, bool) {
|
||||||
users := map[string]string{}
|
users := map[string]string{}
|
||||||
|
|
||||||
var anyEnabled, anyDisabled bool
|
var anyEnabled, anyDisabled bool
|
||||||
|
|
||||||
for line := range strings.SplitSeq(s, "\n") {
|
for line := range strings.SplitSeq(s, "\n") {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if line == "" {
|
if line == "" {
|
||||||
@@ -139,6 +144,7 @@ func parseWindowsUserScreenLock(s string) (map[string]string, bool, bool) {
|
|||||||
|
|
||||||
sid := strings.TrimSpace(line[:idx])
|
sid := strings.TrimSpace(line[:idx])
|
||||||
value := strings.TrimSpace(line[idx+1:])
|
value := strings.TrimSpace(line[idx+1:])
|
||||||
|
|
||||||
if sid == "" {
|
if sid == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -168,10 +174,12 @@ func windowsFirewall(ctx context.Context) Result {
|
|||||||
"raw": primary.Stdout,
|
"raw": primary.Stdout,
|
||||||
}
|
}
|
||||||
profiles, allEnabled := parseWindowsFirewallProfiles(primary.Stdout)
|
profiles, allEnabled := parseWindowsFirewallProfiles(primary.Stdout)
|
||||||
|
|
||||||
ev["profiles"] = profiles
|
ev["profiles"] = profiles
|
||||||
if allEnabled {
|
if allEnabled {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,11 +193,13 @@ func windowsFirewall(ctx context.Context) Result {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ev := map[string]any{
|
ev := map[string]any{
|
||||||
"backend": "netsh",
|
"backend": "netsh",
|
||||||
"raw": truncate(fallback.Stdout, 600),
|
"raw": truncate(fallback.Stdout, 600),
|
||||||
}
|
}
|
||||||
stateLines, anyOff := parseNetshFirewallStates(fallback.Stdout)
|
stateLines, anyOff := parseNetshFirewallStates(fallback.Stdout)
|
||||||
|
|
||||||
ev["state_lines"] = stateLines
|
ev["state_lines"] = stateLines
|
||||||
if len(stateLines) > 0 && !anyOff {
|
if len(stateLines) > 0 && !anyOff {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
@@ -205,22 +215,28 @@ func parseWindowsFirewallProfiles(s string) (map[string]string, bool) {
|
|||||||
profiles := map[string]string{}
|
profiles := map[string]string{}
|
||||||
allEnabled := true
|
allEnabled := true
|
||||||
any := false
|
any := false
|
||||||
|
|
||||||
for profile := range strings.SplitSeq(s, ";") {
|
for profile := range strings.SplitSeq(s, ";") {
|
||||||
parts := strings.SplitN(strings.TrimSpace(profile), "=", 2)
|
parts := strings.SplitN(strings.TrimSpace(profile), "=", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
name := strings.TrimSpace(parts[0])
|
name := strings.TrimSpace(parts[0])
|
||||||
value := strings.TrimSpace(parts[1])
|
value := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles[name] = value
|
profiles[name] = value
|
||||||
any = true
|
any = true
|
||||||
|
|
||||||
if !strings.EqualFold(value, "true") {
|
if !strings.EqualFold(value, "true") {
|
||||||
allEnabled = false
|
allEnabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return profiles, any && allEnabled
|
return profiles, any && allEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,9 +245,12 @@ func parseWindowsFirewallProfiles(s string) (map[string]string, bool) {
|
|||||||
// case-insensitive.
|
// case-insensitive.
|
||||||
func parseNetshFirewallStates(s string) ([]string, bool) {
|
func parseNetshFirewallStates(s string) ([]string, bool) {
|
||||||
var states []string
|
var states []string
|
||||||
|
|
||||||
anyOff := false
|
anyOff := false
|
||||||
|
|
||||||
for line := range strings.SplitSeq(s, "\n") {
|
for line := range strings.SplitSeq(s, "\n") {
|
||||||
trimmed := strings.TrimSpace(line)
|
trimmed := strings.TrimSpace(line)
|
||||||
|
|
||||||
lower := strings.ToLower(trimmed)
|
lower := strings.ToLower(trimmed)
|
||||||
if !strings.HasPrefix(lower, "state") {
|
if !strings.HasPrefix(lower, "state") {
|
||||||
continue
|
continue
|
||||||
@@ -243,6 +262,7 @@ func parseNetshFirewallStates(s string) ([]string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
value := fields[len(fields)-1]
|
value := fields[len(fields)-1]
|
||||||
|
|
||||||
states = append(states, value)
|
states = append(states, value)
|
||||||
if value != "on" {
|
if value != "on" {
|
||||||
anyOff = true
|
anyOff = true
|
||||||
@@ -264,6 +284,7 @@ func windowsTimeSync(ctx context.Context) Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ev := map[string]any{"raw": truncate(out.Stdout, 400)}
|
ev := map[string]any{"raw": truncate(out.Stdout, 400)}
|
||||||
|
|
||||||
lower := strings.ToLower(out.Stdout)
|
lower := strings.ToLower(out.Stdout)
|
||||||
if strings.Contains(lower, "source:") && !strings.Contains(lower, "local cmos clock") {
|
if strings.Contains(lower, "source:") && !strings.Contains(lower, "local cmos clock") {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
@@ -279,6 +300,7 @@ func windowsOSVersion(ctx context.Context) Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
caption := powershell(ctx, `(Get-CimInstance Win32_OperatingSystem).Caption`)
|
caption := powershell(ctx, `(Get-CimInstance Win32_OperatingSystem).Caption`)
|
||||||
|
|
||||||
return pass(
|
return pass(
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"version": out.Stdout,
|
"version": out.Stdout,
|
||||||
@@ -294,21 +316,26 @@ func windowsAutoUpdate(ctx context.Context) Result {
|
|||||||
`-ErrorAction SilentlyContinue; `+
|
`-ErrorAction SilentlyContinue; `+
|
||||||
`"$($au.NoAutoUpdate);$($au.AUOptions)"`,
|
`"$($au.NoAutoUpdate);$($au.AUOptions)"`,
|
||||||
)
|
)
|
||||||
|
|
||||||
ev := map[string]any{}
|
ev := map[string]any{}
|
||||||
if out.Err != nil {
|
if out.Err != nil {
|
||||||
ev["error"] = out.Err.Error()
|
ev["error"] = out.Err.Error()
|
||||||
ev["stderr"] = out.Stderr
|
ev["stderr"] = out.Stderr
|
||||||
|
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(strings.TrimSpace(out.Stdout), ";", 2)
|
parts := strings.SplitN(strings.TrimSpace(out.Stdout), ";", 2)
|
||||||
|
|
||||||
var noAutoUpdate, auOptions string
|
var noAutoUpdate, auOptions string
|
||||||
if len(parts) >= 1 {
|
if len(parts) >= 1 {
|
||||||
noAutoUpdate = strings.TrimSpace(parts[0])
|
noAutoUpdate = strings.TrimSpace(parts[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(parts) >= 2 {
|
if len(parts) >= 2 {
|
||||||
auOptions = strings.TrimSpace(parts[1])
|
auOptions = strings.TrimSpace(parts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
ev["no_auto_update"] = noAutoUpdate
|
ev["no_auto_update"] = noAutoUpdate
|
||||||
ev["au_options"] = auOptions
|
ev["au_options"] = auOptions
|
||||||
|
|
||||||
@@ -316,6 +343,7 @@ func windowsAutoUpdate(ctx context.Context) Result {
|
|||||||
if noAutoUpdate == "1" {
|
if noAutoUpdate == "1" {
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AUOptions semantics:
|
// AUOptions semantics:
|
||||||
// 2 — notify before download (no auto-install)
|
// 2 — notify before download (no auto-install)
|
||||||
// 3 — auto download, prompt to install
|
// 3 — auto download, prompt to install
|
||||||
@@ -340,7 +368,9 @@ func windowsAutoUpdate(ctx context.Context) Result {
|
|||||||
ev["wuauserv"] = "running"
|
ev["wuauserv"] = "running"
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
ev["wuauserv"] = "stopped"
|
ev["wuauserv"] = "stopped"
|
||||||
|
|
||||||
return fail(ev)
|
return fail(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,6 +386,7 @@ func windowsPasswordPolicy(ctx context.Context) Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ev := map[string]any{"raw": truncate(out.Stdout, 400)}
|
ev := map[string]any{"raw": truncate(out.Stdout, 400)}
|
||||||
|
|
||||||
lower := strings.ToLower(out.Stdout)
|
lower := strings.ToLower(out.Stdout)
|
||||||
if strings.Contains(lower, "minimum password length") && !strings.Contains(lower, "length: 0") {
|
if strings.Contains(lower, "minimum password length") && !strings.Contains(lower, "length: 0") {
|
||||||
return pass(ev)
|
return pass(ev)
|
||||||
@@ -381,6 +412,7 @@ func windowsMalwareProtection(ctx context.Context) Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(out.Stdout, ";")
|
parts := strings.Split(out.Stdout, ";")
|
||||||
|
|
||||||
ev := map[string]any{"raw": out.Stdout}
|
ev := map[string]any{"raw": out.Stdout}
|
||||||
if len(parts) < 3 {
|
if len(parts) < 3 {
|
||||||
return unknown(ev)
|
return unknown(ev)
|
||||||
@@ -389,8 +421,10 @@ func windowsMalwareProtection(ctx context.Context) Result {
|
|||||||
antivirusOn := strings.EqualFold(strings.TrimSpace(parts[0]), "True")
|
antivirusOn := strings.EqualFold(strings.TrimSpace(parts[0]), "True")
|
||||||
realtimeOn := strings.EqualFold(strings.TrimSpace(parts[1]), "True")
|
realtimeOn := strings.EqualFold(strings.TrimSpace(parts[1]), "True")
|
||||||
serviceOn := strings.EqualFold(strings.TrimSpace(parts[2]), "True")
|
serviceOn := strings.EqualFold(strings.TrimSpace(parts[2]), "True")
|
||||||
|
|
||||||
ev["antivirus_enabled"] = antivirusOn
|
ev["antivirus_enabled"] = antivirusOn
|
||||||
ev["real_time_protection"] = realtimeOn
|
ev["real_time_protection"] = realtimeOn
|
||||||
|
|
||||||
ev["am_service_enabled"] = serviceOn
|
ev["am_service_enabled"] = serviceOn
|
||||||
if len(parts) >= 4 {
|
if len(parts) >= 4 {
|
||||||
ev["signatures_last_updated"] = strings.TrimSpace(parts[3])
|
ev["signatures_last_updated"] = strings.TrimSpace(parts[3])
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ func commandCandidates(cmd string) []string {
|
|||||||
if systemRoot == "" {
|
if systemRoot == "" {
|
||||||
systemRoot = `C:\Windows`
|
systemRoot = `C:\Windows`
|
||||||
}
|
}
|
||||||
|
|
||||||
system32 := filepath.Join(systemRoot, "System32")
|
system32 := filepath.Join(systemRoot, "System32")
|
||||||
|
|
||||||
switch strings.ToLower(cmd) {
|
switch strings.ToLower(cmd) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func collectOSVersion() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
out, _ = runQuiet(ctx, "uname", "-sr")
|
out, _ = runQuiet(ctx, "uname", "-sr")
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,5 +54,6 @@ func collectSerialNumber() string {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
out, _ := runQuiet(ctx, "kenv", "smbios.system.serial")
|
out, _ := runQuiet(ctx, "kenv", "smbios.system.serial")
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func collectOSVersion() string {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
out, _ := runQuiet(ctx, "uname", "-sr")
|
out, _ := runQuiet(ctx, "uname", "-sr")
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func collectOSVersion() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
out, _ = runQuiet(ctx, "uname", "-sr")
|
out, _ = runQuiet(ctx, "uname", "-sr")
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,5 +67,6 @@ func collectSerialNumber() string {
|
|||||||
"-Command",
|
"-Command",
|
||||||
"(Get-CimInstance Win32_BIOS).SerialNumber",
|
"(Get-CimInstance Win32_BIOS).SerialNumber",
|
||||||
)
|
)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ func Install(cfg Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = sf.Close() }()
|
defer func() { _ = sf.Close() }()
|
||||||
|
|
||||||
if err := rcTmpl.Execute(sf, cfg); err != nil {
|
if err := rcTmpl.Execute(sf, cfg); err != nil {
|
||||||
return fmt.Errorf("cannot render rc.d script: %w", err)
|
return fmt.Errorf("cannot render rc.d script: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ func Install(cfg Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = f.Close() }()
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
if err := tmpl.Execute(f, cfg); err != nil {
|
if err := tmpl.Execute(f, cfg); err != nil {
|
||||||
return fmt.Errorf("cannot render systemd unit: %w", err)
|
return fmt.Errorf("cannot render systemd unit: %w", err)
|
||||||
}
|
}
|
||||||
@@ -87,6 +88,7 @@ func Install(cfg Config) error {
|
|||||||
|
|
||||||
func Uninstall(cfg Config) error {
|
func Uninstall(cfg Config) error {
|
||||||
_ = exec.Command("systemctl", "disable", "--now", "probo-agent.service").Run()
|
_ = exec.Command("systemctl", "disable", "--now", "probo-agent.service").Run()
|
||||||
|
|
||||||
if err := os.Remove(systemdUnitPath); err != nil && !errors.Is(err, os.ErrNotExist) {
|
if err := os.Remove(systemdUnitPath); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
return fmt.Errorf("cannot remove systemd unit: %w", err)
|
return fmt.Errorf("cannot remove systemd unit: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,11 @@ func Install(cfg Config) error {
|
|||||||
if cfg.ExePath == "" {
|
if cfg.ExePath == "" {
|
||||||
return errors.New("executable path is required")
|
return errors.New("executable path is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Dir == "" {
|
if cfg.Dir == "" {
|
||||||
return errors.New("state directory is required")
|
return errors.New("state directory is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
name := DefaultWindowsName
|
name := DefaultWindowsName
|
||||||
|
|
||||||
bin := fmt.Sprintf(`"%s" run --dir "%s"`, cfg.ExePath, cfg.Dir)
|
bin := fmt.Sprintf(`"%s" run --dir "%s"`, cfg.ExePath, cfg.Dir)
|
||||||
@@ -45,6 +47,7 @@ func Install(cfg Config) error {
|
|||||||
).CombinedOutput(); err != nil {
|
).CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf("cannot run sc.exe create: %w: %s", err, strings.TrimSpace(string(out)))
|
return fmt.Errorf("cannot run sc.exe create: %w: %s", err, strings.TrimSpace(string(out)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart on failure.
|
// Restart on failure.
|
||||||
if out, err := exec.Command(
|
if out, err := exec.Command(
|
||||||
"sc.exe",
|
"sc.exe",
|
||||||
@@ -57,14 +60,17 @@ func Install(cfg Config) error {
|
|||||||
).CombinedOutput(); err != nil {
|
).CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf("cannot run sc.exe failure: %w: %s", err, strings.TrimSpace(string(out)))
|
return fmt.Errorf("cannot run sc.exe failure: %w: %s", err, strings.TrimSpace(string(out)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if out, err := exec.Command("sc.exe", "start", name).CombinedOutput(); err != nil {
|
if out, err := exec.Command("sc.exe", "start", name).CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf("cannot run sc.exe start: %w: %s", err, strings.TrimSpace(string(out)))
|
return fmt.Errorf("cannot run sc.exe start: %w: %s", err, strings.TrimSpace(string(out)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Uninstall(cfg Config) error {
|
func Uninstall(cfg Config) error {
|
||||||
name := DefaultWindowsName
|
name := DefaultWindowsName
|
||||||
|
|
||||||
_ = exec.Command("sc.exe", "stop", name).Run()
|
_ = exec.Command("sc.exe", "stop", name).Run()
|
||||||
if out, err := exec.Command("sc.exe", "delete", name).CombinedOutput(); err != nil {
|
if out, err := exec.Command("sc.exe", "delete", name).CombinedOutput(); err != nil {
|
||||||
msg := strings.TrimSpace(string(out))
|
msg := strings.TrimSpace(string(out))
|
||||||
@@ -74,5 +80,6 @@ func Uninstall(cfg Config) error {
|
|||||||
|
|
||||||
return fmt.Errorf("cannot run sc.exe delete: %w: %s", err, msg)
|
return fmt.Errorf("cannot run sc.exe delete: %w: %s", err, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import "strings"
|
|||||||
|
|
||||||
func isWindowsServiceMissing(out string) bool {
|
func isWindowsServiceMissing(out string) bool {
|
||||||
lower := strings.ToLower(out)
|
lower := strings.ToLower(out)
|
||||||
|
|
||||||
return strings.Contains(lower, "1060") ||
|
return strings.Contains(lower, "1060") ||
|
||||||
strings.Contains(lower, "does not exist as an installed service") ||
|
strings.Contains(lower, "does not exist as an installed service") ||
|
||||||
strings.Contains(lower, "specified service does not exist")
|
strings.Contains(lower, "specified service does not exist")
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ func replaceBinary(dst, src string) error {
|
|||||||
// Try to roll back the running binary swap.
|
// Try to roll back the running binary swap.
|
||||||
_ = os.Rename(oldPath, dst)
|
_ = os.Rename(oldPath, dst)
|
||||||
_ = os.Remove(staging)
|
_ = os.Remove(staging)
|
||||||
|
|
||||||
return fmt.Errorf("cannot install new binary at %s: %w", dst, err)
|
return fmt.Errorf("cannot install new binary at %s: %w", dst, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,5 +69,6 @@ func CleanupAfterRestart(exePath string) {
|
|||||||
if exePath == "" {
|
if exePath == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = os.Remove(exePath + oldSuffix)
|
_ = os.Remove(exePath + oldSuffix)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user