allow specifying access key for backing up private demos

This commit is contained in:
Robin Appelman 2026-05-14 19:11:40 +02:00
commit a1b3b598e6
6 changed files with 50 additions and 15 deletions

7
Cargo.lock generated
View file

@ -78,6 +78,7 @@ dependencies = [
"futures-util",
"main_error",
"md5 0.8.0",
"secretfile",
"thiserror 2.0.18",
"tokio",
"tracing",
@ -940,6 +941,12 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "secretfile"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e1f99fdbcc14f9d8292bd680cb6ec6c265d141e5073f61024fcddc891e405c1"
[[package]]
name = "serde"
version = "1.0.228"

View file

@ -14,6 +14,7 @@ tracing = "0.1.44"
tracing-subscriber = "0.3.23"
futures-util = "0.3.32"
md5 = "0.8.0"
secretfile = "0.1.1"
[profile.release]
lto = true

View file

@ -2,13 +2,19 @@
Backup program for demos.tf demos.
A simple program that incrementally backs up every demo file from demos.tf to a local directory.
A simple program that incrementally backs up every demo file from demos.tf to a
local directory.
## Usage
The following environment variables are required for the program
The following environment variables are required for the program:
STORAGE_ROOT: The directory to store the demos in
STATE_FILE: The textfile to store the backup progress in between runs
The program will look in a .env file if the variables aren't set in the environment
The following optional environment variables can additionaly be supplied:
ACCESS_KEY_FILE: File containig the api access key, required to backup private demos
The program will look in a .env file if the variables aren't set in the
environment

View file

@ -36,6 +36,10 @@ in {
default = "*:0/10";
description = "Interval to run the service";
};
keyFile = mkOption {
type = types.nullOr types.str;
description = "access key file path";
};
package = mkOption {
type = types.package;
@ -48,15 +52,24 @@ in {
systemd.services.demostf-backup = {
description = "Backup demos for demos.tf";
environment = {
environment =
{
STORAGE_ROOT = cfg.target;
SOURCE = cfg.api;
STATE_FILE = cfg.stateFile;
RUST_LOG = cfg.logLevel;
}
// optionalAttrs (cfg.keyFile != null) {
ACCESS_KEY_FILE = "$CREDENTIALS_DIRECTORY/api_key";
};
serviceConfig = {
ExecStart = "${cfg.package}/bin/demostf-backup";
LoadCredential = optionals (cfg.keyFile != null) [
"api_key:${cfg.keyFile}"
];
ReadWritePaths = [cfg.target cfg.stateFile];
Restart = "on-failure";
User = cfg.user;

View file

@ -1,5 +1,5 @@
use crate::store::Store;
use crate::Error;
use crate::store::Store;
use demostf_client::{ApiClient, Demo, ListOrder, ListParams};
use std::time::Duration;
use tokio::time::timeout;
@ -11,11 +11,13 @@ pub struct Backup {
}
impl Backup {
pub fn new(store: Store) -> Self {
Backup {
store,
client: ApiClient::new(),
pub fn new(store: Store, access_key: Option<String>) -> Self {
let mut client = ApiClient::new();
if let Some(access_key) = access_key {
info!("using access key");
client.set_access_key(access_key);
}
Backup { store, client }
}
#[instrument(skip_all, fields(demo.id = demo.id, demo.name = name))]

View file

@ -29,7 +29,13 @@ async fn main() -> Result<(), MainError> {
let mut args: HashMap<_, _> = dotenvy::vars().collect();
let store = Store::new(args.get("STORAGE_ROOT").expect("no STORAGE_ROOT set"));
let state_path = PathBuf::from(args.remove("STATE_FILE").expect("no STATE_FILE set"));
let backup = Backup::new(store);
let key_file = args.remove("ACCESS_KEY_FILE");
let access_key = key_file
.as_deref()
.map(secretfile::load)
.transpose()?
.filter(|key| !key.is_empty());
let backup = Backup::new(store, access_key);
let last_page = if state_path.is_file() {
max(