conduit_cli/core/engine/workflow/loader/
install.rs1use crate::core::domain::loader::Loader;
2use crate::core::engine::resolver::loader::ResolvedLoader;
3use crate::core::engine::workflow::Workflow;
4use crate::core::schemas::lock::{HashKind, Lockfile};
5use crate::core::schemas::manifest::Manifest;
6use crate::errors::{ConduitError, ConduitResult};
7use std::io::Error;
8use std::path::Path;
9use std::process::Command;
10
11impl Workflow {
12 pub async fn execute_installation(
13 &self,
14 resolved: &ResolvedLoader,
15 hash: &str,
16 kind: HashKind,
17 loader_type: &Loader,
18 mc_version: &str,
19 ) -> ConduitResult<()> {
20 let is_installer = resolved.file_name.contains("installer");
21 let target_name = if is_installer {
22 "installer.jar"
23 } else {
24 "server.jar"
25 };
26 let target_path = self.project_root.join(target_name);
27
28 if is_installer {
29 let source = self.ctx.store.object_path(hash, kind);
30 tokio::fs::copy(source, &target_path).await?;
31
32 self.run_java_installer(&target_path, loader_type, mc_version)?;
33 let _ = tokio::fs::remove_file(target_path).await;
34 } else {
35 self.ctx.store.link_object(hash, kind, &target_path).await?;
36 }
37
38 self.post_install_cleanup(loader_type).await
39 }
40
41 fn run_java_installer(
42 &self,
43 path: &std::path::Path,
44 loader_type: &Loader,
45 mc_version: &str,
46 ) -> ConduitResult<()> {
47 let mut cmd = Command::new("java");
48 cmd.arg("-jar").arg(path);
49
50 match loader_type {
51 Loader::Fabric => {
52 cmd.arg("server")
53 .arg("-mcversion")
54 .arg(mc_version)
55 .arg("-downloadMinecraft");
56 }
57 _ => {
58 cmd.arg("--installServer");
59 }
60 }
61
62 let status = cmd
63 .current_dir(&self.project_root)
64 .stdout(std::process::Stdio::null())
65 .stderr(std::process::Stdio::null())
66 .status()
67 .map_err(|e| ConduitError::Io(Error::other(e.to_string())))?;
68
69 if !status.success() {
70 return Err(ConduitError::Io(Error::other(
71 "installer exited with error",
72 )));
73 }
74 Ok(())
75 }
76
77 async fn post_install_cleanup(&self, loader_type: &Loader) -> ConduitResult<()> {
78 tokio::fs::write(self.project_root.join("eula.txt"), "eula=true").await?;
79
80 if let Loader::Forge { version } = loader_type {
81 let mc_version = version.split('-').next().unwrap_or("");
82 let is_old = mc_version
83 .split('.')
84 .nth(1)
85 .and_then(|v| v.parse::<u32>().ok())
86 .is_some_and(|v| v <= 16);
87
88 if is_old {
89 let mut entries = tokio::fs::read_dir(&self.project_root).await?;
90 while let Some(entry) = entries.next_entry().await? {
91 let name = entry.file_name().to_string_lossy().to_lowercase();
92 if name.contains("forge")
93 && Path::new(&name)
94 .extension()
95 .is_some_and(|ext| ext.eq_ignore_ascii_case("jar"))
96 && !name.contains("installer")
97 {
98 let _ =
99 tokio::fs::rename(entry.path(), self.project_root.join("server.jar"))
100 .await;
101 break;
102 }
103 }
104 }
105 }
106
107 let files_to_delete = [
108 "installer.jar.log",
109 "run.sh",
110 "run.bat",
111 "user_jvm_args.txt",
112 ];
113 for file in files_to_delete {
114 let path = self.project_root.join(file);
115 if path.exists() {
116 let _ = tokio::fs::remove_file(path).await;
117 }
118 }
119 Ok(())
120 }
121
122 pub fn ensure_loader_presence(
123 &self,
124 lock: &Lockfile,
125 manifest: &Manifest,
126 ) -> ConduitResult<bool> {
127 if lock.instance.loader != manifest.project.loader
128 || lock.instance.minecraft_version != manifest.project.minecraft
129 {
130 return Ok(false);
131 }
132
133 match &lock.instance.loader {
134 Loader::Neoforge { .. } => {
135 let libs = self.project_root.join("libraries");
136 Ok(libs.exists())
137 }
138 Loader::Forge { version } => {
139 if Self::is_modern_forge(version) {
140 let libs = self.project_root.join("libraries");
141 Ok(libs.exists())
142 } else {
143 Ok(self.project_root.join("server.jar").exists())
144 }
145 }
146 Loader::Fabric => {
147 let has_launch_jar = self.project_root.join("fabric-server-launch.jar").exists();
148 let has_server_jar = self.project_root.join("server.jar").exists();
149 Ok(has_launch_jar || has_server_jar)
150 }
151 _ => Ok(self.project_root.join("server.jar").exists()),
152 }
153 }
154}