Fix issues with configuration directories
- Move the old data directory if possible. - Fix extension path to not use a hard-coded path and instead use the data directory. - Create every part of the path during startup. - Create each path when a connection is made as well in case they are deleted while the server is running. - Create every part of the path before saving settings or writing a file using the resource endpoint.
This commit is contained in:
parent
0a9f5d8eee
commit
e597d49912
|
@ -97,3 +97,22 @@ export const parse = (arg: string): any => { // tslint:disable-line no-any
|
||||||
|
|
||||||
return arg ? convert(JSON.parse(arg)) : arg;
|
return arg ? convert(JSON.parse(arg)) : arg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mkdirP = async (path: string): Promise<void> => {
|
||||||
|
// Since our fills require this file, we can't import them up top or we get
|
||||||
|
// circular dependency issue.
|
||||||
|
const { mkdir } = require("fs") as typeof import("fs");
|
||||||
|
const { promisify } = require("util") as typeof import("util");
|
||||||
|
const split = path.replace(/^\/*|\/*$/g, "").split("/");
|
||||||
|
let dir = "";
|
||||||
|
while (split.length > 0) {
|
||||||
|
dir += "/" + split.shift();
|
||||||
|
try {
|
||||||
|
await promisify(mkdir)(dir);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "EEXIST" && error.code !== "EISDIR") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
import * as path from "path";
|
|
||||||
import { mkdir } from "fs";
|
|
||||||
import { promisify } from "util";
|
|
||||||
import { logger, field } from "@coder/logger";
|
import { logger, field } from "@coder/logger";
|
||||||
import { Pong, ClientMessage, WorkingInitMessage, ServerMessage } from "../proto";
|
import { Pong, ClientMessage, WorkingInitMessage, ServerMessage } from "../proto";
|
||||||
import { evaluate, ActiveEvaluation } from "./evaluate";
|
import { evaluate, ActiveEvaluation } from "./evaluate";
|
||||||
import { ForkProvider } from "../common/helpers";
|
import { ForkProvider } from "../common/helpers";
|
||||||
import { ReadWriteConnection } from "../common/connection";
|
import { ReadWriteConnection } from "../common/connection";
|
||||||
|
import { mkdirP } from "../common/util";
|
||||||
|
|
||||||
export interface ServerOptions {
|
export interface ServerOptions {
|
||||||
readonly workingDirectory: string;
|
readonly workingDirectory: string;
|
||||||
readonly dataDirectory: string;
|
readonly dataDirectory: string;
|
||||||
|
readonly cacheDirectory: string;
|
||||||
readonly builtInExtensionsDirectory: string;
|
readonly builtInExtensionsDirectory: string;
|
||||||
readonly fork?: ForkProvider;
|
readonly fork?: ForkProvider;
|
||||||
}
|
}
|
||||||
|
@ -42,24 +41,11 @@ export class Server {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the data directory exists.
|
Promise.all([
|
||||||
const mkdirP = async (path: string): Promise<void> => {
|
mkdirP(this.options.cacheDirectory),
|
||||||
const split = path.replace(/^\/*|\/*$/g, "").split("/");
|
mkdirP(this.options.dataDirectory),
|
||||||
let dir = "";
|
mkdirP(this.options.workingDirectory),
|
||||||
while (split.length > 0) {
|
]).catch((error) => {
|
||||||
dir += "/" + split.shift();
|
|
||||||
try {
|
|
||||||
await promisify(mkdir)(dir);
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "EEXIST" && error.code !== "EISDIR") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Promise.all([ mkdirP(path.join(this.options.dataDirectory, "User", "workspaceStorage")) ]).then(() => {
|
|
||||||
logger.info("Created data directory");
|
|
||||||
}).catch((error) => {
|
|
||||||
logger.error(error.message, field("error", error));
|
logger.error(error.message, field("error", error));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,12 @@ describe("Server", () => {
|
||||||
const dataDirectory = "/tmp/example";
|
const dataDirectory = "/tmp/example";
|
||||||
const workingDirectory = "/working/dir";
|
const workingDirectory = "/working/dir";
|
||||||
const builtInExtensionsDirectory = "/tmp/example";
|
const builtInExtensionsDirectory = "/tmp/example";
|
||||||
|
const cacheDirectory = "/tmp/cache";
|
||||||
const client = createClient({
|
const client = createClient({
|
||||||
|
builtInExtensionsDirectory,
|
||||||
|
cacheDirectory,
|
||||||
dataDirectory,
|
dataDirectory,
|
||||||
workingDirectory,
|
workingDirectory,
|
||||||
builtInExtensionsDirectory,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get init msg", (done) => {
|
it("should get init msg", (done) => {
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { field, logger } from "@coder/logger";
|
import { field, logger } from "@coder/logger";
|
||||||
|
import { mkdirP } from "@coder/protocol";
|
||||||
import { ServerMessage, SharedProcessActiveMessage } from "@coder/protocol/src/proto";
|
import { ServerMessage, SharedProcessActiveMessage } from "@coder/protocol/src/proto";
|
||||||
import { Command, flags } from "@oclif/command";
|
import { Command, flags } from "@oclif/command";
|
||||||
import { fork, ForkOptions, ChildProcess } from "child_process";
|
import { fork, ForkOptions, ChildProcess } from "child_process";
|
||||||
import { randomFillSync } from "crypto";
|
import { randomFillSync } from "crypto";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import * as os from "os";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as WebSocket from "ws";
|
import * as WebSocket from "ws";
|
||||||
import { createApp } from "./server";
|
import { createApp } from "./server";
|
||||||
|
@ -50,6 +52,20 @@ export class Entry extends Command {
|
||||||
const dataDir = path.resolve(flags["data-dir"] || path.join(dataHome, "code-server"));
|
const dataDir = path.resolve(flags["data-dir"] || path.join(dataHome, "code-server"));
|
||||||
const workingDir = path.resolve(args["workdir"]);
|
const workingDir = path.resolve(args["workdir"]);
|
||||||
|
|
||||||
|
if (!fs.existsSync(dataDir)) {
|
||||||
|
const oldDataDir = path.resolve(path.join(os.homedir(), ".code-server"));
|
||||||
|
if (fs.existsSync(oldDataDir)) {
|
||||||
|
fs.renameSync(oldDataDir, dataDir);
|
||||||
|
logger.info(`Moved data directory from ${oldDataDir} to ${dataDir}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
mkdirP(cacheHome),
|
||||||
|
mkdirP(dataDir),
|
||||||
|
mkdirP(workingDir),
|
||||||
|
]);
|
||||||
|
|
||||||
setupNativeModules(dataDir);
|
setupNativeModules(dataDir);
|
||||||
const builtInExtensionsDir = path.resolve(buildDir || path.join(__dirname, ".."), "build/extensions");
|
const builtInExtensionsDir = path.resolve(buildDir || path.join(__dirname, ".."), "build/extensions");
|
||||||
if (flags["bootstrap-fork"]) {
|
if (flags["bootstrap-fork"]) {
|
||||||
|
@ -74,14 +90,6 @@ export class Entry extends Command {
|
||||||
return requireFork(modulePath, JSON.parse(flags.args!), builtInExtensionsDir);
|
return requireFork(modulePath, JSON.parse(flags.args!), builtInExtensionsDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(dataDir)) {
|
|
||||||
fs.mkdirSync(dataDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(cacheHome)) {
|
|
||||||
fs.mkdirSync(cacheHome);
|
|
||||||
}
|
|
||||||
|
|
||||||
const logDir = path.join(cacheHome, "code-server/logs", new Date().toISOString().replace(/[-:.TZ]/g, ""));
|
const logDir = path.join(cacheHome, "code-server/logs", new Date().toISOString().replace(/[-:.TZ]/g, ""));
|
||||||
process.env.VSCODE_LOGS = logDir;
|
process.env.VSCODE_LOGS = logDir;
|
||||||
|
|
||||||
|
@ -173,6 +181,7 @@ export class Entry extends Command {
|
||||||
builtInExtensionsDirectory: builtInExtensionsDir,
|
builtInExtensionsDirectory: builtInExtensionsDir,
|
||||||
dataDirectory: dataDir,
|
dataDirectory: dataDir,
|
||||||
workingDirectory: workingDir,
|
workingDirectory: workingDir,
|
||||||
|
cacheDirectory: cacheHome,
|
||||||
fork: (modulePath: string, args: string[], options: ForkOptions): ChildProcess => {
|
fork: (modulePath: string, args: string[], options: ForkOptions): ChildProcess => {
|
||||||
if (options && options.env && options.env.AMD_ENTRYPOINT) {
|
if (options && options.env && options.env.AMD_ENTRYPOINT) {
|
||||||
return forkModule(options.env.AMD_ENTRYPOINT, args, options, dataDir);
|
return forkModule(options.env.AMD_ENTRYPOINT, args, options, dataDir);
|
||||||
|
@ -188,11 +197,6 @@ export class Entry extends Command {
|
||||||
} : undefined,
|
} : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!fs.existsSync(workingDir)) {
|
|
||||||
logger.info("Creating working directory", field("working-dir", workingDir));
|
|
||||||
fs.mkdirSync(workingDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Starting webserver...", field("host", flags.host), field("port", flags.port));
|
logger.info("Starting webserver...", field("host", flags.host), field("port", flags.port));
|
||||||
app.server.listen(flags.port, flags.host);
|
app.server.listen(flags.port, flags.host);
|
||||||
let clientId = 1;
|
let clientId = 1;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { logger, field } from "@coder/logger";
|
import { logger, field } from "@coder/logger";
|
||||||
import { ReadWriteConnection } from "@coder/protocol";
|
import { mkdirP, ReadWriteConnection } from "@coder/protocol";
|
||||||
import { Server, ServerOptions } from "@coder/protocol/src/node/server";
|
import { Server, ServerOptions } from "@coder/protocol/src/node/server";
|
||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
|
@ -20,7 +20,7 @@ import safeCompare = require("safe-compare");
|
||||||
import { TunnelCloseCode } from "@coder/tunnel/src/common";
|
import { TunnelCloseCode } from "@coder/tunnel/src/common";
|
||||||
import { handle as handleTunnel } from "@coder/tunnel/src/server";
|
import { handle as handleTunnel } from "@coder/tunnel/src/server";
|
||||||
import { createPortScanner } from "./portScanner";
|
import { createPortScanner } from "./portScanner";
|
||||||
import { buildDir, isCli } from "./constants";
|
import { buildDir } from "./constants";
|
||||||
|
|
||||||
interface CreateAppOptions {
|
interface CreateAppOptions {
|
||||||
registerMiddleware?: (app: express.Application) => void;
|
registerMiddleware?: (app: express.Application) => void;
|
||||||
|
@ -257,8 +257,9 @@ export const createApp = async (options: CreateAppOptions): Promise<{
|
||||||
req.on("data", (chunk) => {
|
req.on("data", (chunk) => {
|
||||||
data.push(chunk);
|
data.push(chunk);
|
||||||
});
|
});
|
||||||
req.on("end", () => {
|
req.on("end", async () => {
|
||||||
const body = data.join("");
|
const body = data.join("");
|
||||||
|
await mkdirP(path.dirname(fullPath));
|
||||||
fs.writeFileSync(fullPath, body);
|
fs.writeFileSync(fullPath, body);
|
||||||
logger.debug("Wrote resource", field("path", fullPath), field("content-length", body.length));
|
logger.debug("Wrote resource", field("path", fullPath), field("content-length", body.length));
|
||||||
res.status(200);
|
res.status(200);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Severity from "vs/base/common/severity";
|
||||||
import { INotificationService } from "vs/platform/notification/common/notification";
|
import { INotificationService } from "vs/platform/notification/common/notification";
|
||||||
import { IStatusbarService, StatusbarAlignment } from "vs/platform/statusbar/common/statusbar";
|
import { IStatusbarService, StatusbarAlignment } from "vs/platform/statusbar/common/statusbar";
|
||||||
import * as paths from "./fill/paths";
|
import * as paths from "./fill/paths";
|
||||||
|
import product from "./fill/product";
|
||||||
import "./vscode.scss";
|
import "./vscode.scss";
|
||||||
import { MenuId, MenuRegistry } from "vs/platform/actions/common/actions";
|
import { MenuId, MenuRegistry } from "vs/platform/actions/common/actions";
|
||||||
import { CommandsRegistry } from "vs/platform/commands/common/commands";
|
import { CommandsRegistry } from "vs/platform/commands/common/commands";
|
||||||
|
@ -14,6 +15,7 @@ class VSClient extends IdeClient {
|
||||||
protected initialize(): Promise<void> {
|
protected initialize(): Promise<void> {
|
||||||
return this.task("Start workbench", 1000, async (data, sharedData) => {
|
return this.task("Start workbench", 1000, async (data, sharedData) => {
|
||||||
paths._paths.initialize(data, sharedData);
|
paths._paths.initialize(data, sharedData);
|
||||||
|
product.initialize(data);
|
||||||
process.env.SHELL = data.shell;
|
process.env.SHELL = data.shell;
|
||||||
// At this point everything should be filled, including `os`. `os` also
|
// At this point everything should be filled, including `os`. `os` also
|
||||||
// relies on `initData` but it listens first so it initialize before this
|
// relies on `initData` but it listens first so it initialize before this
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import * as path from "path";
|
||||||
import * as paths from "./paths";
|
import * as paths from "./paths";
|
||||||
import * as environment from "vs/platform/environment/node/environmentService";
|
import * as environment from "vs/platform/environment/node/environmentService";
|
||||||
|
|
||||||
|
@ -5,6 +6,10 @@ export class EnvironmentService extends environment.EnvironmentService {
|
||||||
public get sharedIPCHandle(): string {
|
public get sharedIPCHandle(): string {
|
||||||
return paths.getSocketPath() || super.sharedIPCHandle;
|
return paths.getSocketPath() || super.sharedIPCHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get extensionsPath(): string {
|
||||||
|
return path.join(paths.getAppDataPath(), "extensions");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = environment as typeof environment;
|
const target = environment as typeof environment;
|
||||||
|
|
|
@ -1,26 +1,37 @@
|
||||||
|
import { InitData } from "@coder/protocol";
|
||||||
import { IProductConfiguration } from "vs/platform/product/node/product";
|
import { IProductConfiguration } from "vs/platform/product/node/product";
|
||||||
|
|
||||||
const product = {
|
class Product implements IProductConfiguration {
|
||||||
nameShort: "code-server",
|
public nameShort = "code-server";
|
||||||
nameLong: "code-server",
|
public nameLong = "code-server";
|
||||||
dataFolderName: ".code-server",
|
|
||||||
extensionsGallery: {
|
private _dataFolderName: string | undefined;
|
||||||
|
public get dataFolderName(): string {
|
||||||
|
if (!this._dataFolderName) {
|
||||||
|
throw new Error("trying to access data folder name before it has been set");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._dataFolderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public extensionsGallery = {
|
||||||
serviceUrl: global && global.process && global.process.env.SERVICE_URL
|
serviceUrl: global && global.process && global.process.env.SERVICE_URL
|
||||||
|| process.env.SERVICE_URL
|
|| process.env.SERVICE_URL
|
||||||
|| "https://v1.extapi.coder.com",
|
|| "https://v1.extapi.coder.com",
|
||||||
},
|
};
|
||||||
extensionExecutionEnvironments: {
|
|
||||||
|
public extensionExecutionEnvironments = {
|
||||||
"wayou.vscode-todo-highlight": "worker",
|
"wayou.vscode-todo-highlight": "worker",
|
||||||
"vscodevim.vim": "worker",
|
"vscodevim.vim": "worker",
|
||||||
"coenraads.bracket-pair-colorizer": "worker",
|
"coenraads.bracket-pair-colorizer": "worker",
|
||||||
},
|
};
|
||||||
fetchUrl: "",
|
|
||||||
} as IProductConfiguration;
|
|
||||||
|
|
||||||
if (process.env['VSCODE_DEV']) {
|
public fetchUrl = "";
|
||||||
product.nameShort += ' Dev';
|
|
||||||
product.nameLong += ' Dev';
|
public initialize(_data: InitData): void {
|
||||||
product.dataFolderName += '-dev';
|
// Nothing at the moment; dataFolderName isn't used since we override the
|
||||||
|
// extension path.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default product;
|
export default new Product();
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { readFile, writeFile, mkdir } from "fs";
|
import { readFile, writeFile } from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
import { IDisposable } from "@coder/disposable";
|
import { IDisposable } from "@coder/disposable";
|
||||||
import { logger, field } from "@coder/logger";
|
import { logger, field } from "@coder/logger";
|
||||||
|
import { mkdirP } from "@coder/protocol";
|
||||||
import { Event } from "vs/base/common/event";
|
import { Event } from "vs/base/common/event";
|
||||||
import * as workspaceStorage from "vs/base/node/storage";
|
import * as workspaceStorage from "vs/base/node/storage";
|
||||||
import * as globalStorage from "vs/platform/storage/node/storageIpc";
|
import * as globalStorage from "vs/platform/storage/node/storageIpc";
|
||||||
|
@ -77,9 +78,7 @@ class StorageDatabase implements workspaceStorage.IStorageDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async save(): Promise<void> {
|
private async save(): Promise<void> {
|
||||||
try {
|
await mkdirP(path.dirname(this.path));
|
||||||
await promisify(mkdir)(path.dirname(this.path));
|
|
||||||
} catch (ex) {}
|
|
||||||
|
|
||||||
return promisify(writeFile)(this.path, this.content);
|
return promisify(writeFile)(this.path, this.content);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue