runewarp 0.1.0

Runewarp is an ingress tunneling tool for exposing local services without moving TLS termination to the edge. Clients connect out over QUIC, so you can publish services without putting your backend directly on the Internet or leaking your public IP.
Documentation
use std::fs;
use std::path::Path;
use std::process::{Command, Output};

use tempfile::TempDir;

fn write_repo_files(repo_root: &Path, version: &str, changelog: &str) {
    fs::write(
        repo_root.join("Cargo.toml"),
        format!("[package]\nname = \"runewarp\"\nversion = \"{version}\"\nedition = \"2024\"\n"),
    )
    .unwrap();
    fs::write(repo_root.join("CHANGELOG.md"), changelog).unwrap();
}

fn run_validator(repo_root: &Path, args: &[&str]) -> Output {
    let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("scripts")
        .join("validate-release-gates.sh");

    Command::new("bash")
        .arg(script_path)
        .args(args)
        .arg("--repo-root")
        .arg(repo_root)
        .output()
        .unwrap()
}

fn run_git(repo_root: &Path, args: &[&str]) {
    let status = Command::new("git")
        .args(args)
        .current_dir(repo_root)
        .status()
        .unwrap();
    assert!(status.success(), "git command failed: {args:?}");
}

fn init_git_repo_with_origin_main(repo_root: &Path) {
    let remote_root = repo_root.join("remote.git");
    fs::create_dir_all(&remote_root).unwrap();

    run_git(&remote_root, &["init", "--bare", "-q"]);
    run_git(repo_root, &["init", "-q", "-b", "main"]);
    run_git(repo_root, &["config", "user.name", "Runewarp Tests"]);
    run_git(repo_root, &["config", "user.email", "tests@example.com"]);
    run_git(repo_root, &["config", "commit.gpgsign", "false"]);
    run_git(
        repo_root,
        &["remote", "add", "origin", remote_root.to_str().unwrap()],
    );
    run_git(repo_root, &["add", "Cargo.toml", "CHANGELOG.md"]);
    run_git(repo_root, &["commit", "-qm", "test release gates"]);
    run_git(repo_root, &["push", "-u", "origin", "main"]);
}

fn init_git_repo_with_signed_tag(
    repo_root: &Path,
    tag: &str,
    signer_principal: &str,
) -> std::path::PathBuf {
    init_git_repo_with_origin_main(repo_root);

    let signing_key = repo_root.join("signing-key");
    let allowed_signers = repo_root.join("allowed_signers");
    let keygen_status = Command::new("ssh-keygen")
        .args([
            "-t",
            "ed25519",
            "-N",
            "",
            "-C",
            signer_principal,
            "-f",
            signing_key.to_str().unwrap(),
        ])
        .status()
        .unwrap();
    assert!(keygen_status.success());

    let public_key = fs::read_to_string(signing_key.with_extension("pub")).unwrap();
    fs::write(allowed_signers, format!("{signer_principal} {public_key}")).unwrap();

    run_git(repo_root, &["config", "gpg.format", "ssh"]);
    run_git(repo_root, &["config", "gpg.ssh.program", "ssh-keygen"]);
    run_git(
        repo_root,
        &["config", "user.signingkey", signing_key.to_str().unwrap()],
    );
    run_git(repo_root, &["tag", "-s", tag, "-m", tag]);

    repo_root.join("allowed_signers")
}

fn write_allowed_signers(repo_root: &Path, signer_principal: &str) -> std::path::PathBuf {
    let signing_key = repo_root.join("alternate-signing-key");
    let allowed_signers = repo_root.join("alternate_allowed_signers");
    let keygen_status = Command::new("ssh-keygen")
        .args([
            "-t",
            "ed25519",
            "-N",
            "",
            "-C",
            signer_principal,
            "-f",
            signing_key.to_str().unwrap(),
        ])
        .status()
        .unwrap();
    assert!(keygen_status.success());

    let public_key = fs::read_to_string(signing_key.with_extension("pub")).unwrap();
    fs::write(&allowed_signers, format!("{signer_principal} {public_key}")).unwrap();
    allowed_signers
}

#[test]
fn rehearsal_mode_accepts_a_matching_release_tag_without_a_git_tag_object() {
    let temp_dir = TempDir::new().unwrap();
    write_repo_files(
        temp_dir.path(),
        "0.1.0",
        "# Changelog\n\nAll notable changes to Runewarp will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).\n\n## [0.1.0] - 2026-05-29\n\n### Added\n\n- Public release metadata contract.\n",
    );
    init_git_repo_with_origin_main(temp_dir.path());

    let output = run_validator(temp_dir.path(), &["rehearsal", "--tag", "v0.1.0"]);

    assert!(output.status.success(), "{output:?}");
}

