Skip to main content

mj_controller/targets/
bootstrap.rs

1use super::*;
2
3/// Load the account's login exports, then interpret our script as POSIX sh.
4/// User shells such as zsh have reserved variables and different syntax.
5pub fn ssh_login_script(ssh: &SshTarget, script: &str) -> CommandSpec {
6    let login_command = format!("exec /bin/sh -c {}", posix_quote(script));
7    ssh_command(
8        ssh,
9        [
10            "sh",
11            "-c",
12            "exec \"$SHELL\" -lc \"$1\"",
13            "mj-login",
14            &login_command,
15        ],
16    )
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ExecutionBoundary<'a> {
21    Direct,
22    Container {
23        engine: &'a str,
24        container_id: &'a str,
25    },
26    Ssh(&'a SshTarget),
27    SshContainer {
28        engine: &'a str,
29        ssh: &'a SshTarget,
30        container_id: &'a str,
31    },
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct HarnessProbe<'a> {
36    pub executable: &'a str,
37    pub version_args: &'a [&'a str],
38    pub bridge_executable: Option<&'a str>,
39}
40
41/// Compatibility is intentionally interpreted by the controller. A successful
42/// probe permits an image-baked tool to be reused; a missing/incompatible tool
43/// causes the controller to upload/install its release-owned copy.
44pub fn bootstrap_probe_plan(
45    boundary: ExecutionBoundary<'_>,
46    harness: HarnessProbe<'_>,
47) -> Result<CommandPlan> {
48    validate_executable(harness.executable)?;
49    let mut commands = vec![
50        at_boundary(
51            boundary,
52            std::iter::once(harness.executable)
53                .chain(harness.version_args.iter().copied())
54                .map(str::to_owned)
55                .collect(),
56        )
57        .purpose("probe harness version"),
58    ];
59    if let Some(bridge) = harness.bridge_executable {
60        validate_executable(bridge)?;
61        commands.push(
62            at_boundary(boundary, vec![bridge.to_owned(), "--version".to_owned()])
63                .purpose("probe ACP bridge version"),
64        );
65    }
66    commands.push(
67        at_boundary(boundary, vec!["git".to_owned(), "--version".to_owned()]).purpose("probe Git"),
68    );
69    Ok(CommandPlan {
70        description: "probe reusable target tools".to_owned(),
71        commands,
72    })
73}
74
75/// Thin Linux Git bootstrap. Managed containers also receive GitHub CLI and
76/// its HTTPS credential helper so an injected `GH_TOKEN` works before clone.
77pub fn install_git_plan(boundary: ExecutionBoundary<'_>) -> CommandPlan {
78    let managed_container = matches!(
79        boundary,
80        ExecutionBoundary::Container { .. } | ExecutionBoundary::SshContainer { .. }
81    );
82    let script = if managed_container {
83        "set -eu; if ! command -v git >/dev/null 2>&1 || ! command -v gh >/dev/null 2>&1; then SUDO=''; if [ \"$(id -u)\" != 0 ]; then command -v sudo >/dev/null 2>&1 && sudo -n true || { echo 'Git and GitHub CLI installation requires root or passwordless sudo' >&2; exit 1; }; SUDO='sudo -n'; fi; if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update; $SUDO apt-get install -y git gh ca-certificates curl; elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y git gh ca-certificates curl; elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y git gh ca-certificates curl; elif command -v apk >/dev/null 2>&1; then $SUDO apk add --no-cache git github-cli ca-certificates curl; else echo 'Unsupported package manager; install Git and GitHub CLI in the image' >&2; exit 1; fi; fi; git config --global credential.https://github.com.helper '!gh auth git-credential'; git config --global credential.https://gist.github.com.helper '!gh auth git-credential'"
84    } else {
85        "set -eu; if command -v git >/dev/null 2>&1; then exit 0; fi; SUDO=''; if [ \"$(id -u)\" != 0 ]; then command -v sudo >/dev/null 2>&1 && sudo -n true || { echo 'Git installation requires root or passwordless sudo' >&2; exit 1; }; SUDO='sudo -n'; fi; if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update; $SUDO apt-get install -y git ca-certificates curl; elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y git ca-certificates curl; elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y git ca-certificates curl; elif command -v apk >/dev/null 2>&1; then $SUDO apk add --no-cache git ca-certificates curl; else echo 'Unsupported package manager; install Git manually' >&2; exit 1; fi"
86    };
87    CommandPlan {
88        description: "install missing Git".to_owned(),
89        commands: vec![
90            at_boundary(
91                boundary,
92                vec!["sh".to_owned(), "-c".to_owned(), script.to_owned()],
93            )
94            .purpose("install Git")
95            .stage(ProvisionStage::Cloning),
96        ],
97    }
98}
99
100/// Shared [`CommandSpec::parallel_group`] marker for one bundle's per-repository
101/// clone/init commands. Every `clone_commands` call builds its own
102/// [`CommandPlan`], so a single fixed marker never mixes batches across plans.
103pub(super) const BUNDLE_REPOSITORIES_PARALLEL_GROUP: u32 = 1;
104
105pub(super) fn clone_commands(
106    bundle: &ProjectBundleSpec,
107    workspace: &str,
108    wrap: impl Fn(Vec<String>) -> CommandSpec,
109) -> Vec<CommandSpec> {
110    let mut commands = vec![
111        wrap(vec![
112            "mkdir".to_owned(),
113            "-p".to_owned(),
114            workspace.to_owned(),
115        ])
116        .purpose("create bundle workspace")
117        .stage(ProvisionStage::Cloning),
118    ];
119    for repository in &bundle.repositories {
120        let destination = format!("{workspace}/{}", repository.destination);
121        let url = repository
122            .url
123            .as_ref()
124            .expect("validated network repository");
125        let mut args = vec!["git".to_owned(), "clone".to_owned()];
126        for push_url in &repository.push_urls {
127            args.extend([
128                "--config".into(),
129                format!("remote.origin.pushurl={push_url}"),
130            ]);
131        }
132        if let Some(reference) = &repository.reference {
133            args.extend(["--reference-if-able".to_owned(), reference.clone()]);
134        }
135        args.push("--".to_owned());
136        args.push(url.clone());
137        args.push(destination);
138        commands.push(
139            wrap(args)
140                .purpose(format!("clone {}", repository.destination))
141                .stage(ProvisionStage::Cloning)
142                .parallel_group(BUNDLE_REPOSITORIES_PARALLEL_GROUP),
143        );
144    }
145    commands
146}
147
148/// Install Move transport on newly provisioned Linux instances.
149pub fn install_rsync_plan(boundary: ExecutionBoundary<'_>) -> CommandPlan {
150    let script = "set -eu; if rsync --protect-args --version >/dev/null 2>&1; then exit 0; fi; SUDO=''; if [ \"$(id -u)\" != 0 ]; then command -v sudo >/dev/null 2>&1 && sudo -n true || { echo 'rsync installation requires root or passwordless sudo' >&2; exit 1; }; SUDO='sudo -n'; fi; if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update; $SUDO apt-get install -y rsync; elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y rsync; elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y rsync; elif command -v apk >/dev/null 2>&1; then $SUDO apk add --no-cache rsync; else echo 'Unsupported package manager; install rsync 3.0 or newer in the image' >&2; exit 1; fi; rsync --protect-args --version >/dev/null";
151    CommandPlan {
152        description: "install EC2 Move transport".into(),
153        commands: vec![
154            at_boundary(boundary, vec!["sh".into(), "-c".into(), script.into()])
155                .purpose("install supported rsync")
156                .stage(ProvisionStage::Provisioning),
157        ],
158    }
159}