refactor(app-proxy): remove the stable time freeze (#2856)

This commit is contained in:
Hampus
2026-09-20 04:02:47 +02:00
committed by GitHub
parent 86043212f2
commit 3256af8d92
17 changed files with 11 additions and 1469 deletions
+1 -15
View File
@@ -19,7 +19,6 @@ use std::fs::{self, File};
use std::path::{Path, PathBuf};
const DEFAULT_PUBLIC_ASSET_BASE_URL: &str = "https://fluxerstatic.com";
const DEFAULT_APP_PROXY_TIME_FREEZE_ENABLED: &str = "true";
const DEFAULT_APP_PROXY_BUNDLE_LOCAL_ASSETS: &str = "false";
const DEFAULT_STATIC_BUCKET: &str = "fluxer-static";
const DEFAULT_S3_ENDPOINT: &str = "https://ewr1.vultrobjects.com";
@@ -178,11 +177,6 @@ fn bake_command(targets: &[&str]) -> Result<CommandSpec> {
.env("IMAGE_REPO", image_repo()?)
.env("BUILD_VERSION", build_version)
.env("PUBLIC_ASSET_BASE_URL", public_asset_base_url)
.env(
"FLUXER_APP_PROXY_TIME_FREEZE_ENABLED",
env::var("FLUXER_APP_PROXY_TIME_FREEZE_ENABLED")
.unwrap_or_else(|_| DEFAULT_APP_PROXY_TIME_FREEZE_ENABLED.to_string()),
)
.env(
"BUNDLE_LOCAL_ASSETS",
env::var("BUNDLE_LOCAL_ASSETS")
@@ -636,11 +630,7 @@ mod tests {
.args(["buildx", "bake", "-f", "fluxer_app_proxy/docker-bake.hcl"])
.env("IMAGE_REPO", "ghcr.io/example/fluxer-app-proxy")
.env("BUILD_VERSION", "2026.520.1")
.env("PUBLIC_ASSET_BASE_URL", DEFAULT_PUBLIC_ASSET_BASE_URL)
.env(
"FLUXER_APP_PROXY_TIME_FREEZE_ENABLED",
DEFAULT_APP_PROXY_TIME_FREEZE_ENABLED,
);
.env("PUBLIC_ASSET_BASE_URL", DEFAULT_PUBLIC_ASSET_BASE_URL);
assert_eq!(command.program, OsString::from("docker"));
assert_eq!(
@@ -656,10 +646,6 @@ mod tests {
OsString::from("BUILD_VERSION"),
OsString::from("2026.520.1")
)));
assert!(command.env.contains(&(
OsString::from("FLUXER_APP_PROXY_TIME_FREEZE_ENABLED"),
OsString::from(DEFAULT_APP_PROXY_TIME_FREEZE_ENABLED)
)));
}
#[test]
@@ -1,13 +0,0 @@
[package]
name = "fluxer-content-update-frozen-snapshot"
version = "0.1.0"
edition.workspace = true
license.workspace = true
[dependencies]
anyhow = "1.0.104"
base64 = "0.23.1"
sha2 = "0.11.0"
[dev-dependencies]
tempfile = "3.27.0"
@@ -1,158 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use anyhow::{Context, Result, bail};
use base64::Engine;
use sha2::{Digest, Sha256};
use std::fs;
use std::path::Path;
pub fn generate_snapshot_source(static_dir: &Path) -> Result<String> {
if !static_dir.is_dir() {
bail!("{} is not a directory", static_dir.display());
}
let index_html = read_file(&static_dir.join("index.html"))?;
let sw_js = read_file(&static_dir.join("sw.js"))?;
let version_json = read_file(&static_dir.join("version.json"))?;
let sha = snapshot_sha(&index_html, &sw_js, &version_json);
let mut output = String::new();
output.push_str(&format!("const SNAPSHOT_SHA: &str = \"{sha}\";\n\n"));
output.push_str(&format!(
"// -- base64-encoded index.html ({} bytes) --\n",
index_html.len()
));
output.push_str(&format_const(
"STABLE_INDEX_HTML",
&base64_standard(&index_html),
));
output.push_str(&format!(
"// -- base64-encoded sw.js ({} bytes) --\n",
sw_js.len()
));
output.push_str(&format_const("STABLE_SW_JS", &base64_standard(&sw_js)));
output.push_str(&format!(
"// -- base64-encoded version.json ({} bytes) --\n",
version_json.len()
));
output.push_str(&format_const(
"STABLE_VERSION_JSON",
&base64_standard(&version_json),
));
Ok(output)
}
fn read_file(path: &Path) -> Result<Vec<u8>> {
if !path.exists() {
bail!("{} does not exist", path.display());
}
fs::read(path).with_context(|| format!("failed to read {}", path.display()))
}
fn snapshot_sha(index_html: &[u8], sw_js: &[u8], version_json: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(index_html);
hasher.update(sw_js);
hasher.update(version_json);
bytes_to_lower_hex(&hasher.finalize())
}
fn bytes_to_lower_hex(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(bytes.len() * 2);
for byte in bytes {
out.push(HEX[(byte >> 4) as usize] as char);
out.push(HEX[(byte & 0x0f) as usize] as char);
}
out
}
fn base64_standard(data: &[u8]) -> String {
base64::engine::general_purpose::STANDARD.encode(data)
}
fn break_lines(value: &str, width: usize) -> Vec<&str> {
value
.as_bytes()
.chunks(width)
.map(|chunk| std::str::from_utf8(chunk).expect("base64 should be ascii"))
.collect()
}
fn format_const(name: &str, data: &str) -> String {
let escaped = break_lines(data, 100).join("\\\n");
format!("const {name}: &str = \"\\\n{escaped}\\\n\";\n")
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn emits_snapshot_constants_with_combined_sha() {
let dir = tempdir().unwrap();
let index_html = b"<!doctype html><html><body>fixture</body></html>";
let sw_js = b"\"use strict\";\nself.addEventListener('install', () => {});\n";
let version_json = br#"{"sha":"fixture","buildNumber":7}"#;
fs::write(dir.path().join("index.html"), index_html).unwrap();
fs::write(dir.path().join("sw.js"), sw_js).unwrap();
fs::write(dir.path().join("version.json"), version_json).unwrap();
let output = generate_snapshot_source(dir.path()).unwrap();
let expected_sha = snapshot_sha(index_html, sw_js, version_json);
assert!(output.starts_with(&format!(
"const SNAPSHOT_SHA: &str = \"{expected_sha}\";\n\n"
)));
assert!(output.contains("// -- base64-encoded index.html (48 bytes) --"));
assert!(output.contains("// -- base64-encoded sw.js (58 bytes) --"));
assert!(output.contains("// -- base64-encoded version.json (33 bytes) --"));
assert!(output.contains(&format_const(
"STABLE_INDEX_HTML",
&base64_standard(index_html)
)));
assert!(output.contains(&format_const("STABLE_SW_JS", &base64_standard(sw_js))));
assert!(output.contains(&format_const(
"STABLE_VERSION_JSON",
&base64_standard(version_json)
)));
}
#[test]
fn wraps_base64_constants_at_100_columns() {
let data = "a".repeat(205);
let formatted = format_const("TEST", &data);
let lines = formatted.lines().collect::<Vec<_>>();
assert_eq!(lines[0], "const TEST: &str = \"\\");
assert_eq!(lines[1], format!("{}\\", "a".repeat(100)));
assert_eq!(lines[2], format!("{}\\", "a".repeat(100)));
assert_eq!(lines[3], format!("{}\\", "a".repeat(5)));
assert_eq!(lines[4], "\";");
}
#[test]
fn reports_non_directory_static_path() {
let dir = tempdir().unwrap();
let err = generate_snapshot_source(&dir.path().join("missing"))
.unwrap_err()
.to_string();
assert!(err.contains("missing is not a directory"));
}
#[test]
fn reports_missing_required_file() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("index.html"), b"index").unwrap();
let err = generate_snapshot_source(dir.path())
.unwrap_err()
.to_string();
assert!(err.contains("sw.js does not exist"));
}
}
@@ -1,36 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use anyhow::Result;
use fluxer_content_update_frozen_snapshot::generate_snapshot_source;
use std::env;
use std::path::PathBuf;
use std::process;
fn main() {
if let Err(err) = run() {
eprintln!("error: {err:#}");
process::exit(1);
}
}
fn run() -> Result<()> {
let mut args = env::args_os();
let program = args
.next()
.and_then(|value| PathBuf::from(value).file_name().map(|name| name.to_owned()))
.and_then(|name| name.into_string().ok())
.unwrap_or_else(|| "fluxer-content-update-frozen-snapshot".to_owned());
let Some(static_dir) = args.next() else {
eprintln!("usage: {program} <static_dir>");
process::exit(1);
};
if args.next().is_some() {
eprintln!("usage: {program} <static_dir>");
process::exit(1);
}
let output = generate_snapshot_source(&PathBuf::from(static_dir))?;
print!("{output}");
Ok(())
}