mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-09 12:36:16 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7506c69c3c | |||
| eb133f6254 | |||
| 251f2dd1de | |||
| 82c5dcf16f | |||
| 59f0c90836 | |||
| b0e64a5e5a | |||
| 733d3ece0e | |||
| 59b228ce39 | |||
| 080bcbaf97 |
@@ -23,7 +23,7 @@ export const clearExitNodes: CommandModule<
|
|||||||
// Delete all exit nodes
|
// Delete all exit nodes
|
||||||
const deletedCount = await db
|
const deletedCount = await db
|
||||||
.delete(exitNodes)
|
.delete(exitNodes)
|
||||||
.where(eq(exitNodes.exitNodeId, exitNodes.exitNodeId)) .returning();; // delete all
|
.where(eq(exitNodes.exitNodeId, exitNodes.exitNodeId)).returning();; // delete all
|
||||||
|
|
||||||
console.log(`Deleted ${deletedCount.length} exit node(s) from the database`);
|
console.log(`Deleted ${deletedCount.length} exit node(s) from the database`);
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -152,7 +152,7 @@
|
|||||||
"siteResourcesHowToAccess": "How to access",
|
"siteResourcesHowToAccess": "How to access",
|
||||||
"siteResourcesTargetsOnSite": "Targets on this site",
|
"siteResourcesTargetsOnSite": "Targets on this site",
|
||||||
"siteSetting": "{siteName} Settings",
|
"siteSetting": "{siteName} Settings",
|
||||||
"siteNewtTunnel": "Newt Site (Recommended)",
|
"siteNewtTunnel": "Pangolin Site (Recommended)",
|
||||||
"siteNewtTunnelDescription": "Easiest way to create an entrypoint into any network. No extra setup.",
|
"siteNewtTunnelDescription": "Easiest way to create an entrypoint into any network. No extra setup.",
|
||||||
"siteWg": "Basic WireGuard",
|
"siteWg": "Basic WireGuard",
|
||||||
"siteWgDescription": "Use any WireGuard client to establish a tunnel. Manual NAT setup required.",
|
"siteWgDescription": "Use any WireGuard client to establish a tunnel. Manual NAT setup required.",
|
||||||
@@ -2898,7 +2898,7 @@
|
|||||||
"editInternalResourceDialogAlias": "Alias",
|
"editInternalResourceDialogAlias": "Alias",
|
||||||
"editInternalResourceDialogAliasDescription": "An optional internal DNS alias for this resource.",
|
"editInternalResourceDialogAliasDescription": "An optional internal DNS alias for this resource.",
|
||||||
"createInternalResourceDialogNoSitesAvailable": "No Sites Available",
|
"createInternalResourceDialogNoSitesAvailable": "No Sites Available",
|
||||||
"createInternalResourceDialogNoSitesAvailableDescription": "You need to have at least one Newt site with a subnet configured to create private resources.",
|
"createInternalResourceDialogNoSitesAvailableDescription": "You need to have at least one site with a subnet configured to create private resources.",
|
||||||
"createInternalResourceDialogClose": "Close",
|
"createInternalResourceDialogClose": "Close",
|
||||||
"createInternalResourceDialogCreateClientResource": "Create Private Resource",
|
"createInternalResourceDialogCreateClientResource": "Create Private Resource",
|
||||||
"createInternalResourceDialogCreateClientResourceDescription": "Create a new resource that will only be accessible to clients connected to the organization",
|
"createInternalResourceDialogCreateClientResourceDescription": "Create a new resource that will only be accessible to clients connected to the organization",
|
||||||
@@ -3767,6 +3767,7 @@
|
|||||||
"noData": "No Data",
|
"noData": "No Data",
|
||||||
"machineClients": "Machine Clients",
|
"machineClients": "Machine Clients",
|
||||||
"install": "Install",
|
"install": "Install",
|
||||||
|
"downloadInstaller": "Download Installer",
|
||||||
"run": "Run",
|
"run": "Run",
|
||||||
"envFile": "Environment File",
|
"envFile": "Environment File",
|
||||||
"serviceFile": "Service File",
|
"serviceFile": "Service File",
|
||||||
@@ -4363,5 +4364,6 @@
|
|||||||
"rdpUnicodeKeyboardMode": "Unicode keyboard mode",
|
"rdpUnicodeKeyboardMode": "Unicode keyboard mode",
|
||||||
"sessionToolbarShow": "Show toolbar",
|
"sessionToolbarShow": "Show toolbar",
|
||||||
"sessionToolbarHide": "Hide toolbar",
|
"sessionToolbarHide": "Hide toolbar",
|
||||||
"actionUpdateSiteApprovals": "Update Site Approvals"
|
"actionUpdateSiteApprovals": "Update Site Approvals",
|
||||||
|
"check": "Check"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export async function withRetry<T>(
|
|||||||
const jitter = Math.random() * baseDelay;
|
const jitter = Math.random() * baseDelay;
|
||||||
const delay = baseDelay + jitter;
|
const delay = baseDelay + jitter;
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`Transient DB error in ${context}, retrying attempt ${attempt}/${maxRetries} after ${delay.toFixed(0)}ms`,
|
`Transient DB issue in ${context}, retrying attempt ${attempt}/${maxRetries} after ${delay.toFixed(0)}ms`,
|
||||||
{ code: error?.code ?? error?.cause?.code }
|
{ code: error?.code ?? error?.cause?.code }
|
||||||
);
|
);
|
||||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
|
|||||||
@@ -348,8 +348,8 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.pipe(z.string())
|
.pipe(z.string())
|
||||||
.transform((url) => url.toLowerCase()),
|
.transform((url) => url.toLowerCase()),
|
||||||
subnet_group: z.string().optional().default("100.89.137.0/20"),
|
subnet_group: z.string().optional().default("100.89.137.0/18"),
|
||||||
block_size: z.number().positive().gt(0).optional().default(24),
|
block_size: z.number().positive().gt(0).optional().default(22),
|
||||||
site_block_size: z
|
site_block_size: z
|
||||||
.number()
|
.number()
|
||||||
.positive()
|
.positive()
|
||||||
|
|||||||
@@ -2478,7 +2478,12 @@ hybridRouter.post(
|
|||||||
destinations: destinations
|
destinations: destinations
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
if (!(
|
||||||
|
error instanceof Error &&
|
||||||
|
error.message === "Exit node not allowed"
|
||||||
|
)) {
|
||||||
|
logger.error(error);
|
||||||
|
}
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of a proprietary work.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is licensed under the Fossorial Commercial License.
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||||
|
*
|
||||||
|
* This file is not licensed under the AGPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
|
export interface ExitNodeOnlineEvent {
|
||||||
|
exitNodeId: number;
|
||||||
|
endpoint: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EXIT_NODE_ONLINE_EVENT = "exit-node-online";
|
||||||
|
|
||||||
|
export const exitNodeEvents = new EventEmitter();
|
||||||
@@ -12,11 +12,27 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { db, exitNodes, newts, sites } from "@server/db";
|
import { db, newts, sites } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import redisManager from "#private/lib/redis";
|
import redisManager from "#private/lib/redis";
|
||||||
// import { sendToClient } from "#private/routers/ws";
|
import { sendToClient } from "../ws";
|
||||||
|
import {
|
||||||
|
exitNodeEvents,
|
||||||
|
EXIT_NODE_ONLINE_EVENT,
|
||||||
|
ExitNodeOnlineEvent
|
||||||
|
} from "./exitNodeEvents";
|
||||||
|
|
||||||
|
exitNodeEvents.on(
|
||||||
|
EXIT_NODE_ONLINE_EVENT,
|
||||||
|
({ exitNodeId, endpoint }: ExitNodeOnlineEvent) => {
|
||||||
|
scheduleExitNodeReconnect(exitNodeId, endpoint).catch((error) => {
|
||||||
|
logger.error("Failed to schedule exit node reconnect", {
|
||||||
|
error
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const INITIAL_DELAY_MS = 15 * 1000; // 15 seconds before first check
|
const INITIAL_DELAY_MS = 15 * 1000; // 15 seconds before first check
|
||||||
const CHECK_INTERVAL_MS = 10 * 1000; // Check every 10 seconds
|
const CHECK_INTERVAL_MS = 10 * 1000; // Check every 10 seconds
|
||||||
@@ -26,7 +42,7 @@ const REDIS_HASH_PREFIX = "exit-node-reconnect:";
|
|||||||
|
|
||||||
interface PendingReconnect {
|
interface PendingReconnect {
|
||||||
startTime: number;
|
startTime: number;
|
||||||
reachableAt: string;
|
endpoint: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In-memory tracking for this node
|
// In-memory tracking for this node
|
||||||
@@ -40,15 +56,15 @@ let schedulerInterval: NodeJS.Timeout | null = null;
|
|||||||
*/
|
*/
|
||||||
export async function scheduleExitNodeReconnect(
|
export async function scheduleExitNodeReconnect(
|
||||||
exitNodeId: number,
|
exitNodeId: number,
|
||||||
reachableAt: string
|
endpoint: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
logger.info(
|
logger.info(
|
||||||
`Scheduling newt reconnect for exit node ${exitNodeId} (reachableAt: ${reachableAt})`
|
`Scheduling newt reconnect for exit node ${exitNodeId} (endpoint: ${endpoint})`
|
||||||
);
|
);
|
||||||
|
|
||||||
const entry: PendingReconnect = {
|
const entry: PendingReconnect = {
|
||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
reachableAt
|
endpoint
|
||||||
};
|
};
|
||||||
|
|
||||||
pendingReconnects.set(exitNodeId, entry);
|
pendingReconnects.set(exitNodeId, entry);
|
||||||
@@ -63,8 +79,8 @@ export async function scheduleExitNodeReconnect(
|
|||||||
);
|
);
|
||||||
await redisManager.hset(
|
await redisManager.hset(
|
||||||
`${REDIS_HASH_PREFIX}${exitNodeId}`,
|
`${REDIS_HASH_PREFIX}${exitNodeId}`,
|
||||||
"reachableAt",
|
"endpoint",
|
||||||
reachableAt
|
endpoint
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,14 +117,14 @@ async function processPendingReconnects(): Promise<void> {
|
|||||||
`${REDIS_HASH_PREFIX}${id}`,
|
`${REDIS_HASH_PREFIX}${id}`,
|
||||||
"startTime"
|
"startTime"
|
||||||
);
|
);
|
||||||
const reachableAt = await redisManager.hget(
|
const endpoint = await redisManager.hget(
|
||||||
`${REDIS_HASH_PREFIX}${id}`,
|
`${REDIS_HASH_PREFIX}${id}`,
|
||||||
"reachableAt"
|
"endpoint"
|
||||||
);
|
);
|
||||||
if (startTimeStr && reachableAt) {
|
if (startTimeStr && endpoint) {
|
||||||
toProcess.set(id, {
|
toProcess.set(id, {
|
||||||
startTime: parseInt(startTimeStr, 10),
|
startTime: parseInt(startTimeStr, 10),
|
||||||
reachableAt
|
endpoint
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +151,7 @@ async function processPendingReconnects(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the exit node HTTP endpoint is reachable
|
// Check if the exit node HTTP endpoint is reachable
|
||||||
const pingUrl = `${entry.reachableAt}/ping`;
|
const pingUrl = `http://${entry.endpoint}/ping`;
|
||||||
try {
|
try {
|
||||||
await axios.get(pingUrl, { timeout: 5000 });
|
await axios.get(pingUrl, { timeout: 5000 });
|
||||||
} catch {
|
} catch {
|
||||||
@@ -150,47 +166,47 @@ async function processPendingReconnects(): Promise<void> {
|
|||||||
`Exit node ${exitNodeId} is reachable. Sending newt/wg/reconnect to connected newts.`
|
`Exit node ${exitNodeId} is reachable. Sending newt/wg/reconnect to connected newts.`
|
||||||
);
|
);
|
||||||
|
|
||||||
// await sendReconnectToNewts(exitNodeId);
|
await sendReconnectToNewts(exitNodeId);
|
||||||
await removePending(exitNodeId);
|
await removePending(exitNodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// async function sendReconnectToNewts(exitNodeId: number): Promise<void> {
|
async function sendReconnectToNewts(exitNodeId: number): Promise<void> {
|
||||||
// try {
|
try {
|
||||||
// const connectedNewts = await db
|
const connectedNewts = await db
|
||||||
// .select({ newtId: newts.newtId })
|
.select({ newtId: newts.newtId })
|
||||||
// .from(newts)
|
.from(newts)
|
||||||
// .innerJoin(sites, eq(newts.siteId, sites.siteId))
|
.innerJoin(sites, eq(newts.siteId, sites.siteId))
|
||||||
// .where(eq(sites.exitNodeId, exitNodeId));
|
.where(eq(sites.exitNodeId, exitNodeId));
|
||||||
|
|
||||||
// if (connectedNewts.length === 0) {
|
if (connectedNewts.length === 0) {
|
||||||
// logger.debug(
|
logger.debug(
|
||||||
// `No newts found for exit node ${exitNodeId}, nothing to reconnect`
|
`No newts found for exit node ${exitNodeId}, nothing to reconnect`
|
||||||
// );
|
);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// logger.info(
|
logger.info(
|
||||||
// `Sending newt/wg/reconnect to ${connectedNewts.length} newt(s) for exit node ${exitNodeId}`
|
`Sending newt/wg/reconnect to ${connectedNewts.length} newt(s) for exit node ${exitNodeId}`
|
||||||
// );
|
);
|
||||||
|
|
||||||
// const reconnectMessage = {
|
const reconnectMessage = {
|
||||||
// type: "newt/wg/reconnect",
|
type: "newt/wg/reconnect",
|
||||||
// data: {}
|
data: {}
|
||||||
// };
|
};
|
||||||
|
|
||||||
// await Promise.allSettled(
|
await Promise.allSettled(
|
||||||
// connectedNewts.map(({ newtId }) =>
|
connectedNewts.map(({ newtId }) =>
|
||||||
// sendToClient(newtId, reconnectMessage)
|
sendToClient(newtId, reconnectMessage)
|
||||||
// )
|
)
|
||||||
// );
|
);
|
||||||
// } catch (error) {
|
} catch (error) {
|
||||||
// logger.error(
|
logger.error(
|
||||||
// `Failed to send reconnect messages for exit node ${exitNodeId}`,
|
`Failed to send reconnect messages for exit node ${exitNodeId}`,
|
||||||
// { error }
|
{ error }
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
async function removePending(exitNodeId: number): Promise<void> {
|
async function removePending(exitNodeId: number): Promise<void> {
|
||||||
pendingReconnects.delete(exitNodeId);
|
pendingReconnects.delete(exitNodeId);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { MessageHandler } from "@server/routers/ws";
|
|||||||
import { RemoteExitNode } from "@server/db";
|
import { RemoteExitNode } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { scheduleExitNodeReconnect } from "./exitNodeReconnectScheduler";
|
import { exitNodeEvents, EXIT_NODE_ONLINE_EVENT } from "./exitNodeEvents";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles ping messages from clients and responds with pong
|
* Handles ping messages from clients and responds with pong
|
||||||
@@ -40,7 +40,7 @@ export const handleRemoteExitNodePingMessage: MessageHandler = async (
|
|||||||
try {
|
try {
|
||||||
// Fetch the current state before updating so we can detect the offline→online transition
|
// Fetch the current state before updating so we can detect the offline→online transition
|
||||||
const [currentExitNode] = await db
|
const [currentExitNode] = await db
|
||||||
.select({ online: exitNodes.online, reachableAt: exitNodes.reachableAt })
|
.select({ online: exitNodes.online, endpoint: exitNodes.endpoint })
|
||||||
.from(exitNodes)
|
.from(exitNodes)
|
||||||
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId))
|
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
@@ -55,12 +55,14 @@ export const handleRemoteExitNodePingMessage: MessageHandler = async (
|
|||||||
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId));
|
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId));
|
||||||
|
|
||||||
// If the exit node was offline and is now coming online, schedule newt reconnects
|
// If the exit node was offline and is now coming online, schedule newt reconnects
|
||||||
if (currentExitNode && !currentExitNode.online && currentExitNode.reachableAt) {
|
if (
|
||||||
scheduleExitNodeReconnect(
|
currentExitNode &&
|
||||||
remoteExitNode.exitNodeId,
|
!currentExitNode.online &&
|
||||||
currentExitNode.reachableAt
|
currentExitNode.endpoint
|
||||||
).catch((error) => {
|
) {
|
||||||
logger.error("Failed to schedule exit node reconnect", { error });
|
exitNodeEvents.emit(EXIT_NODE_ONLINE_EVENT, {
|
||||||
|
exitNodeId: remoteExitNode.exitNodeId,
|
||||||
|
endpoint: currentExitNode.endpoint
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!resources || resources.length === 0) {
|
if (!resources || resources.length === 0) {
|
||||||
logger.error(
|
logger.warn(
|
||||||
`handleOlmServerInitAddPeerHandshake: Resource not found`
|
`handleOlmServerInitAddPeerHandshake: Resource not found`
|
||||||
);
|
);
|
||||||
await sendCancel();
|
await sendCancel();
|
||||||
@@ -94,7 +94,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
|
|||||||
|
|
||||||
if (resources.length > 1) {
|
if (resources.length > 1) {
|
||||||
// error but this should not happen because the nice id cant contain a dot and the alias has to have a dot and both have to be unique within the org so there should never be multiple matches
|
// error but this should not happen because the nice id cant contain a dot and the alias has to have a dot and both have to be unique within the org so there should never be multiple matches
|
||||||
logger.error(
|
logger.warn(
|
||||||
`handleOlmServerInitAddPeerHandshake: Multiple resources found matching the criteria`
|
`handleOlmServerInitAddPeerHandshake: Multiple resources found matching the criteria`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -119,7 +119,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (currentResourceAssociationCaches.length === 0) {
|
if (currentResourceAssociationCaches.length === 0) {
|
||||||
logger.error(
|
logger.warn(
|
||||||
`handleOlmServerInitAddPeerHandshake: Client ${client.clientId} does not have access to resource ${resource.siteResourceId}`
|
`handleOlmServerInitAddPeerHandshake: Client ${client.clientId} does not have access to resource ${resource.siteResourceId}`
|
||||||
);
|
);
|
||||||
await sendCancel();
|
await sendCancel();
|
||||||
@@ -127,7 +127,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!resource.networkId) {
|
if (!resource.networkId) {
|
||||||
logger.error(
|
logger.warn(
|
||||||
`handleOlmServerInitAddPeerHandshake: Resource ${resource.siteResourceId} has no network`
|
`handleOlmServerInitAddPeerHandshake: Resource ${resource.siteResourceId} has no network`
|
||||||
);
|
);
|
||||||
await sendCancel();
|
await sendCancel();
|
||||||
@@ -141,7 +141,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
|
|||||||
.where(eq(siteNetworks.networkId, resource.networkId));
|
.where(eq(siteNetworks.networkId, resource.networkId));
|
||||||
|
|
||||||
if (!siteRows || siteRows.length === 0) {
|
if (!siteRows || siteRows.length === 0) {
|
||||||
logger.error(
|
logger.warn(
|
||||||
`handleOlmServerInitAddPeerHandshake: No sites found for resource ${resource.siteResourceId}`
|
`handleOlmServerInitAddPeerHandshake: No sites found for resource ${resource.siteResourceId}`
|
||||||
);
|
);
|
||||||
await sendCancel();
|
await sendCancel();
|
||||||
@@ -164,9 +164,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sitesToProcess.length === 0) {
|
if (sitesToProcess.length === 0) {
|
||||||
logger.error(
|
logger.warn(`handleOlmServerInitAddPeerHandshake: No sites to process`);
|
||||||
`handleOlmServerInitAddPeerHandshake: No sites to process`
|
|
||||||
);
|
|
||||||
await sendCancel();
|
await sendCancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -193,7 +191,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!site.exitNodeId) {
|
if (!site.exitNodeId) {
|
||||||
logger.error(
|
logger.warn(
|
||||||
`handleOlmServerInitAddPeerHandshake: Site ${site.siteId} has no exit node, skipping`
|
`handleOlmServerInitAddPeerHandshake: Site ${site.siteId} has no exit node, skipping`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
@@ -205,7 +203,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
|
|||||||
.where(eq(exitNodes.exitNodeId, site.exitNodeId));
|
.where(eq(exitNodes.exitNodeId, site.exitNodeId));
|
||||||
|
|
||||||
if (!exitNode) {
|
if (!exitNode) {
|
||||||
logger.error(
|
logger.warn(
|
||||||
`handleOlmServerInitAddPeerHandshake: Exit node not found for site ${site.siteId}, skipping`
|
`handleOlmServerInitAddPeerHandshake: Exit node not found for site ${site.siteId}, skipping`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
@@ -229,7 +227,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!handshakeInitiated) {
|
if (!handshakeInitiated) {
|
||||||
logger.error(
|
logger.warn(
|
||||||
`handleOlmServerInitAddPeerHandshake: No accessible sites with valid exit nodes found, cancelling chain`
|
`handleOlmServerInitAddPeerHandshake: No accessible sites with valid exit nodes found, cancelling chain`
|
||||||
);
|
);
|
||||||
await sendCancel();
|
await sendCancel();
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export default async function OrgLayout(props: {
|
|||||||
subscriptionStatus = subRes.data.data;
|
subscriptionStatus = subRes.data.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If subscription fetch fails, keep subscriptionStatus as null
|
// If subscription fetch fails, keep subscriptionStatus as null
|
||||||
console.error("Failed to fetch subscription status:", error);
|
// console.error("Failed to fetch subscription status:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
SettingsSectionTitle
|
SettingsSectionTitle
|
||||||
} from "./Settings";
|
} from "./Settings";
|
||||||
import { CheckboxWithLabel } from "./ui/checkbox";
|
import { CheckboxWithLabel } from "./ui/checkbox";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
import { OptionSelect, type OptionSelectOption } from "./OptionSelect";
|
import { OptionSelect, type OptionSelectOption } from "./OptionSelect";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -18,11 +19,14 @@ import {
|
|||||||
FaLinux,
|
FaLinux,
|
||||||
FaWindows
|
FaWindows
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
import { ExternalLink } from "lucide-react";
|
import { Download, ExternalLink } from "lucide-react";
|
||||||
import { SiKubernetes, SiNixos } from "react-icons/si";
|
import { SiKubernetes, SiNixos } from "react-icons/si";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
|
||||||
export type CommandItem = string | { title: string; command: string };
|
export type CommandItem =
|
||||||
|
| string
|
||||||
|
| { title: string; command: string }
|
||||||
|
| { title: string; link: string };
|
||||||
|
|
||||||
const PLATFORMS = [
|
const PLATFORMS = [
|
||||||
"linux",
|
"linux",
|
||||||
@@ -41,14 +45,12 @@ export type NewtSiteInstallCommandsProps = {
|
|||||||
id: string;
|
id: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
version?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function NewtSiteInstallCommands({
|
export function NewtSiteInstallCommands({
|
||||||
id,
|
id,
|
||||||
secret,
|
secret,
|
||||||
endpoint,
|
endpoint
|
||||||
version = "latest"
|
|
||||||
}: NewtSiteInstallCommandsProps) {
|
}: NewtSiteInstallCommandsProps) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const { env } = useEnvContext();
|
const { env } = useEnvContext();
|
||||||
@@ -88,25 +90,39 @@ export function NewtSiteInstallCommands({
|
|||||||
Run: [
|
Run: [
|
||||||
{
|
{
|
||||||
title: t("install"),
|
title: t("install"),
|
||||||
command: `curl -fsSL https://static.pangolin.net/get-newt.sh | bash`
|
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("run"),
|
title: t("run"),
|
||||||
command: `${runAsRootPrefix}newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
|
command: `${runAsRootPrefix}pangolin up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Systemd Service": [
|
"Auto Systemd Service": [
|
||||||
{
|
{
|
||||||
title: t("install"),
|
title: t("install"),
|
||||||
command: `curl -fsSL https://static.pangolin.net/get-newt.sh | bash`
|
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("run"),
|
||||||
|
command: `sudo pangolin service install site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("check"),
|
||||||
|
command: `sudo pangolin service status site`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Manual Systemd Service": [
|
||||||
|
{
|
||||||
|
title: t("install"),
|
||||||
|
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("envFile"),
|
title: t("envFile"),
|
||||||
command: `# Create the directory and environment file
|
command: `# Create the directory and environment file
|
||||||
sudo install -d -m 0755 /etc/newt
|
sudo install -d -m 0755 /etc/pangolin
|
||||||
sudo tee /etc/newt/newt.env > /dev/null << 'EOF'
|
sudo tee /etc/pangolin/pangolin-site.env > /dev/null << 'EOF'
|
||||||
NEWT_ID=${id}
|
SITE_ID=${id}
|
||||||
NEWT_SECRET=${secret}
|
SITE_SECRET=${secret}
|
||||||
PANGOLIN_ENDPOINT=${endpoint}${
|
PANGOLIN_ENDPOINT=${endpoint}${
|
||||||
!acceptClients
|
!acceptClients
|
||||||
? `
|
? `
|
||||||
@@ -119,11 +135,11 @@ DISABLE_SSH=true`
|
|||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
sudo chmod 600 /etc/newt/newt.env`
|
sudo chmod 600 /etc/pangolin/pangolin-site.env`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("serviceFile"),
|
title: t("serviceFile"),
|
||||||
command: `sudo tee /etc/systemd/system/newt.service > /dev/null << 'EOF'
|
command: `sudo tee /etc/systemd/system/pangolin-site.service > /dev/null << 'EOF'
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Newt
|
Description=Newt
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
@@ -133,8 +149,8 @@ After=network-online.target
|
|||||||
Type=simple
|
Type=simple
|
||||||
User=root
|
User=root
|
||||||
Group=root
|
Group=root
|
||||||
EnvironmentFile=/etc/newt/newt.env
|
EnvironmentFile=/etc/pangolin/pangolin-site.env
|
||||||
ExecStart=/usr/local/bin/newt
|
ExecStart=/home/owen/fossorial/cli/bin/pangolin up site
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=2
|
RestartSec=2
|
||||||
UMask=0077
|
UMask=0077
|
||||||
@@ -148,7 +164,7 @@ EOF`
|
|||||||
{
|
{
|
||||||
title: t("enableAndStart"),
|
title: t("enableAndStart"),
|
||||||
command: `sudo systemctl daemon-reload
|
command: `sudo systemctl daemon-reload
|
||||||
sudo systemctl enable --now newt`
|
sudo systemctl enable --now pangolin-site`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -156,23 +172,51 @@ sudo systemctl enable --now newt`
|
|||||||
Run: [
|
Run: [
|
||||||
{
|
{
|
||||||
title: t("install"),
|
title: t("install"),
|
||||||
command: `curl -fsSL https://static.pangolin.net/get-newt.sh | bash`
|
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("run"),
|
title: t("run"),
|
||||||
command: `newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
|
command: `pangolin up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
Service: [
|
||||||
|
{
|
||||||
|
title: t("install"),
|
||||||
|
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("run"),
|
||||||
|
command: `sudo pangolin service install site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("check"),
|
||||||
|
command: `sudo pangolin service status site`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
windows: {
|
windows: {
|
||||||
x64: [
|
Run: [
|
||||||
{
|
{
|
||||||
title: t("install"),
|
title: t("install"),
|
||||||
command: `curl -o newt.exe -L "https://github.com/fosrl/newt/releases/download/${version}/newt_windows_amd64.exe"`
|
link: `https://github.com/fosrl/cli/releases/latest/download/pangolin-cli_windows_installer.msi`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("run"),
|
title: t("run"),
|
||||||
command: `newt.exe --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
|
command: `pangolin up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
Service: [
|
||||||
|
{
|
||||||
|
title: t("install"),
|
||||||
|
link: `https://github.com/fosrl/cli/releases/latest/download/pangolin-cli_windows_installer.msi`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("run"),
|
||||||
|
command: `pangolin service install site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("check"),
|
||||||
|
command: `pangolin service status site`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -180,19 +224,20 @@ sudo systemctl enable --now newt`
|
|||||||
"Docker Compose": [
|
"Docker Compose": [
|
||||||
`services:
|
`services:
|
||||||
newt:
|
newt:
|
||||||
image: fosrl/newt
|
image: fosrl/pangolin-cli
|
||||||
container_name: newt
|
container_name: newt
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- PANGOLIN_ENDPOINT=${endpoint}
|
- PANGOLIN_ENDPOINT=${endpoint}
|
||||||
- NEWT_ID=${id}
|
- SITE_ID=${id}
|
||||||
- NEWT_SECRET=${secret}${acceptClientsEnv}`
|
- SITE_SECRET=${secret}${acceptClientsEnv}`
|
||||||
],
|
],
|
||||||
"Docker Run": [
|
"Docker Run": [
|
||||||
`docker run -dit --network host fosrl/newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
|
`docker run -dit --network host fosrl/pangolin-cli up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
kubernetes: {
|
kubernetes: {
|
||||||
|
// we are leaving this using newt until we change it to use the cli
|
||||||
"Helm Chart": [
|
"Helm Chart": [
|
||||||
`helm repo add fossorial https://charts.fossorial.io`,
|
`helm repo add fossorial https://charts.fossorial.io`,
|
||||||
`helm repo update fossorial`,
|
`helm repo update fossorial`,
|
||||||
@@ -216,15 +261,15 @@ sudo systemctl enable --now newt`
|
|||||||
podman: {
|
podman: {
|
||||||
"Podman Quadlet": [
|
"Podman Quadlet": [
|
||||||
`[Unit]
|
`[Unit]
|
||||||
Description=Newt container
|
Description=Pangolin Site Container
|
||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=newt
|
ContainerName=pangolin-site
|
||||||
Image=docker.io/fosrl/newt
|
Image=docker.io/fosrl/pangolin-cli
|
||||||
Environment=PANGOLIN_ENDPOINT=${endpoint}
|
Environment=PANGOLIN_ENDPOINT=${endpoint}
|
||||||
Environment=NEWT_ID=${id}
|
Environment=SITE_ID=${id}
|
||||||
Environment=NEWT_SECRET=${secret}${!acceptClients ? "\nEnvironment=DISABLE_CLIENTS=true" : ""}
|
Environment=SITE_SECRET=${secret}${!acceptClients ? "\nEnvironment=DISABLE_CLIENTS=true" : ""}
|
||||||
# Secret=newt-secret,type=env,target=NEWT_SECRET
|
# Secret=pangolin-secret,type=env,target=SITE_SECRET
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Restart=always
|
Restart=always
|
||||||
@@ -233,12 +278,12 @@ Restart=always
|
|||||||
WantedBy=default.target`
|
WantedBy=default.target`
|
||||||
],
|
],
|
||||||
"Podman Run": [
|
"Podman Run": [
|
||||||
`podman run -dit docker.io/fosrl/newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
|
`podman run -dit docker.io/fosrl/pangolin-cli up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
nixos: {
|
nixos: {
|
||||||
Flake: [
|
Flake: [
|
||||||
`${runAsRootPrefix}nix run 'nixpkgs#fosrl-newt' -- --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
|
`${runAsRootPrefix}nix run 'nixpkgs#pangolin-cli' -- up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -277,9 +322,7 @@ WantedBy=default.target`
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<OptionSelect<string>
|
<OptionSelect<string>
|
||||||
label={
|
label={t("method")}
|
||||||
platform === "windows" ? t("architecture") : t("method")
|
|
||||||
}
|
|
||||||
options={getArchitectures(platform).map((arch) => ({
|
options={getArchitectures(platform).map((arch) => ({
|
||||||
value: arch,
|
value: arch,
|
||||||
label: arch
|
label: arch
|
||||||
@@ -377,14 +420,23 @@ WantedBy=default.target`
|
|||||||
)}
|
)}
|
||||||
<div className="mt-2 space-y-3">
|
<div className="mt-2 space-y-3">
|
||||||
{commands.map((item, index) => {
|
{commands.map((item, index) => {
|
||||||
|
const isLink =
|
||||||
|
typeof item !== "string" && "link" in item;
|
||||||
const commandText =
|
const commandText =
|
||||||
typeof item === "string" ? item : item.command;
|
typeof item === "string"
|
||||||
|
? item
|
||||||
|
: isLink
|
||||||
|
? undefined
|
||||||
|
: item.command;
|
||||||
|
const linkHref = isLink
|
||||||
|
? (item as { link: string }).link
|
||||||
|
: undefined;
|
||||||
const title =
|
const title =
|
||||||
typeof item === "string"
|
typeof item === "string"
|
||||||
? undefined
|
? undefined
|
||||||
: item.title;
|
: item.title;
|
||||||
|
|
||||||
const key = `${title ?? ""}::${commandText}`;
|
const key = `${title ?? ""}::${commandText ?? linkHref}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={key}>
|
<div key={key}>
|
||||||
@@ -393,10 +445,23 @@ WantedBy=default.target`
|
|||||||
{title}
|
{title}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<CopyTextBox
|
{isLink ? (
|
||||||
text={commandText}
|
<Button
|
||||||
outline={true}
|
asChild
|
||||||
/>
|
variant="outline"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<a href={linkHref}>
|
||||||
|
<Download className="h-4 w-4 mr-2" />
|
||||||
|
{t("downloadInstaller")}
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<CopyTextBox
|
||||||
|
text={commandText!}
|
||||||
|
outline={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -456,11 +521,11 @@ function getPlatformName(platformName: Platform) {
|
|||||||
function getArchitectures(platform: Platform) {
|
function getArchitectures(platform: Platform) {
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case "linux":
|
case "linux":
|
||||||
return ["Run", "Systemd Service"];
|
return ["Run", "Auto Systemd Service", "Manual Systemd Service"];
|
||||||
case "macos":
|
case "macos":
|
||||||
return ["Run"];
|
return ["Run", "Service"];
|
||||||
case "windows":
|
case "windows":
|
||||||
return ["x64"];
|
return ["Run", "Service"];
|
||||||
case "docker":
|
case "docker":
|
||||||
return ["Docker Compose", "Docker Run"];
|
return ["Docker Compose", "Docker Run"];
|
||||||
case "kubernetes":
|
case "kubernetes":
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Terminal } from "lucide-react";
|
import { Download, Terminal } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { FaDocker, FaWindows } from "react-icons/fa";
|
import { FaDocker, FaWindows } from "react-icons/fa";
|
||||||
@@ -10,9 +10,13 @@ import {
|
|||||||
SettingsSectionHeader,
|
SettingsSectionHeader,
|
||||||
SettingsSectionTitle
|
SettingsSectionTitle
|
||||||
} from "./Settings";
|
} from "./Settings";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
import { OptionSelect, type OptionSelectOption } from "./OptionSelect";
|
import { OptionSelect, type OptionSelectOption } from "./OptionSelect";
|
||||||
|
|
||||||
export type CommandItem = string | { title: string; command: string };
|
export type CommandItem =
|
||||||
|
| string
|
||||||
|
| { title: string; command: string }
|
||||||
|
| { title: string; link: string };
|
||||||
|
|
||||||
const PLATFORMS = ["unix", "docker", "windows"] as const;
|
const PLATFORMS = ["unix", "docker", "windows"] as const;
|
||||||
|
|
||||||
@@ -22,14 +26,12 @@ export type OlmInstallCommandsProps = {
|
|||||||
id: string;
|
id: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
version?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function OlmInstallCommands({
|
export function OlmInstallCommands({
|
||||||
id,
|
id,
|
||||||
secret,
|
secret,
|
||||||
endpoint,
|
endpoint
|
||||||
version = "latest"
|
|
||||||
}: OlmInstallCommandsProps) {
|
}: OlmInstallCommandsProps) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
@@ -40,14 +42,28 @@ export function OlmInstallCommands({
|
|||||||
|
|
||||||
const commandList: Record<Platform, Record<string, CommandItem[]>> = {
|
const commandList: Record<Platform, Record<string, CommandItem[]>> = {
|
||||||
unix: {
|
unix: {
|
||||||
All: [
|
Run: [
|
||||||
{
|
{
|
||||||
title: t("install"),
|
title: t("install"),
|
||||||
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | sudo bash`
|
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | sudo bash`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("run"),
|
title: t("run"),
|
||||||
command: `sudo pangolin up --id ${id} --secret ${secret} --endpoint ${endpoint} --attach`
|
command: `sudo pangolin up client --id ${id} --secret ${secret} --endpoint ${endpoint} --attach`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
Service: [
|
||||||
|
{
|
||||||
|
title: t("install"),
|
||||||
|
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("run"),
|
||||||
|
command: `sudo pangolin service install client --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("check"),
|
||||||
|
command: `sudo pangolin service status client`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -73,15 +89,28 @@ export function OlmInstallCommands({
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
windows: {
|
windows: {
|
||||||
x64: [
|
Run: [
|
||||||
{
|
{
|
||||||
title: t("install"),
|
title: t("install"),
|
||||||
command: `# Download and run the installer to install Olm first\n
|
link: `https://github.com/fosrl/cli/releases/latest/download/pangolin-cli_windows_installer.msi`
|
||||||
curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/olm_windows_installer.exe"`
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("run"),
|
title: t("run"),
|
||||||
command: `olm.exe --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
command: `pangolin up client --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
Service: [
|
||||||
|
{
|
||||||
|
title: t("install"),
|
||||||
|
link: `https://github.com/fosrl/cli/releases/latest/download/pangolin-cli_windows_installer.msi`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("run"),
|
||||||
|
command: `pangolin service install client --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("check"),
|
||||||
|
command: `pangolin service status client`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -121,9 +150,7 @@ curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/ol
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<OptionSelect<string>
|
<OptionSelect<string>
|
||||||
label={
|
label={t("method")}
|
||||||
platform === "docker" ? t("method") : t("architecture")
|
|
||||||
}
|
|
||||||
options={getArchitectures(platform).map((arch) => ({
|
options={getArchitectures(platform).map((arch) => ({
|
||||||
value: arch,
|
value: arch,
|
||||||
label: arch
|
label: arch
|
||||||
@@ -138,8 +165,17 @@ curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/ol
|
|||||||
<p className="font-semibold mb-3">{t("commands")}</p>
|
<p className="font-semibold mb-3">{t("commands")}</p>
|
||||||
<div className="mt-2 space-y-3">
|
<div className="mt-2 space-y-3">
|
||||||
{commands.map((item, index) => {
|
{commands.map((item, index) => {
|
||||||
|
const isLink =
|
||||||
|
typeof item !== "string" && "link" in item;
|
||||||
const commandText =
|
const commandText =
|
||||||
typeof item === "string" ? item : item.command;
|
typeof item === "string"
|
||||||
|
? item
|
||||||
|
: isLink
|
||||||
|
? undefined
|
||||||
|
: item.command;
|
||||||
|
const linkHref = isLink
|
||||||
|
? (item as { link: string }).link
|
||||||
|
: undefined;
|
||||||
const title =
|
const title =
|
||||||
typeof item === "string"
|
typeof item === "string"
|
||||||
? undefined
|
? undefined
|
||||||
@@ -152,10 +188,23 @@ curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/ol
|
|||||||
{title}
|
{title}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<CopyTextBox
|
{isLink ? (
|
||||||
text={commandText}
|
<Button
|
||||||
outline={true}
|
asChild
|
||||||
/>
|
variant="outline"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<a href={linkHref}>
|
||||||
|
<Download className="h-4 w-4 mr-2" />
|
||||||
|
{t("downloadInstaller")}
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<CopyTextBox
|
||||||
|
text={commandText!}
|
||||||
|
outline={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -169,13 +218,13 @@ curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/ol
|
|||||||
function getArchitectures(platform: Platform) {
|
function getArchitectures(platform: Platform) {
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case "unix":
|
case "unix":
|
||||||
return ["All"];
|
return ["Run", "Service"];
|
||||||
case "windows":
|
case "windows":
|
||||||
return ["x64"];
|
return ["Run", "Service"];
|
||||||
case "docker":
|
case "docker":
|
||||||
return ["Docker Compose", "Docker Run"];
|
return ["Docker Compose", "Docker Run"];
|
||||||
default:
|
default:
|
||||||
return ["x64"];
|
return ["Run"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react-jsx",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@server/*": [
|
"@server/*": [
|
||||||
|
|||||||
Reference in New Issue
Block a user