addons/isl-server/proxy/existingServerStateFiles.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import fs from 'node:fs';
b69ab319import os from 'node:os';
b69ab3110import path from 'node:path';
b69ab3111import {nullthrows} from 'shared/utils';
b69ab3112import rmtree from './rmtree';
b69ab3113
b69ab3114export type ExistingServerInfo = {
b69ab3115 sensitiveToken: string;
b69ab3116 challengeToken: string;
b69ab3117 logFileLocation: string;
b69ab3118 /** Which command name was used to launch this server instance,
b69ab3119 * so it can be propagated to run further sl commands by the server.
b69ab3120 * Usually, "sl". */
b69ab3121 command: string;
b69ab3122 /**
b69ab3123 * `sl version` string. If the version of sl changes, we shouldn't reuse that server instance,
b69ab3124 * due to potential incompatibilities between the old running server javascript and the new client javascript.
b69ab3125 */
b69ab3126 slVersion: string;
b69ab3127};
b69ab3128
b69ab3129const cacheDir =
b69ab3130 process.platform == 'win32'
b69ab3131 ? path.join(nullthrows(process.env.LOCALAPPDATA), 'cache')
b69ab3132 : process.platform == 'darwin'
b69ab3133 ? path.join(os.homedir(), 'Library/Caches')
b69ab3134 : process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
b69ab3135
b69ab3136/**
b69ab3137 * Per-user cache dir with restrictive permissions.
b69ab3138 * Inside this folder will be a number of files, one per port for an active ISL server.
b69ab3139 */
b69ab3140const savedActiveServerUrlsDirectory = path.join(cacheDir, 'sapling-isl');
b69ab3141
b69ab3142function fileNameForPort(port: number): string {
b69ab3143 return `reusable_server_${port}`;
b69ab3144}
b69ab3145
b69ab3146function isMode700(stat: fs.Stats): boolean {
b69ab3147 // eslint-disable-next-line no-bitwise
b69ab3148 return (stat.mode & 0o777) === 0o700;
b69ab3149}
b69ab3150
b69ab3151/**
b69ab3152 * Make a temp directory with restrictive permissions where we can write existing server information.
b69ab3153 * Ensures directory has proper restrictive mode if the directory already exists.
b69ab3154 */
b69ab3155export async function ensureExistingServerFolder(): Promise<void> {
b69ab3156 await fs.promises.mkdir(savedActiveServerUrlsDirectory, {
b69ab3157 // directory needs rwx
b69ab3158 mode: 0o700,
b69ab3159 recursive: true,
b69ab3160 });
b69ab3161
b69ab3162 const stat = await fs.promises.stat(savedActiveServerUrlsDirectory);
b69ab3163 if (process.platform !== 'win32' && !isMode700(stat)) {
b69ab3164 throw new Error(
b69ab3165 `active servers folder ${savedActiveServerUrlsDirectory} has the wrong permissions: ${stat.mode}`,
b69ab3166 );
b69ab3167 }
b69ab3168 if (stat.isSymbolicLink()) {
b69ab3169 throw new Error(`active servers folder ${savedActiveServerUrlsDirectory} is a symlink`);
b69ab3170 }
b69ab3171}
b69ab3172
b69ab3173export function deleteExistingServerFile(port: number): Promise<void> {
b69ab3174 const folder = path.join(savedActiveServerUrlsDirectory, fileNameForPort(port));
b69ab3175 if (typeof fs.promises.rm === 'function') {
b69ab3176 return fs.promises.rm(folder, {force: true});
b69ab3177 } else {
b69ab3178 return rmtree(folder);
b69ab3179 }
b69ab3180}
b69ab3181
b69ab3182export async function writeExistingServerFile(
b69ab3183 port: number,
b69ab3184 data: ExistingServerInfo,
b69ab3185): Promise<void> {
b69ab3186 await fs.promises.writeFile(
b69ab3187 path.join(savedActiveServerUrlsDirectory, fileNameForPort(port)),
b69ab3188 JSON.stringify(data),
b69ab3189 {encoding: 'utf-8', flag: 'w', mode: 0o600},
b69ab3190 );
b69ab3191}
b69ab3192
b69ab3193export async function readExistingServerFile(port: number): Promise<ExistingServerInfo> {
b69ab3194 // TODO: do we need to verify the permissions of this file?
b69ab3195 const data: string = await fs.promises.readFile(
b69ab3196 path.join(savedActiveServerUrlsDirectory, fileNameForPort(port)),
b69ab3197 {encoding: 'utf-8', flag: 'r'},
b69ab3198 );
b69ab3199 return JSON.parse(data) as ExistingServerInfo;
b69ab31100}