Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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>`. 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
Copy Markdown

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