cli, daemon: add olares-cli status backed by olaresd /system/status (#2917)

* cli, daemon: add `olares-cli status` backed by olaresd /system/status

Adds a new top-level `olares-cli status` command that calls the local
olaresd daemon's `/system/status` HTTP endpoint and prints either a
grouped, annotated human-readable report or raw JSON.

To avoid duplicating the response schema, the daemon's `state` struct
and related enums are extracted into a new shared package
`cli/pkg/daemon/state`. The daemon now type-aliases those types so all
existing daemon call sites keep compiling unchanged.

Made-with: Cursor

* cli: drop unused state.APIResponse envelope type

The HTTP client in cli/pkg/daemon/api/client.go intentionally uses an
inline anonymous envelope with json.RawMessage for the data field so
that --json mode can passthrough the bytes verbatim, so the public
APIResponse{Data State} type defined here had zero references. Remove
it; if a strongly-typed consumer ever shows up, re-add then.

Addresses Cursor Bugbot feedback on PR #2917.

Made-with: Cursor
This commit is contained in:
Peng Peng 2026-04-21 00:29:50 +08:00 committed by GitHub
parent 7841231c2f
commit 61d793e4c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1261 additions and 132 deletions

View file

@ -19,5 +19,6 @@ func NewOSCommands() []*cobra.Command {
NewCmdStart(),
NewCmdStop(),
NewCmdUpgradeOs(),
NewCmdStatus(),
}
}

270
cli/cmd/ctl/os/status.go Normal file
View file

@ -0,0 +1,270 @@
package os
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/beclab/Olares/cli/pkg/daemon/api"
"github.com/beclab/Olares/cli/pkg/daemon/state"
)
// statusOptions holds the flags accepted by `olares-cli status`.
type statusOptions struct {
endpoint string
json bool
timeout time.Duration
}
// NewCmdStatus returns the cobra command for `olares-cli status`.
//
// The command is a thin wrapper around the local olaresd daemon's
// /system/status endpoint. olaresd binds to 127.0.0.1:18088 and the
// endpoint is loopback-only on the daemon side, so the command must
// run on the same host as olaresd (typically the master node).
func NewCmdStatus() *cobra.Command {
opts := &statusOptions{
endpoint: api.DefaultEndpoint,
timeout: api.DefaultTimeout,
}
long := buildStatusLong()
cmd := &cobra.Command{
Use: "status",
Short: "Print the current Olares system status reported by olaresd",
Long: long,
Example: ` # Pretty-printed grouped report (default)
olares-cli status
# Raw JSON payload (forwarded verbatim from olaresd)
olares-cli status --json | jq
# Non-default daemon endpoint
olares-cli status --endpoint http://127.0.0.1:18088 --timeout 10s`,
Run: func(cmd *cobra.Command, args []string) {
if err := runStatus(cmd.Context(), cmd.OutOrStdout(), opts); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
},
}
cmd.Flags().StringVar(&opts.endpoint, "endpoint", opts.endpoint,
"Base URL of the local olaresd daemon. Override only when olaresd binds to a non-default address.")
cmd.Flags().BoolVar(&opts.json, "json", opts.json,
"Print the raw JSON payload from olaresd (the data field), suitable for piping to tools like jq.")
cmd.Flags().DurationVar(&opts.timeout, "timeout", opts.timeout,
"Maximum time to wait for the olaresd response.")
return cmd
}
func runStatus(ctx context.Context, out io.Writer, opts *statusOptions) error {
if ctx == nil {
ctx = context.Background()
}
client := api.NewClient(opts.endpoint, opts.timeout)
st, raw, err := client.GetSystemStatus(ctx)
if err != nil {
return err
}
if opts.json {
// Pretty-print to keep the output friendly when not piped.
var pretty interface{}
if jerr := json.Unmarshal(raw, &pretty); jerr == nil {
b, merr := json.MarshalIndent(pretty, "", " ")
if merr == nil {
_, err = fmt.Fprintln(out, string(b))
return err
}
}
_, err = fmt.Fprintln(out, string(raw))
return err
}
return printHumanReadable(out, st)
}
// printHumanReadable renders the State as grouped sections, each with
// padded labels and inline annotations for state values. The width
// is chosen so that values line up when printed in a typical 80-col
// terminal without truncating common content.
func printHumanReadable(out io.Writer, s *state.State) error {
const labelWidth = 20
w := &writer{out: out, label: labelWidth}
w.section("Olares")
w.kv("State", string(s.TerminusState), s.TerminusState.Describe())
w.kv("Olaresd state", string(s.TerminusdState), "")
w.kv("Name", strPtr(s.TerminusName), "")
w.kv("Version", strPtr(s.TerminusVersion), "")
w.kv("Olaresd version", strPtr(s.OlaresdVersion), "")
w.kv("Installed at", formatEpoch(s.InstalledTime), "")
w.kv("Initialized at", formatEpoch(s.InitializedTime), "")
w.section("System")
w.kv("Device", strPtr(s.DeviceName), "")
w.kv("Hostname", strPtr(s.HostName), "")
w.kv("OS", joinOSInfo(s.OsType, s.OsArch, s.OsInfo), "")
w.kv("OS version", s.OsVersion, "")
w.kv("CPU", s.CpuInfo, "")
w.kv("Memory", s.Memory, "")
w.kv("Disk", s.Disk, "")
w.kv("GPU", strPtr(s.GpuInfo), "")
w.section("Network")
w.kv("Wired", yesNo(s.WiredConnected), "")
w.kv("Wi-Fi", yesNo(s.WifiConnected), "")
w.kv("Wi-Fi SSID", strPtr(s.WifiSSID), "")
w.kv("Host IP", s.HostIP, "")
w.kv("External IP", s.ExternalIP, "")
w.section("Install / Uninstall")
w.kv("Installing", string(s.InstallingState), s.InstallingProgress)
w.kv("Uninstalling", string(s.UninstallingState), s.UninstallingProgress)
w.section("Upgrade")
w.kv("Target", s.UpgradingTarget, "")
w.kv("State", string(s.UpgradingState), s.UpgradingProgress)
w.kv("Step", s.UpgradingStep, "")
w.kv("Last error", s.UpgradingError, "")
w.kv("Download state", string(s.UpgradingDownloadState), s.UpgradingDownloadProgress)
w.kv("Download step", s.UpgradingDownloadStep, "")
w.kv("Download error", s.UpgradingDownloadError, "")
if s.UpgradingRetryNum > 0 {
w.kv("Retry count", fmt.Sprintf("%d", s.UpgradingRetryNum), "")
}
if s.UpgradingNextRetryAt != nil {
w.kv("Next retry at", s.UpgradingNextRetryAt.Local().Format(time.RFC3339), "")
}
w.section("Logs collection")
w.kv("State", string(s.CollectingLogsState), s.CollectingLogsError)
w.section("Pressures")
if len(s.Pressure) == 0 {
w.line("(none)")
} else {
for _, p := range s.Pressure {
w.kv(p.Type, p.Message, "")
}
}
w.section("Other")
w.kv("FRP enabled", s.FRPEnable, "")
w.kv("FRP server", s.DefaultFRPServer, "")
w.kv("Container mode", strPtr(s.ContainerMode), "")
return w.err
}
// writer collects formatting helpers in one place so the section
// printer above stays declarative.
type writer struct {
out io.Writer
label int
err error
}
func (w *writer) writef(format string, a ...interface{}) {
if w.err != nil {
return
}
_, w.err = fmt.Fprintf(w.out, format, a...)
}
func (w *writer) section(name string) {
w.writef("\n%s\n", name)
}
func (w *writer) line(s string) {
w.writef(" %s\n", s)
}
func (w *writer) kv(key, value, note string) {
if value == "" {
value = "-"
}
if note != "" {
w.writef(" %-*s %s (%s)\n", w.label, key, value, note)
} else {
w.writef(" %-*s %s\n", w.label, key, value)
}
}
func strPtr(p *string) string {
if p == nil {
return ""
}
return *p
}
func yesNo(b bool) string {
if b {
return "yes"
}
return "no"
}
func formatEpoch(p *int64) string {
if p == nil || *p == 0 {
return ""
}
return time.Unix(*p, 0).Local().Format("2006-01-02 15:04:05 -0700")
}
func joinOSInfo(parts ...string) string {
out := make([]string, 0, len(parts))
for _, p := range parts {
if p != "" {
out = append(out, p)
}
}
return strings.Join(out, " ")
}
// buildStatusLong constructs the cobra Long help text. State
// descriptions are sourced from state.TerminusState.Describe() so
// that adding a new state here automatically updates the help.
func buildStatusLong() string {
var b strings.Builder
b.WriteString(`Print the current Olares system status reported by the local olaresd daemon.
This command sends an HTTP GET to the daemon's /system/status endpoint
(default: http://127.0.0.1:18088/system/status). The endpoint is
loopback-only on the daemon side, so this command must run on the same
host as olaresd (typically the master node).
The default output is a grouped, human-readable report:
Olares installation lifecycle, version, names, key timestamps
System hardware and OS facts about the host
Network connectivity and IP addresses
Install / Uninstall progress of in-flight install or uninstall
Upgrade progress of in-flight upgrade (download + install phases)
Logs collection state of the most recent log collection job
Pressures active kubelet node pressure conditions, if any
Other FRP, container mode, etc.
Pass --json to get the raw daemon payload instead, useful for scripting.
Olares system states (TerminusState):
`)
for _, s := range state.AllTerminusStates() {
fmt.Fprintf(&b, " %-20s %s\n", string(s), s.Describe())
}
return b.String()
}