#[test]
fn rehearsal_mode_rejects_a_candidate_commit_outside_origin_main() {
    let temp_dir = TempDir::new().unwrap();
    write_repo_files(
        temp_dir.path(),
        "0.1.0",
        "# Changelog\n\nAll notable changes to Runewarp will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).\n\n## [0.1.0] - 2026-05-29\n\n### Added\n\n- Public release metadata contract.\n",
    );
    init_git_repo_with_origin_main(temp_dir.path());
    fs::write(temp_dir.path().join("feature.txt"), "feature branch only\n").unwrap();
    run_git(
        temp_dir.path(),
        &["checkout", "-qb", "feature/release-candidate"],
    );
    run_git(temp_dir.path(), &["add", "feature.txt"]);
    run_git(
        temp_dir.path(),
        &["commit", "-qm", "feature-only candidate"],
    );

    let output = run_validator(temp_dir.path(), &["rehearsal", "--tag", "v0.1.0"]);

    assert!(!output.status.success(), "{output:?}");
}

#[test]
fn tag_mode_accepts_an_ssh_signed_tag_from_the_allowed_signers_file() {
    let temp_dir = TempDir::new().unwrap();
    write_repo_files(
        temp_dir.path(),
        "0.1.0",
        "# Changelog\n\nAll notable changes to Runewarp will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).\n\n## [0.1.0] - 2026-05-29\n\n### Added\n\n- Public release metadata contract.\n",
    );
    let allowed_signers =
        init_git_repo_with_signed_tag(temp_dir.path(), "v0.1.0", "release@test.example");

    let output = run_validator(
        temp_dir.path(),
        &[
            "tag",
            "--tag",
            "v0.1.0",
            "--allowed-signers-file",
            allowed_signers.to_str().unwrap(),
        ],
    );

    assert!(output.status.success(), "{output:?}");
}

#[test]
fn tag_mode_rejects_a_tag_signed_by_an_untrusted_key() {
    let temp_dir = TempDir::new().unwrap();
    write_repo_files(
        temp_dir.path(),
        "0.1.0",
        "# Changelog\n\nAll notable changes to Runewarp will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).\n\n## [0.1.0] - 2026-05-29\n\n### Added\n\n- Public release metadata contract.\n",
    );
    init_git_repo_with_signed_tag(temp_dir.path(), "v0.1.0", "release@test.example");
    let allowed_signers = write_allowed_signers(temp_dir.path(), "other@test.example");

    let output = run_validator(
        temp_dir.path(),
        &[
            "tag",
            "--tag",
            "v0.1.0",
            "--allowed-signers-file",
            allowed_signers.to_str().unwrap(),
        ],
    );

    assert!(!output.status.success(), "{output:?}");
}

#[test]
fn tag_mode_rejects_a_signed_tag_for_a_commit_outside_origin_main() {
    let temp_dir = TempDir::new().unwrap();
    write_repo_files(
        temp_dir.path(),
        "0.1.0",
        "# Changelog\n\nAll notable changes to Runewarp will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).\n\n## [0.1.0] - 2026-05-29\n\n### Added\n\n- Public release metadata contract.\n",
    );
    init_git_repo_with_origin_main(temp_dir.path());
    fs::write(temp_dir.path().join("feature.txt"), "feature branch only\n").unwrap();
    run_git(
        temp_dir.path(),
        &["checkout", "-qb", "feature/release-candidate"],
    );
    run_git(temp_dir.path(), &["add", "feature.txt"]);
    run_git(
        temp_dir.path(),
        &["commit", "-qm", "feature-only candidate"],
    );

    let signing_key = temp_dir.path().join("feature-signing-key");
    let allowed_signers = temp_dir.path().join("feature_allowed_signers");
    let keygen_status = Command::new("ssh-keygen")
        .args([
            "-t",
            "ed25519",
            "-N",
            "",
            "-C",
            "release@test.example",
            "-f",
            signing_key.to_str().unwrap(),
        ])
        .status()
        .unwrap();
    assert!(keygen_status.success());
    let public_key = fs::read_to_string(signing_key.with_extension("pub")).unwrap();
    fs::write(
        &allowed_signers,
        format!("release@test.example {public_key}"),
    )
    .unwrap();

    run_git(temp_dir.path(), &["config", "gpg.format", "ssh"]);
    run_git(
        temp_dir.path(),
        &["config", "gpg.ssh.program", "ssh-keygen"],
    );
    run_git(
        temp_dir.path(),
        &["config", "user.signingkey", signing_key.to_str().unwrap()],
    );
    run_git(temp_dir.path(), &["tag", "-s", "v0.1.0", "-m", "v0.1.0"]);

    let output = run_validator(
        temp_dir.path(),
        &[
            "tag",
            "--tag",
            "v0.1.0",
            "--allowed-signers-file",
            allowed_signers.to_str().unwrap(),
        ],
    );

    assert!(!output.status.success(), "{output:?}");
}