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
39 changes: 39 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Rust build artifacts
target/
**/*.rs.bk
*.pdb

# Git
.git/
.gitignore

# Documentation and development files
*.md
LICENSE
SECURITY.md
CONTRIBUTING.md

# Development and editor files
.vscode/
.idea/
*.swp
*.swo
*~

# OS files
.DS_Store
Thumbs.db

# Database files (if you don't want to include existing data)
immo/database/

# Docker files (avoid recursion)
Dockerfile*
.dockerignore

# Logs
*.log

# Temporary files
tmp/
temp/
3 changes: 0 additions & 3 deletions .gitattributes

This file was deleted.

20 changes: 20 additions & 0 deletions .github/workflows/cargo-fmt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Cargo Fmt
permissions:
contents: read

on:
push:
branches:
- main

pull_request:
branches:
- main

jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- run: cargo fmt --check
24 changes: 24 additions & 0 deletions .github/workflows/clippy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Clippy check
permissions:
contents: read

# Make sure CI fails on all warnings, including Clippy lints
env:
RUSTFLAGS: "-Dwarnings"

on:
push:
branches:
- main

pull_request:
branches:
- main

jobs:
clippy_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Run Clippy
run: cargo clippy --all-targets --all-features
Empty file removed .github/workflows/linting.yml
Empty file.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ Thumbs.db
# For example, if you have a vendor directory for C dependencies:
# /vendor/

nostr-relay
immo
56 changes: 36 additions & 20 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,35 @@ Please read these guidelines before submitting a pull request or opening an issu
We strive to maintain clean, readable, and maintainable code.
Please follow these guidelines when contributing to the project:

- Follow the [Effective Go](https://golang.org/doc/effective_go.html) guidelines.
- Follow the [Go Doc Comments](https://go.dev/doc/comment) guidelines.
- Follow the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/) and [The Rust Programming Language](https://doc.rust-lang.org/book/) best practices.
- Use `rustfmt` for code formatting and `clippy` for linting.
- Follow the principles of clean code as outlined in
Robert C. Martin's "[Clean Code](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)" book.
- Write tests/benchmarks for new code or changes to existing code, and make sure all tests pass before submitting a pull request.
- Write comprehensive tests and benchmarks for new code or changes to existing code.
- Ensure all tests pass before submitting a pull request.
- Use meaningful variable and function names.
- Prefer explicit error handling over panic.

### Makefile Targets
### Development Commands

There are some makefile targets that you can use when developing this codebase:
Use these Cargo commands for development:

- `devtools`: will install all devtools you need in the development process.
- `unit-test`, `test`, `test-race`: runs all existing tests.
- `fmt`: formats the code using gofumpt. (Run before `check` target always.)
- `check`: runs golangci-lint linter based on its [config](./.golangci.yml).
- `build`: build an immortal binary on the `build/immortal` path.
- `pre-commit`: executes formatter, linter, and tests.
- `compose-up`: spins up the development docker compose, which runs all required third-party development services.
- `compose-down`: stops the development docker compose stuff.
- `models-generate`: generates the SQL tables using sqlboiler, only use it when you change the database.
- `cargo check`: quickly check your code for compile errors
- `cargo fmt`: format the code using rustfmt
- `cargo clippy`: run the Clippy linter for additional checks
- `cargo test`: run all tests
- `cargo bench`: run benchmarks
- `cargo build`: build the project
- `cargo run`: build and run the project
- `cargo doc --open`: generate and open documentation

### Code Style

- Use `snake_case` for variable and function names.
- Use `PascalCase` for types and traits.
- Use `SCREAMING_SNAKE_CASE` for constants.
- Document public APIs with `///` doc comments.
- Use `#[must_use]` attribute for functions that return values that should be used.

### Error and Log Messages

Expand All @@ -42,16 +52,22 @@ Error and log messages should not start with a capital letter (unless it's a pro

### Testing

All changes to the core must contain proper and well-defined unit tests, also previous tests must be passed as well.
This codebase uses `testify` for unit tests, make sure you follow this guide for tests:
All changes to the core must contain proper and well-defined unit tests. Previous tests must continue to pass.
This codebase uses Rust's built-in testing framework:

- For panic cases, make sure you use `assert.Panics`
- For checking err using `assert.ErrorIs` make sure you pass the expected error as the second argument.
- For checking equality using `assert.Equal`, make sure you pass the expected value as the first argument.
- Use `#[test]` attribute for unit tests.
- Use `#[cfg(test)]` module for test-only code.
- Use `assert!`, `assert_eq!`, `assert_ne!` macros for assertions.
- Use `#[should_panic]` attribute for tests that should panic.
- Place integration tests in the `tests/` directory.

### Benchmarking

Make sure you follow [this guide](https://100go.co/89-benchmarks) when you write or change benchmarks to reach an accurate result.
Use Rust's built-in benchmarking framework or the `criterion` crate for benchmarks:

- Use `#[bench]` attribute for benchmarks (requires nightly Rust).
- For stable Rust, use the `criterion` crate for more detailed benchmarking.
- Run benchmarks with `cargo bench`.

### Help Messages

Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ edition = "2024"
[dependencies]
nostr-lmdb = "0.44.0"
nostr-relay-builder = "0.44.0"
serde = "1.0.228"
tokio = { version = "1.48.0", features = ["full"] }
toml = "0.9.8"
76 changes: 76 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Multi-stage build for optimal image size and security
FROM rust:1.83-slim-bullseye AS builder

# Install build dependencies
RUN apt-get update && apt-get install -y \
pkg-config \
libssl-dev \
liblmdb-dev \
&& rm -rf /var/lib/apt/lists/*

# Create app user for security
RUN useradd -m -u 1001 appuser

# Set working directory
WORKDIR /app

# Copy manifests first for better layer caching
COPY Cargo.toml Cargo.lock ./

# Create a dummy main.rs to build dependencies
RUN mkdir src && echo "fn main() {}" > src/main.rs

# Build dependencies (this layer will be cached unless Cargo files change)
RUN cargo build --release && rm -rf src target/release/deps/immortal*

# Copy source code
COPY src/ ./src/

# Build the application
RUN cargo build --release

# Runtime stage - use minimal base image
FROM debian:bullseye-slim AS runtime

# Install runtime dependencies only
RUN apt-get update && apt-get install -y \
liblmdb0 \
libssl1.1 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean

# Create app user (same UID as builder for consistency)
RUN useradd -m -u 1001 appuser

# Create working directory and data directory
WORKDIR /app
RUN mkdir -p /app/immo/database && chown -R appuser:appuser /app

# Copy the built binary from builder stage
COPY --from=builder /app/target/release/immortal /usr/local/bin/immortal

# Copy configuration file
COPY config.toml /app/config.toml

# Ensure binary is executable and owned by appuser
RUN chmod +x /usr/local/bin/immortal && chown appuser:appuser /usr/local/bin/immortal /app/config.toml

# Switch to non-root user
USER appuser

# Expose the default port (configurable via config.toml)
EXPOSE 7777

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:7777/ || exit 1

# Use exec form for proper signal handling
ENTRYPOINT ["/usr/local/bin/immortal"]

# Add labels for better maintainability
LABEL maintainer="dezh-tech" \
version="0.1.0" \
description="Immortal Nostr Relay" \
org.opencontainers.image.source="https://github.com/dezh-tech/immortal"
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,34 @@ Immortal
<br/>


The Immortal is a [Nostr](https://nostr.com) relay implementation in Golang.
The Immortal is a [Nostr](https://nostr.com) relay implementation in Rust.

Immortal is aimed and designed to be **scalable**, **high-performance**, and **configurable**. It's a good choice for paid relays or big community relays, and not a good choice for a personal relay.

## Supported NIPs
## Installation & Running

You can find the details of supported NIPs [here](./documents/NIPs.md).
### Prerequisites

- Rust 1.70 or later
- Cargo (comes with Rust)

### Building from Source

```bash
# Clone the repository
git clone https://github.com/dezh-tech/immortal.git
cd immortal

# Build the project
cargo build --release

# Run the relay
cargo run --release
```

### Configuration

The relay can be configured using a `config.toml` file. See the example configuration in the repository.

## Updates

Expand Down
10 changes: 1 addition & 9 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,9 +1 @@
# Security Policy

This is a full guide for reporting and behaving with security vulnerabilities in this project.

## Reporting a Vulnerability

If you find any security vulnerability in this project, you can send a full report to [security@dezh.tech](mailto:security@dezh.tech). We will review your report in around 1 week or more and respond to you.
We MAY consider paying a bounty reward to you using only Bitcoin (On-chain, Lightning, Cashu) depending on the vulnerability security level. By publishing any public writeup or report before a response from us you will not receive any bounty reward.
After our response to the vulnerability report, you/we can publish a public writeup or report about the vulnerability.
# TODO
2 changes: 2 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
port=7777
working_dir="immo"
10 changes: 10 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pub mod types;

use crate::config::types::Config;
use std::fs;

pub fn load(path: &str) -> Result<Config, Box<dyn std::error::Error>> {
let s = fs::read_to_string(path)?;
let cfg: Config = toml::from_str(&s)?;
Ok(cfg)
}
7 changes: 7 additions & 0 deletions src/config/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use serde::Deserialize;

#[derive(Debug, Deserialize)]
pub struct Config {
pub working_dir: String,
pub port: u16,
}
12 changes: 5 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
use nostr_lmdb::NostrLMDB;
use nostr_relay_builder::prelude::*;

mod config;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Open a database (all databases that implements `NostrDatabase` trait can be used).
let database = NostrLMDB::open("nostr-relay")?;
// Configure the relay.
let cfg = config::load("config.toml")?;
let database = NostrLMDB::open(format!("{}/database", cfg.working_dir))?;
let builder = RelayBuilder::default()
.port(7777)
.port(cfg.port)
.database(database)
.rate_limit(RateLimit {
max_reqs: 128,
notes_per_minute: 30,
});

// Construct the relay instance.
let relay = LocalRelay::new(builder);

// Start the relay.
relay.run().await?;

println!("Relay listening on {}", relay.url().await);

// Keep the process running.
tokio::signal::ctrl_c().await?;

Ok(())
Expand Down