Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Glues offers various storage options to suit your needs:
- Point Glues at an HTTP proxy that exposes the same set of operations as the local backend.
- Run the bundled proxy server with `glues server memory` (replace `memory` with `file`, `redb`, `git`, or `mongo` as needed). The server listens on `127.0.0.1:4000` by default; use `--listen` to change the address.
- Protect externally reachable servers with an auth token. Set `GLUES_SERVER_TOKEN` or pass `--auth-token <value>` when launching the server. The TUI's Proxy flow will prompt for the token and send it as a `Bearer` header. Leave the field empty to connect to a token-free server on your local machine.
- To expose only part of a notebook, pass `--allowed-directory <directory-id>`. (Gather the target ID first by running the server once without the flag and calling `RootId`/`FetchDirectories`, or by inspecting the storage backend.) The proxy will treat that directory as the virtual root: reads and writes outside the subtree are rejected, and clients receive the allowed directory ID when requesting the root.
- In the TUI entry menu choose `Proxy` (shortcut `[p]`), enter the proxy URL (e.g. `http://127.0.0.1:4000`), provide the token if required, and Glues will talk to the remote backend just like it does locally.

> **Web build:** The browser version of Glues persists configuration through GlueSQL WebStorage (LocalStorage) and currently exposes the **Instant**, **IndexedDB**, and **Proxy** backends. Use IndexedDB to keep notes in-browser across sessions, or point the Proxy option at a running Glues proxy server to keep data outside the browser sandbox.
Expand Down
49 changes: 49 additions & 0 deletions server/src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use {
clap::{Args, Parser, Subcommand},
glues_core::types::DirectoryId,
std::net::SocketAddr,
};

#[derive(Clone, Args)]
pub struct ServerArgs {
#[arg(long, default_value = "127.0.0.1:4000")]
pub listen: SocketAddr,

#[arg(long, env = "GLUES_SERVER_TOKEN")]
pub auth_token: Option<String>,

#[arg(long)]
pub allowed_directory: Option<DirectoryId>,

#[command(subcommand)]
pub storage: StorageCommand,
}

#[derive(Parser)]
#[command(author, version, about = "Glues proxy server")]
struct Cli {
#[command(flatten)]
args: ServerArgs,
}

#[derive(Subcommand, Clone)]
pub enum StorageCommand {
/// In-memory storage (data resets on restart)
Memory,
/// File storage backend rooted at the given path
File { path: String },
/// redb single-file storage backend
Redb { path: String },
/// Git storage backend
Git {
path: String,
remote: String,
branch: String,
},
/// MongoDB storage backend
Mongo { conn_str: String, db_name: String },
}

pub fn parse_args() -> ServerArgs {
Cli::parse().args
}
63 changes: 15 additions & 48 deletions server/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
mod args;
mod proxy_access;
pub mod state;

pub use args::{ServerArgs, StorageCommand, parse_args};

use {
axum::{
Json, Router,
Expand All @@ -8,60 +14,20 @@ use {
response::Response,
routing::{get, post},
},
clap::{Args, Parser, Subcommand},
color_eyre::Result,
glues_core::backend::{
CoreBackend,
local::Db,
proxy::{ProxyServer, request::ProxyRequest, response::ProxyResponse},
},
std::{net::SocketAddr, sync::Arc},
tokio::{net::TcpListener, signal, sync::Mutex as AsyncMutex},
std::sync::Arc,
tokio::{net::TcpListener, signal},
tower_http::cors::{Any, CorsLayer},
tracing::{error, info, warn},
tracing_subscriber::EnvFilter,
};

#[derive(Clone, Args)]
pub struct ServerArgs {
#[arg(long, default_value = "127.0.0.1:4000")]
pub listen: SocketAddr,

#[arg(long, env = "GLUES_SERVER_TOKEN")]
pub auth_token: Option<String>,

#[command(subcommand)]
pub storage: StorageCommand,
}

#[derive(Parser)]
#[command(author, version, about = "Glues proxy server")]
struct Cli {
#[command(flatten)]
args: ServerArgs,
}

#[derive(Subcommand, Clone)]
pub enum StorageCommand {
/// In-memory storage (data resets on restart)
Memory,
/// File storage backend rooted at the given path
File { path: String },
/// redb single-file storage backend
Redb { path: String },
/// Git storage backend
Git {
path: String,
remote: String,
branch: String,
},
/// MongoDB storage backend
Mongo { conn_str: String, db_name: String },
}

pub fn parse_args() -> ServerArgs {
Cli::parse().args
}
use state::ServerState;

pub async fn run(args: ServerArgs) -> Result<()> {
color_eyre::install()?;
Expand All @@ -73,11 +39,13 @@ pub async fn run(args: ServerArgs) -> Result<()> {
let ServerArgs {
listen,
auth_token,
allowed_directory,
storage,
} = args;
Comment on lines 39 to 44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Treat empty auth tokens as “no auth” to avoid enforcing Bearer (blank).

Filter out empty/whitespace-only tokens at startup.

Apply this diff:

     let ServerArgs {
         listen,
-        auth_token,
+        auth_token,
         allowed_directory,
         storage,
     } = args;
 
-    let backend = build_backend(storage).await?;
+    // Treat empty or whitespace-only tokens as None.
+    let auth_token = auth_token.and_then(|t| {
+        let trimmed = t.trim().to_owned();
+        if trimmed.is_empty() { None } else { Some(trimmed) }
+    });
+
+    let backend = build_backend(storage).await?;

No behavior change needed below; the existing if let Some(token) branch will now skip for empty inputs.

Also applies to: 61-69

🤖 Prompt for AI Agents
In server/src/lib.rs around lines 39-44 (and similarly 61-69), the extracted
auth_token should treat empty or whitespace-only strings as None so the existing
if let Some(token) branch won't enforce a blank "Bearer " header; modify the
initialization to trim the auth_token and set it to None when it's empty or only
whitespace (e.g., map Some(s) to s.trim().is_empty() ? None :
Some(s.trim().to_string())), ensuring downstream logic remains unchanged.


let backend = build_backend(storage).await?;
let server = Arc::new(AsyncMutex::new(ProxyServer::new(backend)));
let server = ProxyServer::new(backend);
let state = Arc::new(ServerState::new(server, allowed_directory).await?);

let cors = CorsLayer::new()
.allow_origin(Any)
Expand All @@ -87,7 +55,7 @@ pub async fn run(args: ServerArgs) -> Result<()> {
let mut app = Router::new()
.route("/", post(handle_proxy))
.route("/health", get(health))
.with_state(server.clone())
.with_state(state.clone())
.layer(cors);

if let Some(token) = auth_token.as_ref() {
Expand Down Expand Up @@ -137,11 +105,10 @@ async fn build_backend(storage: StorageCommand) -> Result<Box<dyn CoreBackend +
}

async fn handle_proxy(
State(server): State<Arc<AsyncMutex<ProxyServer>>>,
State(state): State<Arc<ServerState>>,
Json(request): Json<ProxyRequest>,
) -> (StatusCode, Json<ProxyResponse>) {
let mut server = server.lock_owned().await;
let response = server.handle(request).await;
let response = state.handle(request).await;
(StatusCode::OK, Json(response))
}

Expand Down
Loading