Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/main/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const APPLET_DEV_TMP_FOLDER_PREFIX = 'moss-applet-dev';

export interface CliOpts {
profile?: string;
fork?: string;
devConfig?: string | undefined;
devDataDir?: string | undefined;
agentIdx?: number | undefined;
Expand All @@ -54,6 +55,7 @@ export interface CliOpts {

export interface RunOptions {
profile: string | undefined;
fork: string | undefined;
appstoreNetworkSeed: string;
devInfo: WeAppletDevInfo | undefined;
bootstrapUrl: string | undefined;
Expand All @@ -70,6 +72,8 @@ export interface RunOptions {
}

export function validateArgs(args: CliOpts): RunOptions {
const fork = args.fork;

// validate --profile argument
const allowedProfilePattern = /^[0-9a-zA-Z-]+$/;
if (args.profile && !allowedProfilePattern.test(args.profile)) {
Expand Down Expand Up @@ -126,6 +130,9 @@ export function validateArgs(args: CliOpts): RunOptions {
if (args.holochainPath && typeof args.holochainPath !== 'string') {
throw new Error('The --holochain-path argument must be of type string.');
}
if (fork && typeof fork !== 'string') {
throw new Error('The --fork argument must be of type string.');
}
if (args.holochainRustLog && typeof args.holochainRustLog !== 'string') {
throw new Error('The --holochain-rust-log argument must be of type string.');
}
Expand Down Expand Up @@ -176,6 +183,7 @@ export function validateArgs(args: CliOpts): RunOptions {

return {
profile,
fork: fork ? fork : undefined,
appstoreNetworkSeed,
devInfo,
bootstrapUrl: args.bootstrapUrl,
Expand Down Expand Up @@ -319,7 +327,7 @@ function readAndValidateDevConfig(
const allGroupNetworkSeeds = groups.map((group) => group.networkSeed);
const uniqueGroupNetworkSeeds = new Set(allGroupNetworkSeeds);
if (uniqueGroupNetworkSeeds.size !== allGroupNetworkSeeds.length) {
throw new Error(`Invalid We dev config: Group network seeds must all be unique.`);
throw new Error(`Invalid We dev config: Group network seeds must all be unique.`);
}
// validate applets
applets.forEach((applet) => {
Expand Down Expand Up @@ -389,7 +397,7 @@ function readAndValidateDevConfig(
});

if (configObject && !configObject.toolCurations) {
configObject.toolCurations = [];
configObject.toolCurations = [];
}

const allAppletNames = applets.map((applet) => applet.name);
Expand Down
131 changes: 65 additions & 66 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ program
'-p, --profile <string>',
'Runs Moss with a custom profile with its own dedicated data store.',
)
.option(
'--fork <string>',
'When importing existing data, appends this suffix to all imported network seeds.',
)
.option(
'-n, --network-seed <string>',
'Installs any default apps with the provided network seed in case there are any and have not yet been installed.',
Expand Down Expand Up @@ -416,6 +420,7 @@ if (!RUNNING_WITH_COMMAND) {
let SYSTRAY: Tray | undefined = undefined;
let isAppQuitting = false;
let LOCAL_SERVICES_HANDLE: childProcess.ChildProcessWithoutNullStreams | undefined;
const skipLegacyProfileImportPrompt = !!RUN_OPTIONS.profile && !RUN_OPTIONS.fork;
const WAL_WINDOWS: Record<
string,
{
Expand Down Expand Up @@ -1059,21 +1064,21 @@ if (!RUNNING_WITH_COMMAND) {
app.quit();
}
}),
ipcMain.handle('get-network-overrides', () => {
const overrides = WE_FILE_SYSTEM.getNetworkOverrides();
const networkUrls = getNetworkUrls();
return {
overrides,
defaults: {
bootstrapUrl: PRODUCTION_BOOTSTRAP_URLS[0],
relayUrl: PRODUCTION_RELAY_URLS[0],
},
current: {
bootstrapUrl: networkUrls.bootstrap_urls[0],
relayUrl: networkUrls.relay_urls[0],
},
};
});
ipcMain.handle('get-network-overrides', () => {
const overrides = WE_FILE_SYSTEM.getNetworkOverrides();
const networkUrls = getNetworkUrls();
return {
overrides,
defaults: {
bootstrapUrl: PRODUCTION_BOOTSTRAP_URLS[0],
relayUrl: PRODUCTION_RELAY_URLS[0],
},
current: {
bootstrapUrl: networkUrls.bootstrap_urls[0],
relayUrl: networkUrls.relay_urls[0],
},
};
});
ipcMain.handle('set-network-overrides', async (_e, overrides: { bootstrapUrl?: string; relayUrl?: string }) => {
WE_FILE_SYSTEM.setNetworkOverrides(overrides);
// Relaunch Moss
Expand Down Expand Up @@ -1110,7 +1115,7 @@ if (!RUNNING_WITH_COMMAND) {
app.relaunch(options);
app.quit();
});
ipcMain.handle('is-main-window-focused', (): boolean | undefined => MAIN_WINDOW?.isFocused());
ipcMain.handle('is-main-window-focused', (): boolean | undefined => MAIN_WINDOW?.isFocused());
ipcMain.handle(
'notification',
(
Expand Down Expand Up @@ -1486,7 +1491,7 @@ if (!RUNNING_WITH_COMMAND) {
let network_info: NetworkInfo = { bootstrap_urls: [], signal_urls: [], relay_urls: [] };
try {
network_info = getNetworkUrls();
} catch (e) {console.error('Failed to get network urls', e)}
} catch (e) { console.error('Failed to get network urls', e) }
/** */
return HOLOCHAIN_MANAGER
? {
Expand Down Expand Up @@ -1527,6 +1532,11 @@ if (!RUNNING_WITH_COMMAND) {
return !WE_FILE_SYSTEM.keystoreInitialized();
});
ipcMain.handle('find-legacy-profiles', (): LegacyProfileInfo[] => {
// Skip the legacy import choice for explicit custom profiles, but still let
// the renderer continue into the normal first-launch setup flow.
if (skipLegacyProfileImportPrompt) {
return [];
}
return findLegacyProfiles(app);
});
ipcMain.handle('get-lair-binary-version', (): string => {
Expand Down Expand Up @@ -1800,6 +1810,19 @@ if (!RUNNING_WITH_COMMAND) {
// Execute a groups import from a parsed array. Used by both dialog-based and
// auto-import (pending) flows.
const runGroupsImport = async (groups: GroupExportEntry[]): Promise<ImportResult[]> => {
const forkImportedSeed = (seed: string | undefined): string | undefined => {
if (!seed) {
console.warn('No seed provided for group. Skipping seed forking.');
return undefined;
} else if (!RUN_OPTIONS.fork) {
console.warn('Fork option is enabled but no seed fork string provided. Skipping seed forking.');
return seed;
} else {
console.log(`Forking imported seed "${seed}" with fork "${RUN_OPTIONS.fork}"`);
return `${seed}${RUN_OPTIONS.fork}`;
}
};

const allApps = await HOLOCHAIN_MANAGER!.adminWebsocket.listApps({});
let myPubKey = globalPubKeyFromListAppsResponse(allApps);
if (!myPubKey) myPubKey = await getOrCreateAgentPubKey();
Expand All @@ -1814,7 +1837,9 @@ if (!RUNNING_WITH_COMMAND) {
for (let gi = 0; gi < groups.length; gi++) {
const group = groups[gi];
const current = gi + 1;
const { networkSeed, progenitor, groupProfile, agentProfile, description } = group;
const { progenitor, groupProfile, agentProfile, description } = group;
const networkSeed = forkImportedSeed(group.networkSeed);
console.log(`Importing group ${current}/${total}: "${groupProfile?.name || 'Unnamed'}" with network seed "${networkSeed}" and progenitor "${progenitor}"`);

if (!networkSeed) {
results.push({ groupName: groupProfile?.name, status: 'error', error: 'Missing network seed' });
Expand Down Expand Up @@ -1858,7 +1883,7 @@ if (!RUNNING_WITH_COMMAND) {
});

let importGroupStatus: ImportStatus;
if (amProgenitor) {
if (amProgenitor || !progenitor) {
if (groupProfile) {
emitProgress(current, groupProfile?.name, 'setting-profile');
await appWs.callZome({
Expand All @@ -1878,62 +1903,27 @@ if (!RUNNING_WITH_COMMAND) {
}
importGroupStatus = 'created';
} else {
let profileSynced = false;
const deadline = Date.now() + 20000;
while (Date.now() < deadline) {
const secondsLeft = Math.ceil((deadline - Date.now()) / 1000);
emitProgress(current, groupProfile?.name, 'waiting-for-sync', { secondsLeft });
await new Promise((r) => setTimeout(r, 5000));
try {
const profileRecord = await appWs.callZome({
role_name: 'group',
zome_name: 'group',
fn_name: 'get_group_profile',
payload: { input: null, local: false },
});
if (profileRecord) { profileSynced = true; break; }
} catch (_pollErr) {
// continue polling
}
}

if (profileSynced) {
importGroupStatus = 'joined';
} else if (!progenitor) {
if (groupProfile) {
emitProgress(current, groupProfile?.name, 'setting-profile');
await appWs.callZome({
role_name: 'group',
zome_name: 'group',
fn_name: 'set_group_profile',
payload: { name: groupProfile.name, icon_src: groupProfile.icon_src },
});
}
if (description) {
await appWs.callZome({
role_name: 'group',
zome_name: 'group',
fn_name: 'set_group_meta_data',
payload: { name: 'description', data: description, permission_hash: null },
});
}
importGroupStatus = 'created';
} else {
importGroupStatus = 'joined-no-profile';
}
importGroupStatus = 'joined-no-profile';
}

// Set the agent's own profile in this group if we have one from the export.
if (agentProfile) {
try {
const normalizedAgentFields =
agentProfile.fields &&
typeof agentProfile.fields === 'object' &&
!Array.isArray(agentProfile.fields)
? agentProfile.fields
: {};

emitProgress(current, groupProfile?.name, 'setting-profile');
await appWs.callZome({
role_name: 'group',
zome_name: 'profiles',
fn_name: 'create_profile',
payload: {
nickname: agentProfile.nickname,
fields: agentProfile.fields,
fields: normalizedAgentFields,
},
});
} catch (profileErr) {
Expand All @@ -1942,8 +1932,17 @@ if (!RUNNING_WITH_COMMAND) {
}

if (group.tools && group.tools.length > 0) {
if (progenitor && !amProgenitor) {
console.log(
`Skipping tool import for group ${appId} because importing agent is not the listed progenitor.`
);
}
}

if (group.tools && group.tools.length > 0 && (amProgenitor || !progenitor)) {
for (let ti = 0; ti < group.tools.length; ti++) {
const tool = group.tools[ti];
const toolNetworkSeed = forkImportedSeed(tool.network_seed);
emitProgress(current, groupProfile?.name, 'installing-tool', {
toolName: tool.toolName || tool.custom_name,
toolIndex: ti + 1,
Expand Down Expand Up @@ -1995,7 +1994,7 @@ if (!RUNNING_WITH_COMMAND) {
sha256_ui: sha256Ui,
sha256_webhapp: sha256Webhapp,
distribution_info: JSON.stringify(newDistributionInfo),
network_seed: tool.network_seed,
network_seed: toolNetworkSeed,
properties: {},
};

Expand Down Expand Up @@ -2057,7 +2056,7 @@ if (!RUNNING_WITH_COMMAND) {
fs.writeFileSync(webHappPath, new Uint8Array(buffer));
const { happPath } = await rustUtils.saveHappOrWebhapp(webHappPath, happsDir, uisDir);
happToBeInstalledPath = happPath;
try { fs.rmSync(tmpImportDir, { recursive: true }); } catch (_) {}
try { fs.rmSync(tmpImportDir, { recursive: true }); } catch (_) { }
}

const appAssetsInfo: AppAssetsInfo = deriveAppAssetsInfo(
Expand All @@ -2073,7 +2072,7 @@ if (!RUNNING_WITH_COMMAND) {
source: { type: 'path', value: happToBeInstalledPath },
installed_app_id: appletAppId,
agent_key: myPubKey,
network_seed: tool.network_seed,
network_seed: toolNetworkSeed,
});
await HOLOCHAIN_MANAGER!.adminWebsocket.enableApp({ installed_app_id: appletAppId });

Expand Down
Loading