View file

@ -0,0 +1,138 @@
// Package api is a thin HTTP client for the local olaresd daemon.
//
// olaresd listens on TCP port 18088 by default and exposes JSON
// endpoints under paths like /system/status, /system/ifs, etc. All
// endpoints are loopback-only (RequireLocal middleware on the daemon
// side), so the client expects to talk to 127.0.0.1.
package api
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/beclab/Olares/cli/pkg/daemon/state"
)
// DefaultEndpoint is the loopback URL where olaresd listens by
// default. Callers normally do not need to override this.
const DefaultEndpoint = "http://127.0.0.1:18088"
// DefaultTimeout is the per-request timeout used when the caller
// does not supply one.
const DefaultTimeout = 5 * time.Second
// Client is a small HTTP client for the olaresd daemon. The zero
// value uses DefaultEndpoint and DefaultTimeout; callers may
// override either field before issuing requests.
type Client struct {
// Endpoint is the base URL of olaresd, e.g.
// "http://127.0.0.1:18088". Trailing slashes are stripped.
Endpoint string
// Timeout bounds the total time spent on a single HTTP request,
// including dialing, TLS, and reading the response body.
Timeout time.Duration
// HTTPClient lets tests substitute a custom transport. When
// nil, the client constructs one on demand using Timeout.
HTTPClient *http.Client
}
// NewClient returns a Client that hits the given endpoint with the
// supplied timeout. Pass an empty endpoint or zero timeout to fall
// back to DefaultEndpoint / DefaultTimeout.
func NewClient(endpoint string, timeout time.Duration) *Client {
return &Client{Endpoint: endpoint, Timeout: timeout}
}
func (c *Client) endpoint() string {
ep := c.Endpoint
if ep == "" {
ep = DefaultEndpoint
}
return strings.TrimRight(ep, "/")
}
func (c *Client) httpClient() *http.Client {
if c.HTTPClient != nil {
return c.HTTPClient
}
t := c.Timeout
if t <= 0 {
t = DefaultTimeout
}
return &http.Client{Timeout: t}
}
// GetSystemStatus calls GET /system/status and returns:
// - the parsed State struct for programmatic consumption;
// - the raw `data` JSON bytes so callers that want to forward
// the response (for example, the CLI's --json mode) can do so
// without a re-marshal round trip.
//
// The error message always contains the endpoint URL and the HTTP
// status (when applicable) so users can tell the difference between
// "olaresd is down" and "olaresd returned an error".
func (c *Client) GetSystemStatus(ctx context.Context) (*state.State, []byte, error) {
u, err := url.Parse(c.endpoint())
if err != nil {
return nil, nil, fmt.Errorf("invalid olaresd endpoint %q: %w", c.endpoint(), err)
}
u.Path = "/system/status"
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, nil, fmt.Errorf("build request to %s: %w", u.String(), err)
}
req.Header.Set("Accept", "application/json")
resp, err := c.httpClient().Do(req)
if err != nil {
return nil, nil, fmt.Errorf("request olaresd at %s (is olaresd running?): %w", u.String(), err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, fmt.Errorf("read response from %s: %w", u.String(), err)
}
if resp.StatusCode != http.StatusOK {
return nil, nil, fmt.Errorf("olaresd %s returned HTTP %d: %s", u.String(), resp.StatusCode, truncate(string(body), 200))
}
// olaresd wraps payloads as {code, message, data}. We need the
// raw `data` slice both to populate State and to expose it
// verbatim to --json callers.
var raw struct {
Code int `json:"code"`
Message string `json:"message"`
Data json.RawMessage `json:"data"`
}
if err := json.Unmarshal(body, &raw); err != nil {
return nil, nil, fmt.Errorf("decode response envelope from %s: %w", u.String(), err)
}
if raw.Code != http.StatusOK {
return nil, nil, fmt.Errorf("olaresd returned code=%d message=%q", raw.Code, raw.Message)
}
var s state.State
if err := json.Unmarshal(raw.Data, &s); err != nil {
return nil, nil, fmt.Errorf("decode state payload from %s: %w", u.String(), err)
}
return &s, []byte(raw.Data), nil
}
func truncate(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n] + "..."
}

View file

