Skip to content

Commit e597d49

Browse files
committed
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.
1 parent 0a9f5d8 commit e597d49

File tree

9 files changed

+87
-58
lines changed

9 files changed

+87
-58
lines changed

packages/protocol/src/common/util.ts

+19
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,22 @@ export const parse = (arg: string): any => { // tslint:disable-line no-any
9797

9898
return arg ? convert(JSON.parse(arg)) : arg;
9999
};
100+
101+
export const mkdirP = async (path: string): Promise<void> => {
102+
// Since our fills require this file, we can't import them up top or we get
103+
// circular dependency issue.
104+
const { mkdir } = require("fs") as typeof import("fs");
105+
const { promisify } = require("util") as typeof import("util");
106+
const split = path.replace(/^\/*|\/*$/g, "").split("/");
107+
let dir = "";
108+
while (split.length > 0) {
109+
dir += "/" + split.shift();
110+
try {
111+
await promisify(mkdir)(dir);
112+
} catch (error) {
113+
if (error.code !== "EEXIST" && error.code !== "EISDIR") {
114+
throw error;
115+
}
116+
}
117+
}
118+
};

packages/protocol/src/node/server.ts

+7-21
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import * as os from "os";
2-
import * as path from "path";
3-
import { mkdir } from "fs";
4-
import { promisify } from "util";
52
import { logger, field } from "@coder/logger";
63
import { Pong, ClientMessage, WorkingInitMessage, ServerMessage } from "../proto";
74
import { evaluate, ActiveEvaluation } from "./evaluate";
85
import { ForkProvider } from "../common/helpers";
96
import { ReadWriteConnection } from "../common/connection";
7+
import { mkdirP } from "../common/util";
108

119
export interface ServerOptions {
1210
readonly workingDirectory: string;
1311
readonly dataDirectory: string;
12+
readonly cacheDirectory: string;
1413
readonly builtInExtensionsDirectory: string;
1514
readonly fork?: ForkProvider;
1615
}
@@ -42,24 +41,11 @@ export class Server {
4241
return;
4342
}
4443

45-
// Ensure the data directory exists.
46-
const mkdirP = async (path: string): Promise<void> => {
47-
const split = path.replace(/^\/*|\/*$/g, "").split("/");
48-
let dir = "";
49-
while (split.length > 0) {
50-
dir += "/" + split.shift();
51-
try {
52-
await promisify(mkdir)(dir);
53-
} catch (error) {
54-
if (error.code !== "EEXIST" && error.code !== "EISDIR") {
55-
throw error;
56-
}
57-
}
58-
}
59-
};
60-
Promise.all([ mkdirP(path.join(this.options.dataDirectory, "User", "workspaceStorage")) ]).then(() => {
61-
logger.info("Created data directory");
62-
}).catch((error) => {
44+
Promise.all([
45+
mkdirP(this.options.cacheDirectory),
46+
mkdirP(this.options.dataDirectory),
47+
mkdirP(this.options.workingDirectory),
48+
]).catch((error) => {
6349
logger.error(error.message, field("error", error));
6450
});
6551

packages/protocol/test/server.test.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ describe("Server", () => {
44
const dataDirectory = "/tmp/example";
55
const workingDirectory = "/working/dir";
66
const builtInExtensionsDirectory = "/tmp/example";
7+
const cacheDirectory = "/tmp/cache";
78
const client = createClient({
9+
builtInExtensionsDirectory,
10+
cacheDirectory,
811
dataDirectory,
912
workingDirectory,
10-
builtInExtensionsDirectory,
1113
});
1214

1315
it("should get init msg", (done) => {

packages/server/src/cli.ts

+17-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { field, logger } from "@coder/logger";
2+
import { mkdirP } from "@coder/protocol";
23
import { ServerMessage, SharedProcessActiveMessage } from "@coder/protocol/src/proto";
34
import { Command, flags } from "@oclif/command";
45
import { fork, ForkOptions, ChildProcess } from "child_process";
56
import { randomFillSync } from "crypto";
67
import * as fs from "fs";
8+
import * as os from "os";
79
import * as path from "path";
810
import * as WebSocket from "ws";
911
import { createApp } from "./server";
@@ -50,6 +52,20 @@ export class Entry extends Command {
5052
const dataDir = path.resolve(flags["data-dir"] || path.join(dataHome, "code-server"));
5153
const workingDir = path.resolve(args["workdir"]);
5254

55+
if (!fs.existsSync(dataDir)) {
56+
const oldDataDir = path.resolve(path.join(os.homedir(), ".code-server"));
57+
if (fs.existsSync(oldDataDir)) {
58+
fs.renameSync(oldDataDir, dataDir);
59+
logger.info(`Moved data directory from ${oldDataDir} to ${dataDir}`);
60+
}
61+
}
62+
63+
await Promise.all([
64+
mkdirP(cacheHome),
65+
mkdirP(dataDir),
66+
mkdirP(workingDir),
67+
]);
68+
5369
setupNativeModules(dataDir);
5470
const builtInExtensionsDir = path.resolve(buildDir || path.join(__dirname, ".."), "build/extensions");
5571
if (flags["bootstrap-fork"]) {
@@ -74,14 +90,6 @@ export class Entry extends Command {
7490
return requireFork(modulePath, JSON.parse(flags.args!), builtInExtensionsDir);
7591
}
7692

77-
if (!fs.existsSync(dataDir)) {
78-
fs.mkdirSync(dataDir);
79-
}
80-
81-
if (!fs.existsSync(cacheHome)) {
82-
fs.mkdirSync(cacheHome);
83-
}
84-
8593
const logDir = path.join(cacheHome, "code-server/logs", new Date().toISOString().replace(/[-:.TZ]/g, ""));
8694
process.env.VSCODE_LOGS = logDir;
8795

@@ -173,6 +181,7 @@ export class Entry extends Command {
173181
builtInExtensionsDirectory: builtInExtensionsDir,
174182
dataDirectory: dataDir,
175183
workingDirectory: workingDir,
184+
cacheDirectory: cacheHome,
176185
fork: (modulePath: string, args: string[], options: ForkOptions): ChildProcess => {
177186
if (options && options.env && options.env.AMD_ENTRYPOINT) {
178187
return forkModule(options.env.AMD_ENTRYPOINT, args, options, dataDir);
@@ -188,11 +197,6 @@ export class Entry extends Command {
188197
} : undefined,
189198
});
190199

191-
if (!fs.existsSync(workingDir)) {
192-
logger.info("Creating working directory", field("working-dir", workingDir));
193-
fs.mkdirSync(workingDir);
194-
}
195-
196200
logger.info("Starting webserver...", field("host", flags.host), field("port", flags.port));
197201
app.server.listen(flags.port, flags.host);
198202
let clientId = 1;

packages/server/src/server.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { logger, field } from "@coder/logger";
2-
import { ReadWriteConnection } from "@coder/protocol";
2+
import { mkdirP, ReadWriteConnection } from "@coder/protocol";
33
import { Server, ServerOptions } from "@coder/protocol/src/node/server";
44
import * as express from "express";
55
//@ts-ignore
@@ -20,7 +20,7 @@ import safeCompare = require("safe-compare");
2020
import { TunnelCloseCode } from "@coder/tunnel/src/common";
2121
import { handle as handleTunnel } from "@coder/tunnel/src/server";
2222
import { createPortScanner } from "./portScanner";
23-
import { buildDir, isCli } from "./constants";
23+
import { buildDir } from "./constants";
2424

2525
interface CreateAppOptions {
2626
registerMiddleware?: (app: express.Application) => void;
@@ -257,8 +257,9 @@ export const createApp = async (options: CreateAppOptions): Promise<{
257257
req.on("data", (chunk) => {
258258
data.push(chunk);
259259
});
260-
req.on("end", () => {
260+
req.on("end", async () => {
261261
const body = data.join("");
262+
await mkdirP(path.dirname(fullPath));
262263
fs.writeFileSync(fullPath, body);
263264
logger.debug("Wrote resource", field("path", fullPath), field("content-length", body.length));
264265
res.status(200);

packages/vscode/src/client.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Severity from "vs/base/common/severity";
44
import { INotificationService } from "vs/platform/notification/common/notification";
55
import { IStatusbarService, StatusbarAlignment } from "vs/platform/statusbar/common/statusbar";
66
import * as paths from "./fill/paths";
7+
import product from "./fill/product";
78
import "./vscode.scss";
89
import { MenuId, MenuRegistry } from "vs/platform/actions/common/actions";
910
import { CommandsRegistry } from "vs/platform/commands/common/commands";
@@ -14,6 +15,7 @@ class VSClient extends IdeClient {
1415
protected initialize(): Promise<void> {
1516
return this.task("Start workbench", 1000, async (data, sharedData) => {
1617
paths._paths.initialize(data, sharedData);
18+
product.initialize(data);
1719
process.env.SHELL = data.shell;
1820
// At this point everything should be filled, including `os`. `os` also
1921
// relies on `initData` but it listens first so it initialize before this

packages/vscode/src/fill/environmentService.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import * as path from "path";
12
import * as paths from "./paths";
23
import * as environment from "vs/platform/environment/node/environmentService";
34

45
export class EnvironmentService extends environment.EnvironmentService {
56
public get sharedIPCHandle(): string {
67
return paths.getSocketPath() || super.sharedIPCHandle;
78
}
9+
10+
public get extensionsPath(): string {
11+
return path.join(paths.getAppDataPath(), "extensions");
12+
}
813
}
914

1015
const target = environment as typeof environment;

packages/vscode/src/fill/product.ts

+27-16
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,37 @@
1+
import { InitData } from "@coder/protocol";
12
import { IProductConfiguration } from "vs/platform/product/node/product";
23

3-
const product = {
4-
nameShort: "code-server",
5-
nameLong: "code-server",
6-
dataFolderName: ".code-server",
7-
extensionsGallery: {
4+
class Product implements IProductConfiguration {
5+
public nameShort = "code-server";
6+
public nameLong = "code-server";
7+
8+
private _dataFolderName: string | undefined;
9+
public get dataFolderName(): string {
10+
if (!this._dataFolderName) {
11+
throw new Error("trying to access data folder name before it has been set");
12+
}
13+
14+
return this._dataFolderName;
15+
}
16+
17+
public extensionsGallery = {
818
serviceUrl: global && global.process && global.process.env.SERVICE_URL
919
|| process.env.SERVICE_URL
1020
|| "https://v1.extapi.coder.com",
11-
},
12-
extensionExecutionEnvironments: {
21+
};
22+
23+
public extensionExecutionEnvironments = {
1324
"wayou.vscode-todo-highlight": "worker",
1425
"vscodevim.vim": "worker",
1526
"coenraads.bracket-pair-colorizer": "worker",
16-
},
17-
fetchUrl: "",
18-
} as IProductConfiguration;
19-
20-
if (process.env['VSCODE_DEV']) {
21-
product.nameShort += ' Dev';
22-
product.nameLong += ' Dev';
23-
product.dataFolderName += '-dev';
27+
};
28+
29+
public fetchUrl = "";
30+
31+
public initialize(_data: InitData): void {
32+
// Nothing at the moment; dataFolderName isn't used since we override the
33+
// extension path.
34+
}
2435
}
2536

26-
export default product;
37+
export default new Product();

packages/vscode/src/fill/storageDatabase.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { readFile, writeFile, mkdir } from "fs";
1+
import { readFile, writeFile } from "fs";
22
import * as path from "path";
33
import { promisify } from "util";
44
import { IDisposable } from "@coder/disposable";
55
import { logger, field } from "@coder/logger";
6+
import { mkdirP } from "@coder/protocol";
67
import { Event } from "vs/base/common/event";
78
import * as workspaceStorage from "vs/base/node/storage";
89
import * as globalStorage from "vs/platform/storage/node/storageIpc";
@@ -77,9 +78,7 @@ class StorageDatabase implements workspaceStorage.IStorageDatabase {
7778
}
7879

7980
private async save(): Promise<void> {
80-
try {
81-
await promisify(mkdir)(path.dirname(this.path));
82-
} catch (ex) {}
81+
await mkdirP(path.dirname(this.path));
8382

8483
return promisify(writeFile)(this.path, this.content);
8584
}

0 commit comments

Comments
 (0)