refactor!: rebrand wraith to alknet

Rename all crates, CLI commands, constants, type names, doc comments,
and documentation from wraith to alknet. Includes wire-protocol changes:
ALPN wraith-ssh -> alknet-ssh, reserved destination prefix wraith- ->
alknet-, SSH auth username wraith -> alknet.
This commit is contained in:
2026-06-05 10:04:32 +00:00
parent af7f4d0006
commit 596c89ce24
101 changed files with 552 additions and 552 deletions

View File

@@ -1,13 +1,13 @@
[package]
name = "wraith-core"
name = "alknet-core"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "Core library for Wraith: pluggable SSH tunnel transport, SOCKS5 proxy, port forwarding, and authentication"
description = "Core library for Alknet: pluggable SSH tunnel transport, SOCKS5 proxy, port forwarding, and authentication"
repository.workspace = true
[lib]
name = "wraith_core"
name = "alknet_core"
[features]
default = []
@@ -36,9 +36,9 @@ async-trait = "0.1"
ipnetwork = "0.21.1"
[dev-dependencies]
wraith-core = { path = ".", features = ["testutil", "tls", "iroh"] }
alknet-core = { path = ".", features = ["testutil", "tls", "iroh"] }
tempfile = "3"
rcgen = "0.14"
rand_core = "0.6"
ssh-key = { version = "0.6", features = ["ed25519", "alloc"] }
rand = "0.10.1"
rand = "0.10.1"

View File