@ -0,0 +1,202 @@
package state
// TerminusDState is the lifecycle state of the olaresd daemon process.
type TerminusDState string
const (
// Initialize means olaresd has just started and is still
// bootstrapping its watchers and configuration.
Initialize TerminusDState = "initialize"
// Running means olaresd has finished initialization and is
// serving requests normally.
Running TerminusDState = "running"
)
// ProcessingState is the lifecycle of a long-running operation that
// olaresd reports progress for (install, uninstall, upgrade, log
// collection).
type ProcessingState string
const (
// Completed means the operation finished successfully.
Completed ProcessingState = "completed"
// Failed means the operation finished with an error. Inspect the
// associated *Error field for details.
Failed ProcessingState = "failed"
// InProgress means the operation is currently running.
InProgress ProcessingState = "in-progress"
)
// TerminusState is the high-level state machine value for the Olares
// system as observed from this node. Use Describe() to obtain a
// human-readable, one-line summary suitable for end-user output.
type TerminusState string
const (
// NotInstalled means Olares is not installed on this node.
NotInstalled TerminusState = "not-installed"
// Installing means an installation is currently in progress.
Installing TerminusState = "installing"
// InstallFailed means the most recent installation attempt
// failed. Re-run install or uninstall to recover.
InstallFailed TerminusState = "install-failed"
// Uninitialized means Olares is installed but the admin user has
// not completed initial activation yet.
Uninitialized TerminusState = "uninitialized"
// Initializing means the admin user is going through the initial
// activation flow.
Initializing TerminusState = "initializing"
// InitializeFailed means the initial activation failed.
InitializeFailed TerminusState = "initialize-failed"
// TerminusRunning means Olares is fully installed, activated,
// and all key pods are healthy.
TerminusRunning TerminusState = "terminus-running"
// InvalidIpAddress means the node's IP has changed since
// installation; run change-ip to fix it.
InvalidIpAddress TerminusState = "invalid-ip-address"
// SystemError means one or more critical pods are not running,
// or the cluster API is unreachable.
SystemError TerminusState = "system-error"
// SelfRepairing means olaresd is automatically attempting to
// recover from a system error.
SelfRepairing TerminusState = "self-repairing"
// IPChanging means a change-ip operation is currently running.
IPChanging TerminusState = "ip-changing"
// IPChangeFailed means the most recent change-ip attempt failed.
IPChangeFailed TerminusState = "ip-change-failed"
// AddingNode means a worker node is currently being joined.
AddingNode TerminusState = "adding-node"
// RemovingNode means a worker node is currently being removed.
RemovingNode TerminusState = "removing-node"
// Uninstalling means an uninstall is currently in progress.
Uninstalling TerminusState = "uninstalling"
// Upgrading means an upgrade install phase is currently running.
// The dedicated download phase does not flip TerminusState.
Upgrading TerminusState = "upgrading"
// DiskModifing means a storage reconfiguration is in progress.
DiskModifing TerminusState = "disk-modifing"
// Shutdown means the system is in the process of shutting down.
Shutdown TerminusState = "shutdown"
// Restarting means the node has been up for less than the
// stabilization window (3 minutes for healthy systems, 10 for
// degraded ones), so reported pod state may be stale.
Restarting TerminusState = "restarting"
// Checking means olaresd has not yet completed the first status
// probe. This is the default value before WatchStatus runs.
Checking TerminusState = "checking"
// NetworkNotReady means no usable internal IPv4 address was
// detected on this node.
NetworkNotReady TerminusState = "network-not-ready"
)
// String returns the wire value of the state, allowing TerminusState
// to satisfy fmt.Stringer.
func (s TerminusState) String() string {
return string(s)
}
// Describe returns a one-line, end-user oriented explanation of the
// state value. Empty values are rendered as "unknown state". Unknown
// values are returned as-is so the CLI can still display them.
func (s TerminusState) Describe() string {
switch s {
case NotInstalled:
return "Olares is not installed on this node"
case Installing:
return "Olares is currently being installed"
case InstallFailed:
return "the most recent install attempt failed"
case Uninitialized:
return "Olares is installed but the admin user has not been activated yet"
case Initializing:
return "the admin user activation is in progress"
case InitializeFailed:
return "the admin user activation failed"
case TerminusRunning:
return "Olares is running normally"
case InvalidIpAddress:
return "the node IP changed since install; run change-ip to recover"
case SystemError:
return "one or more critical pods are not running"
case SelfRepairing:
return "olaresd is attempting automatic recovery"
case IPChanging:
return "a change-ip operation is in progress"
case IPChangeFailed:
return "the most recent change-ip attempt failed"
case AddingNode:
return "a worker node is being joined"
case RemovingNode:
return "a worker node is being removed"
case Uninstalling:
return "Olares is being uninstalled"
case Upgrading:
return "an upgrade is being applied"
case DiskModifing:
return "the storage layout is being modified"
case Shutdown:
return "the system is shutting down"
case Restarting:
return "the node was just restarted, status will stabilize shortly"
case Checking:
return "olaresd has not finished the first status probe yet"
case NetworkNotReady:
return "no usable internal IPv4 address detected"
case "":
return "unknown state"
default:
return string(s)
}
}
// AllTerminusStates returns the full list of TerminusState values in
// a stable, documentation-friendly order. It is used by the CLI's
// long help text and by the docs generator.
func AllTerminusStates() []TerminusState {
return []TerminusState{
Checking,
NetworkNotReady,
NotInstalled,
Installing,
InstallFailed,
Uninitialized,
Initializing,
InitializeFailed,
TerminusRunning,
Restarting,
InvalidIpAddress,
IPChanging,
IPChangeFailed,
SystemError,
SelfRepairing,
AddingNode,
RemovingNode,
Uninstalling,
Upgrading,
DiskModifing,
Shutdown,
}
}

View file

@ -0,0 +1,234 @@
// Package state defines the data types exposed by the local olaresd
// daemon's GET /system/status endpoint.
//
// These types are intentionally placed in the cli module so that both
// the olaresd daemon and the olares-cli command line tool can share
// the same wire format. The daemon (which already imports this module
// via its go.mod) re-exports these types as aliases, while the CLI
// uses them directly to unmarshal the HTTP response.
//
// Only data types belong here. Business logic that depends on
// daemon-internal packages (state validators, status probing, etc.)
// must remain in the daemon module.
package state
import "time"
// State is the full system status snapshot maintained by olaresd.
// It is refreshed every 5s by the daemon's status watcher and served
// as the `data` field of the GET /system/status response.
//
// All fields use JSON tags that match the wire format byte for byte;
// do not rename JSON keys without updating every consumer (CLI,
// frontend, mDNS clients, etc.).
type State struct {
// TerminusdState is the lifecycle state of the olaresd daemon
// itself. Possible values: "initialize" (just started, still
// bootstrapping) or "running" (fully initialized).
TerminusdState TerminusDState `json:"terminusdState"`
// TerminusState is the high-level state of the Olares system.
// It drives both UI display and command admission control. See
// the TerminusState constants below for the full enumeration
// and call Describe() to obtain a one-line explanation.
TerminusState TerminusState `json:"terminusState"`
// TerminusName is the Olares ID of the admin user, e.g.
// "alice@olares.cn". It is read from the local release file when
// available and refreshed from the cluster once Olares is up.
TerminusName *string `json:"terminusName,omitempty"`
// TerminusVersion is the installed Olares version (semver), e.g.
// "1.12.0".
TerminusVersion *string `json:"terminusVersion,omitempty"`
// InstalledTime is the Unix epoch (seconds) at which Olares
// finished installing on this node. Nil before install completes.
InstalledTime *int64 `json:"installedTime,omitempty"`
// InitializedTime is the Unix epoch (seconds) at which the admin
// user finished the initial activation. Nil before activation.
InitializedTime *int64 `json:"initializedTime,omitempty"`
// OlaresdVersion is the running olaresd binary version. Useful
// for diagnosing version drift between olaresd and the rest of
// Olares after a partial upgrade.
OlaresdVersion *string `json:"olaresdVersion,omitempty"`
// InstallFinishedTime is daemon-internal: the wall clock time at
// which the most recent install finished. Used to derive
// InstalledTime when the cluster is not reachable yet. Excluded
// from the wire format.
InstallFinishedTime *time.Time `json:"-"`
// DeviceName is the user-friendly device name (model / chassis
// name) detected from the host.
DeviceName *string `json:"device_name,omitempty"`
// HostName is the kernel hostname of the node.
HostName *string `json:"host_name,omitempty"`
// OsType is the OS family, e.g. "linux" or "darwin".
OsType string `json:"os_type"`
// OsArch is the CPU architecture, e.g. "amd64" or "arm64".
OsArch string `json:"os_arch"`
// OsInfo is a human-readable OS distribution string, e.g.
// "Ubuntu 22.04".
OsInfo string `json:"os_info"`
// OsVersion is the OS version string, e.g. "22.04".
OsVersion string `json:"os_version"`
// CpuInfo is the CPU model name as reported by the OS.
CpuInfo string `json:"cpu_info"`
// GpuInfo is the GPU model name when one is detected.
GpuInfo *string `json:"gpu_info,omitempty"`
// Memory is the total physical memory, formatted as "<n> G".
Memory string `json:"memory"`
// Disk is the total filesystem size of the node's data partition,
// formatted as "<n> G".
Disk string `json:"disk"`
// WifiConnected is true when the active default route is over
// Wi-Fi. The JSON key is "wifiConnected".
WifiConnected bool `json:"wifiConnected"`
// WifiSSID is the SSID of the connected Wi-Fi network, when
// WifiConnected is true.
WifiSSID *string `json:"wifiSSID,omitempty"`
// WiredConnected is true when the node has an active Ethernet
// connection.
WiredConnected bool `json:"wiredConnected"`
// HostIP is the internal LAN IPv4 address that Olares uses to
// register itself in /etc/hosts and to reach other nodes.
HostIP string `json:"hostIp"`
// ExternalIP is the public IPv4 address as observed by an
// external probe. Refreshed at most once per minute.
ExternalIP string `json:"externalIp"`
// ExternalIPProbeTime is daemon-internal: when the external IP
// probe last ran. Excluded from the wire format.
ExternalIPProbeTime time.Time `json:"-"`
// InstallingState reports the progress of an installation in
// flight: "in-progress", "completed", "failed", or empty.
InstallingState ProcessingState `json:"installingState"`
// InstallingProgress is a free-form human-readable description
// of the current installation step.
InstallingProgress string `json:"installingProgress"`
// InstallingProgressNum is daemon-internal: the latest numeric
// install progress percentage. Excluded from the wire format.
InstallingProgressNum int `json:"-"`
// UninstallingState mirrors InstallingState for the uninstall
// flow.
UninstallingState ProcessingState `json:"uninstallingState"`
// UninstallingProgress is a free-form description of the
// current uninstall step.
UninstallingProgress string `json:"uninstallingProgress"`
// UninstallingProgressNum is daemon-internal: the latest numeric
// uninstall progress percentage. Excluded from the wire format.
UninstallingProgressNum int `json:"-"`
// UpgradingTarget is the target version of the in-flight
// upgrade, e.g. "1.13.0". Empty when no upgrade is queued.
UpgradingTarget string `json:"upgradingTarget"`
// UpgradingRetryNum is the number of times the upgrader has
// retried after a transient failure.
UpgradingRetryNum int `json:"upgradingRetryNum"`
// UpgradingNextRetryAt is the wall-clock time at which the next
// retry will fire, when retries are pending.
UpgradingNextRetryAt *time.Time `json:"upgradingNextRetryAt,omitempty"`
// UpgradingState is the lifecycle of the install phase of the
// upgrade ("in-progress", "completed", "failed", or empty).
UpgradingState ProcessingState `json:"upgradingState"`
// UpgradingStep is the name of the current upgrade step.
UpgradingStep string `json:"upgradingStep"`
// UpgradingProgress is the free-form progress message for the
// current upgrade step.
UpgradingProgress string `json:"upgradingProgress"`
// UpgradingProgressNum is daemon-internal: the latest numeric
// upgrade progress percentage. Excluded from the wire format.
UpgradingProgressNum int `json:"-"`
// UpgradingError is the most recent error seen during upgrade.
// Empty when no error has occurred.
UpgradingError string `json:"upgradingError"`
// UpgradingDownloadState is the lifecycle of the download phase
// of the upgrade. Olares splits download and install into two
// phases so that downloads can complete in the background
// without changing TerminusState to "upgrading".
UpgradingDownloadState ProcessingState `json:"upgradingDownloadState"`
// UpgradingDownloadStep is the name of the current download step.
UpgradingDownloadStep string `json:"upgradingDownloadStep"`
// UpgradingDownloadProgress is the free-form progress message
// for the current download step.
UpgradingDownloadProgress string `json:"upgradingDownloadProgress"`
// UpgradingDownloadProgressNum is daemon-internal: the latest
// numeric download progress percentage. Excluded from the wire
// format.
UpgradingDownloadProgressNum int `json:"-"`
// UpgradingDownloadError is the most recent error from the
// download phase. Empty when no error has occurred.
UpgradingDownloadError string `json:"upgradingDownloadError"`
// CollectingLogsState is the lifecycle of the most recent log
// collection job triggered through olaresd.
CollectingLogsState ProcessingState `json:"collectingLogsState"`
// CollectingLogsError is the error from the most recent log
// collection job, when it failed.
CollectingLogsError string `json:"collectingLogsError"`
// DefaultFRPServer is the FRP server address used when frp is
// enabled. Sourced from the FRP_SERVER env var.
DefaultFRPServer string `json:"defaultFrpServer"`
// FRPEnable indicates whether the FRP-based reverse tunnel is
// turned on. Sourced from the FRP_ENABLE env var.
FRPEnable string `json:"frpEnable"`
// ContainerMode is set when olaresd is running inside a
// container, mirroring the CONTAINER_MODE env var.
ContainerMode *string `json:"containerMode,omitempty"`
// Pressure lists the kubernetes node-condition pressures
// currently active on this node (memory pressure, disk pressure,
// PID pressure, etc.). Empty when the node is healthy.
Pressure []NodePressure `json:"pressures,omitempty"`
}
// NodePressure represents a non-Ready kubernetes node condition that
// is currently true on this node, e.g. MemoryPressure, DiskPressure,
// PIDPressure, NetworkUnavailable.
type NodePressure struct {
// Type is the kubernetes node condition type, e.g.
// "MemoryPressure".
Type string `json:"type"`
// Message is the human-readable explanation provided by kubelet.
Message string `json:"message"`
}

