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::app::project::{Mode, Project};
use crate::app::{files, images, process};
pub(super) fn create(project: &Project, destination: &Path) -> Result<()> {
crate::app::check(project)?;
crate::app::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::app::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(super) 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::app::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)
}
|