1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! The `aion server` subcommand: argument surface for running the full Aion
//! workflow server in-process via [`aion_server::run()`], plus the control
//! verbs (`stop`, `status`) that act on a server already running under this
//! machine's Aion home.
use std::net::SocketAddr;
use std::num::{NonZeroU32, NonZeroU64, NonZeroUsize};
use std::path::PathBuf;
use aion_server::config::{CliOverrides, ServerConfig};
use crate::console;
use clap::{Args, Subcommand};
/// The whole `aion server` surface: bare `aion server` runs the server with
/// the flattened [`ServerArgs`]; `aion server stop` / `aion server status`
/// are control verbs against the recorded running server. Run flags and a
/// control verb are mutually exclusive by construction
/// (`args_conflicts_with_subcommands`), so `aion server stop --config x.toml`
/// parses the verb's own `--config`, never the run surface's.
#[derive(Args, Clone, Debug)]
#[command(args_conflicts_with_subcommands = true)]
pub struct ServerCommand {
/// Control verb, when one was given.
#[command(subcommand)]
pub command: Option<ServerSubcommand>,
/// The run-the-server argument surface (the default with no verb).
#[command(flatten)]
pub run: ServerArgs,
}
/// Control verbs for a server already running under this Aion home.
#[derive(Subcommand, Clone, Debug)]
pub enum ServerSubcommand {
/// Stop the running server: verify the recorded incarnation, send
/// SIGTERM, wait for the drain, and report the drain outcome by name.
Stop(crate::server_stop::StopArgs),
/// Report which server is actually running: the pid-file record, the
/// live build identity from its own listener, and readiness — each
/// layer's absence reported as its own distinct state.
Status(crate::server_status::StatusArgs),
/// Hand this home over from the running server to a fresh one: signal the
/// running server, start its successor while it drains, and narrate both
/// until the new one is serving.
Restart(crate::server_restart::RestartArgs),
}
/// Arguments for `aion server`, identical to the surface of the former
/// standalone `aion-server` binary.
#[derive(Args, Clone, Debug)]
pub struct ServerArgs {
/// Path to a TOML server configuration file. Optional when using local defaults.
#[arg(long)]
config: Option<PathBuf>,
/// Override the HTTP/JSON and ops-console listener address.
#[arg(long)]
listen_address: Option<SocketAddr>,
/// Override the gRPC listener address this server binds.
///
/// This is the server's own bind address. The global `--endpoint` is the
/// address a *client* dials and has no effect here; `aion server` refuses
/// rather than start on a different port than the operator named.
#[arg(long)]
grpc_address: Option<SocketAddr>,
/// RETIRED. Selected the libSQL backend, which no longer exists. Kept only so
/// that passing it is refused by name with the haematite remedy rather than
/// rejected as an unknown flag, which would name nothing.
#[arg(long)]
store_url: Option<String>,
/// Number of engine scheduler worker threads.
#[arg(long)]
scheduler_threads: Option<NonZeroUsize>,
/// Recorded calls to one `(module, function, arity)` before the embedded VM
/// JIT-compiles it. Omitted, the VM applies its own default.
///
/// This moves the moment of compilation; it does not switch compilation
/// off. A very large value defers it past any realistic workload, but the
/// call counter saturates rather than stopping, so compilation still occurs
/// on the far side of that horizon.
#[arg(long = "jit-threshold")]
jit_threshold: Option<NonZeroU32>,
/// Maximum graceful drain duration in seconds.
#[arg(long = "drain-timeout")]
drain_timeout_seconds: Option<NonZeroU64>,
/// Workflow package archive to load at startup. Repeat to load multiple packages.
#[arg(long = "workflow-package")]
workflow_packages: Vec<PathBuf>,
/// Path to the external `gleam` binary that commissions the server-side
/// authoring loop. Setting it mounts the `/authoring/*` endpoints (and
/// requires `--authoring-project-root`); absent, the server compiles no
/// Gleam and deploys pre-built `.aion` files only.
#[arg(long = "gleam-path")]
gleam_path: Option<PathBuf>,
/// Built Gleam workflow project root that submitted authoring source is
/// written into and packaged from. Required when `--gleam-path` is set.
#[arg(long = "authoring-project-root")]
authoring_project_root: Option<PathBuf>,
/// Open the ops console in the default browser once the HTTP listener is up.
/// Resolves the served URL from the effective config (the same
/// `--listen-address`/config/default precedence the server uses).
#[arg(long)]
open: bool,
}
impl ServerArgs {
/// Whether `--open` was passed.
#[must_use]
pub fn open(&self) -> bool {
self.open
}
}
/// Resolve the served HTTP URL from the effective config and, once the listener
/// answers its own liveness probe, open it in the default browser. Best-effort:
/// any failure (config load, listener never live, no opener) is logged and
/// dropped so it never affects the server run.
///
/// The URL, the probe, and the opener are the shared [`crate::console`]
/// plumbing the bare `aion` launcher also uses, so "up" means the same thing
/// on both paths: HTTP 200 from `/health/live`, not a bare TCP accept.
pub fn spawn_browser_open(overrides: &CliOverrides) {
let address = match ServerConfig::load(overrides) {
Ok(config) => {
let (_store, runtime) = config.into_parts();
runtime.listen.http
}
Err(error) => {
eprintln!("aion server --open: could not resolve listen address: {error}");
return;
}
};
tokio::spawn(async move {
if console::wait_until_live(address, console::LIVE_BUDGET).await {
let url = console::served_url(address);
match console::open_browser(&url) {
Ok(()) => eprintln!("aion server --open: opening {url}"),
Err(error) => {
eprintln!("aion server --open: could not open {url}: {error}");
}
}
} else {
eprintln!(
"aion server --open: listener at {address} did not come up; not opening browser"
);
}
});
}
impl From<ServerArgs> for CliOverrides {
fn from(args: ServerArgs) -> Self {
Self {
config_path: args.config,
listen_address: args.listen_address,
grpc_address: args.grpc_address,
store_url: args.store_url,
scheduler_threads: args.scheduler_threads.map(NonZeroUsize::get),
jit_threshold: args.jit_threshold.map(NonZeroU32::get),
drain_timeout_seconds: args.drain_timeout_seconds.map(NonZeroU64::get),
workflow_packages: args.workflow_packages,
gleam_path: args.gleam_path,
authoring_project_root: args.authoring_project_root,
}
}
}