View file

@ -5,13 +5,13 @@ go 1.24.11
replace (
bytetrade.io/web3os/backups-sdk => github.com/Above-Os/backups-sdk v0.1.17
bytetrade.io/web3os/bfl => github.com/beclab/bfl v0.3.36
github.com/beclab/Olares/cli => ../cli
github.com/labstack/echo/v4 => github.com/eball/echo/v4 v4.13.4-patch
k8s.io/api => k8s.io/api v0.34.0
k8s.io/apimachinery => k8s.io/apimachinery v0.34.0
k8s.io/client-go => k8s.io/client-go v0.34.0
kubesphere.io/api => ../../kubesphere-ext/staging/src/kubesphere.io/api/
sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.19.6
)
require (
@ -19,7 +19,7 @@ require (
github.com/Masterminds/semver/v3 v3.4.0
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/beclab/Olares/cli v0.0.0-20251230161135-5264df60cc33
github.com/beclab/Olares/framework/app-service v0.0.0-20251225061130-909b7656fd70
github.com/beclab/Olares/framework/app-service v0.0.0-20260311124303-23a6533bc2ad
github.com/containerd/containerd v1.7.29
github.com/distribution/distribution/v3 v3.0.0
github.com/dustin/go-humanize v1.0.1
@ -46,7 +46,7 @@ require (
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/shirou/gopsutil/v4 v4.25.7
github.com/sirupsen/logrus v1.9.3
github.com/spf13/pflag v1.0.7
github.com/spf13/pflag v1.0.10
github.com/txn2/txeh v1.5.5
github.com/vishvananda/netlink v1.3.0
go.opentelemetry.io/otel/trace v1.40.0
@ -173,7 +173,7 @@ require (
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/sdk v1.40.0 // indirect

View file

@ -24,10 +24,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/beclab/Olares/cli v0.0.0-20251230161135-5264df60cc33 h1:WYuUPOT/p26aCDJGJEDai1v7YM6QHiaFDusBVynnbBY=
github.com/beclab/Olares/cli v0.0.0-20251230161135-5264df60cc33/go.mod h1:ixhzBK5XIovsRB5djk44TChsOK4wum2q4y/hZxJKlNw=
github.com/beclab/Olares/framework/app-service v0.0.0-20251225061130-909b7656fd70 h1:U3z6m0hokD1gzl788BrUdxCbDyAjdOBBXA8ilYgn6VQ=
github.com/beclab/Olares/framework/app-service v0.0.0-20251225061130-909b7656fd70/go.mod h1:D9wl7y3obLqXMqfubMROMgdxWAwInnKNrFC//d0nyIA=
github.com/beclab/Olares/framework/app-service v0.0.0-20260311124303-23a6533bc2ad h1:nmQCNbJNtgTqcusySeeyd9LQOK2jyk78QAjzmvyyPYg=
github.com/beclab/Olares/framework/app-service v0.0.0-20260311124303-23a6533bc2ad/go.mod h1:D9wl7y3obLqXMqfubMROMgdxWAwInnKNrFC//d0nyIA=
github.com/beclab/bfl v0.3.36 h1:PgeSPGc+XoONiwFsKq9xX8rqcL4kVM1G/ut0lYYj/js=
github.com/beclab/bfl v0.3.36/go.mod h1:A82u38MxYk1C3Lqnm4iUUK4hBeY9HHIs+xU4V93OnJk=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -331,8 +329,8 @@ github.com/soypat/cyw43439 v0.0.0-20240609122733-da9153086796 h1:1/r2URInjjFtWqT
github.com/soypat/cyw43439 v0.0.0-20240609122733-da9153086796/go.mod h1:1Otjk6PRhfzfcVHeWMEeku/VntFqWghUwuSQyivb2vE=
github.com/soypat/seqs v0.0.0-20240527012110-1201bab640ef h1:phH95I9wANjTYw6bSYLZDQfNvao+HqYDom8owbNa0P4=
github.com/soypat/seqs v0.0.0-20240527012110-1201bab640ef/go.mod h1:oCVCNGCHMKoBj97Zp9znLbQ1nHxpkmOY9X+UAGzOxc8=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@ -386,8 +384,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6h
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=

View file

@ -87,7 +87,7 @@ func (h *Handlers) RunCommand(next func(ctx *fiber.Ctx, cmd commands.Interface)
return func(ctx *fiber.Ctx) error {
c := cmdNew()
err := state.CurrentState.TerminusState.ValidateOp(c)
err := state.ValidateOp(state.CurrentState.TerminusState, c)
if err != nil {
return h.ErrJSON(ctx, http.StatusForbidden, err.Error())
}

View file

@ -9,6 +9,7 @@ import (
"sync"
"time"
clistate "github.com/beclab/Olares/cli/pkg/daemon/state"
"github.com/beclab/Olares/daemon/internel/watcher"
"github.com/beclab/Olares/daemon/pkg/commands"
"github.com/beclab/Olares/daemon/pkg/nets"
@ -19,68 +20,11 @@ import (
"github.com/pbnjay/memory"
)
type state struct {
TerminusdState TerminusDState `json:"terminusdState"`
TerminusState TerminusState `json:"terminusState"`
TerminusName *string `json:"terminusName,omitempty"`
TerminusVersion *string `json:"terminusVersion,omitempty"`
InstalledTime *int64 `json:"installedTime,omitempty"`
InitializedTime *int64 `json:"initializedTime,omitempty"`
OlaresdVersion *string `json:"olaresdVersion,omitempty"`
InstallFinishedTime *time.Time `json:"-"`
// sys info
DeviceName *string `json:"device_name,omitempty"`
HostName *string `json:"host_name,omitempty"`
OsType string `json:"os_type"`
OsArch string `json:"os_arch"`
OsInfo string `json:"os_info"`
OsVersion string `json:"os_version"`
CpuInfo string `json:"cpu_info"`
GpuInfo *string `json:"gpu_info,omitempty"`
Memory string `json:"memory"`
Disk string `json:"disk"`
// network info
WikiConnected bool `json:"wifiConnected"`
WifiSSID *string `json:"wifiSSID,omitempty"`
WiredConnected bool `json:"wiredConnected"`
HostIP string `json:"hostIp"`
ExternalIP string `json:"externalIp"`
ExternalIPProbeTime time.Time `json:"-"`
// installing / uninstalling / upgrading state
InstallingState ProcessingState `json:"installingState"`
InstallingProgress string `json:"installingProgress"`
InstallingProgressNum int `json:"-"`
UninstallingState ProcessingState `json:"uninstallingState"`
UninstallingProgress string `json:"uninstallingProgress"`
UninstallingProgressNum int `json:"-"`
UpgradingTarget string `json:"upgradingTarget"`
UpgradingRetryNum int `json:"upgradingRetryNum"`
UpgradingNextRetryAt *time.Time `json:"upgradingNextRetryAt,omitempty"`
UpgradingState ProcessingState `json:"upgradingState"`
UpgradingStep string `json:"upgradingStep"`
UpgradingProgress string `json:"upgradingProgress"`
UpgradingProgressNum int `json:"-"`
UpgradingError string `json:"upgradingError"`
UpgradingDownloadState ProcessingState `json:"upgradingDownloadState"`
UpgradingDownloadStep string `json:"upgradingDownloadStep"`
UpgradingDownloadProgress string `json:"upgradingDownloadProgress"`
UpgradingDownloadProgressNum int `json:"-"`
UpgradingDownloadError string `json:"upgradingDownloadError"`
CollectingLogsState ProcessingState `json:"collectingLogsState"`
CollectingLogsError string `json:"collectingLogsError"`
DefaultFRPServer string `json:"defaultFrpServer"`
FRPEnable string `json:"frpEnable"`
ContainerMode *string `json:"containerMode,omitempty"`
Pressure []utils.NodePressure `json:"pressures,omitempty"`
}
// state is a daemon-local alias of the canonical wire-format struct
// defined in cli/pkg/daemon/state. Keeping it as an alias means
// existing daemon code keeps using the same field names without
// changes, while the CLI shares the exact same data type.
type state = clistate.State
var CurrentState state
var StateTrigger chan struct{}
@ -92,11 +36,15 @@ func init() {
StateTrigger = make(chan struct{})
}
func (c *state) ChangeTerminusStateTo(s TerminusState) {
// ChangeTerminusStateTo updates the global TerminusState under the
// shared mutex. It used to be a method on state, but methods cannot
// be defined on a type alias whose underlying type lives in another
// package, so it is now a package-level function.
func ChangeTerminusStateTo(s TerminusState) {
TerminusStateMu.Lock()
defer TerminusStateMu.Unlock()
c.TerminusState = s
CurrentState.TerminusState = s
}
func bToGb(b uint64) string {
@ -193,7 +141,7 @@ func CheckCurrentStatus(ctx context.Context) error {
}
// clear value
CurrentState.WikiConnected = false
CurrentState.WifiConnected = false
CurrentState.WifiSSID = nil
CurrentState.WiredConnected = false
for _, i := range ips {
@ -201,7 +149,7 @@ func CheckCurrentStatus(ctx context.Context) error {
if d, ok := devices[i.Iface.Name]; ok {
switch d.Type {
case "wifi":
CurrentState.WikiConnected = true
CurrentState.WifiConnected = true
CurrentState.WifiSSID = &d.Connection
case "ethernet":
CurrentState.WiredConnected = true
@ -212,7 +160,7 @@ func CheckCurrentStatus(ctx context.Context) error {
// for macos
if strings.HasPrefix(i.Iface.Name, "wl") {
CurrentState.WikiConnected = true
CurrentState.WifiConnected = true
CurrentState.WifiSSID = utils.WifiName()
}

View file

@ -1,8 +1,13 @@
package state
type TerminusDState string
import clistate "github.com/beclab/Olares/cli/pkg/daemon/state"
// TerminusDState is the lifecycle state of olaresd itself. The
// canonical definition lives in the cli module and is re-exported
// here as an alias so the daemon can use the unqualified name.
type TerminusDState = clistate.TerminusDState
const (
Initialize TerminusDState = "initialize"
Running TerminusDState = "running"
Initialize = clistate.Initialize
Running = clistate.Running
)

View file

@ -3,52 +3,57 @@ package state
import (
"errors"
clistate "github.com/beclab/Olares/cli/pkg/daemon/state"
"github.com/beclab/Olares/daemon/pkg/commands"
)
type ProcessingState string
// Wire-format types live in the cli module so that olaresd and the
// olares-cli command share a single source of truth. Daemon code keeps
// using the unqualified names below via these type aliases.
type ProcessingState = clistate.ProcessingState
const (
Completed ProcessingState = "completed"
Failed ProcessingState = "failed"
InProgress ProcessingState = "in-progress"
Completed = clistate.Completed
Failed = clistate.Failed
InProgress = clistate.InProgress
)
type TerminusState string
type TerminusState = clistate.TerminusState
const (
NotInstalled TerminusState = "not-installed"
Installing TerminusState = "installing"
InstallFailed TerminusState = "install-failed"
Uninitialized TerminusState = "uninitialized"
Initializing TerminusState = "initializing"
InitializeFailed TerminusState = "initialize-failed"
TerminusRunning TerminusState = "terminus-running"
InvalidIpAddress TerminusState = "invalid-ip-address"
SystemError TerminusState = "system-error"
SelfRepairing TerminusState = "self-repairing"
IPChanging TerminusState = "ip-changing"
IPChangeFailed TerminusState = "ip-change-failed"
AddingNode TerminusState = "adding-node"
RemovingNode TerminusState = "removing-node"
Uninstalling TerminusState = "uninstalling"
Upgrading TerminusState = "upgrading"
DiskModifing TerminusState = "disk-modifing"
Shutdown TerminusState = "shutdown"
Restarting TerminusState = "restarting"
Checking TerminusState = "checking"
NetworkNotReady TerminusState = "network-not-ready"
NotInstalled = clistate.NotInstalled
Installing = clistate.Installing
InstallFailed = clistate.InstallFailed
Uninitialized = clistate.Uninitialized
Initializing = clistate.Initializing
InitializeFailed = clistate.InitializeFailed
TerminusRunning = clistate.TerminusRunning
InvalidIpAddress = clistate.InvalidIpAddress
SystemError = clistate.SystemError
SelfRepairing = clistate.SelfRepairing
IPChanging = clistate.IPChanging
IPChangeFailed = clistate.IPChangeFailed
AddingNode = clistate.AddingNode
RemovingNode = clistate.RemovingNode
Uninstalling = clistate.Uninstalling
Upgrading = clistate.Upgrading
DiskModifing = clistate.DiskModifing
Shutdown = clistate.Shutdown
Restarting = clistate.Restarting
Checking = clistate.Checking
NetworkNotReady = clistate.NetworkNotReady
)
func (s TerminusState) String() string {
return string(s)
// ValidateOp returns nil if the operation is allowed in the given
// state, or a descriptive error otherwise. It replaces the previous
// (TerminusState).ValidateOp method, since methods cannot be defined
// on an alias of a type from another package.
func ValidateOp(s TerminusState, op commands.Interface) error {
return getValidator(s).ValidateOp(op)
}
func (s TerminusState) ValidateOp(op commands.Interface) error {
return s.getValidator().ValidateOp(op)
}
func (s TerminusState) getValidator() Validator {
func getValidator(s TerminusState) Validator {
switch s {
case NotInstalled:
return &NotInstalledValidator{}

View file

@ -75,7 +75,7 @@ func (i *changeIp) Execute(ctx context.Context, p any) (res any, err error) {
klog.Error("cannot backup prev ip, ", err)
}
state.CurrentState.ChangeTerminusStateTo(state.IPChanging)
state.ChangeTerminusStateTo(state.IPChanging)
// remove PREV_IP_CHANGE_FAILED tag file if exists
if _, err = os.Stat(commands.PREV_IP_CHANGE_FAILED); err == nil {
@ -114,7 +114,7 @@ func (i *changeIp) watch(ctx context.Context) {
// double check
if i.tailLog() {
klog.Info("change ip command finished, change state")
state.CurrentState.ChangeTerminusStateTo(successState)
state.ChangeTerminusStateTo(successState)
return
} else {
klog.Warning("check log file, process not succeed")
@ -123,12 +123,12 @@ func (i *changeIp) watch(ctx context.Context) {
}
}
klog.Warning("change ip killed")
state.CurrentState.ChangeTerminusStateTo(state.SystemError)
state.ChangeTerminusStateTo(state.SystemError)
return
case <-ticker.C:
if i.tailLog() {
klog.Info("watch log succeed")
state.CurrentState.ChangeTerminusStateTo(successState)
state.ChangeTerminusStateTo(successState)
return
}
}
@ -175,7 +175,7 @@ func (i *changeIp) tailLog() (finished bool) {
func (i *changeIp) setFailedState() {
if i.isRetryChange {
klog.Error("retry ip change failed")
state.CurrentState.ChangeTerminusStateTo(state.SystemError)
state.ChangeTerminusStateTo(state.SystemError)
} else {
// create a ip change failed tag file, make the ip-change command can
// be resumed if the device get reboot
@ -184,6 +184,6 @@ func (i *changeIp) setFailedState() {
klog.Error("write ip change failed tag file error, ", err)
}
state.CurrentState.ChangeTerminusStateTo(state.IPChangeFailed)
state.ChangeTerminusStateTo(state.IPChangeFailed)
}
}

View file

@ -92,7 +92,7 @@ func (i *install) watch(ctx context.Context) {
return
} else {
state.CurrentState.InstallingState = state.Failed
state.CurrentState.ChangeTerminusStateTo(state.InstallFailed)
state.ChangeTerminusStateTo(state.InstallFailed)
}
}
return

View file

@ -45,7 +45,7 @@ func (i *uninstall) Execute(ctx context.Context, p any) (res any, err error) {
return nil, err
}
state.CurrentState.ChangeTerminusStateTo(state.Uninstalling)
state.ChangeTerminusStateTo(state.Uninstalling)
state.CurrentState.UninstallingState = state.InProgress
state.CurrentState.UninstallingProgress = "1%"
state.CurrentState.UninstallingProgressNum = 1
@ -67,7 +67,7 @@ func (i *uninstall) watch(ctx context.Context) {
}
if !installed {
state.CurrentState.ChangeTerminusStateTo(state.NotInstalled)
state.ChangeTerminusStateTo(state.NotInstalled)
state.CurrentState.UninstallingState = state.Completed
state.CurrentState.UninstallingProgress = "100%"
state.CurrentState.UninstallingProgressNum = 100
@ -124,7 +124,7 @@ func (i *uninstall) tailLog() (finished bool) {
state.CurrentState.UninstallingState = state.InProgress
} else {
state.CurrentState.UninstallingState = state.Completed
state.CurrentState.ChangeTerminusStateTo(state.NotInstalled)
state.ChangeTerminusStateTo(state.NotInstalled)
return true
}
}

View file

@ -541,7 +541,7 @@ func GetNodesPressure(ctx context.Context, client kubernetes.Interface) (map[str
for _, node := range nodes.Items {
for _, condition := range node.Status.Conditions {
if condition.Type != corev1.NodeReady && condition.Status == corev1.ConditionTrue {
status[node.Name] = append(status[node.Name], NodePressure{Type: condition.Type, Message: condition.Message})
status[node.Name] = append(status[node.Name], NodePressure{Type: string(condition.Type), Message: condition.Message})
}
}
}

View file

@ -1,7 +1,7 @@
package utils
import (
corev1 "k8s.io/api/core/v1"
clistate "github.com/beclab/Olares/cli/pkg/daemon/state"
"k8s.io/apimachinery/pkg/runtime/schema"
)
@ -15,7 +15,9 @@ var (
}
)
type NodePressure struct {
Type corev1.NodeConditionType `json:"type"`
Message string `json:"message"`
}
// NodePressure is a daemon-local alias of the canonical wire type
// shared with the olares-cli module. The Type field is plain string
// rather than corev1.NodeConditionType because the JSON wire format
// uses a string anyway and the cli module avoids depending on
// k8s.io/api/core/v1.
type NodePressure = clistate.NodePressure

View file

@ -60,6 +60,7 @@ To get detailed help for any command, run `olares-cli help`.
| `prepare` | `olares-cli prepare [options]` | Prepares the environment for the installation process, including setting up essential services and configurations of Olares. |
| `release` | `olares-cli release [options]` | Packages Olares installation resources for distribution or deployment. |
| `start` | `olares-cli start [options]` | Starts Olares services and components. |
| `status` | `olares-cli status [options]` | Queries the local olaresd daemon and prints the current Olares system status. |
| `stop` | `olares-cli stop [options]` | Stops Olares services and components. |
| `uninstall` | `olares-cli uninstall [options]` | Uninstalls Olares completely, or roll back the installation to a specific phase. |
| `upgrade` | `olares-cli upgrade <subcommand> [options]` | Upgrades Olares and checks upgrade readiness and compatibility.|

View file

@ -0,0 +1,162 @@
# `status`
## Synopsis
The `status` command prints the current Olares system state by calling the local olaresd daemon's `/system/status` HTTP endpoint.
The endpoint is bound to `127.0.0.1:18088` and only accepts loopback traffic, so this command must run on the same host as the daemon (typically the master node).
```bash
olares-cli status [options]
```
By default, the output is grouped into human-readable sections:
- **Olares**: installation lifecycle, version, names, key timestamps.
- **System**: hardware and OS facts about the host.
- **Network**: wired/Wi-Fi connectivity, internal and external IP addresses.
- **Install / Uninstall**: progress of an in-flight install or uninstall.
- **Upgrade**: progress of an in-flight upgrade (download and install phases).
- **Logs collection**: state of the most recent log collection job.
- **Pressures**: active kubelet node pressure conditions, if any.
- **Other**: FRP, container mode, etc.
Use `--json` to receive the raw payload returned by olaresd, suitable for scripting or piping to tools like `jq`.
## Options
| Option | Usage | Required | Default |
|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------------------------|
| `--endpoint` | Base URL of the local olaresd daemon. Override only when olaresd is bound to a non-default address. | No | `http://127.0.0.1:18088` |
| `--json` | Print the raw JSON payload returned by olaresd (the `data` field), suitable for piping to tools like `jq`. | No | `false` |
| `--timeout` | Maximum time to wait for the olaresd response. | No | `5s` |
| `--help` | Show command help. | No | N/A |
## Examples
```bash
# Pretty-printed grouped report (default)
olares-cli status
# Raw JSON payload (forwarded verbatim from olaresd)
olares-cli status --json | jq
# Custom daemon endpoint and longer timeout
olares-cli status --endpoint http://127.0.0.1:18088 --timeout 10s
```
## Field reference
The table below lists the fields returned by `olaresd` (the `data` object of the JSON response) and the labels they appear under in the grouped output.
### Olares
| Field | JSON key | Meaning |
|-------------------|--------------------|----------------------------------------------------------------------------------------------------------|
| State | `terminusState` | High-level system state. See [State values](#state-values). |
| Olaresd state | `terminusdState` | Lifecycle of the olaresd daemon itself: `initialize` while bootstrapping, `running` once ready. |
| Name | `terminusName` | Olares ID of the admin user, e.g. `alice@olares.cn`. |
| Version | `terminusVersion` | Installed Olares version (semver). |
| Olaresd version | `olaresdVersion` | Running olaresd binary version. Useful for spotting drift after partial upgrades. |
| Installed at | `installedTime` | Unix epoch (seconds) when Olares finished installing on this node. |
| Initialized at | `initializedTime` | Unix epoch (seconds) when the admin user finished initial activation. |
### System
| Field | JSON key | Meaning |
|-------------|---------------|----------------------------------------------------------------------|
| Device | `device_name` | User-friendly device or chassis name. |
| Hostname | `host_name` | Kernel hostname. |
| OS | `os_type` / `os_arch` / `os_info` | OS family, CPU architecture, distro string. |
| OS version | `os_version` | OS version string, e.g. `22.04`. |
| CPU | `cpu_info` | CPU model name. |
| Memory | `memory` | Total physical memory, formatted as `<N> G`. |
| Disk | `disk` | Total filesystem size of the data partition, formatted as `<N> G`. |
| GPU | `gpu_info` | GPU model name when one is detected. |
### Network
| Field | JSON key | Meaning |
|-------------|------------------|----------------------------------------------------------------------|
| Wired | `wiredConnected` | `yes` when an Ethernet connection is active. |
| Wi-Fi | `wifiConnected` | `yes` when the active default route is over Wi-Fi. |
| Wi-Fi SSID | `wifiSSID` | SSID of the connected Wi-Fi network, when applicable. |
| Host IP | `hostIp` | Internal LAN IPv4 address used by Olares to reach other nodes. |
| External IP | `externalIp` | Public IPv4 address as observed by an external probe (refreshed at most once per minute). |
### Install / Uninstall
| Field | JSON key | Meaning |
|----------------|-------------------------|-------------------------------------------------------------------------------|
| Installing | `installingState` | Lifecycle of the in-flight install: `in-progress`, `completed`, `failed`. |
| | `installingProgress` | Free-form description of the current install step (shown as the inline note). |
| Uninstalling | `uninstallingState` | Lifecycle of the in-flight uninstall. |
| | `uninstallingProgress` | Free-form description of the current uninstall step. |
### Upgrade
| Field | JSON key | Meaning |
|----------------|--------------------------------|-----------------------------------------------------------------------------------|
| Target | `upgradingTarget` | Target version of the in-flight upgrade. |
| State | `upgradingState` | Lifecycle of the install phase of the upgrade. |
| | `upgradingProgress` | Free-form progress message (inline note). |
| Step | `upgradingStep` | Name of the current upgrade step. |
| Last error | `upgradingError` | Most recent error from the install phase. |
| Download state | `upgradingDownloadState` | Lifecycle of the download phase. |
| | `upgradingDownloadProgress` | Free-form progress message for the download (inline note). |
| Download step | `upgradingDownloadStep` | Name of the current download step. |
| Download error | `upgradingDownloadError` | Most recent error from the download phase. |
| Retry count | `upgradingRetryNum` | Number of times the upgrader has retried after a transient failure (only shown when > 0). |
| Next retry at | `upgradingNextRetryAt` | Wall-clock time at which the next retry will fire (only shown when set). |
### Logs collection
| Field | JSON key | Meaning |
|-------------|--------------------------|------------------------------------------------------------------|
| State | `collectingLogsState` | Lifecycle of the most recent log collection job triggered through olaresd. |
| | `collectingLogsError` | Error from the most recent log collection job (inline note). |
### Pressures
The `pressures` array lists kubelet node-condition pressures that are currently true on this node. The grouped output shows `(none)` when the node is healthy.
| Field | JSON key | Meaning |
|---------|-----------|------------------------------------------------------------------------|
| Type | `type` | Kubernetes node condition type, e.g. `MemoryPressure`, `DiskPressure`. |
| Message | `message` | Human-readable explanation provided by kubelet. |
### Other
| Field | JSON key | Meaning |
|----------------|---------------------|------------------------------------------------------------------------|
| FRP enabled | `frpEnable` | Whether the FRP-based reverse tunnel is turned on (sourced from `FRP_ENABLE`). |
| FRP server | `defaultFrpServer` | FRP server address (sourced from `FRP_SERVER`). |
| Container mode | `containerMode` | Set when olaresd is running inside a container (sourced from `CONTAINER_MODE`). |
## State values
The `terminusState` field can take one of the following values. The CLI sources its descriptions from the same enumeration, so the table below stays in sync with the CLI output.
| Value | Meaning |
|----------------------|--------------------------------------------------------------------------|
| `checking` | olaresd has not finished the first status probe yet. |
| `network-not-ready` | No usable internal IPv4 address detected. |
| `not-installed` | Olares is not installed on this node. |
| `installing` | Olares is currently being installed. |
| `install-failed` | The most recent install attempt failed. |
| `uninitialized` | Olares is installed but the admin user has not been activated yet. |
| `initializing` | The admin user activation is in progress. |
| `initialize-failed` | The admin user activation failed. |
| `terminus-running` | Olares is running normally. |
| `restarting` | The node was just restarted; status will stabilize shortly. |
| `invalid-ip-address` | The node IP changed since install; run `change-ip` to recover. |
| `ip-changing` | A `change-ip` operation is in progress. |
| `ip-change-failed` | The most recent `change-ip` attempt failed. |
| `system-error` | One or more critical pods are not running. |
| `self-repairing` | olaresd is attempting automatic recovery. |
| `adding-node` | A worker node is being joined. |
| `removing-node` | A worker node is being removed. |
| `uninstalling` | Olares is being uninstalled. |
| `upgrading` | An upgrade is being applied. |
| `disk-modifing` | The storage layout is being modified. |
| `shutdown` | The system is shutting down. |

View file

@ -60,6 +60,7 @@ Olares 命令行工具使用如下语法:
| `prepare` | `olares-cli prepare [选项]` | 为安装过程准备环境,包括设置 Olares 的基础服务和配置。 |
| `release` | `olares-cli release [选项]` | 打包 Olares 安装资源以供分发或部署。|
| `start` | `olares-cli start [选项]` | 启动 Olares 服务和组件。 |
| `status` | `olares-cli status [选项]` | 查询本机 olaresd 守护进程,输出当前 Olares 系统状态。 |
| `stop` | `olares-cli stop [选项]` | 停止 Olares 服务和组件。 |
| `uninstall` | `olares-cli uninstall [选项]` | 完全卸载 Olares或将安装回滚到特定阶段。 |
| `upgrade` | `olares-cli upgrade <子命令> [选项]` | 升级 Olares检查升级准备情况与兼容性。 |

View file

@ -0,0 +1,162 @@
# `status`
## 命令说明
`status` 命令通过调用本机 olaresd 守护进程的 `/system/status` HTTP 接口,输出当前 Olares 系统的状态。
该接口绑定在 `127.0.0.1:18088`,仅接受本地回环流量,因此 `status` 命令必须与 olaresd 运行在同一台机器(通常是主节点)上。
```bash
olares-cli status [选项]
```
默认输出按以下分组展示,便于人工阅读:
- **Olares**:安装生命周期、版本、用户名、关键时间戳。
- **System**:主机的硬件和操作系统信息。
- **Network**:有线/Wi-Fi 连接状态、内/外网 IP 地址。
- **Install / Uninstall**:正在进行的安装或卸载进度。
- **Upgrade**:正在进行的升级进度(包括下载阶段和安装阶段)。
- **Logs collection**:最近一次日志收集任务的状态。
- **Pressures**:节点上当前激活的 kubelet 节点压力条件(若有)。
- **Other**FRP、容器模式等其他信息。
加上 `--json` 可以输出 olaresd 返回的原始 JSON便于脚本化处理或与 `jq` 等工具配合使用。
## 选项
| 选项 | 用途 | 是否必需 | 默认值 |
|--------------|----------------------------------------------------------------------------------------------|----------|--------------------------|
| `--endpoint` | 本机 olaresd 守护进程的基础 URL。仅当 olaresd 监听在非默认地址时才需要修改。 | 否 | `http://127.0.0.1:18088` |
| `--json` | 直接输出 olaresd 返回的原始 JSON即响应中的 `data` 字段),适合配合 `jq` 等工具使用。 | 否 | `false` |
| `--timeout` | 等待 olaresd 响应的最长时间。 | 否 | `5s` |
| `--help` | 显示命令帮助。 | 否 | 无 |
## 使用示例
```bash
# 默认输出:分组的人工可读报表
olares-cli status
# 原始 JSON 输出,原样转发自 olaresd
olares-cli status --json | jq
# 指定守护进程地址并延长超时时间
olares-cli status --endpoint http://127.0.0.1:18088 --timeout 10s
```
## 字段参考
下表列出 olaresd 返回的字段(即 JSON 响应中 `data` 对象的字段),以及它们在分组输出中显示的标签。
### Olares
| 字段 | JSON Key | 含义 |
|------------------|--------------------|--------------------------------------------------------------------------------------------|
| State | `terminusState` | 系统的高层状态,详见 [状态值列表](#状态值列表)。 |
| Olaresd state | `terminusdState` | olaresd 守护进程自身的生命周期:启动初始化时为 `initialize`,初始化完成后为 `running`。 |
| Name | `terminusName` | 管理员的 Olares ID例如 `alice@olares.cn`。 |
| Version | `terminusVersion` | 已安装的 Olares 版本(语义化版本号)。 |
| Olaresd version | `olaresdVersion` | 当前运行的 olaresd 二进制版本。可用于排查升级后的版本漂移。 |
| Installed at | `installedTime` | Olares 安装完成时间Unix 时间戳,单位秒)。 |
| Initialized at | `initializedTime` | 管理员完成初始激活的时间Unix 时间戳,单位秒)。 |
### System
| 字段 | JSON Key | 含义 |
|------------|---------------|--------------------------------------------|
| Device | `device_name` | 用户友好的设备/机型名称。 |
| Hostname | `host_name` | 内核报告的主机名。 |
| OS | `os_type` / `os_arch` / `os_info` | 操作系统类型、CPU 架构、发行版描述。 |
| OS version | `os_version` | 操作系统版本号,例如 `22.04`。 |
| CPU | `cpu_info` | CPU 型号。 |
| Memory | `memory` | 物理内存总量,格式为 `<N> G`。 |
| Disk | `disk` | 数据分区的文件系统总容量,格式为 `<N> G`。 |
| GPU | `gpu_info` | 检测到的 GPU 型号(若有)。 |
### Network
| 字段 | JSON Key | 含义 |
|-------------|------------------|---------------------------------------------------------------------|
| Wired | `wiredConnected` | 检测到有线连接时为 `yes`。 |
| Wi-Fi | `wifiConnected` | 默认路由走 Wi-Fi 时为 `yes`。 |
| Wi-Fi SSID | `wifiSSID` | 已连接 Wi-Fi 的 SSID。 |
| Host IP | `hostIp` | Olares 用于互联的内网 IPv4 地址。 |
| External IP | `externalIp` | 通过外部探测获取的公网 IPv4 地址(每分钟最多刷新一次)。 |
### Install / Uninstall
| 字段 | JSON Key | 含义 |
|---------------|-------------------------|-------------------------------------------------------------------|
| Installing | `installingState` | 进行中的安装任务的生命周期:`in-progress`、`completed`、`failed`。 |
| | `installingProgress` | 当前安装步骤的描述(在分组输出中以括号形式跟随显示)。 |
| Uninstalling | `uninstallingState` | 进行中的卸载任务的生命周期。 |
| | `uninstallingProgress` | 当前卸载步骤的描述。 |
### Upgrade
| 字段 | JSON Key | 含义 |
|----------------|--------------------------------|----------------------------------------------------------------------------|
| Target | `upgradingTarget` | 进行中升级的目标版本。 |
| State | `upgradingState` | 升级安装阶段的生命周期。 |
| | `upgradingProgress` | 升级安装阶段的进度描述(括号显示)。 |
| Step | `upgradingStep` | 当前升级步骤的名称。 |
| Last error | `upgradingError` | 升级安装阶段最近一次报错信息。 |
| Download state | `upgradingDownloadState` | 升级下载阶段的生命周期。 |
| | `upgradingDownloadProgress` | 升级下载阶段的进度描述(括号显示)。 |
| Download step | `upgradingDownloadStep` | 当前下载步骤的名称。 |
| Download error | `upgradingDownloadError` | 升级下载阶段最近一次报错信息。 |
| Retry count | `upgradingRetryNum` | 升级被自动重试的次数(仅当大于 0 时显示)。 |
| Next retry at | `upgradingNextRetryAt` | 下一次重试的预定时间(仅当存在时显示)。 |
### Logs collection
| 字段 | JSON Key | 含义 |
|------------|--------------------------|-----------------------------------------------------------------------|
| State | `collectingLogsState` | 通过 olaresd 触发的最近一次日志收集任务的生命周期。 |
| | `collectingLogsError` | 最近一次日志收集任务的错误信息(括号显示)。 |
### Pressures
`pressures` 数组列出当前节点上为真的 kubelet 节点压力条件。当节点健康时,分组输出中会显示 `(none)`
| 字段 | JSON Key | 含义 |
|---------|-----------|---------------------------------------------------------------------|
| Type | `type` | Kubernetes 节点条件类型,例如 `MemoryPressure`、`DiskPressure`。 |
| Message | `message` | kubelet 给出的可读说明。 |
### Other
| 字段 | JSON Key | 含义 |
|----------------|---------------------|---------------------------------------------------------------------|
| FRP enabled | `frpEnable` | FRP 反向通道是否启用(来自环境变量 `FRP_ENABLE`)。 |
| FRP server | `defaultFrpServer` | FRP 服务器地址(来自环境变量 `FRP_SERVER`)。 |
| Container mode | `containerMode` | olaresd 运行在容器内时设置(来自环境变量 `CONTAINER_MODE`)。 |
## 状态值列表
`terminusState` 字段可能取以下值。CLI 也使用同一份枚举生成描述,因此下表始终与 CLI 输出保持一致。
| 取值 | 含义 |
|-----------------------|-----------------------------------------------------------------|
| `checking` | olaresd 还未完成首次状态探测。 |
| `network-not-ready` | 未检测到可用的内网 IPv4 地址。 |
| `not-installed` | 当前节点未安装 Olares。 |
| `installing` | Olares 正在安装中。 |
| `install-failed` | 最近一次安装失败。 |
| `uninitialized` | Olares 已安装,但管理员账户尚未激活。 |
| `initializing` | 管理员账户正在激活中。 |
| `initialize-failed` | 管理员账户激活失败。 |
| `terminus-running` | Olares 运行正常。 |
| `restarting` | 节点刚刚重启,状态会在短时间内稳定。 |
| `invalid-ip-address` | 节点 IP 已变更,需要执行 `change-ip` 恢复。 |
| `ip-changing` | `change-ip` 操作正在进行。 |
| `ip-change-failed` | 最近一次 `change-ip` 操作失败。 |
| `system-error` | 关键 Pod 未正常运行。 |
| `self-repairing` | olaresd 正在尝试自动修复。 |
| `adding-node` | 正在加入 worker 节点。 |
| `removing-node` | 正在移除 worker 节点。 |
| `uninstalling` | Olares 正在卸载中。 |
| `upgrading` | 升级正在执行中。 |
| `disk-modifing` | 存储布局正在调整中。 |
| `shutdown` | 系统正在关机。 |