mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-10-06 15:29:07 +00:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
889735f8d0 | ||
![]() |
2eb4db3ddb | ||
![]() |
587cb3dc11 | ||
![]() |
b6db781ce2 | ||
![]() |
5d951daaf8 | ||
![]() |
b5fc5cff4b | ||
![]() |
ad1b389a53 | ||
![]() |
3b08a91de3 |
43
CHANGELOG.md
43
CHANGELOG.md
@ -16,6 +16,41 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
## [0.51.0] - 2025-09-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added identity/main.go to generate identity and peer id
|
||||||
|
- Added encryption module identity.go for reusable identity create, save etc funtions
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated make file to support identity/main.go
|
||||||
|
- Updated node/node.go on loadOrCreateIdentity to use encryption.identity
|
||||||
|
- Updated cli/main.go to remove fallbacks for identity
|
||||||
|
- Updated install-debros-network.sh script to use new ./cmd/identity and fixed port order on print
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
|
||||||
|
## [0.50.1] - 2025-09-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed wrong URL /v1/db to /v1/rqlite
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
## [0.50.0] - 2025-09-23
|
## [0.50.0] - 2025-09-23
|
||||||
@ -32,7 +67,6 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
|
|||||||
- Updated node.go to support new rqlite architecture
|
- Updated node.go to support new rqlite architecture
|
||||||
- Updated readme
|
- Updated readme
|
||||||
|
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
@ -65,7 +99,6 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
|
|||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
|
|
||||||
## [0.43.6] - 2025-09-20
|
## [0.43.6] - 2025-09-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@ -88,11 +121,13 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
|
|||||||
## [0.43.4] - 2025-09-18
|
## [0.43.4] - 2025-09-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added extra comments on main.go
|
- Added extra comments on main.go
|
||||||
- Remove backoff_test.go and associated backoff tests
|
- Remove backoff_test.go and associated backoff tests
|
||||||
- Created node_test, write tests for CalculateNextBackoff, AddJitter, GetPeerId, LoadOrCreateIdentity, hasBootstrapConnections
|
- Created node_test, write tests for CalculateNextBackoff, AddJitter, GetPeerId, LoadOrCreateIdentity, hasBootstrapConnections
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- replaced git.debros.io with github.com
|
- replaced git.debros.io with github.com
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
@ -106,20 +141,24 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
|
|||||||
## [0.43.3] - 2025-09-15
|
## [0.43.3] - 2025-09-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- User authentication module with OAuth2 support.
|
- User authentication module with OAuth2 support.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Make file version to 0.43.2
|
- Make file version to 0.43.2
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed cli, network-cli binaries from project
|
- Removed cli, network-cli binaries from project
|
||||||
- Removed AI_CONTEXT.md
|
- Removed AI_CONTEXT.md
|
||||||
- Removed Network.md
|
- Removed Network.md
|
||||||
- Removed unused log from monitoring.go
|
- Removed unused log from monitoring.go
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Resolved race condition when saving settings.
|
- Resolved race condition when saving settings.
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
3
Makefile
3
Makefile
@ -21,7 +21,7 @@ test-e2e:
|
|||||||
|
|
||||||
.PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports
|
.PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports
|
||||||
|
|
||||||
VERSION := 0.50.0-beta
|
VERSION := 0.51.0-beta
|
||||||
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||||
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||||
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'
|
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'
|
||||||
@ -30,6 +30,7 @@ LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date
|
|||||||
build: deps
|
build: deps
|
||||||
@echo "Building network executables (version=$(VERSION))..."
|
@echo "Building network executables (version=$(VERSION))..."
|
||||||
@mkdir -p bin
|
@mkdir -p bin
|
||||||
|
go build -ldflags "$(LDFLAGS)" -o bin/identity ./cmd/identity
|
||||||
go build -ldflags "$(LDFLAGS)" -o bin/node ./cmd/node
|
go build -ldflags "$(LDFLAGS)" -o bin/node ./cmd/node
|
||||||
go build -ldflags "$(LDFLAGS)" -o bin/network-cli cmd/cli/main.go
|
go build -ldflags "$(LDFLAGS)" -o bin/network-cli cmd/cli/main.go
|
||||||
# Inject gateway build metadata via pkg path variables
|
# Inject gateway build metadata via pkg path variables
|
||||||
|
126
README.md
126
README.md
@ -143,6 +143,7 @@ curl -sSL https://github.com/DeBrosOfficial/network/raw/main/scripts/install-deb
|
|||||||
```
|
```
|
||||||
|
|
||||||
**What the Script Does:**
|
**What the Script Does:**
|
||||||
|
|
||||||
- Detects OS, installs Go, RQLite, dependencies
|
- Detects OS, installs Go, RQLite, dependencies
|
||||||
- Creates `debros` system user, secure directory structure
|
- Creates `debros` system user, secure directory structure
|
||||||
- Generates LibP2P identity keys
|
- Generates LibP2P identity keys
|
||||||
@ -152,6 +153,7 @@ curl -sSL https://github.com/DeBrosOfficial/network/raw/main/scripts/install-deb
|
|||||||
- Generates YAML config in `/opt/debros/configs/node.yaml`
|
- Generates YAML config in `/opt/debros/configs/node.yaml`
|
||||||
|
|
||||||
**Directory Structure:**
|
**Directory Structure:**
|
||||||
|
|
||||||
```
|
```
|
||||||
/opt/debros/
|
/opt/debros/
|
||||||
├── bin/ # Binaries
|
├── bin/ # Binaries
|
||||||
@ -163,6 +165,7 @@ curl -sSL https://github.com/DeBrosOfficial/network/raw/main/scripts/install-deb
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Service Management:**
|
**Service Management:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl status debros-node
|
sudo systemctl status debros-node
|
||||||
sudo systemctl start debros-node
|
sudo systemctl start debros-node
|
||||||
@ -261,6 +264,7 @@ logging:
|
|||||||
The .yaml files are required in order for the nodes and the gateway to run correctly.
|
The .yaml files are required in order for the nodes and the gateway to run correctly.
|
||||||
|
|
||||||
node:
|
node:
|
||||||
|
|
||||||
- id (string) Optional node ID. Auto-generated if empty.
|
- id (string) Optional node ID. Auto-generated if empty.
|
||||||
- type (string) "bootstrap" or "node". Default: "node".
|
- type (string) "bootstrap" or "node". Default: "node".
|
||||||
- listen_addresses (string[]) LibP2P listen multiaddrs. Default: ["/ip4/0.0.0.0/tcp/4001"].
|
- listen_addresses (string[]) LibP2P listen multiaddrs. Default: ["/ip4/0.0.0.0/tcp/4001"].
|
||||||
@ -268,6 +272,7 @@ node:
|
|||||||
- max_connections (int) Max peer connections. Default: 50.
|
- max_connections (int) Max peer connections. Default: 50.
|
||||||
|
|
||||||
database:
|
database:
|
||||||
|
|
||||||
- data_dir (string) Directory for database files. Default: "./data/db".
|
- data_dir (string) Directory for database files. Default: "./data/db".
|
||||||
- replication_factor (int) Number of replicas. Default: 3.
|
- replication_factor (int) Number of replicas. Default: 3.
|
||||||
- shard_count (int) Shards for data distribution. Default: 16.
|
- shard_count (int) Shards for data distribution. Default: 16.
|
||||||
@ -278,6 +283,7 @@ database:
|
|||||||
- rqlite_join_address (string) HTTP address of an existing RQLite node to join. Empty for bootstrap.
|
- rqlite_join_address (string) HTTP address of an existing RQLite node to join. Empty for bootstrap.
|
||||||
|
|
||||||
discovery:
|
discovery:
|
||||||
|
|
||||||
- bootstrap_peers (string[]) List of LibP2P multiaddrs of bootstrap peers.
|
- bootstrap_peers (string[]) List of LibP2P multiaddrs of bootstrap peers.
|
||||||
- discovery_interval (duration) How often to announce/discover peers. Default: 15s.
|
- discovery_interval (duration) How often to announce/discover peers. Default: 15s.
|
||||||
- bootstrap_port (int) Default port for bootstrap nodes. Default: 4001.
|
- bootstrap_port (int) Default port for bootstrap nodes. Default: 4001.
|
||||||
@ -286,11 +292,13 @@ discovery:
|
|||||||
- node_namespace (string) Namespace for node identifiers. Default: "default".
|
- node_namespace (string) Namespace for node identifiers. Default: "default".
|
||||||
|
|
||||||
security:
|
security:
|
||||||
|
|
||||||
- enable_tls (bool) Enable TLS for externally exposed services. Default: false.
|
- enable_tls (bool) Enable TLS for externally exposed services. Default: false.
|
||||||
- private_key_file (string) Path to TLS private key (if TLS enabled).
|
- private_key_file (string) Path to TLS private key (if TLS enabled).
|
||||||
- certificate_file (string) Path to TLS certificate (if TLS enabled).
|
- certificate_file (string) Path to TLS certificate (if TLS enabled).
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
|
|
||||||
- level (string) one of "debug", "info", "warn", "error". Default: "info".
|
- level (string) one of "debug", "info", "warn", "error". Default: "info".
|
||||||
- format (string) "json" or "console". Default: "console".
|
- format (string) "json" or "console". Default: "console".
|
||||||
- output_file (string) Empty for stdout; otherwise path to log file.
|
- output_file (string) Empty for stdout; otherwise path to log file.
|
||||||
@ -347,6 +355,7 @@ logging:
|
|||||||
|
|
||||||
Precedence (gateway): Flags > Environment Variables > YAML > Defaults.
|
Precedence (gateway): Flags > Environment Variables > YAML > Defaults.
|
||||||
Environment variables:
|
Environment variables:
|
||||||
|
|
||||||
- GATEWAY_ADDR
|
- GATEWAY_ADDR
|
||||||
- GATEWAY_NAMESPACE
|
- GATEWAY_NAMESPACE
|
||||||
- GATEWAY_BOOTSTRAP_PEERS (comma-separated)
|
- GATEWAY_BOOTSTRAP_PEERS (comma-separated)
|
||||||
@ -385,8 +394,6 @@ bootstrap_peers:
|
|||||||
./bin/network-cli peers # List connected peers
|
./bin/network-cli peers # List connected peers
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Database Operations
|
### Database Operations
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -414,27 +421,27 @@ bootstrap_peers:
|
|||||||
### Database Operations (Gateway REST)
|
### Database Operations (Gateway REST)
|
||||||
|
|
||||||
```http
|
```http
|
||||||
POST /v1/db/exec # Body: {"sql": "INSERT/UPDATE/DELETE/DDL ...", "args": [...]}
|
POST /v1/rqlite/exec # Body: {"sql": "INSERT/UPDATE/DELETE/DDL ...", "args": [...]}
|
||||||
POST /v1/db/find # Body: {"table":"...", "criteria":{"col":val,...}, "options":{...}}
|
POST /v1/rqlite/find # Body: {"table":"...", "criteria":{"col":val,...}, "options":{...}}
|
||||||
POST /v1/db/find-one # Body: same as /find, returns a single row (404 if not found)
|
POST /v1/rqlite/find-one # Body: same as /find, returns a single row (404 if not found)
|
||||||
POST /v1/db/select # Body: {"table":"...", "select":[...], "where":[...], "joins":[...], "order_by":[...], "limit":N, "offset":N, "one":false}
|
POST /v1/rqlite/select # Body: {"table":"...", "select":[...], "where":[...], "joins":[...], "order_by":[...], "limit":N, "offset":N, "one":false}
|
||||||
POST /v1/db/transaction # Body: {"ops":[{"kind":"exec|query","sql":"...","args":[...]}], "return_results": true}
|
POST /v1/rqlite/transaction # Body: {"ops":[{"kind":"exec|query","sql":"...","args":[...]}], "return_results": true}
|
||||||
POST /v1/db/query # Body: {"sql": "SELECT ...", "args": [..]} (legacy-friendly SELECT)
|
POST /v1/rqlite/query # Body: {"sql": "SELECT ...", "args": [..]} (legacy-friendly SELECT)
|
||||||
GET /v1/db/schema # Returns tables/views + create SQL
|
GET /v1/rqlite/schema # Returns tables/views + create SQL
|
||||||
POST /v1/db/create-table # Body: {"schema": "CREATE TABLE ..."}
|
POST /v1/rqlite/create-table # Body: {"schema": "CREATE TABLE ..."}
|
||||||
POST /v1/db/drop-table # Body: {"table": "table_name"}
|
POST /v1/rqlite/drop-table # Body: {"table": "table_name"}
|
||||||
```
|
```
|
||||||
|
|
||||||
Common workflows:
|
Common workflows:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Exec (INSERT/UPDATE/DELETE/DDL)
|
# Exec (INSERT/UPDATE/DELETE/DDL)
|
||||||
curl -X POST "$GW/v1/db/exec" \
|
curl -X POST "$GW/v1/rqlite/exec" \
|
||||||
-H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
|
-H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
|
||||||
-d '{"sql":"INSERT INTO users(name,email) VALUES(?,?)","args":["Alice","alice@example.com"]}'
|
-d '{"sql":"INSERT INTO users(name,email) VALUES(?,?)","args":["Alice","alice@example.com"]}'
|
||||||
|
|
||||||
# Find (criteria + options)
|
# Find (criteria + options)
|
||||||
curl -X POST "$GW/v1/db/find" \
|
curl -X POST "$GW/v1/rqlite/find" \
|
||||||
-H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
|
-H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
|
||||||
-d '{
|
-d '{
|
||||||
"table":"users",
|
"table":"users",
|
||||||
@ -443,7 +450,7 @@ curl -X POST "$GW/v1/db/find" \
|
|||||||
}'
|
}'
|
||||||
|
|
||||||
# Select (fluent builder via JSON)
|
# Select (fluent builder via JSON)
|
||||||
curl -X POST "$GW/v1/db/select" \
|
curl -X POST "$GW/v1/rqlite/select" \
|
||||||
-H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
|
-H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
|
||||||
-d '{
|
-d '{
|
||||||
"table":"orders o",
|
"table":"orders o",
|
||||||
@ -455,7 +462,7 @@ curl -X POST "$GW/v1/db/select" \
|
|||||||
}'
|
}'
|
||||||
|
|
||||||
# Transaction (atomic batch)
|
# Transaction (atomic batch)
|
||||||
curl -X POST "$GW/v1/db/transaction" \
|
curl -X POST "$GW/v1/rqlite/transaction" \
|
||||||
-H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
|
-H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
|
||||||
-d '{
|
-d '{
|
||||||
"return_results": true,
|
"return_results": true,
|
||||||
@ -466,12 +473,12 @@ curl -X POST "$GW/v1/db/transaction" \
|
|||||||
}'
|
}'
|
||||||
|
|
||||||
# Schema
|
# Schema
|
||||||
curl "$GW/v1/db/schema" -H "Authorization: Bearer $API_KEY"
|
curl "$GW/v1/rqlite/schema" -H "Authorization: Bearer $API_KEY"
|
||||||
|
|
||||||
# DDL helpers
|
# DDL helpers
|
||||||
curl -X POST "$GW/v1/db/create-table" -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
|
curl -X POST "$GW/v1/rqlite/create-table" -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
|
||||||
-d '{"schema":"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)"}'
|
-d '{"schema":"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)"}'
|
||||||
curl -X POST "$GW/v1/db/drop-table" -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
|
curl -X POST "$GW/v1/rqlite/drop-table" -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
|
||||||
-d '{"table":"users"}'
|
-d '{"table":"users"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -485,12 +492,14 @@ The CLI features an enhanced authentication system with automatic wallet detecti
|
|||||||
- **Enhanced User Experience:** Streamlined authentication flow with better error handling and user feedback
|
- **Enhanced User Experience:** Streamlined authentication flow with better error handling and user feedback
|
||||||
|
|
||||||
When using operations that require authentication (storage, database, pubsub), the CLI will automatically:
|
When using operations that require authentication (storage, database, pubsub), the CLI will automatically:
|
||||||
|
|
||||||
1. Check for existing valid credentials
|
1. Check for existing valid credentials
|
||||||
2. Prompt for wallet authentication if needed
|
2. Prompt for wallet authentication if needed
|
||||||
3. Handle signature verification
|
3. Handle signature verification
|
||||||
4. Persist credentials for future use
|
4. Persist credentials for future use
|
||||||
|
|
||||||
**Example with automatic authentication:**
|
**Example with automatic authentication:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# First time - will prompt for wallet authentication when needed
|
# First time - will prompt for wallet authentication when needed
|
||||||
./bin/network-cli pubsub publish notifications "Hello World"
|
./bin/network-cli pubsub publish notifications "Hello World"
|
||||||
@ -530,6 +539,7 @@ export GATEWAY_API_KEYS="key1:namespace1,key2:namespace2"
|
|||||||
The gateway features a significantly improved authentication system with the following capabilities:
|
The gateway features a significantly improved authentication system with the following capabilities:
|
||||||
|
|
||||||
#### Key Features
|
#### Key Features
|
||||||
|
|
||||||
- **Automatic Authentication:** No manual auth commands required - authentication happens automatically when needed
|
- **Automatic Authentication:** No manual auth commands required - authentication happens automatically when needed
|
||||||
- **Multi-Wallet Support:** Seamlessly manage multiple wallet credentials with automatic switching
|
- **Multi-Wallet Support:** Seamlessly manage multiple wallet credentials with automatic switching
|
||||||
- **Persistent Sessions:** Wallet credentials are automatically saved and restored
|
- **Persistent Sessions:** Wallet credentials are automatically saved and restored
|
||||||
@ -538,22 +548,26 @@ The gateway features a significantly improved authentication system with the fol
|
|||||||
#### Authentication Methods
|
#### Authentication Methods
|
||||||
|
|
||||||
**Wallet-Based Authentication (Ethereum EIP-191)**
|
**Wallet-Based Authentication (Ethereum EIP-191)**
|
||||||
|
|
||||||
- Uses `personal_sign` for secure wallet verification
|
- Uses `personal_sign` for secure wallet verification
|
||||||
- Supports multiple wallets with automatic detection
|
- Supports multiple wallets with automatic detection
|
||||||
- Addresses are case-insensitive with normalized signature handling
|
- Addresses are case-insensitive with normalized signature handling
|
||||||
|
|
||||||
**JWT Tokens**
|
**JWT Tokens**
|
||||||
|
|
||||||
- Issued by the gateway with configurable expiration
|
- Issued by the gateway with configurable expiration
|
||||||
- JWKS endpoints available at `/v1/auth/jwks` and `/.well-known/jwks.json`
|
- JWKS endpoints available at `/v1/auth/jwks` and `/.well-known/jwks.json`
|
||||||
- Automatic refresh capability
|
- Automatic refresh capability
|
||||||
|
|
||||||
**API Keys**
|
**API Keys**
|
||||||
|
|
||||||
- Support for pre-configured API keys via `Authorization: Bearer <key>` or `X-API-Key` headers
|
- Support for pre-configured API keys via `Authorization: Bearer <key>` or `X-API-Key` headers
|
||||||
- Optional namespace mapping for multi-tenant applications
|
- Optional namespace mapping for multi-tenant applications
|
||||||
|
|
||||||
### API Endpoints
|
### API Endpoints
|
||||||
|
|
||||||
#### Health & Status
|
#### Health & Status
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /health # Basic health check
|
GET /health # Basic health check
|
||||||
GET /v1/health # Detailed health status
|
GET /v1/health # Detailed health status
|
||||||
@ -562,6 +576,7 @@ GET /v1/version # Version information
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Authentication (Public Endpoints)
|
#### Authentication (Public Endpoints)
|
||||||
|
|
||||||
```http
|
```http
|
||||||
POST /v1/auth/challenge # Generate wallet challenge
|
POST /v1/auth/challenge # Generate wallet challenge
|
||||||
POST /v1/auth/verify # Verify wallet signature
|
POST /v1/auth/verify # Verify wallet signature
|
||||||
@ -578,19 +593,20 @@ The gateway now exposes a full HTTP interface over the Go ORM-like client (see `
|
|||||||
|
|
||||||
- Base path: `/v1/db`
|
- Base path: `/v1/db`
|
||||||
- Endpoints:
|
- Endpoints:
|
||||||
- `POST /v1/db/exec` — Execute write/DDL SQL; returns `{ rows_affected, last_insert_id }`
|
- `POST /v1/rqlite/exec` — Execute write/DDL SQL; returns `{ rows_affected, last_insert_id }`
|
||||||
- `POST /v1/db/find` — Map-based criteria; returns `{ items: [...], count: N }`
|
- `POST /v1/rqlite/find` — Map-based criteria; returns `{ items: [...], count: N }`
|
||||||
- `POST /v1/db/find-one` — Single row; 404 if not found
|
- `POST /v1/rqlite/find-one` — Single row; 404 if not found
|
||||||
- `POST /v1/db/select` — Fluent SELECT via JSON (joins, where, order, group, limit, offset)
|
- `POST /v1/rqlite/select` — Fluent SELECT via JSON (joins, where, order, group, limit, offset)
|
||||||
- `POST /v1/db/transaction` — Atomic batch of exec/query ops, optional per-op results
|
- `POST /v1/rqlite/transaction` — Atomic batch of exec/query ops, optional per-op results
|
||||||
- `POST /v1/db/query` — Arbitrary SELECT (legacy-friendly), returns `items`
|
- `POST /v1/rqlite/query` — Arbitrary SELECT (legacy-friendly), returns `items`
|
||||||
- `GET /v1/db/schema` — List user tables/views + create SQL
|
- `GET /v1/rqlite/schema` — List user tables/views + create SQL
|
||||||
- `POST /v1/db/create-table` — Convenience for DDL
|
- `POST /v1/rqlite/create-table` — Convenience for DDL
|
||||||
- `POST /v1/db/drop-table` — Safe drop (identifier validated)
|
- `POST /v1/rqlite/drop-table` — Safe drop (identifier validated)
|
||||||
|
|
||||||
Payload examples are shown in the [Database Operations (Gateway REST)](#database-operations-gateway-rest) section.
|
Payload examples are shown in the [Database Operations (Gateway REST)](#database-operations-gateway-rest) section.
|
||||||
|
|
||||||
#### Network Operations
|
#### Network Operations
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /v1/network/status # Network status
|
GET /v1/network/status # Network status
|
||||||
GET /v1/network/peers # Connected peers
|
GET /v1/network/peers # Connected peers
|
||||||
@ -601,11 +617,13 @@ POST /v1/network/disconnect # Disconnect from peer
|
|||||||
#### Pub/Sub Messaging
|
#### Pub/Sub Messaging
|
||||||
|
|
||||||
**WebSocket Interface**
|
**WebSocket Interface**
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /v1/pubsub/ws?topic=<topic> # WebSocket connection for real-time messaging
|
GET /v1/pubsub/ws?topic=<topic> # WebSocket connection for real-time messaging
|
||||||
```
|
```
|
||||||
|
|
||||||
**REST Interface**
|
**REST Interface**
|
||||||
|
|
||||||
```http
|
```http
|
||||||
POST /v1/pubsub/publish # Publish message to topic
|
POST /v1/pubsub/publish # Publish message to topic
|
||||||
GET /v1/pubsub/topics # List active topics
|
GET /v1/pubsub/topics # List active topics
|
||||||
@ -616,31 +634,34 @@ GET /v1/pubsub/topics # List active topics
|
|||||||
## SDK Authoring Guide
|
## SDK Authoring Guide
|
||||||
|
|
||||||
### Base concepts
|
### Base concepts
|
||||||
|
|
||||||
- OpenAPI: a machine-readable spec is available at `openapi/gateway.yaml` for SDK code generation.
|
- OpenAPI: a machine-readable spec is available at `openapi/gateway.yaml` for SDK code generation.
|
||||||
- **Auth**: send `X-API-Key: <key>` or `Authorization: Bearer <key|JWT>` with every request.
|
- **Auth**: send `X-API-Key: <key>` or `Authorization: Bearer <key|JWT>` with every request.
|
||||||
- **Versioning**: all endpoints are under `/v1/`.
|
- **Versioning**: all endpoints are under `/v1/`.
|
||||||
- **Responses**: mutations return `{status:"ok"}`; queries/lists return JSON; errors return `{ "error": "message" }` with proper HTTP status.
|
- **Responses**: mutations return `{status:"ok"}`; queries/lists return JSON; errors return `{ "error": "message" }` with proper HTTP status.
|
||||||
|
|
||||||
### Key HTTP endpoints for SDKs
|
### Key HTTP endpoints for SDKs
|
||||||
|
|
||||||
- **Database**
|
- **Database**
|
||||||
- Exec: `POST /v1/db/exec` `{sql, args?}` → `{rows_affected,last_insert_id}`
|
- Exec: `POST /v1/rqlite/exec` `{sql, args?}` → `{rows_affected,last_insert_id}`
|
||||||
- Find: `POST /v1/db/find` `{table, criteria, options?}` → `{items,count}`
|
- Find: `POST /v1/rqlite/find` `{table, criteria, options?}` → `{items,count}`
|
||||||
- FindOne: `POST /v1/db/find-one` `{table, criteria, options?}` → single object or 404
|
- FindOne: `POST /v1/rqlite/find-one` `{table, criteria, options?}` → single object or 404
|
||||||
- Select: `POST /v1/db/select` `{table, select?, joins?, where?, order_by?, group_by?, limit?, offset?, one?}`
|
- Select: `POST /v1/rqlite/select` `{table, select?, joins?, where?, order_by?, group_by?, limit?, offset?, one?}`
|
||||||
- Transaction: `POST /v1/db/transaction` `{ops:[{kind,sql,args?}], return_results?}`
|
- Transaction: `POST /v1/rqlite/transaction` `{ops:[{kind,sql,args?}], return_results?}`
|
||||||
- Query: `POST /v1/db/query` `{sql, args?}` → `{items,count}`
|
- Query: `POST /v1/rqlite/query` `{sql, args?}` → `{items,count}`
|
||||||
- Schema: `GET /v1/db/schema`
|
- Schema: `GET /v1/rqlite/schema`
|
||||||
- Create Table: `POST /v1/db/create-table` `{schema}`
|
- Create Table: `POST /v1/rqlite/create-table` `{schema}`
|
||||||
- Drop Table: `POST /v1/db/drop-table` `{table}`
|
- Drop Table: `POST /v1/rqlite/drop-table` `{table}`
|
||||||
- **PubSub**
|
- **PubSub**
|
||||||
- WS Subscribe: `GET /v1/pubsub/ws?topic=<topic>`
|
- WS Subscribe: `GET /v1/pubsub/ws?topic=<topic>`
|
||||||
- Publish: `POST /v1/pubsub/publish` `{topic, data_base64}` → `{status:"ok"}`
|
- Publish: `POST /v1/pubsub/publish` `{topic, data_base64}` → `{status:"ok"}`
|
||||||
- Topics: `GET /v1/pubsub/topics` → `{topics:[...]}`
|
- Topics: `GET /v1/pubsub/topics` → `{topics:[...]}`
|
||||||
|
|
||||||
### Migrations
|
### Migrations
|
||||||
|
|
||||||
- Add column: `ALTER TABLE users ADD COLUMN age INTEGER`
|
- Add column: `ALTER TABLE users ADD COLUMN age INTEGER`
|
||||||
- Change type / add FK (recreate pattern): create `_new` table, copy data, drop old, rename.
|
- Change type / add FK (recreate pattern): create `_new` table, copy data, drop old, rename.
|
||||||
- Always send as one `POST /v1/db/transaction`.
|
- Always send as one `POST /v1/rqlite/transaction`.
|
||||||
|
|
||||||
### Minimal examples
|
### Minimal examples
|
||||||
|
|
||||||
@ -649,8 +670,13 @@ TypeScript (Node)
|
|||||||
```ts
|
```ts
|
||||||
import { GatewayClient } from "../examples/sdk-typescript/src/client";
|
import { GatewayClient } from "../examples/sdk-typescript/src/client";
|
||||||
|
|
||||||
const client = new GatewayClient(process.env.GATEWAY_BASE_URL!, process.env.GATEWAY_API_KEY!);
|
const client = new GatewayClient(
|
||||||
await client.createTable("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
|
process.env.GATEWAY_BASE_URL!,
|
||||||
|
process.env.GATEWAY_API_KEY!
|
||||||
|
);
|
||||||
|
await client.createTable(
|
||||||
|
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"
|
||||||
|
);
|
||||||
const res = await client.query("SELECT name FROM users WHERE id = ?", [1]);
|
const res = await client.query("SELECT name FROM users WHERE id = ?", [1]);
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -664,7 +690,7 @@ KEY = os.environ['GATEWAY_API_KEY']
|
|||||||
H = { 'X-API-Key': KEY, 'Content-Type': 'application/json' }
|
H = { 'X-API-Key': KEY, 'Content-Type': 'application/json' }
|
||||||
|
|
||||||
def query(sql, args=None):
|
def query(sql, args=None):
|
||||||
r = requests.post(f'{BASE}/v1/db/query', json={ 'sql': sql, 'args': args or [] }, headers=H, timeout=15)
|
r = requests.post(f'{BASE}/v1/rqlite/query', json={ 'sql': sql, 'args': args or [] }, headers=H, timeout=15)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return r.json()['rows']
|
return r.json()['rows']
|
||||||
```
|
```
|
||||||
@ -672,7 +698,7 @@ def query(sql, args=None):
|
|||||||
Go
|
Go
|
||||||
|
|
||||||
```go
|
```go
|
||||||
req, _ := http.NewRequest(http.MethodPost, base+"/v1/db/create-table", bytes.NewBufferString(`{"schema":"CREATE TABLE ..."}`))
|
req, _ := http.NewRequest(http.MethodPost, base+"/v1/rqlite/create-table", bytes.NewBufferString(`{"schema":"CREATE TABLE ..."}`))
|
||||||
req.Header.Set("X-API-Key", apiKey)
|
req.Header.Set("X-API-Key", apiKey)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
@ -688,6 +714,7 @@ resp, err := http.DefaultClient.Do(req)
|
|||||||
### Usage Examples
|
### Usage Examples
|
||||||
|
|
||||||
#### Wallet Authentication Flow
|
#### Wallet Authentication Flow
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Get challenge (automatic)
|
# 1. Get challenge (automatic)
|
||||||
curl -X POST http://localhost:6001/v1/auth/challenge
|
curl -X POST http://localhost:6001/v1/auth/challenge
|
||||||
@ -699,26 +726,25 @@ curl -X POST http://localhost:6001/v1/auth/verify \
|
|||||||
-d '{"wallet":"0x...","nonce":"...","signature":"0x..."}'
|
-d '{"wallet":"0x...","nonce":"...","signature":"0x..."}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Real-time Messaging
|
#### Real-time Messaging
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// WebSocket connection
|
// WebSocket connection
|
||||||
const ws = new WebSocket('ws://localhost:6001/v1/pubsub/ws?topic=chat');
|
const ws = new WebSocket("ws://localhost:6001/v1/pubsub/ws?topic=chat");
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
console.log('Received:', event.data);
|
console.log("Received:", event.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send message
|
// Send message
|
||||||
ws.send('Hello, network!');
|
ws.send("Hello, network!");
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
</text>
|
|
||||||
|
|
||||||
|
</text>
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
|
|
||||||
@ -758,6 +784,7 @@ scripts/test-multinode.sh
|
|||||||
## Database Client (Go ORM-like)
|
## Database Client (Go ORM-like)
|
||||||
|
|
||||||
A lightweight ORM-like client over rqlite using Go’s `database/sql`. It provides:
|
A lightweight ORM-like client over rqlite using Go’s `database/sql`. It provides:
|
||||||
|
|
||||||
- Query/Exec for raw SQL
|
- Query/Exec for raw SQL
|
||||||
- A fluent QueryBuilder (`Where`, `InnerJoin`, `LeftJoin`, `OrderBy`, `GroupBy`, `Limit`, `Offset`)
|
- A fluent QueryBuilder (`Where`, `InnerJoin`, `LeftJoin`, `OrderBy`, `GroupBy`, `Limit`, `Offset`)
|
||||||
- Simple repositories with `Find`/`FindOne`
|
- Simple repositories with `Find`/`FindOne`
|
||||||
@ -772,7 +799,7 @@ A lightweight ORM-like client over rqlite using Go’s `database/sql`. It provid
|
|||||||
|
|
||||||
### Quick Start
|
### Quick Start
|
||||||
|
|
||||||
```go
|
````go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -834,7 +861,7 @@ type Post struct {
|
|||||||
CreatedAt time.Time `db:"created_at"`
|
CreatedAt time.Time `db:"created_at"`
|
||||||
}
|
}
|
||||||
func (Post) TableName() string { return "posts" }
|
func (Post) TableName() string { return "posts" }
|
||||||
```
|
````
|
||||||
|
|
||||||
### Basic queries
|
### Basic queries
|
||||||
|
|
||||||
@ -988,7 +1015,6 @@ if err := rqlite.ApplyMigrationsDirs(ctx, db, dirs, logger); err != nil {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@ -363,50 +362,6 @@ func handlePeerID() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: try to extract from local identity files
|
|
||||||
identityPaths := []string{
|
|
||||||
"/opt/debros/data/node/identity.key",
|
|
||||||
"/opt/debros/data/bootstrap/identity.key",
|
|
||||||
"/opt/debros/keys/node/identity.key",
|
|
||||||
"./data/node/identity.key",
|
|
||||||
"./data/bootstrap/identity.key",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range identityPaths {
|
|
||||||
if peerID := extractPeerIDFromFile(path); peerID != "" {
|
|
||||||
if format == "json" {
|
|
||||||
printJSON(map[string]string{"peer_id": peerID, "source": "local_identity"})
|
|
||||||
} else {
|
|
||||||
fmt.Printf("🆔 Peer ID: %s\n", peerID)
|
|
||||||
fmt.Printf("📂 Source: %s\n", path)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check peer.info files as last resort
|
|
||||||
peerInfoPaths := []string{
|
|
||||||
"/opt/debros/data/node/peer.info",
|
|
||||||
"/opt/debros/data/bootstrap/peer.info",
|
|
||||||
"./data/node/peer.info",
|
|
||||||
"./data/bootstrap/peer.info",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range peerInfoPaths {
|
|
||||||
if data, err := os.ReadFile(path); err == nil {
|
|
||||||
multiaddr := strings.TrimSpace(string(data))
|
|
||||||
if peerID := extractPeerIDFromMultiaddr(multiaddr); peerID != "" {
|
|
||||||
if format == "json" {
|
|
||||||
printJSON(map[string]string{"peer_id": peerID, "source": "peer_info"})
|
|
||||||
} else {
|
|
||||||
fmt.Printf("🆔 Peer ID: %s\n", peerID)
|
|
||||||
fmt.Printf("📂 Source: %s\n", path)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "❌ Could not find peer ID. Make sure the node is running or identity files exist.\n")
|
fmt.Fprintf(os.Stderr, "❌ Could not find peer ID. Make sure the node is running or identity files exist.\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@ -470,20 +425,6 @@ func discoverBootstrapPeer() string {
|
|||||||
return "" // Return empty string if no peer info found
|
return "" // Return empty string if no peer info found
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryDecodeBase64(s string) string {
|
|
||||||
// Only try to decode if it looks like base64 (no spaces, reasonable length)
|
|
||||||
if len(s) > 0 && len(s)%4 == 0 && !strings.ContainsAny(s, " \n\r\t") {
|
|
||||||
if decoded, err := base64.StdEncoding.DecodeString(s); err == nil {
|
|
||||||
// Check if decoded result looks like readable text
|
|
||||||
decodedStr := string(decoded)
|
|
||||||
if isPrintableText(decodedStr) {
|
|
||||||
return decodedStr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func isPrintableText(s string) bool {
|
func isPrintableText(s string) bool {
|
||||||
printableCount := 0
|
printableCount := 0
|
||||||
for _, r := range s {
|
for _, r := range s {
|
||||||
|
45
cmd/identity/main.go
Normal file
45
cmd/identity/main.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/DeBrosOfficial/network/pkg/encryption"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var outputPath string
|
||||||
|
var displayOnly bool
|
||||||
|
|
||||||
|
flag.StringVar(&outputPath, "output", "", "Output path for identity key")
|
||||||
|
flag.BoolVar(&displayOnly, "display-only", false, "Only display identity info, don't save")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Generate identity using shared package
|
||||||
|
info, err := encryption.GenerateIdentity()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to generate identity: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If display only, just show the info
|
||||||
|
if displayOnly {
|
||||||
|
fmt.Printf("Node Identity: %s\n", info.PeerID.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to file using shared package
|
||||||
|
if outputPath == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Output path is required")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := encryption.SaveIdentity(info, outputPath); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to save identity: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Generated Node Identity: %s\n", info.PeerID.String())
|
||||||
|
fmt.Printf("Identity saved to: %s\n", outputPath)
|
||||||
|
}
|
@ -170,7 +170,7 @@ func TestGateway_Database_CreateQueryMigrate(t *testing.T) {
|
|||||||
// Create table
|
// Create table
|
||||||
schema := `CREATE TABLE IF NOT EXISTS e2e_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)`
|
schema := `CREATE TABLE IF NOT EXISTS e2e_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)`
|
||||||
body := fmt.Sprintf(`{"schema":%q}`, schema)
|
body := fmt.Sprintf(`{"schema":%q}`, schema)
|
||||||
req, _ := http.NewRequest(http.MethodPost, base+"/v1/db/create-table", strings.NewReader(body))
|
req, _ := http.NewRequest(http.MethodPost, base+"/v1/rqlite/create-table", strings.NewReader(body))
|
||||||
req.Header = authHeader(key)
|
req.Header = authHeader(key)
|
||||||
resp, err := httpClient().Do(req)
|
resp, err := httpClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -183,7 +183,7 @@ func TestGateway_Database_CreateQueryMigrate(t *testing.T) {
|
|||||||
|
|
||||||
// Insert via transaction (simulate migration/data seed)
|
// Insert via transaction (simulate migration/data seed)
|
||||||
txBody := `{"statements":["INSERT INTO e2e_items(name) VALUES ('one')","INSERT INTO e2e_items(name) VALUES ('two')"]}`
|
txBody := `{"statements":["INSERT INTO e2e_items(name) VALUES ('one')","INSERT INTO e2e_items(name) VALUES ('two')"]}`
|
||||||
req, _ = http.NewRequest(http.MethodPost, base+"/v1/db/transaction", strings.NewReader(txBody))
|
req, _ = http.NewRequest(http.MethodPost, base+"/v1/rqlite/transaction", strings.NewReader(txBody))
|
||||||
req.Header = authHeader(key)
|
req.Header = authHeader(key)
|
||||||
resp, err = httpClient().Do(req)
|
resp, err = httpClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -196,7 +196,7 @@ func TestGateway_Database_CreateQueryMigrate(t *testing.T) {
|
|||||||
|
|
||||||
// Query rows
|
// Query rows
|
||||||
qBody := `{"sql":"SELECT name FROM e2e_items ORDER BY id ASC"}`
|
qBody := `{"sql":"SELECT name FROM e2e_items ORDER BY id ASC"}`
|
||||||
req, _ = http.NewRequest(http.MethodPost, base+"/v1/db/query", strings.NewReader(qBody))
|
req, _ = http.NewRequest(http.MethodPost, base+"/v1/rqlite/query", strings.NewReader(qBody))
|
||||||
req.Header = authHeader(key)
|
req.Header = authHeader(key)
|
||||||
resp, err = httpClient().Do(req)
|
resp, err = httpClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -219,7 +219,7 @@ func TestGateway_Database_CreateQueryMigrate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Schema endpoint returns tables
|
// Schema endpoint returns tables
|
||||||
req, _ = http.NewRequest(http.MethodGet, base+"/v1/db/schema", nil)
|
req, _ = http.NewRequest(http.MethodGet, base+"/v1/rqlite/schema", nil)
|
||||||
req.Header = authHeader(key)
|
req.Header = authHeader(key)
|
||||||
resp2, err := httpClient().Do(req)
|
resp2, err := httpClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -239,7 +239,7 @@ func TestGateway_Database_DropTable(t *testing.T) {
|
|||||||
schema := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (id INTEGER PRIMARY KEY, note TEXT)", table)
|
schema := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (id INTEGER PRIMARY KEY, note TEXT)", table)
|
||||||
// create
|
// create
|
||||||
body := fmt.Sprintf(`{"schema":%q}`, schema)
|
body := fmt.Sprintf(`{"schema":%q}`, schema)
|
||||||
req, _ := http.NewRequest(http.MethodPost, base+"/v1/db/create-table", strings.NewReader(body))
|
req, _ := http.NewRequest(http.MethodPost, base+"/v1/rqlite/create-table", strings.NewReader(body))
|
||||||
req.Header = authHeader(key)
|
req.Header = authHeader(key)
|
||||||
resp, err := httpClient().Do(req)
|
resp, err := httpClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -251,7 +251,7 @@ func TestGateway_Database_DropTable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// drop
|
// drop
|
||||||
dbody := fmt.Sprintf(`{"table":%q}`, table)
|
dbody := fmt.Sprintf(`{"table":%q}`, table)
|
||||||
req, _ = http.NewRequest(http.MethodPost, base+"/v1/db/drop-table", strings.NewReader(dbody))
|
req, _ = http.NewRequest(http.MethodPost, base+"/v1/rqlite/drop-table", strings.NewReader(dbody))
|
||||||
req.Header = authHeader(key)
|
req.Header = authHeader(key)
|
||||||
resp, err = httpClient().Do(req)
|
resp, err = httpClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -262,7 +262,7 @@ func TestGateway_Database_DropTable(t *testing.T) {
|
|||||||
t.Fatalf("drop-table status: %d", resp.StatusCode)
|
t.Fatalf("drop-table status: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
// verify not in schema
|
// verify not in schema
|
||||||
req, _ = http.NewRequest(http.MethodGet, base+"/v1/db/schema", nil)
|
req, _ = http.NewRequest(http.MethodGet, base+"/v1/rqlite/schema", nil)
|
||||||
req.Header = authHeader(key)
|
req.Header = authHeader(key)
|
||||||
resp2, err := httpClient().Do(req)
|
resp2, err := httpClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -298,7 +298,7 @@ func TestGateway_Database_RecreateWithFK(t *testing.T) {
|
|||||||
createUsers := fmt.Sprintf(`{"schema":%q}`, fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (id INTEGER PRIMARY KEY, name TEXT, org_id INTEGER, age TEXT)", users))
|
createUsers := fmt.Sprintf(`{"schema":%q}`, fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (id INTEGER PRIMARY KEY, name TEXT, org_id INTEGER, age TEXT)", users))
|
||||||
|
|
||||||
for _, body := range []string{createOrgs, createUsers} {
|
for _, body := range []string{createOrgs, createUsers} {
|
||||||
req, _ := http.NewRequest(http.MethodPost, base+"/v1/db/create-table", strings.NewReader(body))
|
req, _ := http.NewRequest(http.MethodPost, base+"/v1/rqlite/create-table", strings.NewReader(body))
|
||||||
req.Header = authHeader(key)
|
req.Header = authHeader(key)
|
||||||
resp, err := httpClient().Do(req)
|
resp, err := httpClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -311,7 +311,7 @@ func TestGateway_Database_RecreateWithFK(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// seed data
|
// seed data
|
||||||
txSeed := fmt.Sprintf(`{"statements":["INSERT INTO %s(id,name) VALUES (1,'org')","INSERT INTO %s(id,name,org_id,age) VALUES (1,'alice',1,'30')"]}`, orgs, users)
|
txSeed := fmt.Sprintf(`{"statements":["INSERT INTO %s(id,name) VALUES (1,'org')","INSERT INTO %s(id,name,org_id,age) VALUES (1,'alice',1,'30')"]}`, orgs, users)
|
||||||
req, _ := http.NewRequest(http.MethodPost, base+"/v1/db/transaction", strings.NewReader(txSeed))
|
req, _ := http.NewRequest(http.MethodPost, base+"/v1/rqlite/transaction", strings.NewReader(txSeed))
|
||||||
req.Header = authHeader(key)
|
req.Header = authHeader(key)
|
||||||
resp, err := httpClient().Do(req)
|
resp, err := httpClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -331,7 +331,7 @@ func TestGateway_Database_RecreateWithFK(t *testing.T) {
|
|||||||
"DROP TABLE %s",
|
"DROP TABLE %s",
|
||||||
"ALTER TABLE %s_new RENAME TO %s"
|
"ALTER TABLE %s_new RENAME TO %s"
|
||||||
]}`, users, orgs, users, users, users, users, users)
|
]}`, users, orgs, users, users, users, users, users)
|
||||||
req, _ = http.NewRequest(http.MethodPost, base+"/v1/db/transaction", strings.NewReader(txMig))
|
req, _ = http.NewRequest(http.MethodPost, base+"/v1/rqlite/transaction", strings.NewReader(txMig))
|
||||||
req.Header = authHeader(key)
|
req.Header = authHeader(key)
|
||||||
resp, err = httpClient().Do(req)
|
resp, err = httpClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -344,7 +344,7 @@ func TestGateway_Database_RecreateWithFK(t *testing.T) {
|
|||||||
|
|
||||||
// verify schema type change
|
// verify schema type change
|
||||||
qBody := fmt.Sprintf(`{"sql":"PRAGMA table_info(%s)"}`, users)
|
qBody := fmt.Sprintf(`{"sql":"PRAGMA table_info(%s)"}`, users)
|
||||||
req, _ = http.NewRequest(http.MethodPost, base+"/v1/db/query", strings.NewReader(qBody))
|
req, _ = http.NewRequest(http.MethodPost, base+"/v1/rqlite/query", strings.NewReader(qBody))
|
||||||
req.Header = authHeader(key)
|
req.Header = authHeader(key)
|
||||||
resp, err = httpClient().Do(req)
|
resp, err = httpClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -375,7 +375,7 @@ func TestGateway_Database_RecreateWithFK(t *testing.T) {
|
|||||||
if !ageIsInt {
|
if !ageIsInt {
|
||||||
// Fallback: inspect CREATE TABLE SQL from sqlite_master
|
// Fallback: inspect CREATE TABLE SQL from sqlite_master
|
||||||
qBody2 := fmt.Sprintf(`{"sql":"SELECT sql FROM sqlite_master WHERE type='table' AND name='%s'"}`, users)
|
qBody2 := fmt.Sprintf(`{"sql":"SELECT sql FROM sqlite_master WHERE type='table' AND name='%s'"}`, users)
|
||||||
req2, _ := http.NewRequest(http.MethodPost, base+"/v1/db/query", strings.NewReader(qBody2))
|
req2, _ := http.NewRequest(http.MethodPost, base+"/v1/rqlite/query", strings.NewReader(qBody2))
|
||||||
req2.Header = authHeader(key)
|
req2.Header = authHeader(key)
|
||||||
resp3, err := httpClient().Do(req2)
|
resp3, err := httpClient().Do(req2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,81 +1,110 @@
|
|||||||
import WebSocket from 'isomorphic-ws';
|
import WebSocket from "isomorphic-ws";
|
||||||
|
|
||||||
export class GatewayClient {
|
export class GatewayClient {
|
||||||
constructor(private baseUrl: string, private apiKey: string, private http = fetch) {}
|
constructor(
|
||||||
|
private baseUrl: string,
|
||||||
|
private apiKey: string,
|
||||||
|
private http = fetch
|
||||||
|
) {}
|
||||||
|
|
||||||
private headers(json = true): Record<string, string> {
|
private headers(json = true): Record<string, string> {
|
||||||
const h: Record<string, string> = { 'X-API-Key': this.apiKey };
|
const h: Record<string, string> = { "X-API-Key": this.apiKey };
|
||||||
if (json) h['Content-Type'] = 'application/json';
|
if (json) h["Content-Type"] = "application/json";
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
async createTable(schema: string): Promise<void> {
|
async createTable(schema: string): Promise<void> {
|
||||||
const r = await this.http(`${this.baseUrl}/v1/db/create-table`, {
|
const r = await this.http(`${this.baseUrl}/v1/rqlite/create-table`, {
|
||||||
method: 'POST', headers: this.headers(), body: JSON.stringify({ schema })
|
method: "POST",
|
||||||
|
headers: this.headers(),
|
||||||
|
body: JSON.stringify({ schema }),
|
||||||
});
|
});
|
||||||
if (!r.ok) throw new Error(`createTable failed: ${r.status}`);
|
if (!r.ok) throw new Error(`createTable failed: ${r.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async dropTable(table: string): Promise<void> {
|
async dropTable(table: string): Promise<void> {
|
||||||
const r = await this.http(`${this.baseUrl}/v1/db/drop-table`, {
|
const r = await this.http(`${this.baseUrl}/v1/rqlite/drop-table`, {
|
||||||
method: 'POST', headers: this.headers(), body: JSON.stringify({ table })
|
method: "POST",
|
||||||
|
headers: this.headers(),
|
||||||
|
body: JSON.stringify({ table }),
|
||||||
});
|
});
|
||||||
if (!r.ok) throw new Error(`dropTable failed: ${r.status}`);
|
if (!r.ok) throw new Error(`dropTable failed: ${r.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async query<T = any>(sql: string, args: any[] = []): Promise<{ rows: T[] }> {
|
async query<T = any>(sql: string, args: any[] = []): Promise<{ rows: T[] }> {
|
||||||
const r = await this.http(`${this.baseUrl}/v1/db/query`, {
|
const r = await this.http(`${this.baseUrl}/v1/rqlite/query`, {
|
||||||
method: 'POST', headers: this.headers(), body: JSON.stringify({ sql, args })
|
method: "POST",
|
||||||
|
headers: this.headers(),
|
||||||
|
body: JSON.stringify({ sql, args }),
|
||||||
});
|
});
|
||||||
if (!r.ok) throw new Error(`query failed: ${r.status}`);
|
if (!r.ok) throw new Error(`query failed: ${r.status}`);
|
||||||
return r.json();
|
return r.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async transaction(statements: string[]): Promise<void> {
|
async transaction(statements: string[]): Promise<void> {
|
||||||
const r = await this.http(`${this.baseUrl}/v1/db/transaction`, {
|
const r = await this.http(`${this.baseUrl}/v1/rqlite/transaction`, {
|
||||||
method: 'POST', headers: this.headers(), body: JSON.stringify({ statements })
|
method: "POST",
|
||||||
|
headers: this.headers(),
|
||||||
|
body: JSON.stringify({ statements }),
|
||||||
});
|
});
|
||||||
if (!r.ok) throw new Error(`transaction failed: ${r.status}`);
|
if (!r.ok) throw new Error(`transaction failed: ${r.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async schema(): Promise<any> {
|
async schema(): Promise<any> {
|
||||||
const r = await this.http(`${this.baseUrl}/v1/db/schema`, { headers: this.headers(false) });
|
const r = await this.http(`${this.baseUrl}/v1/rqlite/schema`, {
|
||||||
|
headers: this.headers(false),
|
||||||
|
});
|
||||||
if (!r.ok) throw new Error(`schema failed: ${r.status}`);
|
if (!r.ok) throw new Error(`schema failed: ${r.status}`);
|
||||||
return r.json();
|
return r.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Storage
|
// Storage
|
||||||
async put(key: string, value: Uint8Array | string): Promise<void> {
|
async put(key: string, value: Uint8Array | string): Promise<void> {
|
||||||
const body = typeof value === 'string' ? new TextEncoder().encode(value) : value;
|
const body =
|
||||||
const r = await this.http(`${this.baseUrl}/v1/storage/put?key=${encodeURIComponent(key)}`, {
|
typeof value === "string" ? new TextEncoder().encode(value) : value;
|
||||||
method: 'POST', headers: { 'X-API-Key': this.apiKey }, body
|
const r = await this.http(
|
||||||
});
|
`${this.baseUrl}/v1/storage/put?key=${encodeURIComponent(key)}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { "X-API-Key": this.apiKey },
|
||||||
|
body,
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!r.ok) throw new Error(`put failed: ${r.status}`);
|
if (!r.ok) throw new Error(`put failed: ${r.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(key: string): Promise<Uint8Array> {
|
async get(key: string): Promise<Uint8Array> {
|
||||||
const r = await this.http(`${this.baseUrl}/v1/storage/get?key=${encodeURIComponent(key)}`, {
|
const r = await this.http(
|
||||||
headers: { 'X-API-Key': this.apiKey }
|
`${this.baseUrl}/v1/storage/get?key=${encodeURIComponent(key)}`,
|
||||||
});
|
{
|
||||||
|
headers: { "X-API-Key": this.apiKey },
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!r.ok) throw new Error(`get failed: ${r.status}`);
|
if (!r.ok) throw new Error(`get failed: ${r.status}`);
|
||||||
const buf = new Uint8Array(await r.arrayBuffer());
|
const buf = new Uint8Array(await r.arrayBuffer());
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
async exists(key: string): Promise<boolean> {
|
async exists(key: string): Promise<boolean> {
|
||||||
const r = await this.http(`${this.baseUrl}/v1/storage/exists?key=${encodeURIComponent(key)}`, {
|
const r = await this.http(
|
||||||
headers: this.headers(false)
|
`${this.baseUrl}/v1/storage/exists?key=${encodeURIComponent(key)}`,
|
||||||
});
|
{
|
||||||
|
headers: this.headers(false),
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!r.ok) throw new Error(`exists failed: ${r.status}`);
|
if (!r.ok) throw new Error(`exists failed: ${r.status}`);
|
||||||
const j = await r.json();
|
const j = await r.json();
|
||||||
return !!j.exists;
|
return !!j.exists;
|
||||||
}
|
}
|
||||||
|
|
||||||
async list(prefix = ""): Promise<string[]> {
|
async list(prefix = ""): Promise<string[]> {
|
||||||
const r = await this.http(`${this.baseUrl}/v1/storage/list?prefix=${encodeURIComponent(prefix)}`, {
|
const r = await this.http(
|
||||||
headers: this.headers(false)
|
`${this.baseUrl}/v1/storage/list?prefix=${encodeURIComponent(prefix)}`,
|
||||||
});
|
{
|
||||||
|
headers: this.headers(false),
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!r.ok) throw new Error(`list failed: ${r.status}`);
|
if (!r.ok) throw new Error(`list failed: ${r.status}`);
|
||||||
const j = await r.json();
|
const j = await r.json();
|
||||||
return j.keys || [];
|
return j.keys || [];
|
||||||
@ -83,29 +112,42 @@ export class GatewayClient {
|
|||||||
|
|
||||||
async delete(key: string): Promise<void> {
|
async delete(key: string): Promise<void> {
|
||||||
const r = await this.http(`${this.baseUrl}/v1/storage/delete`, {
|
const r = await this.http(`${this.baseUrl}/v1/storage/delete`, {
|
||||||
method: 'POST', headers: this.headers(), body: JSON.stringify({ key })
|
method: "POST",
|
||||||
|
headers: this.headers(),
|
||||||
|
body: JSON.stringify({ key }),
|
||||||
});
|
});
|
||||||
if (!r.ok) throw new Error(`delete failed: ${r.status}`);
|
if (!r.ok) throw new Error(`delete failed: ${r.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// PubSub (minimal)
|
// PubSub (minimal)
|
||||||
subscribe(topic: string, onMessage: (data: Uint8Array) => void): { close: () => void } {
|
subscribe(
|
||||||
const url = new URL(`${this.baseUrl.replace(/^http/, 'ws')}/v1/pubsub/ws`);
|
topic: string,
|
||||||
url.searchParams.set('topic', topic);
|
onMessage: (data: Uint8Array) => void
|
||||||
const ws = new WebSocket(url.toString(), { headers: { 'X-API-Key': this.apiKey } } as any);
|
): { close: () => void } {
|
||||||
ws.binaryType = 'arraybuffer';
|
const url = new URL(`${this.baseUrl.replace(/^http/, "ws")}/v1/pubsub/ws`);
|
||||||
|
url.searchParams.set("topic", topic);
|
||||||
|
const ws = new WebSocket(url.toString(), {
|
||||||
|
headers: { "X-API-Key": this.apiKey },
|
||||||
|
} as any);
|
||||||
|
ws.binaryType = "arraybuffer";
|
||||||
ws.onmessage = (ev: any) => {
|
ws.onmessage = (ev: any) => {
|
||||||
const data = ev.data instanceof ArrayBuffer ? new Uint8Array(ev.data) : new TextEncoder().encode(String(ev.data));
|
const data =
|
||||||
|
ev.data instanceof ArrayBuffer
|
||||||
|
? new Uint8Array(ev.data)
|
||||||
|
: new TextEncoder().encode(String(ev.data));
|
||||||
onMessage(data);
|
onMessage(data);
|
||||||
};
|
};
|
||||||
return { close: () => ws.close() };
|
return { close: () => ws.close() };
|
||||||
}
|
}
|
||||||
|
|
||||||
async publish(topic: string, data: Uint8Array | string): Promise<void> {
|
async publish(topic: string, data: Uint8Array | string): Promise<void> {
|
||||||
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
|
const bytes =
|
||||||
const b64 = Buffer.from(bytes).toString('base64');
|
typeof data === "string" ? new TextEncoder().encode(data) : data;
|
||||||
|
const b64 = Buffer.from(bytes).toString("base64");
|
||||||
const r = await this.http(`${this.baseUrl}/v1/pubsub/publish`, {
|
const r = await this.http(`${this.baseUrl}/v1/pubsub/publish`, {
|
||||||
method: 'POST', headers: this.headers(), body: JSON.stringify({ topic, data_base64: b64 })
|
method: "POST",
|
||||||
|
headers: this.headers(),
|
||||||
|
body: JSON.stringify({ topic, data_base64: b64 }),
|
||||||
});
|
});
|
||||||
if (!r.ok) throw new Error(`publish failed: ${r.status}`);
|
if (!r.ok) throw new Error(`publish failed: ${r.status}`);
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,7 @@ paths:
|
|||||||
key: { type: string }
|
key: { type: string }
|
||||||
responses:
|
responses:
|
||||||
"200": { description: OK }
|
"200": { description: OK }
|
||||||
/v1/db/create-table:
|
/v1/rqlite/create-table:
|
||||||
post:
|
post:
|
||||||
summary: Create tables via SQL DDL
|
summary: Create tables via SQL DDL
|
||||||
requestBody:
|
requestBody:
|
||||||
@ -220,7 +220,7 @@ paths:
|
|||||||
{ schema: { $ref: "#/components/schemas/Error" } },
|
{ schema: { $ref: "#/components/schemas/Error" } },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
/v1/db/drop-table:
|
/v1/rqlite/drop-table:
|
||||||
post:
|
post:
|
||||||
summary: Drop a table
|
summary: Drop a table
|
||||||
requestBody:
|
requestBody:
|
||||||
@ -230,7 +230,7 @@ paths:
|
|||||||
schema: { $ref: "#/components/schemas/DropTableRequest" }
|
schema: { $ref: "#/components/schemas/DropTableRequest" }
|
||||||
responses:
|
responses:
|
||||||
"200": { description: OK }
|
"200": { description: OK }
|
||||||
/v1/db/query:
|
/v1/rqlite/query:
|
||||||
post:
|
post:
|
||||||
summary: Execute a single SQL query
|
summary: Execute a single SQL query
|
||||||
requestBody:
|
requestBody:
|
||||||
@ -262,7 +262,7 @@ paths:
|
|||||||
{ schema: { $ref: "#/components/schemas/Error" } },
|
{ schema: { $ref: "#/components/schemas/Error" } },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
/v1/db/transaction:
|
/v1/rqlite/transaction:
|
||||||
post:
|
post:
|
||||||
summary: Execute multiple SQL statements atomically
|
summary: Execute multiple SQL statements atomically
|
||||||
requestBody:
|
requestBody:
|
||||||
@ -290,7 +290,7 @@ paths:
|
|||||||
{ schema: { $ref: "#/components/schemas/Error" } },
|
{ schema: { $ref: "#/components/schemas/Error" } },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
/v1/db/schema:
|
/v1/rqlite/schema:
|
||||||
get:
|
get:
|
||||||
summary: Get current database schema
|
summary: Get current database schema
|
||||||
responses:
|
responses:
|
||||||
|
71
pkg/encryption/identity.go
Normal file
71
pkg/encryption/identity.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IdentityInfo struct {
|
||||||
|
PrivateKey crypto.PrivKey
|
||||||
|
PublicKey crypto.PubKey
|
||||||
|
PeerID peer.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateIdentity() (*IdentityInfo, error) {
|
||||||
|
priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.Ed25519, 2048, rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerID, err := peer.IDFromPublicKey(pub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &IdentityInfo{
|
||||||
|
PrivateKey: priv,
|
||||||
|
PublicKey: pub,
|
||||||
|
PeerID: peerID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveIdentity(identity *IdentityInfo, path string) error {
|
||||||
|
data, err := crypto.MarshalPrivateKey(identity.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(path, data, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadIdentity(path string) (*IdentityInfo, error) {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, err := crypto.UnmarshalPrivateKey(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pub := priv.GetPublic()
|
||||||
|
peerID, err := peer.IDFromPublicKey(pub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &IdentityInfo{
|
||||||
|
PrivateKey: priv,
|
||||||
|
PublicKey: pub,
|
||||||
|
PeerID: peerID,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -27,7 +27,7 @@ func (g *Gateway) Routes() http.Handler {
|
|||||||
mux.HandleFunc("/v1/auth/logout", g.logoutHandler)
|
mux.HandleFunc("/v1/auth/logout", g.logoutHandler)
|
||||||
mux.HandleFunc("/v1/auth/whoami", g.whoamiHandler)
|
mux.HandleFunc("/v1/auth/whoami", g.whoamiHandler)
|
||||||
|
|
||||||
// rqlite ORM HTTP gateway (mounts /v1/db/* endpoints)
|
// rqlite ORM HTTP gateway (mounts /v1/rqlite/* endpoints)
|
||||||
if g.ormHTTP != nil {
|
if g.ormHTTP != nil {
|
||||||
g.ormHTTP.BasePath = "/v1/rqlite"
|
g.ormHTTP.BasePath = "/v1/rqlite"
|
||||||
g.ormHTTP.RegisterRoutes(mux)
|
g.ormHTTP.RegisterRoutes(mux)
|
||||||
|
@ -2,7 +2,6 @@ package node
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
mathrand "math/rand"
|
mathrand "math/rand"
|
||||||
"os"
|
"os"
|
||||||
@ -23,6 +22,7 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/DeBrosOfficial/network/pkg/config"
|
"github.com/DeBrosOfficial/network/pkg/config"
|
||||||
|
"github.com/DeBrosOfficial/network/pkg/encryption"
|
||||||
"github.com/DeBrosOfficial/network/pkg/logging"
|
"github.com/DeBrosOfficial/network/pkg/logging"
|
||||||
"github.com/DeBrosOfficial/network/pkg/pubsub"
|
"github.com/DeBrosOfficial/network/pkg/pubsub"
|
||||||
database "github.com/DeBrosOfficial/network/pkg/rqlite"
|
database "github.com/DeBrosOfficial/network/pkg/rqlite"
|
||||||
@ -374,65 +374,41 @@ func (n *Node) startLibP2P() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadOrCreateIdentity loads an existing identity or creates a new one
|
||||||
// loadOrCreateIdentity loads an existing identity or creates a new one
|
// loadOrCreateIdentity loads an existing identity or creates a new one
|
||||||
func (n *Node) loadOrCreateIdentity() (crypto.PrivKey, error) {
|
func (n *Node) loadOrCreateIdentity() (crypto.PrivKey, error) {
|
||||||
identityFile := filepath.Join(n.config.Node.DataDir, "identity.key")
|
identityFile := filepath.Join(n.config.Node.DataDir, "identity.key")
|
||||||
|
|
||||||
// Try to load existing identity
|
// Try to load existing identity using the shared package
|
||||||
if _, err := os.Stat(identityFile); err == nil {
|
if _, err := os.Stat(identityFile); err == nil {
|
||||||
data, err := os.ReadFile(identityFile)
|
info, err := encryption.LoadIdentity(identityFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read identity file: %w", err)
|
n.logger.Warn("Failed to load existing identity, creating new one", zap.Error(err))
|
||||||
}
|
|
||||||
|
|
||||||
priv, err := crypto.UnmarshalPrivateKey(data)
|
|
||||||
if err != nil {
|
|
||||||
n.logger.Warn("Failed to unmarshal existing identity, creating new one", zap.Error(err))
|
|
||||||
} else {
|
|
||||||
// Extract peer ID from private key for logging
|
|
||||||
peerID, err := peer.IDFromPrivateKey(priv)
|
|
||||||
if err != nil {
|
|
||||||
n.logger.ComponentInfo(logging.ComponentNode, "Loaded existing identity",
|
|
||||||
zap.String("file", identityFile),
|
|
||||||
zap.String("peer_id", "unable_to_extract"))
|
|
||||||
} else {
|
} else {
|
||||||
n.logger.ComponentInfo(logging.ComponentNode, "Loaded existing identity",
|
n.logger.ComponentInfo(logging.ComponentNode, "Loaded existing identity",
|
||||||
zap.String("file", identityFile),
|
zap.String("file", identityFile),
|
||||||
zap.String("peer_id", peerID.String()))
|
zap.String("peer_id", info.PeerID.String()))
|
||||||
}
|
return info.PrivateKey, nil
|
||||||
return priv, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new identity
|
// Create new identity using shared package
|
||||||
n.logger.Info("Creating new identity", zap.String("file", identityFile))
|
n.logger.Info("Creating new identity", zap.String("file", identityFile))
|
||||||
priv, _, err := crypto.GenerateKeyPairWithReader(crypto.Ed25519, 2048, rand.Reader)
|
info, err := encryption.GenerateIdentity()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate key pair: %w", err)
|
return nil, fmt.Errorf("failed to generate identity: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract peer ID from private key for logging
|
// Save identity using shared package
|
||||||
peerID, err := peer.IDFromPrivateKey(priv)
|
if err := encryption.SaveIdentity(info, identityFile); err != nil {
|
||||||
if err != nil {
|
|
||||||
n.logger.Info("Identity created",
|
|
||||||
zap.String("peer_id", "unable_to_extract"))
|
|
||||||
} else {
|
|
||||||
n.logger.Info("Identity created",
|
|
||||||
zap.String("peer_id", peerID.String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save identity
|
|
||||||
data, err := crypto.MarshalPrivateKey(priv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to marshal private key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.WriteFile(identityFile, data, 0600); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to save identity: %w", err)
|
return nil, fmt.Errorf("failed to save identity: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
n.logger.Info("Identity saved", zap.String("file", identityFile))
|
n.logger.Info("Identity saved",
|
||||||
return priv, nil
|
zap.String("file", identityFile),
|
||||||
|
zap.String("peer_id", info.PeerID.String()))
|
||||||
|
|
||||||
|
return info.PrivateKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerID returns the peer ID of this node
|
// GetPeerID returns the peer ID of this node
|
||||||
|
@ -335,40 +335,8 @@ generate_identity() {
|
|||||||
fi
|
fi
|
||||||
log "Generating node identity..."
|
log "Generating node identity..."
|
||||||
cd "$INSTALL_DIR/src"
|
cd "$INSTALL_DIR/src"
|
||||||
cat > /tmp/generate_identity_custom.go << 'EOF'
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"github.com/libp2p/go-libp2p/core/crypto"
|
|
||||||
"github.com/libp2p/go-libp2p/core/peer"
|
|
||||||
)
|
|
||||||
func main() {
|
|
||||||
var outputPath string
|
|
||||||
flag.StringVar(&outputPath, "output", "", "Output path for identity key")
|
|
||||||
flag.Parse()
|
|
||||||
if outputPath == "" {
|
|
||||||
fmt.Println("Usage: go run generate_identity_custom.go -output <path>")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.Ed25519, 2048, rand.Reader)
|
|
||||||
if err != nil { panic(err) }
|
|
||||||
peerID, err := peer.IDFromPublicKey(pub)
|
|
||||||
if err != nil { panic(err) }
|
|
||||||
data, err := crypto.MarshalPrivateKey(priv)
|
|
||||||
if err != nil { panic(err) }
|
|
||||||
if err := os.MkdirAll(filepath.Dir(outputPath), 0700); err != nil { panic(err) }
|
|
||||||
if err := os.WriteFile(outputPath, data, 0600); err != nil { panic(err) }
|
|
||||||
fmt.Printf("Generated Peer ID: %s\n", peerID.String())
|
|
||||||
fmt.Printf("Identity saved to: %s\n", outputPath)
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
export PATH=$PATH:/usr/local/go/bin
|
export PATH=$PATH:/usr/local/go/bin
|
||||||
sudo -u debros env "PATH=$PATH:/usr/local/go/bin" "GOMOD=$(pwd)" go run /tmp/generate_identity_custom.go -output "$identity_file"
|
sudo -u debros env "PATH=$PATH:/usr/local/go/bin" go run ./cmd/identity -output "$identity_file"
|
||||||
rm /tmp/generate_identity_custom.go
|
|
||||||
success "Node identity generated"
|
success "Node identity generated"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -560,10 +528,10 @@ main() {
|
|||||||
log "${GREEN}Installation Directory:${NOCOLOR} ${CYAN}$INSTALL_DIR${NOCOLOR}"
|
log "${GREEN}Installation Directory:${NOCOLOR} ${CYAN}$INSTALL_DIR${NOCOLOR}"
|
||||||
log "${GREEN}Configuration:${NOCOLOR} ${CYAN}$INSTALL_DIR/configs/node.yaml${NOCOLOR}"
|
log "${GREEN}Configuration:${NOCOLOR} ${CYAN}$INSTALL_DIR/configs/node.yaml${NOCOLOR}"
|
||||||
log "${GREEN}Logs:${NOCOLOR} ${CYAN}$INSTALL_DIR/logs/node.log${NOCOLOR}"
|
log "${GREEN}Logs:${NOCOLOR} ${CYAN}$INSTALL_DIR/logs/node.log${NOCOLOR}"
|
||||||
log "${GREEN}Node Port:${NOCOLOR} ${CYAN}$NODE_PORT${NOCOLOR}"
|
log "${GREEN}LibP2P Port:${NOCOLOR} ${CYAN}$NODE_PORT${NOCOLOR}"
|
||||||
log "${GREEN}RQLite Port:${NOCOLOR} ${CYAN}$RQLITE_PORT${NOCOLOR}"
|
log "${GREEN}RQLite Port:${NOCOLOR} ${CYAN}$RQLITE_PORT${NOCOLOR}"
|
||||||
log "${GREEN}Raft Port:${NOCOLOR} ${CYAN}$RAFT_PORT${NOCOLOR}"
|
|
||||||
log "${GREEN}Gateway Port:${NOCOLOR} ${CYAN}$GATEWAY_PORT${NOCOLOR}"
|
log "${GREEN}Gateway Port:${NOCOLOR} ${CYAN}$GATEWAY_PORT${NOCOLOR}"
|
||||||
|
log "${GREEN}Raft Port:${NOCOLOR} ${CYAN}$RAFT_PORT${NOCOLOR}"
|
||||||
log "${BLUE}==================================================${NOCOLOR}"
|
log "${BLUE}==================================================${NOCOLOR}"
|
||||||
log "${GREEN}Management Commands:${NOCOLOR}"
|
log "${GREEN}Management Commands:${NOCOLOR}"
|
||||||
log "${CYAN} - sudo systemctl status debros-node${NOCOLOR} (Check status)"
|
log "${CYAN} - sudo systemctl status debros-node${NOCOLOR} (Check status)"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user