Skip to content
Merged
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/portal-rest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ portal-wallet = { path = "../portal-wallet" }
portal-sdk = { path = "../portal-sdk" }
portal = { version = "0.1.0", path = "../portal" }
portal-rates = { path = "../portal-rates" }
portal-macros = { path = "../portal-macros" }

nostr = { workspace = true }

Expand Down
2 changes: 1 addition & 1 deletion crates/portal-rest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ docker run -d -p 3000:3000 \
getportal/sdk-daemon:latest
```

Use `ws://localhost:3000/ws` as the SDK `serverUrl` and your token in `client.authenticate(...)`. Check: `curl http://localhost:3000/health` → `OK`.
Use `ws://localhost:3000/ws` as the SDK `serverUrl` and your token in `client.authenticate(...)`. Check: `curl http://localhost:3000/health` → `OK`, `curl http://localhost:3000/version` → `{"version":"0.1.0","git_commit":"a1b2c3d4"}`.

**From source:**

Expand Down
39 changes: 35 additions & 4 deletions crates/portal-rest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;

use portal_wallet::{BreezSparkWallet, NwcWallet, PortalWallet};
use portal_macros::fetch_git_hash;

/// Build-time version from Cargo.toml (used for Docker image tagging and runtime /version endpoint).
pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
/// Git commit hash at build time (from portal_macros::fetch_git_hash! or PORTAL_GIT_HASH env).
const GIT_COMMIT: &str = fetch_git_hash!();

#[derive(Debug, thiserror::Error)]
enum ApiError {
Expand Down Expand Up @@ -107,6 +113,19 @@ async fn health_check() -> &'static str {
"OK"
}

#[derive(Serialize)]
struct VersionResponse {
version: &'static str,
git_commit: &'static str,
}

async fn version() -> Json<VersionResponse> {
Json(VersionResponse {
version: APP_VERSION,
git_commit: GIT_COMMIT,
})
}

async fn handle_ws_upgrade(
ws: WebSocketUpgrade,
State(state): State<AppState>,
Expand All @@ -127,7 +146,12 @@ async fn main() -> anyhow::Result<()> {
.init();

let config = config::Settings::load()?;
info!("Config loaded: {:?}", config);
info!(
listen_port = config.info.listen_port,
wallet = format!("{:?}", config.wallet.ln_backend).to_lowercase(),
relays = config.nostr.relays.join(","),
"Config loaded",
);

// Settings validation
config.validate()?;
Expand Down Expand Up @@ -164,14 +188,21 @@ async fn main() -> anyhow::Result<()> {
market_api: portal_rates::MarketAPI::new().expect("Failed to create market API"),
};

// Create router with middleware
let app = Router::new()
// Public routes (no auth): health and version for Docker/orchestrators and support.
let public = Router::new()
.route("/health", get(health_check))
.route("/version", get(version));

let ws = Router::new()
.route("/ws", get(handle_ws_upgrade))
.layer(middleware::from_fn_with_state(
state.clone(),
auth_middleware,
))
));

let app = Router::new()
.merge(public)
.merge(ws)
.layer(
CorsLayer::new()
.allow_origin(Any)
Expand Down
18 changes: 18 additions & 0 deletions docs/getting-started/docker-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

Deploy the Portal SDK Daemon using Docker for easy setup and management.

## Versioning

The daemon exposes its **application version** (from `portal-rest`’s Cargo.toml) for Docker and support:

- **`GET /version`** (no auth) — returns JSON: `{"version":"0.1.0","git_commit":"a1b2c3d4"}` (or `"unknown"` when not built from git). Use this to verify which image/version is running (e.g. `curl http://localhost:3000/version`).
- **API paths** — the WebSocket API is at **`/ws`** (auth required).

For **Docker Hub**, tag images with the crate version so users can pin to a release:

```bash
# Build and tag with version (e.g. from crates/portal-rest/Cargo.toml)
docker build -t youruser/sdk-daemon:0.1.0 -t youruser/sdk-daemon:latest .
docker push youruser/sdk-daemon:0.1.0
docker push youruser/sdk-daemon:latest
```

Use `:0.1.0` (or the current version) for reproducible deployments; use `:latest` for convenience.

## Quick Deployment

### Using Pre-built Image
Expand Down
38 changes: 34 additions & 4 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
rustc = rustToolchain;
};

# Static build: same Rust 1.90 as overlay, musl target (lnurl-models needs rustc 1.88+)
rustToolchainStatic = pkgs.rust-bin.stable."1.90.0".default.override {
targets = [ "x86_64-unknown-linux-musl" ];
};
staticRustPlatform = pkgs.makeRustPlatform {
cargo = rustToolchainStatic;
rustc = rustToolchainStatic;
};

rest' = platform: platform.buildRustPackage {
pname = "portal-rest";
version = (pkgs.lib.importTOML ./crates/portal-rest/Cargo.toml).package.version;
Expand Down Expand Up @@ -92,7 +101,27 @@
inherit backend;

rest = rest' rustPlatform;
rest-static = rest' pkgs.pkgsStatic.rustPlatform;
# Static binary for Docker: overlay Rust 1.90 + musl stdenv + static openssl
rest-static = staticRustPlatform.buildRustPackage {
pname = "portal-rest";
version = (pkgs.lib.importTOML ./crates/portal-rest/Cargo.toml).package.version;
src = pkgs.lib.sources.sourceFilesBySuffices ./. [ ".rs" "Cargo.toml" "Cargo.lock" "fiatUnits.json" "example.config.toml" ];
cargoHash = "";
cargoLock = {
lockFile = ./Cargo.lock;
outputHashes = {
"cashu-0.11.0" = "sha256-kwwfQX5vDclfa86xPbbkbu+bh1VQXlX+imunUUoTYV4=";
"nostr-0.43.0" = "sha256-1TLthpGnDLUmnBoq2CneWnfTMwRocitbD4+wnrlCA44=";
"breez-sdk-common-0.1.0" = "sha256-b8R4V8L7lM0AOy9NxhiIt+RsIBHJdQPpfw9SN1/P//E=";
};
};
buildAndTestSubdir = "crates/portal-rest";
doCheck = false;
stdenv = pkgs.pkgsStatic.stdenv;
nativeBuildInputs = with pkgs; [ protobuf pkg-config ];
buildInputs = with pkgs.pkgsStatic; [ openssl ];
meta.mainProgram = "rest";
};

rest-docker = let
minimal-closure = pkgs.runCommand "minimal-rust-app" {
Expand All @@ -102,7 +131,7 @@
cp ${rest-static}/bin/rest $out/bin/

for binary in $out/bin/*; do
remove-references-to -t ${pkgs.pkgsStatic.rustPlatform.rust.rustc} "$binary"
remove-references-to -t ${rustToolchainStatic} "$binary"
done
'';
in pkgs.dockerTools.buildLayeredImage {
Expand All @@ -114,9 +143,10 @@
ExposedPorts = {
"3000/tcp" = {};
};
# Only non-secret defaults. Required (AUTH_TOKEN, NOSTR__PRIVATE_KEY) and
# optional env are passed when starting the container (docker run -e / --env-file).
Env = [
"AUTH_TOKEN=remeber-to-change-this"
"NOSTR_RELAYS=wss://relay.getportal.cc,wss://relay.damus.io,wss://relay.nostr.net"
"PORTAL__INFO__LISTEN_PORT=3000"
"RUST_LOG=portal=debug,rest=debug,info"
];
};
Expand Down
Loading