summaryrefslogtreecommitdiff
path: root/src/release.rs
blob: 92f2df4f15f61377d1384731b2be57908e8a9b63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// 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)?;
    crate::chat::build(project)?;
    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)?;
    crate::chat::copy_artifacts(project, &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,
    )?;
    crate::chat::copy_artifacts(&pending, &project.root)?;
    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)
}