diff options
Diffstat (limited to 'src/release.rs')
| -rw-r--r-- | src/release.rs | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/src/release.rs b/src/release.rs new file mode 100644 index 0000000..88b4093 --- /dev/null +++ b/src/release.rs @@ -0,0 +1,131 @@ +// Copyright (C) 2026 Doan Luu Duc Tai <doan@luuduct.ai> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use std::fs; +use std::path::Path; + +use anyhow::{Context, Result, ensure}; + +use crate::project::{Mode, Project}; +use crate::{files, images, process}; + +pub(crate) fn create(project: &Project, destination: &Path) -> Result<()> { + crate::check(project)?; + process::run(project.command("cargo")?.args([ + "build", + "--locked", + "--release", + "--target", + "x86_64-unknown-linux-musl", + ]))?; + files::write( + &project.root.join("bin/stack"), + &fs::read( + project + .root + .join("target/x86_64-unknown-linux-musl/release/stack"), + )?, + 0o755, + )?; + let mut settings = project.settings()?; + settings.mode = Mode::Local; + settings.online = true; + for service in project.configuration(&settings)?.services.values() { + project.ensure_image(&settings, service)?; + } + files::private_dir(destination)?; + let temp = tempfile::Builder::new() + .prefix(".release-") + .tempdir_in(destination)?; + let stage = temp.path().join("stack"); + files::private_dir(&stage)?; + files::copy_source(&project.root, &stage)?; + fs::create_dir_all(stage.join("bin"))?; + fs::copy( + project + .root + .join("target/x86_64-unknown-linux-musl/release/stack"), + stage.join("bin/stack"), + )?; + images::save( + project, + &images::configured(project, &settings)?, + &stage.join("var"), + )?; + files::manifest(&stage, "release")?; + let output = destination.join(format!( + "stack-release-{}.tar.gz", + chrono::Utc::now().format("%Y%m%dT%H%M%SZ") + )); + files::pack(&stage, &output)?; + let filename = output + .file_name() + .context("Missing archive name")? + .to_string_lossy(); + files::write( + &output.with_extension("gz.sha256"), + format!("{} {filename}\n", files::hash_file(&output)?).as_bytes(), + 0o600, + )?; + process::line(format_args!("Release: {}", output.display())) +} + +pub(crate) fn activate(archive: &Path, project: &Project) -> Result<()> { + let temp = tempfile::Builder::new() + .prefix(".activate-") + .tempdir_in(&project.root)?; + let stage = temp.path().join("stack"); + files::extract(archive, &stage)?; + ensure!( + files::verify(&stage)?.kind == "release", + "Activation requires a release archive" + ); + let pending = Project::at(&stage)?; + let mut settings = project.settings()?; + settings.mode = Mode::Production; + settings.online = false; + let lock = stage.join("var/image-lock.json"); + let info: serde_json::Value = serde_json::from_slice(&fs::read(&lock)?)?; + ensure!( + info.get("platform").and_then(serde_json::Value::as_str) + == Some(project.platform()?.as_str()), + "Release architecture does not match the server" + ); + let _config = pending.configuration(&settings)?; + for name in files::SOURCE { + let source = stage.join(name); + if source.exists() { + let target = project.root.join(name); + if target.is_dir() { + files::regular_tree(&target)?; + fs::remove_dir_all(&target)?; + } + files::copy_tree(&source, &target)?; + } + } + files::write( + &project.root.join("bin/stack"), + &fs::read(stage.join("bin/stack"))?, + 0o755, + )?; + for name in ["images.tar", "image-lock.json"] { + files::copy_tree( + &stage.join("var").join(name), + &project.root.join("var").join(name), + )?; + } + images::prepare(project, &mut settings)?; + project.up(&settings) +} |
