Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3de31aa88 | ||
|
|
b6df0be865 | ||
|
|
a89f84f76e | ||
|
|
5a916cc221 | ||
|
|
dcf421ff93 | ||
|
|
d655258311 |
17
Makefile
17
Makefile
@@ -197,4 +197,21 @@ help:
|
|||||||
@echo " make init-db # Set up database"
|
@echo " make init-db # Set up database"
|
||||||
@echo " make force-version # Force regenerate main.h from git"
|
@echo " make force-version # Force regenerate main.h from git"
|
||||||
|
|
||||||
|
# Build fully static MUSL binaries using Docker
|
||||||
|
static-musl-x86_64:
|
||||||
|
@echo "Building fully static MUSL binary for x86_64..."
|
||||||
|
docker buildx build --platform linux/amd64 -f examples/deployment/static-builder.Dockerfile -t c-relay-static-builder-x86_64 --load .
|
||||||
|
docker run --rm -v $(PWD)/build:/output c-relay-static-builder-x86_64 sh -c "cp /c_relay_static_musl_x86_64 /output/"
|
||||||
|
@echo "Static binary created: build/c_relay_static_musl_x86_64"
|
||||||
|
|
||||||
|
static-musl-arm64:
|
||||||
|
@echo "Building fully static MUSL binary for ARM64..."
|
||||||
|
docker buildx build --platform linux/arm64 -f examples/deployment/static-builder.Dockerfile -t c-relay-static-builder-arm64 --load .
|
||||||
|
docker run --rm -v $(PWD)/build:/output c-relay-static-builder-arm64 sh -c "cp /c_relay_static_musl_x86_64 /output/c_relay_static_musl_arm64"
|
||||||
|
@echo "Static binary created: build/c_relay_static_musl_arm64"
|
||||||
|
|
||||||
|
static-musl: static-musl-x86_64 static-musl-arm64
|
||||||
|
@echo "Built static MUSL binaries for both architectures"
|
||||||
|
|
||||||
|
.PHONY: static-musl-x86_64 static-musl-arm64 static-musl
|
||||||
.PHONY: all x86 arm64 test init-db clean clean-all install-deps install-cross-tools install-arm64-deps check-toolchain help force-version
|
.PHONY: all x86 arm64 test init-db clean clean-all install-deps install-cross-tools install-arm64-deps check-toolchain help force-version
|
||||||
62
README.md
62
README.md
@@ -22,6 +22,68 @@ Do NOT modify the formatting, add emojis, or change the text. Keep the simple fo
|
|||||||
- [x] NIP-50: Keywords filter
|
- [x] NIP-50: Keywords filter
|
||||||
- [x] NIP-70: Protected Events
|
- [x] NIP-70: Protected Events
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Get your C-Relay up and running in minutes with a static binary (no dependencies required):
|
||||||
|
|
||||||
|
### 1. Download Static Binary
|
||||||
|
|
||||||
|
Download the latest static release from the [releases page](https://git.laantungir.net/laantungir/c-relay/releases):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Static binary - works on all Linux distributions (no dependencies)
|
||||||
|
wget https://git.laantungir.net/laantungir/c-relay/releases/download/v0.6.0/c-relay-v0.6.0-linux-x86_64-static
|
||||||
|
chmod +x c-relay-v0.6.0-linux-x86_64-static
|
||||||
|
mv c-relay-v0.6.0-linux-x86_64-static c-relay
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Start the Relay
|
||||||
|
|
||||||
|
Simply run the binary - no configuration files needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./c-relay
|
||||||
|
```
|
||||||
|
|
||||||
|
On first startup, you'll see:
|
||||||
|
- **Admin Private Key**: Save this securely! You'll need it for administration
|
||||||
|
- **Relay Public Key**: Your relay's identity on the Nostr network
|
||||||
|
- **Port Information**: Default is 8888, or the next available port
|
||||||
|
|
||||||
|
### 3. Access the Web Interface
|
||||||
|
|
||||||
|
Open your browser and navigate to:
|
||||||
|
```
|
||||||
|
http://localhost:8888/api/
|
||||||
|
```
|
||||||
|
|
||||||
|
The web interface provides:
|
||||||
|
- Real-time configuration management
|
||||||
|
- Database statistics dashboard
|
||||||
|
- Auth rules management
|
||||||
|
- Secure admin authentication with your Nostr identity
|
||||||
|
|
||||||
|
### 4. Test Your Relay
|
||||||
|
|
||||||
|
Test basic connectivity:
|
||||||
|
```bash
|
||||||
|
# Test WebSocket connection
|
||||||
|
curl -H "Accept: application/nostr+json" http://localhost:8888
|
||||||
|
|
||||||
|
# Test with a Nostr client
|
||||||
|
# Add ws://localhost:8888 to your client's relay list
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Configure Your Relay (Optional)
|
||||||
|
|
||||||
|
Use the web interface or send admin commands to customize:
|
||||||
|
- Relay name and description
|
||||||
|
- Authentication rules (whitelist/blacklist)
|
||||||
|
- Connection limits
|
||||||
|
- Proof-of-work requirements
|
||||||
|
|
||||||
|
**That's it!** Your relay is now running with zero configuration required. The event-based configuration system means you can adjust all settings through the web interface or admin API without editing config files.
|
||||||
|
|
||||||
## Web Admin Interface
|
## Web Admin Interface
|
||||||
|
|
||||||
C-Relay includes a **built-in web-based administration interface** accessible at `http://localhost:8888/api/`. The interface provides:
|
C-Relay includes a **built-in web-based administration interface** accessible at `http://localhost:8888/api/`. The interface provides:
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<div class="inline-buttons">
|
<div class="inline-buttons">
|
||||||
<button type="button" id="connect-relay-btn">CONNECT TO RELAY</button>
|
<button type="button" id="connect-relay-btn">CONNECT TO RELAY</button>
|
||||||
<button type="button" id="disconnect-relay-btn" disabled>DISCONNECT</button>
|
<button type="button" id="disconnect-relay-btn" disabled>DISCONNECT</button>
|
||||||
<button type="button" id="test-websocket-btn" disabled>TEST WEBSOCKET</button>
|
<button type="button" id="restart-relay-btn" disabled>RESTART RELAY</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status disconnected" id="relay-connection-status">NOT CONNECTED</div>
|
<div class="status disconnected" id="relay-connection-status">NOT CONNECTED</div>
|
||||||
|
|||||||
120
api/index.js
120
api/index.js
@@ -41,7 +41,7 @@
|
|||||||
const relayConnectionStatus = document.getElementById('relay-connection-status');
|
const relayConnectionStatus = document.getElementById('relay-connection-status');
|
||||||
const connectRelayBtn = document.getElementById('connect-relay-btn');
|
const connectRelayBtn = document.getElementById('connect-relay-btn');
|
||||||
const disconnectRelayBtn = document.getElementById('disconnect-relay-btn');
|
const disconnectRelayBtn = document.getElementById('disconnect-relay-btn');
|
||||||
const testWebSocketBtn = document.getElementById('test-websocket-btn');
|
const restartRelayBtn = document.getElementById('restart-relay-btn');
|
||||||
const configDisplay = document.getElementById('config-display');
|
const configDisplay = document.getElementById('config-display');
|
||||||
const configTableBody = document.getElementById('config-table-body');
|
const configTableBody = document.getElementById('config-table-body');
|
||||||
|
|
||||||
@@ -369,28 +369,28 @@
|
|||||||
relayConnectionStatus.className = 'status connected';
|
relayConnectionStatus.className = 'status connected';
|
||||||
connectRelayBtn.disabled = true;
|
connectRelayBtn.disabled = true;
|
||||||
disconnectRelayBtn.disabled = true;
|
disconnectRelayBtn.disabled = true;
|
||||||
testWebSocketBtn.disabled = true;
|
restartRelayBtn.disabled = true;
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
relayConnectionStatus.textContent = 'CONNECTED';
|
relayConnectionStatus.textContent = 'CONNECTED';
|
||||||
relayConnectionStatus.className = 'status connected';
|
relayConnectionStatus.className = 'status connected';
|
||||||
connectRelayBtn.disabled = true;
|
connectRelayBtn.disabled = true;
|
||||||
disconnectRelayBtn.disabled = false;
|
disconnectRelayBtn.disabled = false;
|
||||||
testWebSocketBtn.disabled = false;
|
restartRelayBtn.disabled = false;
|
||||||
break;
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
relayConnectionStatus.textContent = 'NOT CONNECTED';
|
relayConnectionStatus.textContent = 'NOT CONNECTED';
|
||||||
relayConnectionStatus.className = 'status disconnected';
|
relayConnectionStatus.className = 'status disconnected';
|
||||||
connectRelayBtn.disabled = false;
|
connectRelayBtn.disabled = false;
|
||||||
disconnectRelayBtn.disabled = true;
|
disconnectRelayBtn.disabled = true;
|
||||||
testWebSocketBtn.disabled = true;
|
restartRelayBtn.disabled = true;
|
||||||
break;
|
break;
|
||||||
case 'error':
|
case 'error':
|
||||||
relayConnectionStatus.textContent = 'CONNECTION FAILED';
|
relayConnectionStatus.textContent = 'CONNECTION FAILED';
|
||||||
relayConnectionStatus.className = 'status error';
|
relayConnectionStatus.className = 'status error';
|
||||||
connectRelayBtn.disabled = false;
|
connectRelayBtn.disabled = false;
|
||||||
disconnectRelayBtn.disabled = true;
|
disconnectRelayBtn.disabled = true;
|
||||||
testWebSocketBtn.disabled = true;
|
restartRelayBtn.disabled = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1670,22 +1670,12 @@
|
|||||||
disconnectFromRelay();
|
disconnectFromRelay();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWebSocketBtn.addEventListener('click', function (e) {
|
restartRelayBtn.addEventListener('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const url = relayConnectionUrl.value.trim();
|
sendRestartCommand().catch(error => {
|
||||||
if (!url) {
|
log(`Restart command failed: ${error.message}`, 'ERROR');
|
||||||
log('Please enter a relay URL first', 'ERROR');
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
testWebSocketConnection(url)
|
|
||||||
.then(() => {
|
|
||||||
log('WebSocket test successful', 'INFO');
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
log(`WebSocket test failed: ${error.message}`, 'ERROR');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
@@ -3078,10 +3068,14 @@
|
|||||||
messageDiv.className = 'log-entry';
|
messageDiv.className = 'log-entry';
|
||||||
|
|
||||||
const directionColor = direction === 'sent' ? '#007bff' : '#28a745';
|
const directionColor = direction === 'sent' ? '#007bff' : '#28a745';
|
||||||
|
|
||||||
|
// Convert newlines to <br> tags for proper HTML display
|
||||||
|
const formattedMessage = message.replace(/\n/g, '<br>');
|
||||||
|
|
||||||
messageDiv.innerHTML = `
|
messageDiv.innerHTML = `
|
||||||
<span class="log-timestamp">${timestamp}</span>
|
<span class="log-timestamp">${timestamp}</span>
|
||||||
<span style="color: ${directionColor}; font-weight: bold;">[${direction.toUpperCase()}]</span>
|
<span style="color: ${directionColor}; font-weight: bold;">[${direction.toUpperCase()}]</span>
|
||||||
${message}
|
<span style="white-space: pre-wrap;">${formattedMessage}</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Remove the "No messages received yet" placeholder if it exists
|
// Remove the "No messages received yet" placeholder if it exists
|
||||||
@@ -3144,6 +3138,83 @@
|
|||||||
// DATABASE STATISTICS FUNCTIONS
|
// DATABASE STATISTICS FUNCTIONS
|
||||||
// ================================
|
// ================================
|
||||||
|
|
||||||
|
// Send restart command to restart the relay using Administrator API
|
||||||
|
async function sendRestartCommand() {
|
||||||
|
if (!isLoggedIn || !userPubkey) {
|
||||||
|
log('Must be logged in to restart relay', 'ERROR');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relayPool) {
|
||||||
|
log('SimplePool connection not available', 'ERROR');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
log('Sending restart command to relay...', 'INFO');
|
||||||
|
|
||||||
|
// Create command array for restart
|
||||||
|
const command_array = ["system_command", "restart"];
|
||||||
|
|
||||||
|
// Encrypt the command array directly using NIP-44
|
||||||
|
const encrypted_content = await encryptForRelay(JSON.stringify(command_array));
|
||||||
|
if (!encrypted_content) {
|
||||||
|
throw new Error('Failed to encrypt command array');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create single kind 23456 admin event
|
||||||
|
const restartEvent = {
|
||||||
|
kind: 23456,
|
||||||
|
pubkey: userPubkey,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
tags: [["p", getRelayPubkey()]],
|
||||||
|
content: encrypted_content
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sign the event
|
||||||
|
const signedEvent = await window.nostr.signEvent(restartEvent);
|
||||||
|
if (!signedEvent || !signedEvent.sig) {
|
||||||
|
throw new Error('Event signing failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish via SimplePool
|
||||||
|
const url = relayConnectionUrl.value.trim();
|
||||||
|
const publishPromises = relayPool.publish([url], signedEvent);
|
||||||
|
|
||||||
|
// Use Promise.allSettled to capture per-relay outcomes
|
||||||
|
const results = await Promise.allSettled(publishPromises);
|
||||||
|
|
||||||
|
// Check if any relay accepted the event
|
||||||
|
let successCount = 0;
|
||||||
|
results.forEach((result, index) => {
|
||||||
|
if (result.status === 'fulfilled') {
|
||||||
|
successCount++;
|
||||||
|
log(`Restart command published successfully to relay ${index}`, 'INFO');
|
||||||
|
} else {
|
||||||
|
log(`Restart command failed on relay ${index}: ${result.reason?.message || result.reason}`, 'ERROR');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (successCount === 0) {
|
||||||
|
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
|
||||||
|
throw new Error(`All relays rejected restart command. Details: ${errorDetails}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
log('Restart command sent successfully - relay should restart shortly...', 'INFO');
|
||||||
|
|
||||||
|
// Update connection status to indicate restart is in progress
|
||||||
|
updateRelayConnectionStatus('connecting');
|
||||||
|
relayConnectionStatus.textContent = 'RESTARTING...';
|
||||||
|
|
||||||
|
// The relay will disconnect and need to be reconnected after restart
|
||||||
|
// This will be handled by the WebSocket disconnection event
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log(`Failed to send restart command: ${error.message}`, 'ERROR');
|
||||||
|
updateRelayConnectionStatus('error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Send stats_query command to get database statistics using Administrator API (inner events)
|
// Send stats_query command to get database statistics using Administrator API (inner events)
|
||||||
async function sendStatsQuery() {
|
async function sendStatsQuery() {
|
||||||
if (!isLoggedIn || !userPubkey) {
|
if (!isLoggedIn || !userPubkey) {
|
||||||
@@ -3300,9 +3371,12 @@
|
|||||||
const events7d = document.getElementById('events-7d');
|
const events7d = document.getElementById('events-7d');
|
||||||
const events30d = document.getElementById('events-30d');
|
const events30d = document.getElementById('events-30d');
|
||||||
|
|
||||||
if (events24h) events24h.textContent = data.events_24h || '-';
|
// Access the nested time_stats object from backend response
|
||||||
if (events7d) events7d.textContent = data.events_7d || '-';
|
const timeStats = data.time_stats || {};
|
||||||
if (events30d) events30d.textContent = data.events_30d || '-';
|
|
||||||
|
if (events24h) events24h.textContent = timeStats.last_24h || '0';
|
||||||
|
if (events7d) events7d.textContent = timeStats.last_7d || '0';
|
||||||
|
if (events30d) events30d.textContent = timeStats.last_30d || '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate top pubkeys table
|
// Populate top pubkeys table
|
||||||
|
|||||||
@@ -17,6 +17,33 @@ print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|||||||
COMMIT_MESSAGE=""
|
COMMIT_MESSAGE=""
|
||||||
RELEASE_MODE=false
|
RELEASE_MODE=false
|
||||||
|
|
||||||
|
show_usage() {
|
||||||
|
echo "C-Relay Build and Push Script"
|
||||||
|
echo ""
|
||||||
|
echo "Usage:"
|
||||||
|
echo " $0 \"commit message\" - Default: compile, increment patch, commit & push"
|
||||||
|
echo " $0 -r \"commit message\" - Release: compile x86+arm64, increment minor, create release"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 \"Fixed event validation bug\""
|
||||||
|
echo " $0 --release \"Major release with new features\""
|
||||||
|
echo ""
|
||||||
|
echo "Default Mode (patch increment):"
|
||||||
|
echo " - Compile C-Relay"
|
||||||
|
echo " - Increment patch version (v1.2.3 → v1.2.4)"
|
||||||
|
echo " - Git add, commit with message, and push"
|
||||||
|
echo ""
|
||||||
|
echo "Release Mode (-r flag):"
|
||||||
|
echo " - Compile C-Relay for x86_64 and arm64 (dynamic and static versions)"
|
||||||
|
echo " - Increment minor version, zero patch (v1.2.3 → v1.3.0)"
|
||||||
|
echo " - Git add, commit, push, and create Gitea release"
|
||||||
|
echo ""
|
||||||
|
echo "Requirements for Release Mode:"
|
||||||
|
echo " - For ARM64 builds: make install-arm64-deps (optional - will build x86_64 only if missing)"
|
||||||
|
echo " - For static builds: sudo apt-get install musl-dev libcap-dev libuv1-dev libev-dev"
|
||||||
|
echo " - Gitea token in ~/.gitea_token for release uploads"
|
||||||
|
}
|
||||||
|
|
||||||
# Parse command line arguments
|
# Parse command line arguments
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
@@ -38,32 +65,6 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
show_usage() {
|
|
||||||
echo "C-Relay Build and Push Script"
|
|
||||||
echo ""
|
|
||||||
echo "Usage:"
|
|
||||||
echo " $0 \"commit message\" - Default: compile, increment patch, commit & push"
|
|
||||||
echo " $0 -r \"commit message\" - Release: compile x86+arm64, increment minor, create release"
|
|
||||||
echo ""
|
|
||||||
echo "Examples:"
|
|
||||||
echo " $0 \"Fixed event validation bug\""
|
|
||||||
echo " $0 --release \"Major release with new features\""
|
|
||||||
echo ""
|
|
||||||
echo "Default Mode (patch increment):"
|
|
||||||
echo " - Compile C-Relay"
|
|
||||||
echo " - Increment patch version (v1.2.3 → v1.2.4)"
|
|
||||||
echo " - Git add, commit with message, and push"
|
|
||||||
echo ""
|
|
||||||
echo "Release Mode (-r flag):"
|
|
||||||
echo " - Compile C-Relay for x86_64 and arm64"
|
|
||||||
echo " - Increment minor version, zero patch (v1.2.3 → v1.3.0)"
|
|
||||||
echo " - Git add, commit, push, and create Gitea release"
|
|
||||||
echo ""
|
|
||||||
echo "Requirements for Release Mode:"
|
|
||||||
echo " - For ARM64 builds: make install-arm64-deps (optional - will build x86_64 only if missing)"
|
|
||||||
echo " - Gitea token in ~/.gitea_token for release uploads"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Validate inputs
|
# Validate inputs
|
||||||
if [[ -z "$COMMIT_MESSAGE" ]]; then
|
if [[ -z "$COMMIT_MESSAGE" ]]; then
|
||||||
print_error "Commit message is required"
|
print_error "Commit message is required"
|
||||||
@@ -190,6 +191,35 @@ build_release_binaries() {
|
|||||||
print_status "Only x86_64 binary will be included in release"
|
print_status "Only x86_64 binary will be included in release"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Build static x86_64 version
|
||||||
|
print_status "Building static x86_64 version..."
|
||||||
|
make clean > /dev/null 2>&1
|
||||||
|
if make static-musl-x86_64 > /dev/null 2>&1; then
|
||||||
|
if [[ -f "build/c_relay_static_musl_x86_64" ]]; then
|
||||||
|
cp build/c_relay_static_musl_x86_64 c-relay-static-x86_64
|
||||||
|
print_success "Static x86_64 binary created: c-relay-static-x86_64"
|
||||||
|
else
|
||||||
|
print_warning "Static x86_64 binary not found after compilation"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_warning "Static x86_64 build failed - MUSL development packages may not be installed"
|
||||||
|
print_status "Run 'sudo apt-get install musl-dev libcap-dev libuv1-dev libev-dev' to enable static builds"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to build static ARM64 version
|
||||||
|
print_status "Attempting static ARM64 build..."
|
||||||
|
make clean > /dev/null 2>&1
|
||||||
|
if make static-musl-arm64 > /dev/null 2>&1; then
|
||||||
|
if [[ -f "build/c_relay_static_musl_arm64" ]]; then
|
||||||
|
cp build/c_relay_static_musl_arm64 c-relay-static-arm64
|
||||||
|
print_success "Static ARM64 binary created: c-relay-static-arm64"
|
||||||
|
else
|
||||||
|
print_warning "Static ARM64 binary not found after compilation"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_warning "Static ARM64 build failed - ARM64 cross-compilation or MUSL ARM64 packages not set up"
|
||||||
|
fi
|
||||||
|
|
||||||
# Restore normal build
|
# Restore normal build
|
||||||
make clean > /dev/null 2>&1
|
make clean > /dev/null 2>&1
|
||||||
make > /dev/null 2>&1
|
make > /dev/null 2>&1
|
||||||
@@ -319,12 +349,18 @@ create_gitea_release() {
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{\"tag_name\": \"$NEW_VERSION\", \"name\": \"$NEW_VERSION\", \"body\": \"$COMMIT_MESSAGE\"}")
|
-d "{\"tag_name\": \"$NEW_VERSION\", \"name\": \"$NEW_VERSION\", \"body\": \"$COMMIT_MESSAGE\"}")
|
||||||
|
|
||||||
|
local upload_result=false
|
||||||
|
|
||||||
if echo "$response" | grep -q '"id"'; then
|
if echo "$response" | grep -q '"id"'; then
|
||||||
print_success "Created release $NEW_VERSION"
|
print_success "Created release $NEW_VERSION"
|
||||||
upload_release_binaries "$api_url" "$token"
|
if upload_release_binaries "$api_url" "$token"; then
|
||||||
|
upload_result=true
|
||||||
|
fi
|
||||||
elif echo "$response" | grep -q "already exists"; then
|
elif echo "$response" | grep -q "already exists"; then
|
||||||
print_warning "Release $NEW_VERSION already exists"
|
print_warning "Release $NEW_VERSION already exists"
|
||||||
upload_release_binaries "$api_url" "$token"
|
if upload_release_binaries "$api_url" "$token"; then
|
||||||
|
upload_result=true
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
print_error "Failed to create release $NEW_VERSION"
|
print_error "Failed to create release $NEW_VERSION"
|
||||||
print_error "Response: $response"
|
print_error "Response: $response"
|
||||||
@@ -334,18 +370,29 @@ create_gitea_release() {
|
|||||||
local check_response=$(curl -s -H "Authorization: token $token" "$api_url/releases/tags/$NEW_VERSION")
|
local check_response=$(curl -s -H "Authorization: token $token" "$api_url/releases/tags/$NEW_VERSION")
|
||||||
if echo "$check_response" | grep -q '"id"'; then
|
if echo "$check_response" | grep -q '"id"'; then
|
||||||
print_warning "Release exists but creation response was unexpected"
|
print_warning "Release exists but creation response was unexpected"
|
||||||
upload_release_binaries "$api_url" "$token"
|
if upload_release_binaries "$api_url" "$token"; then
|
||||||
|
upload_result=true
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
print_error "Release does not exist and creation failed"
|
print_error "Release does not exist and creation failed"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Return based on upload success
|
||||||
|
if [[ "$upload_result" == true ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "Binary upload failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to upload release binaries
|
# Function to upload release binaries
|
||||||
upload_release_binaries() {
|
upload_release_binaries() {
|
||||||
local api_url="$1"
|
local api_url="$1"
|
||||||
local token="$2"
|
local token="$2"
|
||||||
|
local upload_success=true
|
||||||
|
|
||||||
# Get release ID with more robust parsing
|
# Get release ID with more robust parsing
|
||||||
print_status "Getting release ID for $NEW_VERSION..."
|
print_status "Getting release ID for $NEW_VERSION..."
|
||||||
@@ -367,37 +414,131 @@ upload_release_binaries() {
|
|||||||
# Upload x86_64 binary
|
# Upload x86_64 binary
|
||||||
if [[ -f "c-relay-x86_64" ]]; then
|
if [[ -f "c-relay-x86_64" ]]; then
|
||||||
print_status "Uploading x86_64 binary..."
|
print_status "Uploading x86_64 binary..."
|
||||||
if curl -s -X POST "$api_url/releases/$release_id/assets" \
|
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
|
||||||
-H "Authorization: token $token" \
|
-H "Authorization: token $token" \
|
||||||
-F "attachment=@c-relay-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64" > /dev/null; then
|
-F "attachment=@c-relay-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64")
|
||||||
print_success "Uploaded x86_64 binary"
|
|
||||||
|
local http_code=$(echo "$upload_response" | tail -n1)
|
||||||
|
local response_body=$(echo "$upload_response" | head -n -1)
|
||||||
|
|
||||||
|
if [[ "$http_code" == "201" ]]; then
|
||||||
|
print_success "Uploaded x86_64 binary successfully"
|
||||||
else
|
else
|
||||||
print_warning "Failed to upload x86_64 binary"
|
print_error "Failed to upload x86_64 binary (HTTP $http_code)"
|
||||||
|
print_error "Response: $response_body"
|
||||||
|
upload_success=false
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
print_warning "x86_64 binary not found: c-relay-x86_64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Upload ARM64 binary
|
# Upload ARM64 binary
|
||||||
if [[ -f "c-relay-arm64" ]]; then
|
if [[ -f "c-relay-arm64" ]]; then
|
||||||
print_status "Uploading ARM64 binary..."
|
print_status "Uploading ARM64 binary..."
|
||||||
if curl -s -X POST "$api_url/releases/$release_id/assets" \
|
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
|
||||||
-H "Authorization: token $token" \
|
-H "Authorization: token $token" \
|
||||||
-F "attachment=@c-relay-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64" > /dev/null; then
|
-F "attachment=@c-relay-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64")
|
||||||
print_success "Uploaded ARM64 binary"
|
|
||||||
|
local http_code=$(echo "$upload_response" | tail -n1)
|
||||||
|
local response_body=$(echo "$upload_response" | head -n -1)
|
||||||
|
|
||||||
|
if [[ "$http_code" == "201" ]]; then
|
||||||
|
print_success "Uploaded ARM64 binary successfully"
|
||||||
else
|
else
|
||||||
print_warning "Failed to upload ARM64 binary"
|
print_error "Failed to upload ARM64 binary (HTTP $http_code)"
|
||||||
|
print_error "Response: $response_body"
|
||||||
|
upload_success=false
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
print_warning "ARM64 binary not found: c-relay-arm64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload static x86_64 binary
|
||||||
|
if [[ -f "c-relay-static-x86_64" ]]; then
|
||||||
|
print_status "Uploading static x86_64 binary..."
|
||||||
|
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
|
||||||
|
-H "Authorization: token $token" \
|
||||||
|
-F "attachment=@c-relay-static-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64-static")
|
||||||
|
|
||||||
|
local http_code=$(echo "$upload_response" | tail -n1)
|
||||||
|
local response_body=$(echo "$upload_response" | head -n -1)
|
||||||
|
|
||||||
|
if [[ "$http_code" == "201" ]]; then
|
||||||
|
print_success "Uploaded static x86_64 binary successfully"
|
||||||
|
else
|
||||||
|
print_error "Failed to upload static x86_64 binary (HTTP $http_code)"
|
||||||
|
print_error "Response: $response_body"
|
||||||
|
upload_success=false
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_warning "Static x86_64 binary not found: c-relay-static-x86_64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload static ARM64 binary
|
||||||
|
if [[ -f "c-relay-static-arm64" ]]; then
|
||||||
|
print_status "Uploading static ARM64 binary..."
|
||||||
|
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
|
||||||
|
-H "Authorization: token $token" \
|
||||||
|
-F "attachment=@c-relay-static-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64-static")
|
||||||
|
|
||||||
|
local http_code=$(echo "$upload_response" | tail -n1)
|
||||||
|
local response_body=$(echo "$upload_response" | head -n -1)
|
||||||
|
|
||||||
|
if [[ "$http_code" == "201" ]]; then
|
||||||
|
print_success "Uploaded static ARM64 binary successfully"
|
||||||
|
else
|
||||||
|
print_error "Failed to upload static ARM64 binary (HTTP $http_code)"
|
||||||
|
print_error "Response: $response_body"
|
||||||
|
upload_success=false
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_warning "Static ARM64 binary not found: c-relay-static-arm64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Return success/failure status
|
||||||
|
if [[ "$upload_success" == true ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to clean up release binaries
|
# Function to clean up release binaries
|
||||||
cleanup_release_binaries() {
|
cleanup_release_binaries() {
|
||||||
if [[ -f "c-relay-x86_64" ]]; then
|
local force_cleanup="$1" # Optional parameter to force cleanup even on failure
|
||||||
rm -f c-relay-x86_64
|
|
||||||
print_status "Cleaned up x86_64 binary"
|
if [[ "$force_cleanup" == "force" ]] || [[ "$upload_success" == true ]]; then
|
||||||
fi
|
if [[ -f "c-relay-x86_64" ]]; then
|
||||||
if [[ -f "c-relay-arm64" ]]; then
|
rm -f c-relay-x86_64
|
||||||
rm -f c-relay-arm64
|
print_status "Cleaned up x86_64 binary"
|
||||||
print_status "Cleaned up ARM64 binary"
|
fi
|
||||||
|
if [[ -f "c-relay-arm64" ]]; then
|
||||||
|
rm -f c-relay-arm64
|
||||||
|
print_status "Cleaned up ARM64 binary"
|
||||||
|
fi
|
||||||
|
if [[ -f "c-relay-static-x86_64" ]]; then
|
||||||
|
rm -f c-relay-static-x86_64
|
||||||
|
print_status "Cleaned up static x86_64 binary"
|
||||||
|
fi
|
||||||
|
if [[ -f "c-relay-static-arm64" ]]; then
|
||||||
|
rm -f c-relay-static-arm64
|
||||||
|
print_status "Cleaned up static ARM64 binary"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_warning "Keeping binary files due to upload failures"
|
||||||
|
print_status "Files available for manual upload:"
|
||||||
|
if [[ -f "c-relay-x86_64" ]]; then
|
||||||
|
print_status " - c-relay-x86_64"
|
||||||
|
fi
|
||||||
|
if [[ -f "c-relay-arm64" ]]; then
|
||||||
|
print_status " - c-relay-arm64"
|
||||||
|
fi
|
||||||
|
if [[ -f "c-relay-static-x86_64" ]]; then
|
||||||
|
print_status " - c-relay-static-x86_64"
|
||||||
|
fi
|
||||||
|
if [[ -f "c-relay-static-arm64" ]]; then
|
||||||
|
print_status " - c-relay-static-arm64"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,14 +574,18 @@ main() {
|
|||||||
git_commit_and_push_no_tag
|
git_commit_and_push_no_tag
|
||||||
|
|
||||||
# Create Gitea release with binaries
|
# Create Gitea release with binaries
|
||||||
create_gitea_release
|
if create_gitea_release; then
|
||||||
|
print_success "Release $NEW_VERSION completed successfully!"
|
||||||
|
print_status "Binaries uploaded to Gitea release"
|
||||||
|
upload_success=true
|
||||||
|
else
|
||||||
|
print_error "Release creation or binary upload failed"
|
||||||
|
upload_success=false
|
||||||
|
fi
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup (only if upload was successful)
|
||||||
cleanup_release_binaries
|
cleanup_release_binaries
|
||||||
|
|
||||||
print_success "Release $NEW_VERSION completed successfully!"
|
|
||||||
print_status "Binaries uploaded to Gitea release"
|
|
||||||
|
|
||||||
else
|
else
|
||||||
print_status "=== DEFAULT MODE ==="
|
print_status "=== DEFAULT MODE ==="
|
||||||
|
|
||||||
|
|||||||
146
build_static.sh
Executable file
146
build_static.sh
Executable file
@@ -0,0 +1,146 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Build fully static MUSL binaries for C-Relay
|
||||||
|
# Produces portable binaries with zero runtime dependencies
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
BUILD_DIR="$SCRIPT_DIR/build"
|
||||||
|
|
||||||
|
echo "Building fully static MUSL binaries for C-Relay..."
|
||||||
|
echo "Project directory: $SCRIPT_DIR"
|
||||||
|
echo "Build directory: $BUILD_DIR"
|
||||||
|
|
||||||
|
# Create build directory
|
||||||
|
mkdir -p "$BUILD_DIR"
|
||||||
|
|
||||||
|
# Check if Docker is available first
|
||||||
|
if command -v docker &> /dev/null && sudo docker buildx version &> /dev/null 2>&1; then
|
||||||
|
echo "Docker available but Alpine repositories are having issues - using native build"
|
||||||
|
USE_DOCKER=false
|
||||||
|
else
|
||||||
|
echo "Docker not available - attempting native MUSL build"
|
||||||
|
USE_DOCKER=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if musl-gcc is available for native build
|
||||||
|
if [ "$USE_DOCKER" = false ]; then
|
||||||
|
if ! command -v musl-gcc &> /dev/null; then
|
||||||
|
echo "Installing musl development tools..."
|
||||||
|
sudo apt update && sudo apt install -y musl-dev musl-tools
|
||||||
|
|
||||||
|
if ! command -v musl-gcc &> /dev/null; then
|
||||||
|
echo "ERROR: Failed to install musl-gcc"
|
||||||
|
echo "Please install musl-dev package manually: sudo apt install musl-dev musl-tools"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$USE_DOCKER" = true ]; then
|
||||||
|
# Docker-based build
|
||||||
|
echo "Building x86_64 static binary with Docker..."
|
||||||
|
sudo docker buildx build \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
-f "$SCRIPT_DIR/examples/deployment/static-builder.Dockerfile" \
|
||||||
|
-t c-relay-static-builder-x86_64 \
|
||||||
|
--load \
|
||||||
|
"$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# Extract x86_64 binary
|
||||||
|
sudo docker run --rm -v "$BUILD_DIR:/output" c-relay-static-builder-x86_64 \
|
||||||
|
sh -c "cp /c_relay_static_musl_x86_64 /output/"
|
||||||
|
|
||||||
|
echo "x86_64 static binary created: $BUILD_DIR/c_relay_static_musl_x86_64"
|
||||||
|
|
||||||
|
# Build ARM64 static binary
|
||||||
|
echo "Building ARM64 static binary with Docker..."
|
||||||
|
sudo docker buildx build \
|
||||||
|
--platform linux/arm64 \
|
||||||
|
-f "$SCRIPT_DIR/examples/deployment/static-builder.Dockerfile" \
|
||||||
|
-t c-relay-static-builder-arm64 \
|
||||||
|
--load \
|
||||||
|
"$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# Extract ARM64 binary
|
||||||
|
sudo docker run --rm -v "$BUILD_DIR:/output" c-relay-static-builder-arm64 \
|
||||||
|
sh -c "cp /c_relay_static_musl_x86_64 /output/c_relay_static_musl_arm64"
|
||||||
|
|
||||||
|
echo "ARM64 static binary created: $BUILD_DIR/c_relay_static_musl_arm64"
|
||||||
|
else
|
||||||
|
# Native static build with regular gcc
|
||||||
|
echo "Building static binary with gcc..."
|
||||||
|
|
||||||
|
# Check for required static libraries
|
||||||
|
echo "Checking for static libraries..."
|
||||||
|
MISSING_LIBS=""
|
||||||
|
|
||||||
|
for lib in libsqlite3.a libssl.a libcrypto.a libz.a; do
|
||||||
|
if ! find /usr/lib* /usr/local/lib* -name "$lib" 2>/dev/null | head -1 | grep -q .; then
|
||||||
|
MISSING_LIBS="$MISSING_LIBS $lib"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# libsecp256k1 might not be available as static lib, so we'll try without it first
|
||||||
|
|
||||||
|
# Initialize submodules if needed
|
||||||
|
if [ ! -f "nostr_core_lib/libnostr_core_x64.a" ]; then
|
||||||
|
echo "Building nostr_core_lib..."
|
||||||
|
git submodule update --init --recursive
|
||||||
|
cd nostr_core_lib && ./build.sh && cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install additional static libraries needed for libwebsockets
|
||||||
|
echo "Installing additional static libraries..."
|
||||||
|
sudo apt install -y libcap-dev libuv1-dev libev-dev
|
||||||
|
|
||||||
|
# Try building with regular gcc and static linking
|
||||||
|
echo "Compiling with gcc -static..."
|
||||||
|
|
||||||
|
# Use the same approach as the regular Makefile but with static linking
|
||||||
|
gcc -static -O2 -Wall -Wextra -std=c99 -g \
|
||||||
|
-I. -Inostr_core_lib -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
|
||||||
|
src/main.c src/config.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c \
|
||||||
|
-o "$BUILD_DIR/c_relay_static_x86_64" \
|
||||||
|
nostr_core_lib/libnostr_core_x64.a \
|
||||||
|
-lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k1 -lssl -lcrypto -L/usr/local/lib -lcurl -lcap -luv_a -lev
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "x86_64 static binary created: $BUILD_DIR/c_relay_static_x86_64"
|
||||||
|
# Also create the musl-named version for compatibility
|
||||||
|
cp "$BUILD_DIR/c_relay_static_x86_64" "$BUILD_DIR/c_relay_static_musl_x86_64"
|
||||||
|
else
|
||||||
|
echo "ERROR: Static build failed"
|
||||||
|
echo "This may be due to missing static libraries or incompatible library versions"
|
||||||
|
echo "Consider using Docker-based build instead"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify binaries
|
||||||
|
echo "Verifying static binaries..."
|
||||||
|
for binary in "$BUILD_DIR"/c_relay_static_musl_*; do
|
||||||
|
if [ -f "$binary" ]; then
|
||||||
|
echo "Binary: $(basename "$binary")"
|
||||||
|
file "$binary"
|
||||||
|
ls -lh "$binary"
|
||||||
|
|
||||||
|
# Test if binary is truly static (no dynamic dependencies)
|
||||||
|
if ldd "$binary" 2>/dev/null | grep -q "not a dynamic executable"; then
|
||||||
|
echo "✓ Binary is fully static"
|
||||||
|
elif ldd "$binary" 2>/dev/null | grep -q "statically linked"; then
|
||||||
|
echo "✓ Binary is statically linked"
|
||||||
|
else
|
||||||
|
echo "⚠ Binary may have dynamic dependencies:"
|
||||||
|
ldd "$binary" 2>/dev/null || echo " (ldd check failed)"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Static build complete!"
|
||||||
|
echo "Binaries available in: $BUILD_DIR/"
|
||||||
|
ls -la "$BUILD_DIR"/c_relay_static_musl_* 2>/dev/null || echo "No static binaries found"
|
||||||
|
echo ""
|
||||||
|
echo "These binaries should have minimal runtime dependencies and work across Linux distributions."
|
||||||
136
examples/deployment/static-builder.Dockerfile
Normal file
136
examples/deployment/static-builder.Dockerfile
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# MUSL-based fully static C-Relay builder
|
||||||
|
# Produces portable binaries with zero runtime dependencies
|
||||||
|
|
||||||
|
FROM alpine:latest AS builder
|
||||||
|
|
||||||
|
# Add alternative mirrors and install build dependencies with retry
|
||||||
|
RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.22/main" > /etc/apk/repositories && \
|
||||||
|
echo "http://dl-cdn.alpinelinux.org/alpine/v3.22/community" >> /etc/apk/repositories && \
|
||||||
|
echo "http://mirror.leaseweb.com/alpine/v3.22/main" >> /etc/apk/repositories && \
|
||||||
|
echo "http://mirror.leaseweb.com/alpine/v3.22/community" >> /etc/apk/repositories && \
|
||||||
|
apk update --no-cache || (sleep 5 && apk update --no-cache) || (sleep 10 && apk update --no-cache)
|
||||||
|
|
||||||
|
# Install build dependencies with retry logic
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
build-base \
|
||||||
|
musl-dev \
|
||||||
|
git \
|
||||||
|
cmake \
|
||||||
|
pkgconfig \
|
||||||
|
autoconf \
|
||||||
|
automake \
|
||||||
|
libtool \
|
||||||
|
openssl-dev \
|
||||||
|
openssl-libs-static \
|
||||||
|
zlib-dev \
|
||||||
|
zlib-static \
|
||||||
|
curl-dev \
|
||||||
|
curl-static \
|
||||||
|
sqlite-dev \
|
||||||
|
sqlite-static \
|
||||||
|
linux-headers || \
|
||||||
|
(sleep 10 && apk add --no-cache \
|
||||||
|
build-base \
|
||||||
|
musl-dev \
|
||||||
|
git \
|
||||||
|
cmake \
|
||||||
|
pkgconfig \
|
||||||
|
autoconf \
|
||||||
|
automake \
|
||||||
|
libtool \
|
||||||
|
openssl-dev \
|
||||||
|
openssl-libs-static \
|
||||||
|
zlib-dev \
|
||||||
|
zlib-static \
|
||||||
|
curl-dev \
|
||||||
|
curl-static \
|
||||||
|
sqlite-dev \
|
||||||
|
sqlite-static \
|
||||||
|
linux-headers)
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Build zlib static (if needed)
|
||||||
|
RUN if [ ! -f /usr/lib/libz.a ]; then \
|
||||||
|
cd /tmp && \
|
||||||
|
wget https://zlib.net/zlib-1.3.1.tar.gz && \
|
||||||
|
tar xzf zlib-1.3.1.tar.gz && \
|
||||||
|
cd zlib-1.3.1 && \
|
||||||
|
./configure --static --prefix=/usr && \
|
||||||
|
make && make install; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build OpenSSL static
|
||||||
|
RUN cd /tmp && \
|
||||||
|
wget https://www.openssl.org/source/openssl-3.0.13.tar.gz && \
|
||||||
|
tar xzf openssl-3.0.13.tar.gz && \
|
||||||
|
cd openssl-3.0.13 && \
|
||||||
|
./Configure linux-x86_64 no-shared --prefix=/usr && \
|
||||||
|
make && make install_sw
|
||||||
|
|
||||||
|
# Build libsecp256k1 static
|
||||||
|
RUN cd /tmp && \
|
||||||
|
git clone https://github.com/bitcoin-core/secp256k1.git && \
|
||||||
|
cd secp256k1 && \
|
||||||
|
./autogen.sh && \
|
||||||
|
./configure --enable-static --disable-shared --prefix=/usr && \
|
||||||
|
make && make install
|
||||||
|
|
||||||
|
# Build libwebsockets static with OpenSSL
|
||||||
|
RUN cd /tmp && \
|
||||||
|
git clone https://github.com/warmcat/libwebsockets.git && \
|
||||||
|
cd libwebsockets && \
|
||||||
|
mkdir build && cd build && \
|
||||||
|
cmake .. \
|
||||||
|
-DLWS_WITH_STATIC=ON \
|
||||||
|
-DLWS_WITH_SHARED=OFF \
|
||||||
|
-DLWS_WITH_SSL=ON \
|
||||||
|
-DLWS_OPENSSL_LIBRARIES="/usr/lib/libssl.a;/usr/lib/libcrypto.a" \
|
||||||
|
-DLWS_OPENSSL_INCLUDE_DIRS="/usr/include" \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=/usr && \
|
||||||
|
make && make install
|
||||||
|
|
||||||
|
# Build curl static (minimal features)
|
||||||
|
RUN cd /tmp && \
|
||||||
|
wget https://curl.se/download/curl-8.6.0.tar.gz && \
|
||||||
|
tar xzf curl-8.6.0.tar.gz && \
|
||||||
|
cd curl-8.6.0 && \
|
||||||
|
./configure \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static \
|
||||||
|
--disable-ldap \
|
||||||
|
--without-libidn2 \
|
||||||
|
--without-brotli \
|
||||||
|
--without-zstd \
|
||||||
|
--without-rtmp \
|
||||||
|
--without-libpsl \
|
||||||
|
--without-krb5 \
|
||||||
|
--with-openssl \
|
||||||
|
--prefix=/usr && \
|
||||||
|
make && make install
|
||||||
|
|
||||||
|
# Copy c-relay source
|
||||||
|
COPY . /build/
|
||||||
|
|
||||||
|
# Initialize submodules
|
||||||
|
RUN git submodule update --init --recursive
|
||||||
|
|
||||||
|
# Build nostr_core_lib
|
||||||
|
RUN cd nostr_core_lib && ./build.sh
|
||||||
|
|
||||||
|
# Build c-relay static
|
||||||
|
RUN make clean && \
|
||||||
|
CC="musl-gcc -static" \
|
||||||
|
CFLAGS="-O2 -Wall -Wextra -std=c99 -g" \
|
||||||
|
LDFLAGS="-static -Wl,--whole-archive -lpthread -Wl,--no-whole-archive" \
|
||||||
|
LIBS="-lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -lsecp256k1 -lssl -lcrypto -lcurl" \
|
||||||
|
make
|
||||||
|
|
||||||
|
# Strip binary for size
|
||||||
|
RUN strip build/c_relay_x86
|
||||||
|
|
||||||
|
# Multi-stage build to produce minimal output
|
||||||
|
FROM scratch AS output
|
||||||
|
COPY --from=builder /build/build/c_relay_x86 /c_relay_static_musl_x86_64
|
||||||
63
src/config.c
63
src/config.c
@@ -16,6 +16,9 @@
|
|||||||
// External database connection (from main.c)
|
// External database connection (from main.c)
|
||||||
extern sqlite3* g_db;
|
extern sqlite3* g_db;
|
||||||
|
|
||||||
|
// External shutdown flag (from main.c)
|
||||||
|
extern volatile sig_atomic_t g_shutdown_flag;
|
||||||
|
|
||||||
// Global unified configuration cache instance
|
// Global unified configuration cache instance
|
||||||
unified_config_cache_t g_unified_cache = {
|
unified_config_cache_t g_unified_cache = {
|
||||||
.cache_lock = PTHREAD_MUTEX_INITIALIZER,
|
.cache_lock = PTHREAD_MUTEX_INITIALIZER,
|
||||||
@@ -3673,19 +3676,19 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
|
|||||||
cJSON* response = cJSON_CreateObject();
|
cJSON* response = cJSON_CreateObject();
|
||||||
cJSON_AddStringToObject(response, "command", "system_status");
|
cJSON_AddStringToObject(response, "command", "system_status");
|
||||||
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
||||||
|
|
||||||
cJSON* status_data = cJSON_CreateObject();
|
cJSON* status_data = cJSON_CreateObject();
|
||||||
cJSON_AddStringToObject(status_data, "database", g_db ? "connected" : "not_available");
|
cJSON_AddStringToObject(status_data, "database", g_db ? "connected" : "not_available");
|
||||||
cJSON_AddStringToObject(status_data, "cache_status", g_unified_cache.cache_valid ? "valid" : "invalid");
|
cJSON_AddStringToObject(status_data, "cache_status", g_unified_cache.cache_valid ? "valid" : "invalid");
|
||||||
|
|
||||||
if (strlen(g_database_path) > 0) {
|
if (strlen(g_database_path) > 0) {
|
||||||
cJSON_AddStringToObject(status_data, "database_path", g_database_path);
|
cJSON_AddStringToObject(status_data, "database_path", g_database_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count configuration items and auth rules
|
// Count configuration items and auth rules
|
||||||
if (g_db) {
|
if (g_db) {
|
||||||
sqlite3_stmt* stmt;
|
sqlite3_stmt* stmt;
|
||||||
|
|
||||||
// Config count
|
// Config count
|
||||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
@@ -3693,7 +3696,7 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
|
|||||||
}
|
}
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth rules count
|
// Auth rules count
|
||||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM auth_rules", -1, &stmt, NULL) == SQLITE_OK) {
|
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM auth_rules", -1, &stmt, NULL) == SQLITE_OK) {
|
||||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
@@ -3702,34 +3705,72 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
|
|||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON_AddItemToObject(response, "data", status_data);
|
cJSON_AddItemToObject(response, "data", status_data);
|
||||||
|
|
||||||
printf("=== System Status ===\n");
|
printf("=== System Status ===\n");
|
||||||
printf("Database: %s\n", g_db ? "Connected" : "Not available");
|
printf("Database: %s\n", g_db ? "Connected" : "Not available");
|
||||||
printf("Cache status: %s\n", g_unified_cache.cache_valid ? "Valid" : "Invalid");
|
printf("Cache status: %s\n", g_unified_cache.cache_valid ? "Valid" : "Invalid");
|
||||||
|
|
||||||
// Get admin pubkey from event for response
|
// Get admin pubkey from event for response
|
||||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||||
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||||
|
|
||||||
if (!admin_pubkey) {
|
if (!admin_pubkey) {
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
snprintf(error_message, error_size, "missing admin pubkey for response");
|
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send response as signed kind 23457 event
|
// Send response as signed kind 23457 event
|
||||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||||
log_success("System status query completed successfully with signed response");
|
log_success("System status query completed successfully with signed response");
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
snprintf(error_message, error_size, "failed to send system status response");
|
snprintf(error_message, error_size, "failed to send system status response");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
else if (strcmp(command, "restart") == 0) {
|
||||||
|
// Build restart acknowledgment response
|
||||||
|
cJSON* response = cJSON_CreateObject();
|
||||||
|
cJSON_AddStringToObject(response, "command", "restart");
|
||||||
|
cJSON_AddStringToObject(response, "status", "initiating_restart");
|
||||||
|
cJSON_AddStringToObject(response, "message", "Relay restart initiated - shutting down gracefully");
|
||||||
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
||||||
|
|
||||||
|
printf("=== Relay Restart Initiated ===\n");
|
||||||
|
printf("Admin requested system restart\n");
|
||||||
|
printf("Sending acknowledgment and initiating shutdown...\n");
|
||||||
|
|
||||||
|
// Get admin pubkey from event for response
|
||||||
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||||
|
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||||
|
|
||||||
|
if (!admin_pubkey) {
|
||||||
|
cJSON_Delete(response);
|
||||||
|
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send acknowledgment response as signed kind 23457 event
|
||||||
|
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||||
|
log_success("Restart acknowledgment sent successfully - initiating shutdown");
|
||||||
|
|
||||||
|
// Trigger graceful shutdown by setting the global shutdown flag
|
||||||
|
g_shutdown_flag = 1;
|
||||||
|
log_info("Shutdown flag set - relay will restart gracefully");
|
||||||
|
|
||||||
|
cJSON_Delete(response);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(response);
|
||||||
|
snprintf(error_message, error_size, "failed to send restart acknowledgment");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
snprintf(error_message, error_size, "invalid: unknown system command '%s'", command);
|
snprintf(error_message, error_size, "invalid: unknown system command '%s'", command);
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
1513
src/dm_admin.c
1513
src/dm_admin.c
File diff suppressed because it is too large
Load Diff
@@ -18,4 +18,10 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
|||||||
int is_nip17_gift_wrap_for_relay(cJSON* gift_wrap_event);
|
int is_nip17_gift_wrap_for_relay(cJSON* gift_wrap_event);
|
||||||
char* generate_stats_json(void);
|
char* generate_stats_json(void);
|
||||||
|
|
||||||
|
// Unified NIP-17 response functions
|
||||||
|
int send_nip17_response(const char* sender_pubkey, const char* response_content,
|
||||||
|
char* error_message, size_t error_size);
|
||||||
|
char* generate_config_text(void);
|
||||||
|
char* generate_stats_text(void);
|
||||||
|
|
||||||
#endif // DM_ADMIN_H
|
#endif // DM_ADMIN_H
|
||||||
File diff suppressed because one or more lines are too long
@@ -45,6 +45,8 @@ int nostr_nip42_verify_auth_event(cJSON *event, const char *challenge_id,
|
|||||||
// Global state
|
// Global state
|
||||||
sqlite3* g_db = NULL; // Non-static so config.c can access it
|
sqlite3* g_db = NULL; // Non-static so config.c can access it
|
||||||
int g_server_running = 1; // Non-static so websockets.c can access it
|
int g_server_running = 1; // Non-static so websockets.c can access it
|
||||||
|
volatile sig_atomic_t g_shutdown_flag = 0; // Non-static so config.c can access it for restart functionality
|
||||||
|
int g_restart_requested = 0; // Non-static so config.c can access it for restart functionality
|
||||||
struct lws_context *ws_context = NULL; // Non-static so websockets.c can access it
|
struct lws_context *ws_context = NULL; // Non-static so websockets.c can access it
|
||||||
|
|
||||||
// NIP-11 relay information structure
|
// NIP-11 relay information structure
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ extern unified_config_cache_t g_unified_cache;
|
|||||||
// Forward declarations for global state
|
// Forward declarations for global state
|
||||||
extern sqlite3* g_db;
|
extern sqlite3* g_db;
|
||||||
extern int g_server_running;
|
extern int g_server_running;
|
||||||
|
extern volatile sig_atomic_t g_shutdown_flag;
|
||||||
|
extern int g_restart_requested;
|
||||||
extern struct lws_context *ws_context;
|
extern struct lws_context *ws_context;
|
||||||
|
|
||||||
// Global subscription manager
|
// Global subscription manager
|
||||||
@@ -644,17 +646,32 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
cJSON* response_event = process_nip17_admin_message(event, nip17_error, sizeof(nip17_error), wsi);
|
cJSON* response_event = process_nip17_admin_message(event, nip17_error, sizeof(nip17_error), wsi);
|
||||||
|
|
||||||
if (!response_event) {
|
if (!response_event) {
|
||||||
log_error("DEBUG NIP17: NIP-17 admin message processing failed");
|
// Check if this is an error or if the command was already handled
|
||||||
result = -1;
|
if (strlen(nip17_error) > 0) {
|
||||||
size_t error_len = strlen(nip17_error);
|
// There was an actual error
|
||||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
log_error("DEBUG NIP17: NIP-17 admin message processing failed");
|
||||||
memcpy(error_message, nip17_error, copy_len);
|
result = -1;
|
||||||
error_message[copy_len] = '\0';
|
size_t error_len = strlen(nip17_error);
|
||||||
|
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||||
|
memcpy(error_message, nip17_error, copy_len);
|
||||||
|
error_message[copy_len] = '\0';
|
||||||
|
|
||||||
char debug_nip17_error_msg[600];
|
char debug_nip17_error_msg[600];
|
||||||
snprintf(debug_nip17_error_msg, sizeof(debug_nip17_error_msg),
|
snprintf(debug_nip17_error_msg, sizeof(debug_nip17_error_msg),
|
||||||
"DEBUG NIP17 ERROR: %.400s", nip17_error);
|
"DEBUG NIP17 ERROR: %.400s", nip17_error);
|
||||||
log_error(debug_nip17_error_msg);
|
log_error(debug_nip17_error_msg);
|
||||||
|
} else {
|
||||||
|
// No error message means the command was already handled (plain text commands)
|
||||||
|
log_success("DEBUG NIP17: NIP-17 admin message processed successfully (already handled)");
|
||||||
|
// Store the original gift wrap event in database
|
||||||
|
if (store_event(event) != 0) {
|
||||||
|
log_error("DEBUG NIP17: Failed to store gift wrap event in database");
|
||||||
|
result = -1;
|
||||||
|
strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1);
|
||||||
|
} else {
|
||||||
|
log_info("DEBUG NIP17: Gift wrap event stored successfully in database");
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log_success("DEBUG NIP17: NIP-17 admin message processed successfully");
|
log_success("DEBUG NIP17: NIP-17 admin message processed successfully");
|
||||||
// Store the original gift wrap event in database (unlike kind 23456)
|
// Store the original gift wrap event in database (unlike kind 23456)
|
||||||
@@ -1138,7 +1155,7 @@ int start_websocket_relay(int port_override, int strict_port) {
|
|||||||
log_success(startup_msg);
|
log_success(startup_msg);
|
||||||
|
|
||||||
// Main event loop with proper signal handling
|
// Main event loop with proper signal handling
|
||||||
while (g_server_running) {
|
while (g_server_running && !g_shutdown_flag) {
|
||||||
int result = lws_service(ws_context, 1000);
|
int result = lws_service(ws_context, 1000);
|
||||||
|
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user