3.3 KB101 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import fs from 'node:fs';
9import os from 'node:os';
10import path from 'node:path';
11import {nullthrows} from 'shared/utils';
12import rmtree from './rmtree';
13
14export type ExistingServerInfo = {
15 sensitiveToken: string;
16 challengeToken: string;
17 logFileLocation: string;
18 /** Which command name was used to launch this server instance,
19 * so it can be propagated to run further sl commands by the server.
20 * Usually, "sl". */
21 command: string;
22 /**
23 * `sl version` string. If the version of sl changes, we shouldn't reuse that server instance,
24 * due to potential incompatibilities between the old running server javascript and the new client javascript.
25 */
26 slVersion: string;
27};
28
29const cacheDir =
30 process.platform == 'win32'
31 ? path.join(nullthrows(process.env.LOCALAPPDATA), 'cache')
32 : process.platform == 'darwin'
33 ? path.join(os.homedir(), 'Library/Caches')
34 : process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
35
36/**
37 * Per-user cache dir with restrictive permissions.
38 * Inside this folder will be a number of files, one per port for an active ISL server.
39 */
40const savedActiveServerUrlsDirectory = path.join(cacheDir, 'sapling-isl');
41
42function fileNameForPort(port: number): string {
43 return `reusable_server_${port}`;
44}
45
46function isMode700(stat: fs.Stats): boolean {
47 // eslint-disable-next-line no-bitwise
48 return (stat.mode & 0o777) === 0o700;
49}
50
51/**
52 * Make a temp directory with restrictive permissions where we can write existing server information.
53 * Ensures directory has proper restrictive mode if the directory already exists.
54 */
55export async function ensureExistingServerFolder(): Promise<void> {
56 await fs.promises.mkdir(savedActiveServerUrlsDirectory, {
57 // directory needs rwx
58 mode: 0o700,
59 recursive: true,
60 });
61
62 const stat = await fs.promises.stat(savedActiveServerUrlsDirectory);
63 if (process.platform !== 'win32' && !isMode700(stat)) {
64 throw new Error(
65 `active servers folder ${savedActiveServerUrlsDirectory} has the wrong permissions: ${stat.mode}`,
66 );
67 }
68 if (stat.isSymbolicLink()) {
69 throw new Error(`active servers folder ${savedActiveServerUrlsDirectory} is a symlink`);
70 }
71}
72
73export function deleteExistingServerFile(port: number): Promise<void> {
74 const folder = path.join(savedActiveServerUrlsDirectory, fileNameForPort(port));
75 if (typeof fs.promises.rm === 'function') {
76 return fs.promises.rm(folder, {force: true});
77 } else {
78 return rmtree(folder);
79 }
80}
81
82export async function writeExistingServerFile(
83 port: number,
84 data: ExistingServerInfo,
85): Promise<void> {
86 await fs.promises.writeFile(
87 path.join(savedActiveServerUrlsDirectory, fileNameForPort(port)),
88 JSON.stringify(data),
89 {encoding: 'utf-8', flag: 'w', mode: 0o600},
90 );
91}
92
93export async function readExistingServerFile(port: number): Promise<ExistingServerInfo> {
94 // TODO: do we need to verify the permissions of this file?
95 const data: string = await fs.promises.readFile(
96 path.join(savedActiveServerUrlsDirectory, fileNameForPort(port)),
97 {encoding: 'utf-8', flag: 'r'},
98 );
99 return JSON.parse(data) as ExistingServerInfo;
100}
101