@@ -41,14 +41,14 @@ impl std::fmt::Display for TransportMode {
}
}
/// Programmatic configuration for a wraith client session.
/// Programmatic configuration for an alknet client session.
///
/// Construct with `ConnectOptions::new(key_source)` and chain builder methods.
/// Call `validate()` before passing to `ClientSession::new()`.
///
/// ```
/// use wraith_core::client::{ConnectOptions, TransportMode};
/// use wraith_core::auth::keys::KeySource;
/// use alknet_core::client::{ConnectOptions, TransportMode};
/// use alknet_core::auth::keys::KeySource;
///
/// let opts = ConnectOptions::new(KeySource::File("/path/to/key".into()))
/// .server("example.com:22")
@@ -312,7 +312,7 @@ impl<T: Transport> ClientSession<T> {
.await;
});
info!("wraith client running: SOCKS5 on {}", socks5_listen);
info!("alknet client running: SOCKS5 on {}", socks5_listen);
#[cfg(unix)]
let signal_done = {
@@ -439,7 +439,7 @@ impl<T: Transport> ClientSession<T> {
fn derive_username() -> String {
std::env::var("USER")
.or_else(|_| std::env::var("USERNAME"))
.unwrap_or_else(|_| "wraith".to_string())
.unwrap_or_else(|_| "alknet".to_string())
}
async fn establish_session<T: Transport>(
@@ -567,7 +567,7 @@ mod tests {
.remote_forward("0.0.0.0:8080:127.0.0.1:3000")
.proxy("socks5://127.0.0.1:1080")
.iroh_relay("https://relay.example.com")
.tls_server_name("wraith.test")
.tls_server_name("alknet.test")
.insecure(true);
assert_eq!(opts.server.as_deref(), Some("example.com:22"));
@@ -577,7 +577,7 @@ mod tests {
assert_eq!(opts.remote_forwards.len(), 1);
assert_eq!(opts.proxy.as_deref(), Some("socks5://127.0.0.1:1080"));
assert_eq!(opts.iroh_relay.as_deref(), Some("https://relay.example.com"));
assert_eq!(opts.tls_server_name.as_deref(), Some("wraith.test"));
assert_eq!(opts.tls_server_name.as_deref(), Some("alknet.test"));
assert!(opts.insecure);
}

View File

@@ -1,4 +1,4 @@
//! Error types for wraith-core.
//! Error types for alknet-core.
//!
//! Layered error hierarchy:
//! - `TransportError` — connection/handshake/timeout errors (trigger reconnection on client)

View File

@@ -1,8 +1,8 @@
//! # wraith-core
//! # alknet-core
//!
//! Core library for [Wraith](https://git.alk.dev/alkdev/wraith), a self-hostable SSH-based
//! Core library for [Alknet](https://git.alk.dev/alkdev/alknet), a self-hostable SSH-based
//! tunnel tool. This crate provides the transport abstraction, SOCKS5 server, port forwarding,
//! authentication, and server handler — everything needed to build a wraith client or server
//! authentication, and server handler — everything needed to build an alknet client or server
//! on top of pluggable transports.
//!
//! > **Alpha software.** This crate depends on solid libraries (russh, tokio, rustls, iroh)
@@ -33,10 +33,10 @@
//!
//! ```no_run
//! use std::sync::Arc;
//! use wraith_core::transport::TcpTransport;
//! use wraith_core::client::{ClientSession, ConnectOptions, TransportMode};
//! use wraith_core::auth::keys::KeySource;
//! use wraith_core::Transport;
//! use alknet_core::transport::TcpTransport;
//! use alknet_core::client::{ClientSession, ConnectOptions, TransportMode};
//! use alknet_core::auth::keys::KeySource;
//! use alknet_core::Transport;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {

View File

@@ -1,6 +1,6 @@
//! Control channel routing for reserved `wraith-*` destinations.
//! Control channel routing for reserved `alknet-*` destinations.
//!
//! SSH channels opened with a destination starting with `wraith-` are intercepted
//! SSH channels opened with a destination starting with `alknet-` are intercepted
//! by the server and routed to a `ControlChannelHandler` instead of proxied to a
//! TCP target. See ADR-018 for the design rationale.
@@ -9,11 +9,11 @@ use std::io;
use async_trait::async_trait;
use tokio::io::{AsyncRead, AsyncWrite};
pub const WRAITH_CONTROL_DESTINATION: &str = "wraith-control";
pub const WRAITH_PREFIX: &str = "wraith-";
pub const ALKNET_CONTROL_DESTINATION: &str = "alknet-control";
pub const ALKNET_PREFIX: &str = "alknet-";
pub fn is_reserved_destination(host: &str) -> bool {
host.starts_with(WRAITH_PREFIX)
host.starts_with(ALKNET_PREFIX)
}
pub trait DuplexStream: AsyncRead + AsyncWrite + Unpin + Send {}
@@ -68,21 +68,21 @@ mod tests {
use tokio::io::duplex;
#[test]
fn wraith_control_destination_constant() {
assert_eq!(WRAITH_CONTROL_DESTINATION, "wraith-control");
fn alknet_control_destination_constant() {
assert_eq!(ALKNET_CONTROL_DESTINATION, "alknet-control");
}
#[test]
fn wraith_prefix_constant() {
assert_eq!(WRAITH_PREFIX, "wraith-");
fn alknet_prefix_constant() {
assert_eq!(ALKNET_PREFIX, "alknet-");
}
#[test]
fn reserved_destination_detected() {
assert!(is_reserved_destination("wraith-control"));
assert!(is_reserved_destination("wraith-status"));
assert!(is_reserved_destination("wraith-events"));
assert!(is_reserved_destination("wraith-"));
assert!(is_reserved_destination("alknet-control"));
assert!(is_reserved_destination("alknet-status"));
assert!(is_reserved_destination("alknet-events"));
assert!(is_reserved_destination("alknet-"));
}
#[test]
@@ -90,17 +90,17 @@ mod tests {
assert!(!is_reserved_destination("example.com"));
assert!(!is_reserved_destination("localhost"));
assert!(!is_reserved_destination("192.168.1.1"));
assert!(!is_reserved_destination("wraith.example.com"));
assert!(!is_reserved_destination("alknet.example.com"));
assert!(!is_reserved_destination(""));
assert!(!is_reserved_destination("wrait-control"));
assert!(!is_reserved_destination("WRAITH-control"));
assert!(!is_reserved_destination("alkne-control"));
assert!(!is_reserved_destination("ALKNET-control"));
}
#[test]
fn prefix_matching_case_sensitive() {
assert!(!is_reserved_destination("Wraith-control"));
assert!(!is_reserved_destination("WRAITH-control"));
assert!(is_reserved_destination("wraith-Control"));
assert!(!is_reserved_destination("Alknet-control"));
assert!(!is_reserved_destination("ALKNET-control"));
assert!(is_reserved_destination("alknet-Control"));
}
#[test]
@@ -187,6 +187,6 @@ mod tests {
#[test]
fn control_channel_destination_matches_prefix() {
assert!(is_reserved_destination(WRAITH_CONTROL_DESTINATION));
assert!(is_reserved_destination(ALKNET_CONTROL_DESTINATION));
}
}

View File

@@ -10,7 +10,7 @@ use russh::ChannelId;
use crate::auth::ServerAuthConfig;
use crate::server::control_channel::{
ControlChannelHandler, ControlChannelRouter, WRAITH_PREFIX,
ControlChannelHandler, ControlChannelRouter, ALKNET_PREFIX,
};
use crate::server::rate_limit::{AuthAttemptLimiter, ConnectionRateLimiter};
@@ -210,7 +210,7 @@ impl Handler for ServerHandler {
originator_port: u32,
_session: &mut Session,
) -> Result<bool, Self::Error> {
if host_to_connect.starts_with(WRAITH_PREFIX) {
if host_to_connect.starts_with(ALKNET_PREFIX) {
if !self.control_channel_router.has_handler() {
return Ok(false);
}
@@ -576,18 +576,18 @@ mod tests {
}
#[test]
fn reserved_wraith_destination_routing() {
fn reserved_alknet_destination_routing() {
use crate::server::control_channel::is_reserved_destination;
assert!(is_reserved_destination("wraith-control"));
assert!(is_reserved_destination("wraith-status"));
assert!(is_reserved_destination("wraith-events"));
assert!(is_reserved_destination("alknet-control"));
assert!(is_reserved_destination("alknet-status"));
assert!(is_reserved_destination("alknet-events"));
assert!(!is_reserved_destination("example.com"));
assert!(!is_reserved_destination("localhost"));
assert!(!is_reserved_destination("wraith.example.com"));
assert!(!is_reserved_destination("alknet.example.com"));
}
#[test]
fn server_handler_without_control_handler_rejects_wraith_destinations() {
fn server_handler_without_control_handler_rejects_alknet_destinations() {
let auth_config = make_empty_auth_config();
let handler = make_handler(auth_config, None, None);
assert!(!handler.control_channel_router().has_handler());

View File

@@ -5,7 +5,7 @@
//! auth, connection rate limiting, auth attempt limiting, stealth mode (fake nginx 404),
//! and outbound proxy routing (direct/SOCKS5/HTTP CONNECT).
//!
//! Destination hosts starting with `wraith-` are reserved for internal use (control channel, ADR-018).
//! Destination hosts starting with `alknet-` are reserved for internal use (control channel, ADR-018).
pub mod channel_proxy;
pub mod control_channel;
@@ -16,8 +16,8 @@ pub mod stealth;
pub use channel_proxy::{connect_outbound, proxy_channel};
pub use control_channel::{
ControlChannelHandler, ControlChannelRouter, DuplexStream, WRAITH_CONTROL_DESTINATION,
WRAITH_PREFIX, is_reserved_destination,
ControlChannelHandler, ControlChannelRouter, DuplexStream, ALKNET_CONTROL_DESTINATION,
ALKNET_PREFIX, is_reserved_destination,
};
pub use handler::{ProxyConfig, ProxyMode, ServerHandler, TransportKind};
pub use rate_limit::{AuthAttemptLimiter, ConnectionRateLimiter};

View File

@@ -40,14 +40,14 @@ impl std::fmt::Display for ServeTransportMode {
}
}
/// Programmatic configuration for a wraith server.
/// Programmatic configuration for an alknet server.
///
/// Construct with `ServeOptions::new(key_source)` and chain builder methods.
/// Call `validate()` before passing to `Server::new()`.
///
/// ```
/// use wraith_core::server::{ServeOptions, ServeTransportMode};
/// use wraith_core::auth::keys::KeySource;
/// use alknet_core::server::{ServeOptions, ServeTransportMode};
/// use alknet_core::auth::keys::KeySource;
///
/// let opts = ServeOptions::new(KeySource::File("/path/to/host_key".into()))
/// .transport_mode(ServeTransportMode::Tcp)
@@ -221,7 +221,7 @@ struct ActiveSession {
join: tokio::task::JoinHandle<()>,
}
/// The wraith SSH server.
/// The alknet SSH server.
///
/// Accepts connections over any `TransportAcceptor`, authenticates via Ed25519 keys
/// or certificate authority, and proxies `direct-tcpip` channels to their targets.
@@ -331,13 +331,13 @@ impl Server {
if self.transport_mode == ServeTransportMode::Iroh {
if let Some(id) = endpoint_info {
info!("wraith server running: transport=iroh endpoint_id={}", id);
info!("alknet server running: transport=iroh endpoint_id={}", id);
} else {
info!("wraith server running: transport=iroh");
info!("alknet server running: transport=iroh");
}
} else {
info!(
"wraith server running: transport={} listen={}",
"alknet server running: transport={} listen={}",
self.transport_mode, self.listen_addr
);
}

View File

@@ -9,7 +9,7 @@ use tokio::io;
use super::{Transport, TransportAcceptor, TransportInfo, TransportKind};
pub const ALPN: &[u8] = b"wraith-ssh";
pub const ALPN: &[u8] = b"alknet-ssh";
const DEFAULT_RELAY_URL: &str = "https://relay.iroh.network/";
/// A client-side iroh QUIC P2P transport that connects to a remote iroh endpoint.
@@ -31,8 +31,8 @@ pub struct IrohTransport {
impl IrohTransport {
/// Create a new iroh transport with its own dedicated endpoint.
///
/// The endpoint is created with the `wraith-ssh` ALPN and the provided
/// relay URL. Use this when wraith is the only iroh service on this node.
/// The endpoint is created with the `alknet-ssh` ALPN and the provided
/// relay URL. Use this when alknet is the only iroh service on this node.
pub async fn new(
node_id: NodeId,
relay_url: Option<RelayUrl>,
@@ -54,9 +54,9 @@ impl IrohTransport {
/// Create an iroh transport using an existing shared endpoint.
///
/// The endpoint must already have the `wraith-ssh` ALPN registered
/// The endpoint must already have the `alknet-ssh` ALPN registered
/// (typically via [`iroh::protocol::Router::builder`]). This enables
/// running wraith alongside iroh-blobs, iroh-gossip, iroh-docs, and
/// running alknet alongside iroh-blobs, iroh-gossip, iroh-docs, and
/// other protocol handlers on the same QUIC endpoint — one connection
/// per peer, multiplexed by ALPN.
pub fn from_endpoint(node_id: NodeId, endpoint: Endpoint) -> Self {
@@ -102,9 +102,9 @@ impl Transport for IrohTransport {
/// [`IrohAcceptor::from_endpoint`] to share an existing iroh `Endpoint`
/// with other protocol handlers (blobs, gossip, docs).
///
/// When using `from_endpoint`, the wraith-ssh ALPN must be registered
/// When using `from_endpoint`, the alknet-ssh ALPN must be registered
/// via an iroh `Router` that calls `Handler::accept()` on incoming
/// connections with the `wraith-ssh` ALPN, then passes the accepted
/// connections with the `alknet-ssh` ALPN, then passes the accepted
/// bidirectional stream to `russh::server::run_stream()`.
pub struct IrohAcceptor {
endpoint: Endpoint,
@@ -112,9 +112,9 @@ pub struct IrohAcceptor {
}
impl IrohAcceptor {
/// Bind a new iroh endpoint with a dedicated `wraith-ssh` ALPN.
/// Bind a new iroh endpoint with a dedicated `alknet-ssh` ALPN.
///
/// Use this when wraith is the only iroh service on this node.
/// Use this when alknet is the only iroh service on this node.
pub async fn bind(
relay_url: Option<RelayUrl>,
proxy_url: Option<url::Url>,
@@ -135,14 +135,14 @@ impl IrohAcceptor {
/// Create an iroh acceptor using an existing shared endpoint.
///
/// The endpoint must already have the `wraith-ssh` ALPN registered
/// The endpoint must already have the `alknet-ssh` ALPN registered
/// (typically via [`iroh::protocol::Router::builder`]). When using a
/// shared endpoint, incoming connections with the `wraith-ssh` ALPN
/// shared endpoint, incoming connections with the `alknet-ssh` ALPN
/// are routed by the Router to a `ProtocolHandler` that this acceptor
/// does not manage — the caller is responsible for bridging the
/// Router's `accept()` callback to this acceptor's stream handling.
///
/// For the standalone case where wraith owns the endpoint, use
/// For the standalone case where alknet owns the endpoint, use
/// [`IrohAcceptor::bind`] instead, which handles the accept loop
/// internally.
pub fn from_endpoint(endpoint: Endpoint) -> Self {

View File

@@ -1,4 +1,4 @@
//! Pluggable transport layer for Wraith.
//! Pluggable transport layer for Alknet.
//!
//! The transport layer produces a duplex byte stream (`AsyncRead + AsyncWrite + Unpin + Send`)
//! that SSH consumes. This is the core architectural abstraction — SSH never opens its own

View File

@@ -293,9 +293,9 @@ mod tests {
fn tls_transport_builder_methods() {
let addr: SocketAddr = "1.2.3.4:443".parse().unwrap();
let transport = TlsTransport::new(addr)
.with_server_name("wraith.test")
.with_server_name("alknet.test")
.with_insecure(true);
assert_eq!(transport.tls_server_name, Some("wraith.test".to_string()));
assert_eq!(transport.tls_server_name, Some("alknet.test".to_string()));
assert!(transport.insecure);
}
@@ -395,7 +395,7 @@ mod tests {
let mut client = transport.connect().await.unwrap();
let (mut server, _info) = accept_handle.await.unwrap();
let msg = b"wraith integration test";
let msg = b"alknet integration test";
client.write_all(msg).await.unwrap();
let mut buf = vec![0u8; msg.len()];
server.read_exact(&mut buf).await.unwrap();

View File

@@ -1,4 +1,4 @@
use wraith_core::testutil::{MockTransport, MockTransportAcceptor, Transport, TransportAcceptor, mock_pair};
use alknet_core::testutil::{MockTransport, MockTransportAcceptor, Transport, TransportAcceptor, mock_pair};
#[tokio::test]
async fn mock_transport_connect() {

View File

@@ -1,16 +1,16 @@
[package]
name = "wraith-napi"
name = "alknet-napi"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "Node.js native addon for Wraith via napi-rs: connect() and serve() SSH tunnel functions"
description = "Node.js native addon for Alknet via napi-rs: connect() and serve() SSH tunnel functions"
repository.workspace = true
[lib]
crate-type = ["cdylib"]
[dependencies]
wraith-core = { path = "../wraith-core", features = ["tls", "iroh"] }
alknet-core = { path = "../alknet-core", features = ["tls", "iroh"] }
napi = { version = "3", features = ["async", "error_anyhow"] }
napi-derive = "3"
tokio = { version = "1", features = ["io-util", "sync", "rt", "macros", "net", "time", "signal"] }

View File

@@ -1,4 +1,4 @@
//! NAPI `connect()` function and `WraithStream` type.
//! NAPI `connect()` function and `AlknetStream` type.
//!
//! Opens a single SSH channel as a duplex stream for programmatic use.
//! Unlike the CLI client, this does not start a SOCKS5 server or port forwards —
@@ -13,15 +13,15 @@ use russh::client;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::Mutex;
use wraith_core::auth::client_auth::{ClientAuthConfig, ClientHandler};
use wraith_core::auth::keys::KeySource;
use wraith_core::transport::{IrohTransport, TcpTransport, TlsTransport, Transport};
use alknet_core::auth::client_auth::{ClientAuthConfig, ClientHandler};
use alknet_core::auth::keys::KeySource;
use alknet_core::transport::{IrohTransport, TcpTransport, TlsTransport, Transport};
const DEFAULT_HOST: &str = "wraith-control";
const DEFAULT_HOST: &str = "alknet-control";
const DEFAULT_PORT: u32 = 0;
#[napi(object)]
pub struct WraithConnectOptions {
pub struct AlknetConnectOptions {
pub server: Option<String>,
pub peer: Option<String>,
pub transport: String,
@@ -53,13 +53,13 @@ fn parse_addr(addr_str: &str) -> Result<SocketAddr> {
}
#[napi]
pub struct WraithStream {
pub struct AlknetStream {
read: Arc<Mutex<tokio::io::ReadHalf<russh::ChannelStream<client::Msg>>>>,
write: Arc<Mutex<tokio::io::WriteHalf<russh::ChannelStream<client::Msg>>>>,
}
#[napi]
impl WraithStream {
impl AlknetStream {
#[napi]
pub async fn read(&self, size: u32) -> Result<Buffer> {
let mut buf = vec![0u8; size as usize];
@@ -96,7 +96,7 @@ impl WraithStream {
}
#[napi]
pub async fn connect(options: WraithConnectOptions) -> Result<WraithStream> {
pub async fn connect(options: AlknetConnectOptions) -> Result<AlknetStream> {
let key_source = resolve_key_source(&options.identity)?;
let auth_config = Arc::new(
ClientAuthConfig::from_key_source(key_source)
@@ -105,7 +105,7 @@ pub async fn connect(options: WraithConnectOptions) -> Result<WraithStream> {
let transport_mode = options.transport.to_lowercase();
let handler = ClientHandler::from_config(&auth_config);
let username = "wraith".to_string();
let username = "alknet".to_string();
let config = Arc::new(client::Config::default());
@@ -232,7 +232,7 @@ pub async fn connect(options: WraithConnectOptions) -> Result<WraithStream> {
let stream = channel.into_stream();
let (read_half, write_half) = tokio::io::split(stream);
Ok(WraithStream {
Ok(AlknetStream {
read: Arc::new(Mutex::new(read_half)),
write: Arc::new(Mutex::new(write_half)),
})

View File

@@ -1,6 +1,6 @@
//! # wraith-napi
//! # alknet-napi
//!
//! Node.js native addon for [Wraith](https://git.alk.dev/alkdev/wraith) via napi-rs.
//! Node.js native addon for [Alknet](https://git.alk.dev/alkdev/alknet) via napi-rs.
//! Exposes `connect()` and `serve()` functions for programmatic SSH tunnel creation.
//!
//! > **Alpha software.** The NAPI interface may change between versions.
@@ -8,7 +8,7 @@
//! # Quick example (Node.js)
//!
//! ```js
//! const { connect, serve } = require('wraith-napi');
//! const { connect, serve } = require('alknet-napi');
//!
//! // Client: open a duplex SSH stream
//! const stream = await connect({

View File

@@ -1,4 +1,4 @@
//! NAPI `serve()` function and `WraithServer` type.
//! NAPI `serve()` function and `AlknetServer` type.
//!
//! Starts an SSH server that emits new channel streams via a
//! `ThreadsafeFunction` callback. Supports TCP, TLS, and iroh transports.
@@ -14,14 +14,14 @@ use russh::Channel;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::Mutex;
use wraith_core::auth::keys::KeySource;
use wraith_core::auth::server_auth::ServerAuthConfig;
use wraith_core::server::rate_limit::{AuthAttemptLimiter, ConnectionRateLimiter};
use wraith_core::server::serve::{ServeOptions, ServeTransportMode, Server};
use wraith_core::transport::{TcpAcceptor, TransportAcceptor};
use alknet_core::auth::keys::KeySource;
use alknet_core::auth::server_auth::ServerAuthConfig;
use alknet_core::server::rate_limit::{AuthAttemptLimiter, ConnectionRateLimiter};
use alknet_core::server::serve::{ServeOptions, ServeTransportMode, Server};
use alknet_core::transport::{TcpAcceptor, TransportAcceptor};
#[napi(object)]
pub struct WraithServeOptions {
pub struct AlknetServeOptions {
pub transport: String,
pub host_key: Option<Either<String, Buffer>>,
pub authorized_keys: Option<Either<String, Buffer>>,
@@ -75,13 +75,13 @@ pub struct ConnectionInfo {
}
#[napi]
pub struct WraithServerStream {
pub struct AlknetServerStream {
read: Arc<Mutex<tokio::io::ReadHalf<russh::ChannelStream<server::Msg>>>>,
write: Arc<Mutex<tokio::io::WriteHalf<russh::ChannelStream<server::Msg>>>>,
}
#[napi]
impl WraithServerStream {
impl AlknetServerStream {
#[napi]
pub async fn read(&self, size: u32) -> napi::Result<Buffer> {
let mut buf = vec![0u8; size as usize];
@@ -208,7 +208,7 @@ impl russh::server::Handler for NapiServerHandler {
_originator_port: u32,
_session: &mut russh::server::Session,
) -> std::result::Result<bool, Self::Error> {
if host_to_connect.starts_with("wraith-") {
if host_to_connect.starts_with("alknet-") {
let guard = self.channel_sender.lock().await;
if let Some(ref tx) = *guard {
let _ = tx.send(channel);
@@ -385,7 +385,7 @@ impl russh::server::Handler for NapiServerHandler {
type ServerTsfn = ThreadsafeFunction<ConnectionEventWrapper, (), ConnectionEventWrapper>;
#[napi]
pub struct WraithServer {
pub struct AlknetServer {
shutdown_tx: tokio::sync::watch::Sender<bool>,
listen_addr: String,
endpoint_id: Option<String>,
@@ -393,7 +393,7 @@ pub struct WraithServer {
}
struct ConnectionEventWrapper {
stream: WraithServerStream,
stream: AlknetServerStream,
info: ConnectionInfo,
}
@@ -408,7 +408,7 @@ impl ToNapiValue for ConnectionEventWrapper {
"Failed to create object"
)?;
let stream_val = <WraithServerStream as ToNapiValue>::to_napi_value(env, val.stream)?;
let stream_val = <AlknetServerStream as ToNapiValue>::to_napi_value(env, val.stream)?;
let key_stream = std::ffi::CString::new("stream").unwrap();
napi::check_status!(
napi::sys::napi_set_named_property(env, raw_obj, key_stream.as_ptr(), stream_val),
@@ -439,7 +439,7 @@ impl TypeName for ConnectionEventWrapper {
impl ValidateNapiValue for ConnectionEventWrapper {}
#[napi]
impl WraithServer {
impl AlknetServer {
#[napi]
pub async fn close(&self) -> napi::Result<()> {
let _ = self.shutdown_tx.send(true);
@@ -470,7 +470,7 @@ impl WraithServer {
}
#[napi]
pub async fn serve(options: WraithServeOptions) -> napi::Result<WraithServer> {
pub async fn serve(options: AlknetServeOptions) -> napi::Result<AlknetServer> {
let host_key_source = resolve_key_source(&options.host_key, "hostKey")?;
let authorized_keys_source = resolve_optional_key_source(&options.authorized_keys);
let cert_authority_source = resolve_optional_key_source(&options.cert_authority);
@@ -543,7 +543,7 @@ pub async fn serve(options: WraithServeOptions) -> napi::Result<WraithServer> {
);
let private_key =
wraith_core::auth::keys::load_private_key(host_key_source).map_err(|e| {
alknet_core::auth::keys::load_private_key(host_key_source.clone()).map_err(|e| {
napi::Error::new(napi::Status::InvalidArg, format!("host key error: {}", e))
})?;
@@ -573,7 +573,7 @@ pub async fn serve(options: WraithServeOptions) -> napi::Result<WraithServer> {
.await;
});
Ok(WraithServer {
Ok(AlknetServer {
shutdown_tx,
listen_addr: actual_listen,
endpoint_id: None,
@@ -581,7 +581,7 @@ pub async fn serve(options: WraithServeOptions) -> napi::Result<WraithServer> {
})
}
ServeTransportMode::Tls => {
use wraith_core::transport::TlsAcceptor;
use alknet_core::transport::TlsAcceptor;
let addr = parse_addr(listen_addr_str)?;
@@ -654,7 +654,7 @@ pub async fn serve(options: WraithServeOptions) -> napi::Result<WraithServer> {
);
let private_key =
wraith_core::auth::keys::load_private_key(host_key_source).map_err(|e| {
alknet_core::auth::keys::load_private_key(host_key_source.clone()).map_err(|e| {
napi::Error::new(napi::Status::InvalidArg, format!("host key error: {}", e))
})?;
@@ -684,7 +684,7 @@ pub async fn serve(options: WraithServeOptions) -> napi::Result<WraithServer> {
.await;
});
Ok(WraithServer {
Ok(AlknetServer {
shutdown_tx,
listen_addr: actual_listen,
endpoint_id: None,
@@ -692,7 +692,7 @@ pub async fn serve(options: WraithServeOptions) -> napi::Result<WraithServer> {
})
}
ServeTransportMode::Iroh => {
use wraith_core::transport::IrohAcceptor;
use alknet_core::transport::IrohAcceptor;
let relay_url: Option<iroh::RelayUrl> = match options.iroh_relay.as_deref() {
Some(u) => Some(u.parse().map_err(|e| {
@@ -736,7 +736,7 @@ pub async fn serve(options: WraithServeOptions) -> napi::Result<WraithServer> {
);
let private_key =
wraith_core::auth::keys::load_private_key(host_key_source).map_err(|e| {
alknet_core::auth::keys::load_private_key(host_key_source).map_err(|e| {
napi::Error::new(napi::Status::InvalidArg, format!("host key error: {}", e))
})?;
@@ -766,7 +766,7 @@ pub async fn serve(options: WraithServeOptions) -> napi::Result<WraithServer> {
.await;
});
Ok(WraithServer {
Ok(AlknetServer {
shutdown_tx,
listen_addr: String::new(),
endpoint_id: Some(iroh_endpoint_id),
@@ -836,7 +836,7 @@ async fn run_accept_loop<A>(
Some(ch) => {
let channel_stream = ch.into_stream();
let (read_half, write_half) = tokio::io::split(channel_stream);
let server_stream = WraithServerStream {
let server_stream = AlknetServerStream {
read: Arc::new(Mutex::new(read_half)),
write: Arc::new(Mutex::new(write_half)),
};

View File

@@ -1,23 +1,23 @@
[package]
name = "wraith"
name = "alknet"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "CLI binary for Wraith: self-hostable SSH tunnel tool with pluggable transports"
description = "CLI binary for Alknet: self-hostable SSH tunnel tool with pluggable transports"
repository.workspace = true
[[bin]]
name = "wraith"
name = "alknet"
path = "src/main.rs"
[features]
default = ["tls", "iroh"]
tls = ["wraith-core/tls", "dep:rustls-pemfile", "dep:rustls-pki-types"]
iroh = ["wraith-core/iroh", "dep:iroh", "dep:url"]
acme = ["wraith-core/acme", "dep:rustls-acme", "dep:rustls", "tls"]
tls = ["alknet-core/tls", "dep:rustls-pemfile", "dep:rustls-pki-types"]
iroh = ["alknet-core/iroh", "dep:iroh", "dep:url"]
acme = ["alknet-core/acme", "dep:rustls-acme", "dep:rustls", "tls"]
[dependencies]
wraith-core = { path = "../wraith-core" }
alknet-core = { path = "../alknet-core" }
clap = { version = "4", features = ["derive", "env"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"

View File

@@ -1,10 +1,10 @@
//! # wraith
//! # alknet
//!
//! CLI binary for [Wraith](https://git.alk.dev/alkdev/wraith), a self-hostable SSH-based tunnel
//! tool. Provides `wraith connect` (client) and `wraith serve` (server) subcommands with
//! CLI binary for [Alknet](https://git.alk.dev/alkdev/alknet), a self-hostable SSH-based tunnel
//! tool. Provides `alknet connect` (client) and `alknet serve` (server) subcommands with
//! pluggable transports (TCP, TLS, iroh).
//!
//! > **Alpha software.** See `wraith-core` for library usage.
//! > **Alpha software.** See `alknet-core` for library usage.
use std::net::SocketAddr;
use std::process;
@@ -12,18 +12,18 @@ use std::sync::Arc;
use anyhow::{anyhow, Result};
use clap::{Parser, Subcommand, ValueEnum};
use wraith_core::auth::keys::KeySource;
use wraith_core::client::{ConnectOptions, TransportMode};
use wraith_core::server::{ServeOptions, ServeTransportMode, Server};
use alknet_core::auth::keys::KeySource;
use alknet_core::client::{ConnectOptions, TransportMode};
use alknet_core::server::{ServeOptions, ServeTransportMode, Server};
#[cfg(feature = "iroh")]
use wraith_core::transport::IrohTransport;
use wraith_core::transport::TcpTransport;
use alknet_core::transport::IrohTransport;
use alknet_core::transport::TcpTransport;
#[cfg(feature = "tls")]
use wraith_core::transport::TlsTransport;
use wraith_core::transport::Transport;
use alknet_core::transport::TlsTransport;
use alknet_core::transport::Transport;
#[derive(Parser)]
#[command(name = "wraith", version, about = "Wraith SSH tunnel tool")]
#[command(name = "alknet", version, about = "Alknet SSH tunnel tool")]
struct Cli {
#[command(subcommand)]
command: Commands,
@@ -32,13 +32,13 @@ struct Cli {
#[derive(Subcommand)]
enum Commands {
#[command(
about = "Connect to a wraith server and start a SOCKS5 proxy / port forwarding session"
about = "Connect to an alknet server and start a SOCKS5 proxy / port forwarding session"
)]
Connect {
#[arg(
long,
help = "TCP/TLS server address (required for tcp/tls transport)",
env = "WRAITH_SERVER"
env = "ALKNET_SERVER"
)]
server: Option<String>,
@@ -51,7 +51,7 @@ enum Commands {
#[arg(long, value_enum, default_value = "tcp", help = "Transport mode")]
transport: TransportModeArg,
#[arg(long, help = "SSH private key path", env = "WRAITH_IDENTITY")]
#[arg(long, help = "SSH private key path", env = "ALKNET_IDENTITY")]
identity: Option<String>,
#[arg(long, default_value = "127.0.0.1:1080", help = "SOCKS5 listen address")]
@@ -76,7 +76,7 @@ enum Commands {
insecure: bool,
},
#[command(about = "Start the wraith server (accept SSH connections)")]
#[command( about = "Start the alknet server (accept SSH connections)")]
Serve {
#[arg(long, help = "SSH host key path (required)")]
key: String,
@@ -263,7 +263,7 @@ async fn run_connect(
insecure: bool,
) -> Result<()> {
let identity_val = identity
.ok_or_else(|| anyhow!("--identity is required (or set WRAITH_IDENTITY env var)"))?;
.ok_or_else(|| anyhow!("--identity is required (or set ALKNET_IDENTITY env var)"))?;
let key_source = KeySource::File(identity_val.into());
let transport_mode: TransportMode = transport.into();
@@ -317,7 +317,7 @@ async fn run_connect(
#[cfg(not(feature = "tls"))]
{
Err(anyhow!(
"TLS transport is not available (wraith-core built without 'tls' feature)"
"TLS transport is not available (alknet-core built without 'tls' feature)"
))
}
#[cfg(feature = "tls")]
@@ -340,7 +340,7 @@ async fn run_connect(
#[cfg(not(feature = "iroh"))]
{
Err(anyhow!(
"iroh transport is not available (wraith-core built without 'iroh' feature)"
"iroh transport is not available (alknet-core built without 'iroh' feature)"
))
}
#[cfg(feature = "iroh")]
@@ -375,7 +375,7 @@ async fn run_connect(
}
async fn connect_and_run<T: Transport>(opts: ConnectOptions, transport: Arc<T>) -> Result<()> {
wraith_core::client::ClientSession::new(opts, transport)
alknet_core::client::ClientSession::new(opts, transport)
.await
.map_err(|e| anyhow!("{e}"))?
.run()
@@ -405,7 +405,7 @@ async fn run_serve(
#[cfg(not(feature = "acme"))]
{
return Err(anyhow!(
"ACME support is not available (wraith built without 'acme' feature)"
"ACME support is not available (alknet built without 'acme' feature)"
));
}
}
@@ -454,7 +454,7 @@ async fn run_serve(
let addr: SocketAddr = listen
.parse()
.map_err(|e| anyhow!("invalid listen address: {e}"))?;
let acceptor = wraith_core::transport::TcpAcceptor::bind(addr)
let acceptor = alknet_core::transport::TcpAcceptor::bind(addr)
.await
.map_err(|e| anyhow!("bind failed: {e}"))?;
server.run(acceptor, None).await.map_err(|e| anyhow!("{e}"))
@@ -463,7 +463,7 @@ async fn run_serve(
#[cfg(not(feature = "tls"))]
{
Err(anyhow!(
"TLS transport is not available (wraith-core built without 'tls' feature)"
"TLS transport is not available (alknet-core built without 'tls' feature)"
))
}
#[cfg(feature = "acme")]
@@ -473,11 +473,11 @@ async fn run_serve(
.parse()
.map_err(|e| anyhow!("invalid listen address: {e}"))?;
let provider = Arc::new(
wraith_core::transport::AcmeCertProvider::domain(domain)
alknet_core::transport::AcmeCertProvider::domain(domain)
.with_production_directory(),
);
let acceptor =
wraith_core::transport::AcmeTlsAcceptor::bind_acme(addr, provider)
alknet_core::transport::AcmeTlsAcceptor::bind_acme(addr, provider)
.await
.map_err(|e| anyhow!("ACME bind failed: {e}"))?;
return server.run(acceptor, None).await.map_err(|e| anyhow!("{e}"));
@@ -506,7 +506,7 @@ async fn run_serve(
let key: PrivateKeyDer<'static> = rustls_pemfile::private_key(&mut &key_data[..])
.map_err(|e| anyhow!("failed to parse TLS private key: {e}"))?
.ok_or_else(|| anyhow!("no private key found in {}", key_path))?;
let acceptor = wraith_core::transport::TlsAcceptor::bind(addr, certs, key, None)
let acceptor = alknet_core::transport::TlsAcceptor::bind(addr, certs, key, None)
.await
.map_err(|e| anyhow!("TLS bind failed: {e}"))?;
server.run(acceptor, None).await.map_err(|e| anyhow!("{e}"))
@@ -516,7 +516,7 @@ async fn run_serve(
#[cfg(not(feature = "iroh"))]
{
Err(anyhow!(
"iroh transport is not available (wraith-core built without 'iroh' feature)"
"iroh transport is not available (alknet-core built without 'iroh' feature)"
))
}
#[cfg(feature = "iroh")]
@@ -533,7 +533,7 @@ async fn run_serve(
Some(u) => Some(u.parse().map_err(|e| anyhow!("invalid proxy URL: {e}"))?),
None => None,
};
let acceptor = wraith_core::transport::IrohAcceptor::bind(relay_url, proxy_url)
let acceptor = alknet_core::transport::IrohAcceptor::bind(relay_url, proxy_url)
.await
.map_err(|e| anyhow!("iroh bind failed: {e}"))?;
let endpoint_id = acceptor.endpoint_id();