diff --git a/core/src/api/mod.rs b/core/src/api/mod.rs
index aba4cdcfb..cf55c0ce4 100644
--- a/core/src/api/mod.rs
+++ b/core/src/api/mod.rs
@@ -85,7 +85,6 @@ pub struct SanitisedNodeConfig {
pub name: String,
pub identity: RemoteIdentity,
pub p2p: NodeConfigP2P,
- pub p2p_discovery: P2PDiscoveryState,
pub features: Vec,
pub preferences: NodePreferences,
pub image_labeler_version: Option,
@@ -98,7 +97,6 @@ impl From for SanitisedNodeConfig {
name: value.name,
identity: value.identity.to_remote_identity(),
p2p: value.p2p,
- p2p_discovery: value.p2p_discovery,
features: value.features,
preferences: value.preferences,
image_labeler_version: value.image_labeler_version,
diff --git a/core/src/api/nodes.rs b/core/src/api/nodes.rs
index dd013a2fc..b5be20f3d 100644
--- a/core/src/api/nodes.rs
+++ b/core/src/api/nodes.rs
@@ -23,6 +23,7 @@ pub(crate) fn mount() -> AlphaRouter {
pub p2p_ipv4_enabled: Option,
pub p2p_ipv6_enabled: Option,
pub p2p_discovery: Option,
+ pub p2p_remote_access: Option,
pub image_labeler_version: Option,
}
R.mutation(|node, args: ChangeNodeNameArgs| async move {
@@ -53,6 +54,12 @@ pub(crate) fn mount() -> AlphaRouter {
if let Some(enabled) = args.p2p_ipv6_enabled {
config.p2p.ipv6 = enabled;
};
+ if let Some(discovery) = args.p2p_discovery {
+ config.p2p.discovery = discovery;
+ };
+ if let Some(remote_access) = args.p2p_remote_access {
+ config.p2p.remote_access = remote_access;
+ };
#[cfg(feature = "ai")]
if let Some(version) = args.image_labeler_version {
diff --git a/core/src/node/config.rs b/core/src/node/config.rs
index e975f2f28..25e225af7 100644
--- a/core/src/node/config.rs
+++ b/core/src/node/config.rs
@@ -65,22 +65,32 @@ fn skip_if_true(value: &bool) -> bool {
*value
}
+fn skip_if_false(value: &bool) -> bool {
+ !*value
+}
+
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
pub struct NodeConfigP2P {
+ #[serde(default)]
+ pub discovery: P2PDiscoveryState,
#[serde(default, skip_serializing_if = "Port::is_random")]
pub port: Port,
#[serde(default = "default_as_true", skip_serializing_if = "skip_if_true")]
pub ipv4: bool,
#[serde(default = "default_as_true", skip_serializing_if = "skip_if_true")]
pub ipv6: bool,
+ #[serde(default, skip_serializing_if = "skip_if_false")]
+ pub remote_access: bool,
}
impl Default for NodeConfigP2P {
fn default() -> Self {
Self {
+ discovery: P2PDiscoveryState::Everyone,
port: Port::Random,
ipv4: true,
ipv6: true,
+ remote_access: false,
}
}
}
@@ -102,8 +112,6 @@ pub struct NodeConfig {
/// P2P config
#[serde(default)]
pub p2p: NodeConfigP2P,
- #[serde(default)]
- pub p2p_discovery: P2PDiscoveryState,
/// Feature flags enabled on the node
#[serde(default)]
pub features: Vec,
@@ -186,7 +194,6 @@ impl ManagedVersion for NodeConfig {
name,
identity: Identity::default(),
p2p: NodeConfigP2P::default(),
- p2p_discovery: P2PDiscoveryState::Everyone,
version: Self::LATEST_VERSION,
features: vec![],
notifications: vec![],
diff --git a/core/src/p2p/manager.rs b/core/src/p2p/manager.rs
index 8919b27f9..8bffca74c 100644
--- a/core/src/p2p/manager.rs
+++ b/core/src/p2p/manager.rs
@@ -184,7 +184,7 @@ impl P2PManager {
.ipv6 = Some(err.to_string());
}
- let should_revert = match config.p2p_discovery {
+ let should_revert = match config.p2p.discovery {
P2PDiscoveryState::Everyone
// TODO: Make `ContactsOnly` work
| P2PDiscoveryState::ContactsOnly => {
@@ -225,7 +225,7 @@ impl P2PManager {
if should_revert {
let _ = self
.node_config
- .write(|c| c.p2p_discovery = P2PDiscoveryState::Disabled)
+ .write(|c| c.p2p.discovery = P2PDiscoveryState::Disabled)
.await;
}
}
@@ -293,8 +293,6 @@ async fn start(
let mut service = unwrap_infallible(service.call(()).await);
tokio::spawn(async move {
- println!("APPLICATION GOT STREAM: {:?}", stream); // TODO
-
let Ok(header) = Header::from_stream(&mut stream).await.map_err(|err| {
error!("Failed to read header from stream: {}", err);
}) else {
@@ -348,7 +346,8 @@ async fn start(
}
Header::Http => {
let remote = stream.remote_identity();
- let Err(err) = operations::rspc::receiver(stream, &mut service).await else {
+ let Err(err) = operations::rspc::receiver(stream, &mut service, &node).await
+ else {
return;
};
diff --git a/core/src/p2p/operations/rspc.rs b/core/src/p2p/operations/rspc.rs
index d6823eed1..25396c0bd 100644
--- a/core/src/p2p/operations/rspc.rs
+++ b/core/src/p2p/operations/rspc.rs
@@ -6,7 +6,7 @@ use sd_p2p::{RemoteIdentity, UnicastStream, P2P};
use tokio::io::AsyncWriteExt;
use tracing::debug;
-use crate::p2p::Header;
+use crate::{p2p::Header, Node};
/// Transfer an rspc query to a remote node.
#[allow(unused)]
@@ -37,6 +37,7 @@ pub async fn remote_rspc(
pub(crate) async fn receiver(
stream: UnicastStream,
service: &mut Router,
+ node: &Node,
) -> Result<(), Box> {
debug!(
"Received http request from peer '{}'",
@@ -45,8 +46,8 @@ pub(crate) async fn receiver(
// TODO: Authentication
#[allow(clippy::todo)]
- if true {
- todo!("You wouldn't download a car!");
+ if node.config.get().await.p2p.remote_access {
+ todo!("No way buddy!");
}
Http::new()
diff --git a/interface/app/$libraryId/settings/client/general.tsx b/interface/app/$libraryId/settings/client/general.tsx
index 25d1b6d36..4a850f276 100644
--- a/interface/app/$libraryId/settings/client/general.tsx
+++ b/interface/app/$libraryId/settings/client/general.tsx
@@ -5,6 +5,7 @@ import {
useBridgeQuery,
useConnectedPeers,
useDebugState,
+ useFeatureFlag,
useZodForm
} from '@sd/client';
import { Button, Card, Input, Select, SelectOption, Slider, Switch, toast, tw, z } from '@sd/ui';
@@ -59,6 +60,14 @@ export const Component = () => {
]),
p2p_ipv4_enabled: z.boolean().optional(),
p2p_ipv6_enabled: z.boolean().optional(),
+ p2p_discovery: z
+ .union([
+ z.literal('Everyone'),
+ z.literal('ContactsOnly'),
+ z.literal('Disabled')
+ ])
+ .optional(),
+ p2p_remote_access: z.boolean().optional(),
image_labeler_version: z.string().optional(),
background_processing_percentage: z.coerce
.number({
@@ -75,6 +84,8 @@ export const Component = () => {
p2p_port: node.data?.p2p.port || { type: 'random' },
p2p_ipv4_enabled: node.data?.p2p.ipv4 || true,
p2p_ipv6_enabled: node.data?.p2p.ipv6 || true,
+ p2p_discovery: node.data?.p2p.discovery || 'Everyone',
+ p2p_remote_access: node.data?.p2p.remote_access || false,
image_labeler_version: node.data?.image_labeler_version ?? undefined,
background_processing_percentage:
node.data?.preferences.thumbnailer.background_processing_percentage || 50
@@ -92,9 +103,8 @@ export const Component = () => {
p2p_port: (value.p2p_port as any) ?? null,
p2p_ipv4_enabled: value.p2p_ipv4_enabled ?? null,
p2p_ipv6_enabled: value.p2p_ipv6_enabled ?? null,
- p2p_discovery: null,
- // p2p_port: value.customOrDefault === 'Default' ? 0 : Number(value.p2p_port),
- // p2p_enabled: value.p2p_enabled ?? null,
+ p2p_discovery: value.p2p_discovery ?? null,
+ p2p_remote_access: value.p2p_remote_access ?? null,
image_labeler_version: value.image_labeler_version ?? null
});
@@ -116,6 +126,8 @@ export const Component = () => {
const { t } = useLocale();
+ const isP2PWipFeatureEnabled = useFeatureFlag('wipP2P');
+
return (
{
}
/>
+
+ {isP2PWipFeatureEnabled && (
+ <>
+
+ {t('spacedrop_description')}
+
+ }
+ >
+
+
+
+
+
+ {t('remote_access_description')}
+
+
+ WARNING: This protocol has no security at the moment
+ and effectively gives root access!
+
+ >
+ }
+ >
+
+ form.setValue('p2p_remote_access', checked)
+ }
+ />
+
+ >
+ )}
>
) : null}
diff --git a/interface/locales/en/common.json b/interface/locales/en/common.json
index a73c138d3..bcd38d736 100644
--- a/interface/locales/en/common.json
+++ b/interface/locales/en/common.json
@@ -139,6 +139,12 @@
"enable_networking": "Enable Networking",
"enable_networking_description": "Allow your node to communicate with other Spacedrive nodes around you.",
"enable_networking_description_required": "Required for library sync or Spacedrop!",
+ "spacedrop": "Spacedrop visibility",
+ "spacedrop_everyone": "Everyone",
+ "spacedrop_contacts_only": "Contacts Only",
+ "spacedrop_disabled": "Disabled",
+ "remote_access": "Enable remote access",
+ "remote_access_description": "Enable other nodes to directly connect to this node.",
"encrypt": "Encrypt",
"encrypt_library": "Encrypt Library",
"encrypt_library_coming_soon": "Library encryption coming soon",
diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts
index 3da3a6c63..c38ecdade 100644
--- a/packages/client/src/core.ts
+++ b/packages/client/src/core.ts
@@ -169,7 +169,7 @@ export type CacheNode = { __type: string; __id: string; "#node": any }
export type CameraData = { device_make: string | null; device_model: string | null; color_space: string | null; color_profile: ColorProfile | null; focal_length: number | null; shutter_speed: number | null; flash: Flash | null; orientation: Orientation; lens_make: string | null; lens_model: string | null; bit_depth: number | null; red_eye: boolean | null; zoom: number | null; iso: number | null; software: string | null; serial_number: string | null; lens_serial_number: string | null; contrast: number | null; saturation: number | null; sharpness: number | null; composite: Composite | null }
-export type ChangeNodeNameArgs = { name: string | null; p2p_port: Port | null; p2p_ipv4_enabled: boolean | null; p2p_ipv6_enabled: boolean | null; p2p_discovery: P2PDiscoveryState | null; image_labeler_version: string | null }
+export type ChangeNodeNameArgs = { name: string | null; p2p_port: Port | null; p2p_ipv4_enabled: boolean | null; p2p_ipv6_enabled: boolean | null; p2p_discovery: P2PDiscoveryState | null; p2p_remote_access: boolean | null; image_labeler_version: string | null }
export type CloudInstance = { id: string; uuid: string; identity: RemoteIdentity; nodeId: string; metadata: { [key in string]: string } }
@@ -450,7 +450,7 @@ export type MediaLocation = { latitude: number; longitude: number; pluscode: Plu
export type MediaMetadata = ({ type: "Image" } & ImageMetadata) | ({ type: "Video" } & VideoMetadata) | ({ type: "Audio" } & AudioMetadata)
-export type NodeConfigP2P = { port: Port; ipv4: boolean; ipv6: boolean }
+export type NodeConfigP2P = { discovery?: P2PDiscoveryState; port: Port; ipv4: boolean; ipv6: boolean; remote_access: boolean }
export type NodePreferences = { thumbnailer: ThumbnailerPreferences }
@@ -462,7 +462,7 @@ id: string;
/**
* name is the display name of the current node. This is set by the user and is shown in the UI. // TODO: Length validation so it can fit in DNS record
*/
-name: string; identity: RemoteIdentity; p2p: NodeConfigP2P; p2p_discovery: P2PDiscoveryState; features: BackendFeature[]; preferences: NodePreferences; image_labeler_version: string | null }) & { data_path: string; device_model: string | null }
+name: string; identity: RemoteIdentity; p2p: NodeConfigP2P; features: BackendFeature[]; preferences: NodePreferences; image_labeler_version: string | null }) & { data_path: string; device_model: string | null }
export type NonIndexedPathItem = { path: string; name: string; extension: string; kind: number; is_dir: boolean; date_created: string; date_modified: string; size_in_bytes_bytes: number[]; hidden: boolean }
diff --git a/packages/client/src/stores/featureFlags.tsx b/packages/client/src/stores/featureFlags.tsx
index 9591f3ca9..4001c6f23 100644
--- a/packages/client/src/stores/featureFlags.tsx
+++ b/packages/client/src/stores/featureFlags.tsx
@@ -11,7 +11,8 @@ export const features = [
'solidJsDemo',
'hostedLocations',
'debugDragAndDrop',
- 'searchTargetSwitcher'
+ 'searchTargetSwitcher',
+ 'wipP2P'
] as const;
// This defines which backend feature flags show up in the UI.