Skip to content
This repository was archived by the owner on Feb 8, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/teleterm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ node-pty](https://github.com/microsoft/node-pty#dependencies).

### Linux

To create an RPM package for an arm64 target you need to provide `USE_SYSTEM_FPM=1` env var.
To create arm64 deb and RPM packages you need to provide `USE_SYSTEM_FPM=1` env var.

### macOS

Expand Down
7 changes: 7 additions & 0 deletions packages/teleterm/build_resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
This is the directory we use as the `buildResources` dir for electron-builder.

By default, electron-builder uses the `build` dir at the project root. However, we already use that
directory for webpack output.

If you see a path in electron-builder docs referring to `build`, you can assume that they meant the
`buildResources` directory.
24 changes: 24 additions & 0 deletions packages/teleterm/build_resources/installer.nsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# https://github.com/electron-userland/electron-builder/blob/v24.0.0-alpha.5/docs/configuration/nsis.md#custom-nsis-script

# electron-builder adds `BUILD_RESOURCES_DIR\x86-unicode` as a plugin dir.
# But that dir name isn't very descriptive, so we add a custom plugin dir.
!addplugindir "${BUILD_RESOURCES_DIR}\nsis-plugins"

# The EnVar plugin is recommended for env var modification as EnvVarUpdate doesn't handle long
# strings very well.
# https://nsis.sourceforge.io/Environmental_Variables:_append,_prepend,_and_remove_entries
# https://nsis.sourceforge.io/EnVar_plug-in

!macro customInstall
# Make EnVar define user env vars instead of system env vars.
EnVar::SetHKCU
EnVar::AddValue "Path" $INSTDIR\resources\bin
!macroend

!macro customUnInstall
EnVar::SetHKCU
# Inside the uninstaller, $INSTDIR is the directory where the uninstaller lies.
# Fortunately, electron-builder puts the uninstaller directly into the actual installation dir.
# https://nsis.sourceforge.io/Docs/Chapter4.html#varother
EnVar::DeleteValue "Path" $INSTDIR\resources\bin
!macroend
25 changes: 25 additions & 0 deletions packages/teleterm/build_resources/linux/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## Differences between deb & RPM scripts

During an upgrade of a deb package from one version to another, the following steps happen:

1. The new version is unpacked, with the new files overwriting the old files.
2. The after-remove of the old version is called.
3. The old files are removed.
4. The after-install of the new version is called.

See:

- [Debian Policy Manual: Upgrading a package](https://www.debian.org/doc/debian-policy/ap-flowcharts.html#id5).
- [Debian Policy Manual: Details of unpack phase of installation or
upgrade](https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html#details-of-unpack-phase-of-installation-or-upgrade)

However, when you upgrade an RPM package, the scriptlets are called in a reverse order:

1. The new version is unpacked, with the new files overwriting the old files.
2. The after-install of the new version is called.
3. The old files are removed.
4. The after-remove of the old version is called.

See [Fedora Docs: Scriptlets - Ordering](https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/#ordering).

This has direct consequences when attempting to use the same scripts for both targets.
52 changes: 52 additions & 0 deletions packages/teleterm/build_resources/linux/after-install.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/bash
set -eu

###
# Default after-install.tpl copied from electron-builder.
# https://github.com/electron-userland/electron-builder/blob/v24.0.0-alpha.5/packages/app-builder-lib/templates/linux/after-install.tpl
###

# SUID chrome-sandbox for Electron 5+
chmod 4755 "/opt/${sanitizedProductName}/chrome-sandbox" || true

update-mime-database /usr/share/mime || true
update-desktop-database /usr/share/applications || true

###
# Custom after-install.tpl script.
###

APP="/opt/${sanitizedProductName}"
BIN=/usr/local/bin
TSH_SYMLINK_SOURCE=$APP/resources/bin/tsh
TSH_SYMLINK_TARGET=$BIN/tsh

# Create $BIN if it doesn't exist.
[ ! -d "$BIN" ] && mkdir -p "$BIN"
Comment on lines +24 to +25
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I'm just following what tsh.pkg does already.


# Link to the Electron app binary.
ln -sf "$APP/${executable}" "$BIN/${executable}"

# Link to the bundled tsh if the symlink doesn't exist already. Otherwise echo a message unless the
# link points to teleport-connect's tsh already.
# Does TSH_SYMLINK_TARGET not exist?
if [ ! -e "$TSH_SYMLINK_TARGET" ]; then
ln -s "$TSH_SYMLINK_SOURCE" "$TSH_SYMLINK_TARGET"
else
message="${executable}: Skipping symlinking $TSH_SYMLINK_TARGET to $TSH_SYMLINK_SOURCE"

# Is TSH_SYMLINK_TARGET a symlink?
if [ -L "$TSH_SYMLINK_TARGET" ]; then
# Does TSH_SYMLINK_TARGET point at something else than TSH_SYMLINK_SOURCE?
# If TSH_SYMLINK_TARGET exists and it points at TSH_SYMLINK_SOURCE already, don't do anything.
if [ ! "$TSH_SYMLINK_TARGET" -ef "$TSH_SYMLINK_SOURCE" ]; then
message+=" because the symlink already exists and it points to $(realpath $TSH_SYMLINK_TARGET)."
echo "$message"
fi
else
message+=" because $TSH_SYMLINK_TARGET already exists and it isn't a symlink."
echo "$message"
fi
fi

# vim: syntax=sh
44 changes: 44 additions & 0 deletions packages/teleterm/build_resources/linux/after-remove.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash
set -eu

# Do not touch symlinks if the package is being upgraded.
#
# Why?
#
# During an upgrade, RPM packages call after-install of new version first followed by after-remove
# of the old version. deb packages do this in reverse order. See README.md in this directory for
# more details.
#
# So, for RPM packages we should not remove the symlinks if the package is being upgraded, otherwise
# the user would end up with no symlinks after an upgrade.
#
# How?
#
# Both deb and RPM pass arguments to the scripts. rpm passes the number of packages of the given
# name which will be left on the system when the action completes. deb passes "upgrade" during an
# upgrade. We can check those args to determine if the package is being upgraded or removed.
#
# https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html#details-of-unpack-phase-of-installation-or-upgrade
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/#_syntax
#
# Is the first argument "upgrade" or "1"?
if [ "$1" = "upgrade" ] || [ "$1" = "1" ]; then
echo "${executable}: Upgrade detected, skipping symlink operations"
exit 0
fi

BIN=/usr/local/bin
TSH_SYMLINK_TARGET=$BIN/tsh

# Remove the link to the Electron app binary.
rm -f "$BIN/${executable}"

# At this point, the app has already been removed from disk. If TSH_SYMLINK_TARGET used to point at
# tsh bundled with the teleport-connect package, it is a broken symlink now.
#
# Is TSH_SYMLINK_TARGET a link that points at a file which doesn't exist?
if [ -L "$TSH_SYMLINK_TARGET" ] && [ ! -e "$TSH_SYMLINK_TARGET" ]; then
rm -f "$TSH_SYMLINK_TARGET"
fi
Comment on lines +39 to +42
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RFD says:

If Connect decides not to create or remove the symlink, it should echo a message explaining the reason.

Here I've decided to not echo a message if Connect doesn't remove the symlink. Idk, it doesn't seem like a necessary thing to me and we already emit a message regarding symlinks during installation.

I'm not even sure if I should emit a message during an upgrade. But I wanted to be able to tell what happened if someone sends logs from an upgrade. OTOH I don't think Linux packages echo stuff from within those scripts?


# vim: syntax=sh
Binary file not shown.
6 changes: 6 additions & 0 deletions packages/teleterm/build_resources/nsis-plugins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
By default, electron-builder adds `${buildResources}\x86-unicode` as the plugin dir. But that name
is not really that descriptive, so we put the plugins under nsis-plugins.

When you download a plugin, its `Plugins` folder is likely to contain .dlls for different
architectures and encodings such as amd64-unicode, x86-ansi, x86-unicode. You should use the .dlls
from the `x86-unicode` dir.
15 changes: 9 additions & 6 deletions packages/teleterm/electron-builder-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ module.exports = {
gatekeeperAssess: false,
// If CONNECT_TSH_APP_PATH is provided, we assume that tsh.app is already signed.
signIgnore: env.CONNECT_TSH_APP_PATH && ['tsh.app'],
icon: 'assets/icon-mac.png',
icon: 'build_resources/icon-mac.png',
// On macOS, helper apps (such as tsh.app) should be under Contents/MacOS, hence using
// `extraFiles` instead of `extraResources`.
// https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle
Expand Down Expand Up @@ -83,7 +83,7 @@ module.exports = {
win: {
target: ['nsis'],
artifactName: '${productName} Setup-${version}.${ext}',
icon: 'assets/icon-win.ico',
icon: 'build_resources/icon-win.ico',
extraResources: [
env.CONNECT_TSH_BIN_PATH && {
from: env.CONNECT_TSH_BIN_PATH,
Expand All @@ -93,19 +93,23 @@ module.exports = {
},
rpm: {
artifactName: '${name}-${version}.${arch}.${ext}',
afterInstall: 'build_resources/linux/after-install.tpl',
afterRemove: 'build_resources/linux/after-remove.tpl',
// --rpm-rpmbuild-define "_build_id_links none" fixes the problem with not being able to install
// Connect's rpm next to other Electron apps.
// https://github.com/gravitational/teleport/issues/18859
fpm: ['--rpm-rpmbuild-define', '_build_id_links none'],
},
deb: {
artifactName: '${name}_${version}_${arch}.${ext}',
afterInstall: 'build_resources/linux/after-install.tpl',
afterRemove: 'build_resources/linux/after-remove.tpl',
},
linux: {
target: ['tar.gz', 'rpm', 'deb'],
artifactName: '${name}-${version}-${arch}.${ext}', //tar.gz
artifactName: '${name}-${version}-${arch}.${ext}', // tar.gz
category: 'Development',
icon: 'assets/icon-linux',
icon: 'build_resources/icon-linux',
extraResources: [
env.CONNECT_TSH_BIN_PATH && {
from: env.CONNECT_TSH_BIN_PATH,
Expand All @@ -114,8 +118,7 @@ module.exports = {
].filter(Boolean),
},
directories: {
buildResources: 'assets',
buildResources: 'build_resources',
output: 'build/release',
},
extraResources: ['./assets/**'],
};
69 changes: 40 additions & 29 deletions packages/teleterm/src/mainProcess/fixtures/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,66 @@ import { createMockFileStorage } from 'teleterm/services/fileStorage/fixtures/mo
// Importing electron breaks the fixtures if that's done from within storybook.
import { createConfigService } from 'teleterm/services/config/configService';

const platform = 'darwin';

export class MockMainProcessClient implements MainProcessClient {
configService: ReturnType<typeof createConfigService>;

constructor(private runtimeSettings: Partial<RuntimeSettings> = {}) {
this.configService = createConfigService(
createMockFileStorage(),
this.getRuntimeSettings().platform
);
}
getRuntimeSettings(): RuntimeSettings {
return {
platform,
dev: true,
userDataDir: '',
binDir: '',
certsDir: '',
kubeConfigsDir: '',
defaultShell: '',
tshd: {
insecure: true,
requestedNetworkAddress: '',
binaryPath: '',
homeDir: '',
flags: [],
},
sharedProcess: {
requestedNetworkAddress: '',
},
tshdEvents: {
requestedNetworkAddress: '',
},
installationId: '123e4567-e89b-12d3-a456-426614174000',
};
return { ...defaultRuntimeSettings, ...this.runtimeSettings };
}

getResolvedChildProcessAddresses = () =>
Promise.resolve({ tsh: '', shared: '' });

openTerminalContextMenu() {}

openClusterContextMenu() {}

openTabContextMenu() {}

showFileSaveDialog() {
return Promise.resolve({ canceled: false, filePath: '' });
}

configService = createConfigService(createMockFileStorage(), platform);

fileStorage = createMockFileStorage();

removeKubeConfig(): Promise<void> {
return Promise.resolve(undefined);
}

forceFocusWindow() {}

async symlinkTshMacOs() {
return true;
}
async removeTshSymlinkMacOs() {
return true;
}
}

const defaultRuntimeSettings = {
platform: 'darwin' as const,
dev: true,
userDataDir: '',
binDir: '',
certsDir: '',
kubeConfigsDir: '',
defaultShell: '',
tshd: {
insecure: true,
requestedNetworkAddress: '',
binaryPath: '',
homeDir: '',
flags: [],
},
sharedProcess: {
requestedNetworkAddress: '',
},
tshdEvents: {
requestedNetworkAddress: '',
},
installationId: '123e4567-e89b-12d3-a456-426614174000',
};
50 changes: 48 additions & 2 deletions packages/teleterm/src/mainProcess/mainProcess.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ChildProcess, fork, spawn } from 'child_process';
import { ChildProcess, fork, spawn, exec } from 'child_process';
import path from 'path';

import fs from 'fs/promises';

import { promisify } from 'util';
import {
app,
dialog,
Expand Down Expand Up @@ -203,6 +203,52 @@ export default class MainProcess {
this.windowsManager.forceFocusWindow();
});

// Used in the `tsh install` command on macOS to make the bundled tsh available in PATH.
// Returns true if tsh got successfully installed, false if the user closed the osascript
// prompt. Throws an error when osascript fails.
ipcMain.handle('main-process-symlink-tsh-macos', async () => {
const source = this.settings.tshd.binaryPath;
const target = '/usr/local/bin/tsh';
const prompt =
'Teleport Connect wants to create a symlink for tsh in /usr/local/bin.';
const command = `osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf '${source}' '${target}'\\" with prompt \\"${prompt}\\" with administrator privileges"`;

try {
await promisify(exec)(command);
this.logger.info(`Created the symlink to ${source} under ${target}`);
return true;
} catch (error) {
// Ignore the error if the user canceled the prompt.
// https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/reference/ASLR_error_codes.html#//apple_ref/doc/uid/TP40000983-CH220-SW2
if (error instanceof Error && error.message.includes('-128')) {
return false;
}
this.logger.error(error);
throw error;
}
});

ipcMain.handle('main-process-remove-tsh-symlink-macos', async () => {
const target = '/usr/local/bin/tsh';
const prompt =
'Teleport Connect wants to remove a symlink for tsh from /usr/local/bin.';
const command = `osascript -e "do shell script \\"rm '${target}'\\" with prompt \\"${prompt}\\" with administrator privileges"`;

try {
await promisify(exec)(command);
this.logger.info(`Removed the symlink under ${target}`);
return true;
} catch (error) {
// Ignore the error if the user canceled the prompt.
// https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/reference/ASLR_error_codes.html#//apple_ref/doc/uid/TP40000983-CH220-SW2
if (error instanceof Error && error.message.includes('-128')) {
return false;
}
this.logger.error(error);
throw error;
}
});

subscribeToTerminalContextMenuEvent();
subscribeToTabContextMenuEvent();
subscribeToConfigServiceEvents(this.configService);
Expand Down
6 changes: 6 additions & 0 deletions packages/teleterm/src/mainProcess/mainProcessClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,11 @@ export default function createMainProcessClient(): MainProcessClient {
forceFocusWindow() {
return ipcRenderer.invoke('main-process-force-focus-window');
},
symlinkTshMacOs() {
return ipcRenderer.invoke('main-process-symlink-tsh-macos');
},
removeTshSymlinkMacOs() {
return ipcRenderer.invoke('main-process-remove-tsh-symlink-macos');
},
};
}
Loading