Skip to main content

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        /// Force recreate containers instead of rolling deploy
22        #[arg(long)]
23        force: bool,
24    },
25
26    /// Verify fleet.toml matches reality on servers
27    Check {
28        /// Filter by server name
29        #[arg(long)]
30        server: Option<String>,
31
32        /// Re-run Ansible hardening playbook
33        #[arg(long)]
34        with_hardening: bool,
35    },
36
37    /// Show fleet-wide status and container info
38    Status {
39        /// Filter by server name
40        #[arg(long)]
41        server: Option<String>,
42
43        /// Keep refreshing every second
44        #[arg(short = 'f', long)]
45        follow: bool,
46
47        /// Show image column
48        #[arg(long)]
49        image: bool,
50
51        /// Show ports column
52        #[arg(long)]
53        ports: bool,
54
55        /// Show size column
56        #[arg(long)]
57        size: bool,
58    },
59
60    /// Tail logs from an app
61    Logs {
62        /// App name
63        app: String,
64
65        /// Follow log output
66        #[arg(short, long)]
67        follow: bool,
68
69        /// Server to tail logs from (defaults to first server)
70        #[arg(long)]
71        server: Option<String>,
72    },
73
74    /// Stop an app's containers (keeps config, files, and DNS intact)
75    Stop {
76        /// App name to stop
77        app: String,
78
79        /// Stop only on this server (defaults to all assigned servers)
80        #[arg(long)]
81        server: Option<String>,
82    },
83
84    /// Restart an app's containers without redeploying
85    Restart {
86        /// App name to restart
87        app: String,
88
89        /// Restart only on this server (defaults to all assigned servers)
90        #[arg(long)]
91        server: Option<String>,
92    },
93
94    /// Remove an app: stop containers, clean up files, DNS, and fleet.toml
95    Remove {
96        /// App name to remove
97        app: String,
98
99        /// Skip confirmation prompt
100        #[arg(long)]
101        yes: bool,
102    },
103
104    /// Initialize a new fleet.toml in the current directory
105    Init,
106
107    /// Manage servers
108    Server {
109        #[command(subcommand)]
110        command: ServerCommand,
111    },
112
113    /// Manage apps in fleet.toml
114    App {
115        #[command(subcommand)]
116        command: AppCommand,
117    },
118
119    /// Manage GitHub Actions self-hosted runners
120    Runner {
121        #[command(subcommand)]
122        command: RunnerCommand,
123    },
124
125    /// Login to external services (runs all if no subcommand given)
126    Login {
127        #[command(subcommand)]
128        command: Option<LoginCommand>,
129    },
130
131    /// Manage app databases
132    Db {
133        #[command(subcommand)]
134        command: DbCommand,
135    },
136
137    /// Manage environment variables in fleet.env.toml
138    Env {
139        /// [app] [key=value...] — list/set env vars
140        args: Vec<String>,
141    },
142
143    /// Update flow CLI to the latest version
144    Update,
145}
146
147#[derive(Subcommand)]
148pub enum LoginCommand {
149    /// Set Cloudflare API token
150    Cf,
151
152    /// Set GitHub Container Registry token
153    Gh,
154}
155
156#[derive(Subcommand)]
157pub enum ServerCommand {
158    /// Add a new server and run Ansible to bootstrap it (interactive wizard if no args given)
159    Add {
160        /// Server name (used as identifier in fleet.toml)
161        name: Option<String>,
162
163        /// Server IP address
164        #[arg(long)]
165        ip: Option<String>,
166
167        /// Override hostname (default: {name}.{domain})
168        #[arg(long)]
169        host: Option<String>,
170
171        /// Deploy user (created by Ansible, used for future SSH)
172        #[arg(long)]
173        user: Option<String>,
174
175        /// SSH user for initial Ansible connection
176        #[arg(long)]
177        ssh_user: Option<String>,
178
179        /// Path to SSH public key for the deploy user
180        #[arg(long)]
181        ssh_key: Option<String>,
182    },
183
184    /// Remove a server from fleet.toml
185    Remove {
186        /// Server name to remove
187        name: String,
188    },
189}
190
191#[derive(Subcommand)]
192pub enum DbCommand {
193    /// Open an interactive psql shell
194    Shell {
195        /// App name (defaults to first app with postgres)
196        app: Option<String>,
197
198        /// Server to connect to (defaults to first server)
199        #[arg(long)]
200        server: Option<String>,
201    },
202
203    /// Dump the database to a local file
204    Dump {
205        /// App name (defaults to first app with postgres)
206        app: Option<String>,
207
208        /// Output file path (default: {app}.sql.gz)
209        #[arg(short, long)]
210        output: Option<String>,
211
212        /// Server to dump from (defaults to first server)
213        #[arg(long)]
214        server: Option<String>,
215    },
216
217    /// Restore the database from a local SQL file
218    Restore {
219        /// App name (defaults to first app with postgres)
220        app: Option<String>,
221
222        /// Path to .sql or .sql.gz file
223        file: String,
224
225        /// Skip confirmation prompt
226        #[arg(long)]
227        yes: bool,
228
229        /// Server to restore to (defaults to first server)
230        #[arg(long)]
231        server: Option<String>,
232    },
233
234    /// List available backups on the server
235    List {
236        /// App name (defaults to first app with postgres)
237        app: Option<String>,
238
239        /// Server to list backups from (defaults to first server)
240        #[arg(long)]
241        server: Option<String>,
242    },
243}
244
245#[derive(Subcommand)]
246pub enum RunnerCommand {
247    /// Add a self-hosted runner (interactive wizard if no args given)
248    Add {
249        /// Runner name (used as identifier in fleet.toml)
250        name: Option<String>,
251
252        /// Server to deploy to (must exist in fleet.toml)
253        #[arg(long)]
254        server: Option<String>,
255
256        /// Runner scope: org or repo
257        #[arg(long)]
258        scope: Option<String>,
259
260        /// Target org name or owner/repo
261        #[arg(long)]
262        target: Option<String>,
263
264        /// Runner label(s) (repeatable)
265        #[arg(long)]
266        label: Vec<String>,
267
268        /// Single-job ephemeral mode (default: true)
269        #[arg(long)]
270        ephemeral: bool,
271    },
272
273    /// Remove a runner from fleet.toml and clean up on server
274    Remove {
275        /// Runner name to remove
276        name: String,
277
278        /// Skip confirmation prompt
279        #[arg(long)]
280        yes: bool,
281    },
282
283    /// List runners and their status from GitHub API
284    List,
285}
286
287#[derive(Subcommand)]
288pub enum AppCommand {
289    /// Add a new app to fleet.toml (interactive wizard if no args given)
290    Add {
291        /// App name (used as identifier in fleet.toml)
292        name: Option<String>,
293
294        /// Docker image (e.g., ghcr.io/org/app:latest)
295        #[arg(long)]
296        image: Option<String>,
297
298        /// Server(s) to deploy to (must exist in fleet.toml, repeatable)
299        #[arg(long)]
300        server: Vec<String>,
301
302        /// Container port (required if routing is used)
303        #[arg(long)]
304        port: Option<u16>,
305
306        /// Domain hostname(s) for Caddy reverse proxy (repeatable)
307        #[arg(long)]
308        domain: Vec<String>,
309
310        /// Health check path (e.g., /health)
311        #[arg(long)]
312        health_path: Option<String>,
313
314        /// Health check interval (e.g., 5s, 1m)
315        #[arg(long)]
316        health_interval: Option<String>,
317
318        /// Direct port mapping(s) in external:internal[/protocol] format (repeatable)
319        #[arg(long, value_name = "EXTERNAL:INTERNAL[/PROTOCOL]")]
320        port_map: Vec<String>,
321
322        /// Deploy strategy: rolling (default) or recreate
323        #[arg(long)]
324        deploy_strategy: Option<String>,
325    },
326
327    /// Add a sidecar service to an existing app
328    AddService {
329        /// App name (must exist in fleet.toml)
330        app: String,
331
332        /// Service name
333        name: String,
334
335        /// Docker image for the service
336        #[arg(long)]
337        image: String,
338
339        /// Volume mount(s) in name:path format (repeatable)
340        #[arg(long)]
341        volume: Vec<String>,
342
343        /// Healthcheck command
344        #[arg(long)]
345        healthcheck: Option<String>,
346
347        /// Service this depends on (must exist in same app)
348        #[arg(long)]
349        depends_on: Option<String>,
350    },
351
352    /// Remove a sidecar service from an app
353    RemoveService {
354        /// App name
355        app: String,
356
357        /// Service name to remove
358        name: String,
359    },
360}