未验证 提交 c870398c 编写于 作者: A Asher

Switch to loose files

For #1306.
上级 f76c809f
......@@ -3,6 +3,6 @@
build
dist*
out*
release
release*
node_modules
binaries
import { Binary } from "@coder/nbin"
import * as cp from "child_process"
import * as fs from "fs-extra"
import * as os from "os"
import Bundler from "parcel-bundler"
import * as path from "path"
import * as util from "util"
enum Task {
Binary = "binary",
Package = "package",
Build = "build",
Watch = "watch",
}
......@@ -16,10 +12,8 @@ enum Task {
class Builder {
private readonly rootPath = path.resolve(__dirname, "..")
private readonly vscodeSourcePath = path.join(this.rootPath, "lib/vscode")
private readonly binariesPath = path.join(this.rootPath, "binaries")
private readonly buildPath = path.join(this.rootPath, "build")
private readonly codeServerVersion: string
private _target?: "darwin" | "alpine" | "linux"
private currentTask?: Task
public constructor() {
......@@ -66,17 +60,9 @@ class Builder {
throw new Error("No task provided")
}
const arch = this.ensureArgument("arch", os.arch().replace(/^x/, "x86_"))
const target = this.ensureArgument("target", await this.target())
const binaryName = `code-server-${this.codeServerVersion}-${target}-${arch}`
switch (task) {
case Task.Watch:
return this.watch()
case Task.Binary:
return this.binary(binaryName)
case Task.Package:
return this.package(binaryName)
case Task.Build:
return this.build()
default:
......@@ -84,29 +70,6 @@ class Builder {
}
}
/**
* Get the target of the system.
*/
private async target(): Promise<"darwin" | "alpine" | "linux"> {
if (!this._target) {
if (os.platform() === "darwin" || (process.env.OSTYPE && /^darwin/.test(process.env.OSTYPE))) {
this._target = "darwin"
} else {
// Alpine's ldd doesn't have a version flag but if you use an invalid flag
// (like --version) it outputs the version to stderr and exits with 1.
const result = await util
.promisify(cp.exec)("ldd --version")
.catch((error) => ({ stderr: error.message, stdout: "" }))
if (/musl/.test(result.stderr) || /musl/.test(result.stdout)) {
this._target = "alpine"
} else {
this._target = "linux"
}
}
}
return this._target
}
/**
* Make sure the argument is set. Display the value if it is.
*/
......@@ -256,114 +219,6 @@ class Builder {
}
}
/**
* Bundles the built code into a binary.
*/
private async binary(binaryName: string): Promise<void> {
const prependCode = async (code: string, relativeFilePath: string): Promise<void> => {
const filePath = path.join(this.buildPath, relativeFilePath)
const content = await fs.readFile(filePath, "utf8")
if (!content.startsWith(code)) {
await fs.writeFile(filePath, code + content)
}
}
// Unpack binaries since we can't run them within the binary.
const unpack = `
if (global.NBIN_LOADED) {
try {
const fs = require("fs-extra")
const rg = require("vscode-ripgrep")
const path = require("path")
const { logger, field } = require("@coder/logger")
const unpackExecutables = async (filePath, destination) => {
logger.debug("unpacking executable", field("src", filePath), field("dest", destination))
await fs.mkdirp(path.dirname(destination))
if (filePath && !(await fs.pathExists(destination))) {
await fs.writeFile(destination, await fs.readFile(filePath))
await fs.chmod(destination, "755")
}
}
unpackExecutables(rg.binaryRgPath, rg.rgPath).catch((error) => console.warn(error))
} catch (error) {
console.warn(error)
}
}
`
// Enable finding files within the binary.
const loader = `
if (!global.NBIN_LOADED) {
try {
const nbin = require("nbin")
nbin.shimNativeFs("${this.buildPath}")
global.NBIN_LOADED = true
require("@coder/logger").logger.debug("shimmed file system at ${this.buildPath}")
const path = require("path")
const rg = require("vscode-ripgrep")
rg.binaryRgPath = rg.rgPath
rg.rgPath = path.join(require("os").tmpdir(), "code-server/binaries", path.basename(rg.binaryRgPath))
} catch (error) {
// Most likely not in the binary.
}
}
`
await this.task("Prepending nbin loader", () => {
return Promise.all([
prependCode(loader, "out/node/entry.js"),
prependCode(loader, "lib/vscode/out/vs/server/entry.js"),
prependCode(loader + unpack, "lib/vscode/out/vs/server/fork.js"),
prependCode(loader, "lib/vscode/out/bootstrap-fork.js"),
prependCode(loader, "lib/vscode/extensions/node_modules/typescript/lib/tsserver.js"),
])
})
const bin = new Binary({
mainFile: path.join(this.buildPath, "out/node/entry.js"),
target: await this.target(),
})
bin.writeFiles(path.join(this.buildPath, "**"))
await fs.mkdirp(this.binariesPath)
const binaryPath = path.join(this.binariesPath, binaryName)
await fs.writeFile(binaryPath, await bin.build())
await fs.chmod(binaryPath, "755")
this.log(`binary: ${binaryPath}`)
}
/**
* Package the binary into a release archive.
*/
private async package(binaryName: string): Promise<void> {
const releasePath = path.join(this.rootPath, "release")
const archivePath = path.join(releasePath, binaryName)
await fs.remove(archivePath)
await fs.mkdirp(archivePath)
await fs.copyFile(path.join(this.binariesPath, binaryName), path.join(archivePath, "code-server"))
await fs.copyFile(path.join(this.rootPath, "README.md"), path.join(archivePath, "README.md"))
await fs.copyFile(path.join(this.vscodeSourcePath, "LICENSE.txt"), path.join(archivePath, "LICENSE.txt"))
await fs.copyFile(
path.join(this.vscodeSourcePath, "ThirdPartyNotices.txt"),
path.join(archivePath, "ThirdPartyNotices.txt"),
)
if ((await this.target()) === "darwin") {
await util.promisify(cp.exec)(`zip -r "${binaryName}.zip" "${binaryName}"`, { cwd: releasePath })
this.log(`archive: ${archivePath}.zip`)
} else {
await util.promisify(cp.exec)(`tar -czf "${binaryName}.tar.gz" "${binaryName}"`, { cwd: releasePath })
this.log(`archive: ${archivePath}.tar.gz`)
}
}
private async watch(): Promise<void> {
let server: cp.ChildProcess | undefined
const restartServer = (): void => {
......
#!/usr/bin/env sh
# code-server.sh -- Run code-server with the bundled Node binary.
cd "$(dirname "$0")" || exit 1
./node ./out/node/entry.js "$@"
......@@ -16,7 +16,7 @@ RUN apt-get install -y dumb-init sudo
RUN apt-get install -y man procps vim nano htop ssh git
RUN adduser --gecos '' --disabled-password coder && \
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
RUN curl -SsL https://github.com/boxboat/fixuid/releases/download/v0.4/fixuid-0.4-linux-amd64.tar.gz | tar -C /usr/local/bin -xzf - && \
chown root:root /usr/local/bin/fixuid && \
......@@ -27,11 +27,12 @@ RUN curl -SsL https://github.com/boxboat/fixuid/releases/download/v0.4/fixuid-0.
RUN rm -rf /var/lib/apt/lists/*
COPY release/code-server*.tar.gz /tmp
RUN cd /tmp && tar -xzf code-server*.tar.gz && \
cp code-server*/code-server /usr/local/bin/code-server
RUN rm -rf /tmp/*
RUN cd /tmp && tar -xzf code-server*.tar.gz && rm code-server*.tar.gz && \
mv code-server* /usr/local/lib/code-server && \
sed 's/\$0/\/usr\/local\/lib\/code-server\/code-server/g' /usr/local/lib/code-server/code-server > /usr/local/bin/code-server && \
chmod +x /usr/local/bin/code-server
EXPOSE 8080
USER coder
WORKDIR /home/coder
ENTRYPOINT ["dumb-init", "fixuid", "-q", "code-server", "--host", "0.0.0.0", "."]
ENTRYPOINT ["dumb-init", "fixuid", "-q", "/usr/local/bin/code-server", "--host", "0.0.0.0", "."]
......@@ -3,52 +3,72 @@
set -euo pipefail
# This script assumes that yarn has already ran.
function main() {
cd "$(dirname "${0}")/.."
source ./ci/lib.sh
function package() {
local target
target=$(uname | tr '[:upper:]' '[:lower:]')
if [[ $target == "linux" ]]; then
# Alpine's ldd doesn't have a version flag but if you use an invalid flag
# (like --version) it outputs the version to stderr and exits with 1.
local ldd_output
ldd_output=$(ldd --version 2>&1 || true)
if echo "$ldd_output" | grep -iq musl; then
target="alpine"
fi
fi
set_version
local arch
arch="$(uname -m)"
echo -n "Creating release..."
# Always minify and package on CI since that's when releases are pushed.
cp "$(command -v node)" ./build
cp README.md ./build
cp LICENSE.txt ./build
cp ./lib/vscode/ThirdPartyNotices.txt ./build
cp ./ci/code-server.sh ./build/code-server
local archive_name="code-server-$VERSION-$target-$arch"
mkdir -p ./release
local ext
if [[ $target == "linux" ]]; then
ext=".tar.gz"
tar -czf "release/$archive_name$ext" --transform "s/^\.\/build/$archive_name/" ./build
else
mv ./build "./$archive_name"
ext=".zip"
zip -r "release/$archive_name$ext" ./code-server
mv "./$archive_name" ./build
fi
echo "done (release/$archive_name)"
mkdir -p "./release-upload/$VERSION"
cp "./release/$archive_name$ext" "./release-upload/$VERSION/$target-$arch.tar.gz"
mkdir -p "./release-upload/latest"
cp "./release/$archive_name$ext" "./release-upload/latest/$target-$arch.tar.gz"
}
# This script assumes that yarn has already ran.
function build() {
# Always minify and package on CI.
if [[ ${CI:-} ]]; then
export MINIFY="true"
export PACKAGE="true"
fi
yarn build
yarn binary
if [[ -n ${PACKAGE:-} ]]; then
yarn package
fi
}
function main() {
cd "$(dirname "${0}")/.."
source ./ci/lib.sh
set_version
build
cd binaries
if [[ -n ${STRIP_BIN_TARGET:-} ]]; then
# In this case provide plainly named binaries.
for binary in code-server*; do
echo "Moving $binary to code-server"
mv "$binary" code-server
done
elif [[ -n ${DRONE_TAG:-} || -n ${TRAVIS_TAG:-} ]]; then
# Prepare directory for uploading binaries on release.
for binary in code-server*; do
mkdir -p "../binary-upload"
local prefix="code-server-$VERSION-"
local target="${binary#$prefix}"
if [[ $target == "linux-x86_64" ]]; then
echo "Copying $binary to ../binary-upload/latest-linux"
cp "$binary" "../binary-upload/latest-linux"
fi
local gcp_dir
gcp_dir="../binary-upload/releases/$VERSION/$target"
mkdir -p "$gcp_dir"
echo "Copying $binary to $gcp_dir/code-server"
cp "$binary" "$gcp_dir/code-server"
done
if [[ ${CI:-} ]]; then
package
fi
}
......
......@@ -179,22 +179,6 @@ index 5a631e0b39..4114bd9287 100644
} else if (typeof process === 'object') {
_isWindows = (process.platform === 'win32');
_isMacintosh = (process.platform === 'darwin');
diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts
index c52f7b3774..967943d27b 100644
--- a/src/vs/base/common/processes.ts
+++ b/src/vs/base/common/processes.ts
@@ -110,7 +110,10 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve
/^ELECTRON_.+$/,
/^GOOGLE_API_KEY$/,
/^VSCODE_.+$/,
- /^SNAP(|_.*)$/
+ /^SNAP(|_.*)$/,
+ // NOTE@coder: Add our variables.
+ /^NBIN_BYPASS$/,
+ /^LAUNCH_VSCODE$/
];
const envKeys = Object.keys(env);
envKeys
diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js
index 2c64061da7..c0ef8faedd 100644
--- a/src/vs/base/node/languagePacks.js
......
......@@ -25,6 +25,5 @@ works internally.
```shell
yarn
yarn build
node build/out/entry.js # You can run the built JavaScript with Node.
yarn binary # Or you can package it into a binary.
node build/out/entry.js # Run the built JavaScript with Node.
```
......@@ -12,12 +12,9 @@
"fmt": "ci/fmt.sh",
"runner": "cd ./ci && NODE_OPTIONS=--max_old_space_size=32384 ts-node ./build.ts",
"build": "yarn runner build",
"watch": "yarn runner watch",
"binary": "yarn runner binary",
"package": "yarn runner package"
"watch": "yarn runner watch"
},
"devDependencies": {
"@coder/nbin": "^1.2.7",
"@types/adm-zip": "^0.4.32",
"@types/fs-extra": "^8.0.1",
"@types/mocha": "^5.2.7",
......
......@@ -200,47 +200,18 @@ export class UpdateHttpProvider extends HttpProvider {
}
logger.debug("Downloaded update", field("path", downloadPath))
// The archive should have a code-server directory at the top level.
try {
const stat = await fs.stat(path.join(downloadPath, "code-server"))
if (!stat.isDirectory()) {
throw new Error("ENOENT")
}
} catch (error) {
throw new Error("no code-server directory found in downloaded archive")
}
// The archive might contain a binary or it might contain loose files.
// This is probably stupid but just check if `node` exists since we
// package it with the loose files.
const isBinary = !(await fs.pathExists(path.join(downloadPath, "code-server/node")))
// The archive should have a directory inside at the top level with the
// same name as the archive.
const directoryPath = path.join(downloadPath, path.basename(downloadPath))
await fs.stat(directoryPath)
// In the binary we need to replace the binary, otherwise we can replace
// the directory.
if (!targetPath) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
targetPath = (process.versions as any).nbin ? process.argv[0] : path.resolve(__dirname, "../../../")
targetPath = path.resolve(__dirname, "../../../")
}
// If we're currently running a binary it must be unlinked to avoid
// ETXTBSY.
try {
const stat = await fs.stat(targetPath)
if (stat.isFile()) {
await fs.unlink(targetPath)
}
} catch (error) {
if (error.code !== "ENOENT") {
throw error
}
}
logger.debug("Replacing files", field("target", targetPath), field("isBinary", isBinary))
if (isBinary) {
await fs.move(path.join(downloadPath, "code-server/code-server"), targetPath, { overwrite: true })
} else {
await fs.move(path.join(downloadPath, "code-server"), targetPath, { overwrite: true })
}
logger.debug("Replacing files", field("target", targetPath))
await fs.move(directoryPath, targetPath, { overwrite: true })
await fs.remove(downloadPath)
......
......@@ -13,9 +13,6 @@ import { generateCertificate, generatePassword, hash, open } from "./util"
import { ipcMain, wrap } from "./wrapper"
const main = async (args: Args): Promise<void> => {
// For any future forking bypass nbin and drop straight to Node.
process.env.NBIN_BYPASS = "true"
const auth = args.auth || AuthType.Password
const originalPassword = auth === AuthType.Password && (process.env.PASSWORD || (await generatePassword()))
......@@ -121,7 +118,6 @@ if (args.help) {
}
process.exit(0)
} else if (args["list-extensions"] || args["install-extension"] || args["uninstall-extension"]) {
process.env.NBIN_BYPASS = "true"
logger.debug("forking vs code cli...")
const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], {
env: {
......
import { logger, field } from "@coder/logger"
import * as cp from "child_process"
import * as fs from "fs-extra"
import * as path from "path"
import { Emitter } from "../common/emitter"
interface HandshakeMessage {
......@@ -200,22 +198,12 @@ export class WrapperProcess {
nodeOptions += ` --max_old_space_size=${(this.options && this.options.maxMemory) || 2048}`
}
// This is to handle the upgrade from binary release to loose release. This
// will only work for users that restart code-server entirely between
// upgrading to this version and the loose file version since this is the
// wrapper code that does not get updated. The hope is that it'll be likely
// for that to happen to most users in that timeframe to minimize disruption
// when loose files are release. This can be removed with that release.
const bundledNodePath = path.join(process.argv[0], "node")
const binary = (await fs.pathExists(bundledNodePath)) ? bundledNodePath : process.argv[0]
// Use spawn (instead of fork) to use the new binary in case it was updated.
return cp.spawn(binary, process.argv.slice(1), {
return cp.spawn(process.argv[0], process.argv.slice(1), {
env: {
...process.env,
CODE_SERVER_PARENT_PID: process.pid.toString(),
NODE_OPTIONS: nodeOptions,
NBIN_BYPASS: undefined,
},
stdio: ["inherit", "inherit", "inherit", "ipc"],
})
......
......@@ -2,7 +2,6 @@ import zip from "adm-zip"
import * as assert from "assert"
import * as fs from "fs-extra"
import * as http from "http"
import * as os from "os"
import * as path from "path"
import * as tar from "tar-fs"
import * as zlib from "zlib"
......@@ -12,12 +11,7 @@ import { SettingsProvider, UpdateSettings } from "../src/node/settings"
import { tmpdir } from "../src/node/util"
describe("update", () => {
const archivePaths = {
loose: path.join(tmpdir, "tests/updates/code-server-loose-source"),
binary: path.join(tmpdir, "tests/updates/code-server-binary-source"),
}
let useBinary = false
const archivePath = path.join(tmpdir, "tests/updates/code-server-loose-source")
let version = "1.0.0"
let spy: string[] = []
const server = http.createServer((request: http.IncomingMessage, response: http.ServerResponse) => {
......@@ -33,8 +27,7 @@ describe("update", () => {
return response.end(JSON.stringify(latest))
}
const path =
(useBinary ? archivePaths.binary : archivePaths.loose) + (request.url.endsWith(".tar.gz") ? ".tar.gz" : ".zip")
const path = archivePath + (request.url.endsWith(".tar.gz") ? ".tar.gz" : ".zip")
const stream = fs.createReadStream(path)
stream.on("error", (error: NodeJS.ErrnoException) => {
......@@ -72,40 +65,36 @@ describe("update", () => {
}
before(async () => {
const archiveName = "code-server-9999999.99999.9999-linux-x86_64"
await fs.remove(path.join(tmpdir, "tests/updates"))
await Promise.all(Object.values(archivePaths).map((p) => fs.mkdirp(path.join(p, "code-server"))))
await fs.mkdirp(path.join(archivePath, archiveName))
await Promise.all([
fs.writeFile(path.join(archivePaths.binary, "code-server", "code-server"), "BINARY"),
fs.writeFile(path.join(archivePaths.loose, "code-server", "code-server"), `console.log("UPDATED")`),
fs.writeFile(path.join(archivePaths.loose, "code-server", "node"), `NODE BINARY`),
fs.writeFile(path.join(archivePath, archiveName, "code-server"), `console.log("UPDATED")`),
fs.writeFile(path.join(archivePath, archiveName, "node"), `NODE BINARY`),
])
await Promise.all(
Object.values(archivePaths).map((p) => {
return Promise.all([
new Promise((resolve, reject) => {
const write = fs.createWriteStream(p + ".tar.gz")
const compress = zlib.createGzip()
compress.pipe(write)
compress.on("error", (error) => compress.destroy(error))
compress.on("close", () => write.end())
tar.pack(p).pipe(compress)
write.on("close", reject)
write.on("finish", () => {
resolve()
})
}),
new Promise((resolve, reject) => {
const zipFile = new zip()
zipFile.addLocalFolder(p)
zipFile.writeZip(p + ".zip", (error) => {
return error ? reject(error) : resolve(error)
})
}),
])
await Promise.all([
new Promise((resolve, reject) => {
const write = fs.createWriteStream(archivePath + ".tar.gz")
const compress = zlib.createGzip()
compress.pipe(write)
compress.on("error", (error) => compress.destroy(error))
compress.on("close", () => write.end())
tar.pack(archivePath).pipe(compress)
write.on("close", reject)
write.on("finish", () => {
resolve()
})
}),
)
new Promise((resolve, reject) => {
const zipFile = new zip()
zipFile.addLocalFolder(archivePath)
zipFile.writeZip(archivePath + ".zip", (error) => {
return error ? reject(error) : resolve(error)
})
}),
])
await new Promise((resolve, reject) => {
server.on("error", reject)
......@@ -216,41 +205,18 @@ describe("update", () => {
assert.equal(`console.log("OLD")`, await fs.readFile(entry, "utf8"))
// Updating should replace the existing version.
await p.downloadUpdate(update, destination)
await p.downloadUpdate(update, destination, "linux")
assert.equal(`console.log("UPDATED")`, await fs.readFile(entry, "utf8"))
// Should still work if there is no existing version somehow.
await fs.remove(destination)
await p.downloadUpdate(update, destination)
assert.equal(`console.log("UPDATED")`, await fs.readFile(entry, "utf8"))
// Try the other platform.
const altTarget = os.platform() === "darwin" ? "linux" : "darwin"
await fs.writeFile(entry, `console.log("OLD")`)
await p.downloadUpdate(update, destination, altTarget)
assert.equal(`console.log("UPDATED")`, await fs.readFile(entry, "utf8"))
// Extracting a binary should also work.
useBinary = true
await p.downloadUpdate(update, destination)
assert.equal(`BINARY`, await fs.readFile(destination, "utf8"))
// Back to flat files.
useBinary = false
await fs.remove(destination)
await p.downloadUpdate(update, destination)
await p.downloadUpdate(update, destination, "linux")
assert.equal(`console.log("UPDATED")`, await fs.readFile(entry, "utf8"))
const target = os.platform()
const targetExt = target === "darwin" ? "zip" : "tar.gz"
const altTargetExt = altTarget === "darwin" ? "zip" : "tar.gz"
assert.deepEqual(spy, [
"/latest",
`/download/${version}/code-server-${version}-${target}-x86_64.${targetExt}`,
`/download/${version}/code-server-${version}-${target}-x86_64.${targetExt}`,
`/download/${version}/code-server-${version}-${altTarget}-x86_64.${altTargetExt}`,
`/download/${version}/code-server-${version}-${target}-x86_64.${targetExt}`,
`/download/${version}/code-server-${version}-${target}-x86_64.${targetExt}`,
`/download/${version}/code-server-${version}-linux-x86_64.tar.gz`,
`/download/${version}/code-server-${version}-linux-x86_64.tar.gz`,
])
})
})
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册