Add Tor hidden service support and fallback relay profile fetching (v0.46.0)
Some checks failed
Go / build-and-release (push) Has been cancelled

- Add pkg/tor package for Tor hidden service integration
- Add Tor config options: ORLY_TOR_ENABLED, ORLY_TOR_PORT, ORLY_TOR_HS_DIR, ORLY_TOR_ONION_ADDRESS
- Extend NIP-11 relay info with addresses field for .onion URLs
- Add fallback relays (Damus, nos.lol, nostr.band, purplepag.es) for profile lookups
- Refactor profile fetching to try local relay first, then fallback relays
- Add Tor setup documentation and deployment scripts

Files modified:
- app/config/config.go: Add Tor configuration options
- app/handle-relayinfo.go: Add ExtendedRelayInfo with addresses field
- app/main.go: Initialize and manage Tor service lifecycle
- app/server.go: Add torService field to Server struct
- app/web/src/constants.js: Add FALLBACK_RELAYS
- app/web/src/nostr.js: Add fallback relay profile fetching
- pkg/tor/: New package for Tor hidden service management
- docs/TOR_SETUP.md: Documentation for Tor configuration
- deploy/orly-tor.service: Systemd service for Tor integration
- scripts/tor-*.sh: Setup scripts for Tor development and production

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
woikos
2026-01-03 05:50:03 +01:00
parent 6056446a73
commit 25d087697e
16 changed files with 1293 additions and 58 deletions

View File

@@ -7,6 +7,14 @@ export const DEFAULT_RELAYS = [
`${wsProtocol}//${window.location.host}/`,
];
// Fallback relays for profile lookups when local relay doesn't have the data
export const FALLBACK_RELAYS = [
'wss://relay.damus.io',
'wss://nos.lol',
'wss://relay.nostr.band',
'wss://purplepag.es',
];
// Replaceable kinds for the recovery dropdown
// Based on NIP-01: kinds 0, 3, and 10000-19999 are replaceable
// kinds 30000-39999 are addressable (parameterized replaceable)

View File

@@ -1,7 +1,7 @@
import { SimplePool } from 'nostr-tools/pool';
import { EventStore } from 'applesauce-core';
import { PrivateKeySigner } from 'applesauce-signers';
import { DEFAULT_RELAYS } from "./constants.js";
import { DEFAULT_RELAYS, FALLBACK_RELAYS } from "./constants.js";
// Nostr client wrapper using nostr-tools
class NostrClient {
@@ -450,65 +450,115 @@ export async function fetchUserProfile(pubkey) {
console.warn("Failed to load cached profile", e);
}
// 2) Fetch profile from relays
const filters = [{
kinds: [0],
authors: [pubkey],
limit: 1
}];
// 2) Fetch profile from local relay first
try {
const filters = [{
kinds: [0],
authors: [pubkey],
limit: 1
}];
const events = await fetchEvents(filters, { timeout: 10000 });
if (events.length > 0) {
const profileEvent = events[0];
console.log("Profile fetched:", profileEvent);
// Cache the event
await putEvent(profileEvent);
// Publish the profile event to the local relay
try {
console.log("Publishing profile event to local relay:", profileEvent.id);
await nostrClient.publish(profileEvent);
console.log("Profile event successfully saved to local relay");
} catch (publishError) {
console.warn("Failed to publish profile to local relay:", publishError);
// Don't fail the whole operation if publishing fails
}
// Parse profile data
const profile = parseProfileFromEvent(profileEvent);
// Notify listeners that an updated profile is available
try {
if (typeof window !== "undefined" && window.dispatchEvent) {
window.dispatchEvent(
new CustomEvent("profile-updated", {
detail: { pubkey, profile, event: profileEvent },
}),
);
}
} catch (e) {
console.warn("Failed to dispatch profile-updated event", e);
}
return profile;
} else {
// No profile found - create a default profile for new users
console.log("No profile found for pubkey, creating default:", pubkey);
return await createDefaultProfile(pubkey);
console.log("Profile fetched from local relay:", profileEvent);
return processProfileEvent(profileEvent, pubkey);
}
} catch (error) {
console.error("Failed to fetch profile:", error);
// Try to create default profile on error too
try {
return await createDefaultProfile(pubkey);
} catch (e) {
console.error("Failed to create default profile:", e);
return null;
}
console.warn("Failed to fetch profile from local relay:", error);
}
// 3) Try fallback relays if local relay doesn't have the profile
console.log("Profile not found on local relay, trying fallback relays:", FALLBACK_RELAYS);
try {
const profileEvent = await fetchProfileFromFallbackRelays(pubkey, filters);
if (profileEvent) {
return processProfileEvent(profileEvent, pubkey);
}
} catch (error) {
console.warn("Failed to fetch profile from fallback relays:", error);
}
// 4) No profile found anywhere - create a default profile for new users
console.log("No profile found for pubkey, creating default:", pubkey);
try {
return await createDefaultProfile(pubkey);
} catch (e) {
console.error("Failed to create default profile:", e);
return null;
}
}
// Helper to fetch profile from fallback relays
async function fetchProfileFromFallbackRelays(pubkey, filters) {
return new Promise((resolve) => {
const events = [];
const timeoutId = setTimeout(() => {
sub.close();
// Return the most recent profile event
if (events.length > 0) {
events.sort((a, b) => b.created_at - a.created_at);
resolve(events[0]);
} else {
resolve(null);
}
}, 5000);
const sub = nostrClient.pool.subscribeMany(
FALLBACK_RELAYS,
filters,
{
onevent(event) {
console.log("Profile event received from fallback relay:", event.id?.substring(0, 8));
events.push(event);
},
oneose() {
clearTimeout(timeoutId);
sub.close();
if (events.length > 0) {
events.sort((a, b) => b.created_at - a.created_at);
resolve(events[0]);
} else {
resolve(null);
}
}
}
);
});
}
// Helper to process and cache a profile event
async function processProfileEvent(profileEvent, pubkey) {
// Cache the event
await putEvent(profileEvent);
// Publish the profile event to the local relay
try {
console.log("Publishing profile event to local relay:", profileEvent.id);
await nostrClient.publish(profileEvent);
console.log("Profile event successfully saved to local relay");
} catch (publishError) {
console.warn("Failed to publish profile to local relay:", publishError);
}
// Parse profile data
const profile = parseProfileFromEvent(profileEvent);
// Notify listeners that an updated profile is available
try {
if (typeof window !== "undefined" && window.dispatchEvent) {
window.dispatchEvent(
new CustomEvent("profile-updated", {
detail: { pubkey, profile, event: profileEvent },
}),
);
}
} catch (e) {
console.warn("Failed to dispatch profile-updated event", e);
}
return profile;
}
/**