add dockerfile
This commit is contained in:
24
Dockerfile
24
Dockerfile
@@ -1,7 +1,29 @@
|
|||||||
FROM rust:1.95.0-trixie AS builder
|
FROM messense/rust-musl-cross:x86_64-musl AS stub-amd64
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
COPY Cargo.toml Cargo.lock* ./
|
COPY Cargo.toml Cargo.lock* ./
|
||||||
COPY crates ./crates
|
COPY crates ./crates
|
||||||
|
RUN --mount=type=cache,target=/root/.cargo/registry \
|
||||||
|
--mount=type=cache,target=/build/target \
|
||||||
|
cargo build --release --target x86_64-unknown-linux-musl -p rsh && \
|
||||||
|
cp target/x86_64-unknown-linux-musl/release/rsh /rsh-x86_64
|
||||||
|
|
||||||
|
FROM messense/rust-musl-cross:aarch64-musl AS stub-arm64
|
||||||
|
WORKDIR /build
|
||||||
|
COPY Cargo.toml Cargo.lock* ./
|
||||||
|
COPY crates ./crates
|
||||||
|
RUN --mount=type=cache,target=/root/.cargo/registry \
|
||||||
|
--mount=type=cache,target=/build/target \
|
||||||
|
cargo build --release --target aarch64-unknown-linux-musl -p rsh && \
|
||||||
|
cp target/aarch64-unknown-linux-musl/release/rsh /rsh-aarch64
|
||||||
|
|
||||||
|
FROM rust:1.95.0-trixie AS builder
|
||||||
|
WORKDIR /build
|
||||||
|
COPY --from=stub-amd64 /rsh-x86_64 /stubs/rsh-x86_64
|
||||||
|
COPY --from=stub-arm64 /rsh-aarch64 /stubs/rsh-aarch64
|
||||||
|
COPY Cargo.toml Cargo.lock* ./
|
||||||
|
COPY crates ./crates
|
||||||
|
ENV RSH_STUB_X86_64=/stubs/rsh-x86_64 \
|
||||||
|
RSH_STUB_AARCH64=/stubs/rsh-aarch64
|
||||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
--mount=type=cache,target=/build/target \
|
--mount=type=cache,target=/build/target \
|
||||||
cargo build --release -p rsh-backend && \
|
cargo build --release -p rsh-backend && \
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
name = "rsh-backend"
|
name = "rsh-backend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rsh-types = { workspace = true }
|
rsh-types = { workspace = true }
|
||||||
|
|||||||
25
crates/rsh-backend/build.rs
Normal file
25
crates/rsh-backend/build.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
fn resolve_stub(env_var: &str, out_name: &str, out_dir: &Path) -> PathBuf {
|
||||||
|
println!("cargo:rerun-if-env-changed={env_var}");
|
||||||
|
let dest = out_dir.join(out_name);
|
||||||
|
if let Ok(src) = std::env::var(env_var) {
|
||||||
|
let src = PathBuf::from(&src);
|
||||||
|
if src.exists() {
|
||||||
|
println!("cargo:rerun-if-changed={}", src.display());
|
||||||
|
std::fs::copy(&src, &dest).expect("copy stub");
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
println!("cargo:warning={env_var} set to {src:?} but file not found; serving 503 for this arch");
|
||||||
|
}
|
||||||
|
std::fs::write(&dest, []).expect("write placeholder");
|
||||||
|
dest
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||||
|
let x86 = resolve_stub("RSH_STUB_X86_64", "rsh-x86_64", &out_dir);
|
||||||
|
let arm = resolve_stub("RSH_STUB_AARCH64", "rsh-aarch64", &out_dir);
|
||||||
|
println!("cargo:rustc-env=STUB_X86_64_PATH={}", x86.display());
|
||||||
|
println!("cargo:rustc-env=STUB_AARCH64_PATH={}", arm.display());
|
||||||
|
}
|
||||||
109
crates/rsh-backend/src/dist.rs
Normal file
109
crates/rsh-backend/src/dist.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
use axum::extract::{Path, Query};
|
||||||
|
use axum::http::{header, HeaderMap, StatusCode};
|
||||||
|
use axum::response::{IntoResponse, Response};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
static STUB_X86_64: &[u8] = include_bytes!(env!("STUB_X86_64_PATH"));
|
||||||
|
static STUB_AARCH64: &[u8] = include_bytes!(env!("STUB_AARCH64_PATH"));
|
||||||
|
|
||||||
|
const SCRIPT_TEMPLATE: &str = r#"#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
SESSION='__SESSION__'
|
||||||
|
BASE='__BASE__'
|
||||||
|
WS='__WS__'
|
||||||
|
A=$(uname -m)
|
||||||
|
case "$A" in
|
||||||
|
x86_64|amd64) ARCH=x86_64 ;;
|
||||||
|
aarch64|arm64) ARCH=aarch64 ;;
|
||||||
|
*) echo "rsh: unsupported arch $A" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
TMP=$(mktemp /tmp/rsh.XXXXXX)
|
||||||
|
trap 'rm -f "$TMP"' EXIT
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
curl -fsSL "$BASE/rsh/$ARCH" -o "$TMP"
|
||||||
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
|
wget -qO "$TMP" "$BASE/rsh/$ARCH"
|
||||||
|
else
|
||||||
|
echo "rsh: need curl or wget" >&2; exit 1
|
||||||
|
fi
|
||||||
|
chmod +x "$TMP"
|
||||||
|
printf 'Password (empty for none): ' >&2
|
||||||
|
stty -echo 2>/dev/null || true
|
||||||
|
IFS= read -r PW || PW=''
|
||||||
|
stty echo 2>/dev/null || true
|
||||||
|
echo >&2
|
||||||
|
trap - EXIT
|
||||||
|
if [ -n "$PW" ]; then
|
||||||
|
exec "$TMP" --url "$WS/ws/stub" --session "$SESSION" --password "$PW"
|
||||||
|
else
|
||||||
|
exec "$TMP" --url "$WS/ws/stub" --session "$SESSION"
|
||||||
|
fi
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct RunQuery {
|
||||||
|
s: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_sh(headers: HeaderMap, Query(q): Query<RunQuery>) -> Response {
|
||||||
|
let session = q.s.unwrap_or_else(|| "default".into());
|
||||||
|
if !is_valid_session(&session) {
|
||||||
|
return (StatusCode::BAD_REQUEST, "invalid session id").into_response();
|
||||||
|
}
|
||||||
|
let (scheme, ws_scheme) = detect_scheme(&headers);
|
||||||
|
let host = headers
|
||||||
|
.get(header::HOST)
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.unwrap_or("localhost");
|
||||||
|
let base = format!("{scheme}://{host}");
|
||||||
|
let ws = format!("{ws_scheme}://{host}");
|
||||||
|
let script = SCRIPT_TEMPLATE
|
||||||
|
.replace("__SESSION__", &session)
|
||||||
|
.replace("__BASE__", &base)
|
||||||
|
.replace("__WS__", &ws);
|
||||||
|
(
|
||||||
|
[(header::CONTENT_TYPE, "text/x-shellscript; charset=utf-8")],
|
||||||
|
script,
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn stub(Path(arch): Path<String>) -> Response {
|
||||||
|
let bytes: &[u8] = match arch.as_str() {
|
||||||
|
"x86_64" | "amd64" => STUB_X86_64,
|
||||||
|
"aarch64" | "arm64" => STUB_AARCH64,
|
||||||
|
_ => return StatusCode::NOT_FOUND.into_response(),
|
||||||
|
};
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return (StatusCode::SERVICE_UNAVAILABLE, "stub not embedded in this build").into_response();
|
||||||
|
}
|
||||||
|
(
|
||||||
|
[
|
||||||
|
(header::CONTENT_TYPE, "application/octet-stream"),
|
||||||
|
(
|
||||||
|
header::CONTENT_DISPOSITION,
|
||||||
|
"attachment; filename=\"rsh\"",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
bytes,
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_session(s: &str) -> bool {
|
||||||
|
!s.is_empty()
|
||||||
|
&& s.len() <= 64
|
||||||
|
&& s.chars().all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '_' || c == '-')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_scheme(headers: &HeaderMap) -> (&'static str, &'static str) {
|
||||||
|
let proto = headers
|
||||||
|
.get("x-forwarded-proto")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.unwrap_or("http");
|
||||||
|
if proto == "https" {
|
||||||
|
("https", "wss")
|
||||||
|
} else {
|
||||||
|
("http", "ws")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
mod auth;
|
mod auth;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod dist;
|
||||||
mod keys;
|
mod keys;
|
||||||
mod persist;
|
mod persist;
|
||||||
mod state;
|
mod state;
|
||||||
@@ -47,6 +48,9 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
|
.route("/", get(dist::run_sh))
|
||||||
|
.route("/run.sh", get(dist::run_sh))
|
||||||
|
.route("/rsh/:arch", get(dist::stub))
|
||||||
.route("/healthz", get(|| async { "ok" }))
|
.route("/healthz", get(|| async { "ok" }))
|
||||||
.route("/ws/stub", get(ws_stub::handler))
|
.route("/ws/stub", get(ws_stub::handler))
|
||||||
.route("/ws/op", get(ws_op::handler))
|
.route("/ws/op", get(ws_op::handler))
|
||||||
|
|||||||
27
justfile
Normal file
27
justfile
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
_default:
|
||||||
|
@just --list
|
||||||
|
|
||||||
|
stubs:
|
||||||
|
rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl
|
||||||
|
cargo build --release --target x86_64-unknown-linux-musl -p rsh
|
||||||
|
cargo build --release --target aarch64-unknown-linux-musl -p rsh
|
||||||
|
@echo "stubs built:"
|
||||||
|
@ls -lh target/x86_64-unknown-linux-musl/release/rsh target/aarch64-unknown-linux-musl/release/rsh
|
||||||
|
|
||||||
|
backend: stubs
|
||||||
|
RSH_STUB_X86_64=target/x86_64-unknown-linux-musl/release/rsh \
|
||||||
|
RSH_STUB_AARCH64=target/aarch64-unknown-linux-musl/release/rsh \
|
||||||
|
cargo build --release -p rsh-backend
|
||||||
|
|
||||||
|
dev: stubs
|
||||||
|
RSH_STUB_X86_64=target/x86_64-unknown-linux-musl/release/rsh \
|
||||||
|
RSH_STUB_AARCH64=target/aarch64-unknown-linux-musl/release/rsh \
|
||||||
|
RSH_DATA=/tmp/rsh-dev \
|
||||||
|
RSH_BIND=127.0.0.1:7777 \
|
||||||
|
cargo run -p rsh-backend
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
docker build -t rsh-backend:local .
|
||||||
|
|
||||||
|
docker-run:
|
||||||
|
docker run --rm -p 7777:7777 -v rsh-data:/var/lib/rsh rsh-backend:local
|
||||||
Reference in New Issue
Block a user