iron/cli.rs
1use clap::{Parser, Subcommand};
2
3#[derive(Parser)]
4#[command(name = "flow", about = "Deploy and manage the Flow fleet")]
5pub struct Cli {
6 #[command(subcommand)]
7 pub command: Command,
8
9 /// Path to fleet.toml (default: fleet.toml in current directory)
10 #[arg(long, global = true, default_value = "fleet.toml")]
11 pub config: String,
12}
13
14#[derive(Subcommand)]
15pub enum Command {
16 /// Deploy an app (or all apps if no name given)
17 Deploy {
18 /// App name to deploy (deploys all if omitted)
19 app: Option<String>,
20 },
21
22 /// Verify fleet.toml matches reality on servers
23 Check {
24 /// Filter by server name
25 #[arg(long)]
26 server: Option<String>,
27 },
28
29 /// Show fleet-wide status and container info
30 Status {
31 /// Filter by server name
32 #[arg(long)]
33 server: Option<String>,
34 },
35
36 /// Tail logs from an app
37 Logs {
38 /// App name
39 app: String,
40
41 /// Follow log output
42 #[arg(short, long)]
43 follow: bool,
44
45 /// Server to tail logs from (defaults to first server)
46 #[arg(long)]
47 server: Option<String>,
48 },
49
50 /// Stop an app's containers (keeps config, files, and DNS intact)
51 Stop {
52 /// App name to stop
53 app: String,
54
55 /// Stop only on this server (defaults to all assigned servers)
56 #[arg(long)]
57 server: Option<String>,
58 },
59
60 /// Restart an app's containers without redeploying
61 Restart {
62 /// App name to restart
63 app: String,
64
65 /// Restart only on this server (defaults to all assigned servers)
66 #[arg(long)]
67 server: Option<String>,
68 },
69
70 /// Remove an app: stop containers, clean up files, DNS, and fleet.toml
71 Remove {
72 /// App name to remove
73 app: String,
74
75 /// Skip confirmation prompt
76 #[arg(long)]
77 yes: bool,
78 },
79
80 /// Initialize a new fleet.toml in the current directory
81 Init,
82
83 /// Manage servers
84 Server {
85 #[command(subcommand)]
86 command: ServerCommand,
87 },
88
89 /// Manage apps in fleet.toml
90 App {
91 #[command(subcommand)]
92 command: AppCommand,
93 },
94
95 /// Login to external services (runs all if no subcommand given)
96 Login {
97 #[command(subcommand)]
98 command: Option<LoginCommand>,
99 },
100}
101
102#[derive(Subcommand)]
103pub enum LoginCommand {
104 /// Set Cloudflare API token
105 Cf,
106
107 /// Set GitHub Container Registry token
108 Gh,
109}
110
111#[derive(Subcommand)]
112pub enum ServerCommand {
113 /// Add a new server and run Ansible to bootstrap it
114 Add {
115 /// Server name (used as identifier in fleet.toml)
116 name: String,
117
118 /// Server IP address
119 #[arg(long)]
120 ip: String,
121
122 /// Override hostname (default: {name}.{domain})
123 #[arg(long)]
124 host: Option<String>,
125
126 /// Deploy user (created by Ansible, used for future SSH)
127 #[arg(long, default_value = "deploy")]
128 user: String,
129
130 /// SSH user for initial Ansible connection
131 #[arg(long, default_value = "root")]
132 ssh_user: String,
133
134 /// Path to SSH public key for the deploy user
135 #[arg(long)]
136 ssh_key: Option<String>,
137 },
138
139 /// Remove a server from fleet.toml
140 Remove {
141 /// Server name to remove
142 name: String,
143 },
144
145 /// Verify a server is properly set up (re-runs Ansible)
146 Check {
147 /// Server name (checks all if omitted)
148 name: Option<String>,
149
150 /// SSH user for Ansible connection
151 #[arg(long, default_value = "root")]
152 ssh_user: String,
153 },
154}
155
156#[derive(Subcommand)]
157pub enum AppCommand {
158 /// Add a new app to fleet.toml
159 Add {
160 /// App name (used as identifier in fleet.toml)
161 name: String,
162
163 /// Docker image (e.g., ghcr.io/org/app:latest)
164 #[arg(long)]
165 image: String,
166
167 /// Server(s) to deploy to (must exist in fleet.toml, repeatable)
168 #[arg(long, required = true, num_args = 1..)]
169 server: Vec<String>,
170
171 /// Container port (required if routing is used)
172 #[arg(long)]
173 port: Option<u16>,
174
175 /// Route hostname(s) for Caddy reverse proxy (repeatable)
176 #[arg(long)]
177 route: Vec<String>,
178
179 /// Health check path (e.g., /health)
180 #[arg(long)]
181 health_path: Option<String>,
182
183 /// Health check interval (e.g., 5s, 1m)
184 #[arg(long)]
185 health_interval: Option<String>,
186
187 /// Direct port mapping(s) in external:internal[/protocol] format (repeatable)
188 #[arg(long, value_name = "EXTERNAL:INTERNAL[/PROTOCOL]")]
189 port_map: Vec<String>,
190
191 /// Deploy strategy: rolling (default) or recreate
192 #[arg(long, default_value = "rolling")]
193 deploy_strategy: String,
194 },
195
196 /// Add a sidecar service to an existing app
197 AddService {
198 /// App name (must exist in fleet.toml)
199 app: String,
200
201 /// Service name
202 name: String,
203
204 /// Docker image for the service
205 #[arg(long)]
206 image: String,
207
208 /// Volume mount(s) in name:path format (repeatable)
209 #[arg(long)]
210 volume: Vec<String>,
211
212 /// Healthcheck command
213 #[arg(long)]
214 healthcheck: Option<String>,
215
216 /// Service this depends on (must exist in same app)
217 #[arg(long)]
218 depends_on: Option<String>,
219 },
220
221 /// Remove a sidecar service from an app
222 RemoveService {
223 /// App name
224 app: String,
225
226 /// Service name to remove
227 name: String,
228 },
229}