package report import ( "context" "os" "regexp" "strconv" "strings" "time" ) // collectAnyone gathers Anyone Protocol relay/client health information. func collectAnyone() *AnyoneReport { r := &AnyoneReport{} // 1. RelayActive: systemctl is-active orama-anyone-relay { ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second) defer cancel() if out, err := runCmd(ctx, "systemctl", "is-active", "orama-anyone-relay"); err == nil { r.RelayActive = strings.TrimSpace(out) == "active" } } // 2. ClientActive: systemctl is-active orama-anyone-client { ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second) defer cancel() if out, err := runCmd(ctx, "systemctl", "is-active", "orama-anyone-client"); err == nil { r.ClientActive = strings.TrimSpace(out) == "active" } } // 3. Mode: derive from active state if r.RelayActive { r.Mode = "relay" } else if r.ClientActive { r.Mode = "client" } // 4. ORPortListening, SocksListening, ControlListening: check ports in ss -tlnp { ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second) defer cancel() if out, err := runCmd(ctx, "ss", "-tlnp"); err == nil { r.ORPortListening = portIsListening(out, 9001) r.SocksListening = portIsListening(out, 9050) r.ControlListening = portIsListening(out, 9051) } } // 5. Bootstrapped / BootstrapPct: parse last "Bootstrapped" line from notices.log { ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second) defer cancel() if out, err := runCmd(ctx, "bash", "-c", `grep "Bootstrapped" /var/log/anon/notices.log 2>/dev/null | tail -1`); err == nil { out = strings.TrimSpace(out) if out != "" { // Parse percentage from lines like: // "... Bootstrapped 100% (done): Done" // "... Bootstrapped 85%: Loading relay descriptors" re := regexp.MustCompile(`Bootstrapped\s+(\d+)%`) if m := re.FindStringSubmatch(out); len(m) >= 2 { if pct, err := strconv.Atoi(m[1]); err == nil { r.BootstrapPct = pct r.Bootstrapped = pct == 100 } } } } } // 6. Fingerprint: read /var/lib/anon/fingerprint if data, err := os.ReadFile("/var/lib/anon/fingerprint"); err == nil { line := strings.TrimSpace(string(data)) // The file may contain "nickname fingerprint" — extract just the fingerprint. fields := strings.Fields(line) if len(fields) >= 2 { r.Fingerprint = fields[1] } else if len(fields) == 1 { r.Fingerprint = fields[0] } } // 7. Nickname: extract from anonrc config { ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second) defer cancel() if out, err := runCmd(ctx, "bash", "-c", `grep "^Nickname" /etc/anon/anonrc 2>/dev/null | awk '{print $2}'`); err == nil { r.Nickname = strings.TrimSpace(out) } } return r }