Skip to content

Instantly share code, notes, and snippets.

@besrabasant
Last active June 22, 2026 02:38
Show Gist options
  • Select an option

  • Save besrabasant/2d2566b7c02c0cbc190bcd025c40cbda to your computer and use it in GitHub Desktop.

Select an option

Save besrabasant/2d2566b7c02c0cbc190bcd025c40cbda to your computer and use it in GitHub Desktop.
A shell utility to run multiple independent Slack instances on Linux, each with its own configuration directory. Useful for logging into different workspaces without mixing data.

Virtual Slack Manager

A shell utility to run multiple independent Slack instances on Linux, each with its own configuration directory. Useful for logging into different workspaces without mixing data.

Features

virtual-slack.sh create <N> → creates a new Slack instance (Slack N) with its own config (~/.config-N)

virtual-slack.sh remove <N> → fully removes the instance and its config

virtual-slack.sh make-default <N> → temporarily make Slack N handle slack:// deep links (needed for browser login)

virtual-slack.sh restore-default → restore the original Slack as the default deep link handler

Notes

Each instance launches separately and can run side-by-side.

Browser links (http/https) always use your normal browser profile.

Slack OAuth login via browser can be routed to any instance by switching the deep-link handler.

#!/bin/bash
set -euo pipefail
# virtual-slack: Manage Slack variants with separate config dirs and deep-link routing
#
# Usage:
# virtual-slack create <number>
# virtual-slack remove <number>
# virtual-slack make-default <number> # make this variant handle slack:// links
# virtual-slack restore-default # restore original Slack as handler
if command -v slack >/dev/null 2>&1; then
_SLACK_BIN="$(command -v slack)"
SRC_BIN="$(readlink -f "$_SLACK_BIN")"
else
SRC_BIN="/usr/lib/slack/slack"
fi
APP_DIR="$HOME/.local/share/applications"
LOCAL_SLACK_DESKTOP="$APP_DIR/slack.desktop"
require_src_bin() {
if [ ! -x "$SRC_BIN" ]; then
echo "Slack binary not found. Expected at: $SRC_BIN"
echo "Adjust SRC_BIN in this script to your system path."
exit 1
fi
}
require_variant_number() {
local num="$1"
if [[ ! "$num" =~ ^[0-9]+$ ]]; then
echo "Error: variant number must be numeric."
exit 1
fi
}
write_local_slack_desktop_variant() {
local num="$1"
local variant_root="$HOME/.local/share/virtual-slack-$num"
local url_launcher="$variant_root/open-slack-$num-url.sh"
cat > "$LOCAL_SLACK_DESKTOP" <<EOF
[Desktop Entry]
Name=Slack
StartupWMClass=browser-windox
Comment=Slack Desktop
GenericName=Slack Client for Linux
Exec=$url_launcher %U
Icon=slack
Type=Application
StartupNotify=true
Categories=GNOME;GTK;Network;InstantMessaging;
MimeType=x-scheme-handler/slack;
EOF
}
write_local_slack_desktop_original() {
cat > "$LOCAL_SLACK_DESKTOP" <<EOF
[Desktop Entry]
Name=Slack
StartupWMClass=Slack
Comment=Slack Desktop
GenericName=Slack Client for Linux
Exec=/usr/bin/slack --gtk-version=3 -s %U
Icon=slack
Type=Application
StartupNotify=true
Categories=GNOME;GTK;Network;InstantMessaging;
MimeType=x-scheme-handler/slack;
EOF
}
set_mime_default() {
local desktop_file="$1"
local mime_file="$2"
local all_desktops
local ordered
mkdir -p "$(dirname "$mime_file")"
touch "$mime_file"
all_desktops="${desktop_file};slack.desktop;slack-2.desktop;slack-3.desktop;"
ordered="$(printf '%s' "$all_desktops" | awk -F';' '
BEGIN { out="" }
{
for (i = 1; i <= NF; i++) {
if ($i != "" && !seen[$i]++) {
out = out $i ";"
}
}
}
END { printf "%s", out }
')"
if ! grep -q '^\[Default Applications\]' "$mime_file"; then
printf '
[Default Applications]
' >> "$mime_file"
fi
if grep -q '^x-scheme-handler/slack=' "$mime_file"; then
sed -i "s|^x-scheme-handler/slack=.*|x-scheme-handler/slack=${desktop_file};|" "$mime_file"
else
awk -v d="$desktop_file" '
BEGIN { done=0 }
{ print }
/^\[Default Applications\]$/ && !done {
print "x-scheme-handler/slack=" d ";"
done=1
}
END {
if (!done) {
print "[Default Applications]"
print "x-scheme-handler/slack=" d ";"
}
}
' "$mime_file" > "${mime_file}.tmp" && mv "${mime_file}.tmp" "$mime_file"
fi
if ! grep -q '^\[Added Associations\]' "$mime_file"; then
printf '\n[Added Associations]\n' >> "$mime_file"
fi
if grep -q '^x-scheme-handler/slack=' "$mime_file"; then
awk -v d="$desktop_file" -v o="$ordered" '
BEGIN { in_default=0; in_added=0 }
/^\[Default Applications\]$/ { in_default=1; in_added=0; print; next }
/^\[Added Associations\]$/ { in_default=0; in_added=1; print; next }
/^\[/ { in_default=0; in_added=0; print; next }
in_default && /^x-scheme-handler\/slack=/ { print "x-scheme-handler/slack=" d ";"; next }
in_added && /^x-scheme-handler\/slack=/ { print "x-scheme-handler/slack=" o; next }
{ print }
' "$mime_file" > "${mime_file}.tmp" && mv "${mime_file}.tmp" "$mime_file"
else
printf '\n[Default Applications]\nx-scheme-handler/slack=%s;\n' "$desktop_file" >> "$mime_file"
printf '\n[Added Associations]\nx-scheme-handler/slack=%s\n' "$ordered" >> "$mime_file"
fi
}
create_slack() {
NUM="$1"
require_variant_number "$NUM"
require_src_bin
DST_BIN_DIR="$(dirname "$SRC_BIN")"
DST_BIN="$DST_BIN_DIR/slack-$NUM"
VAR_ROOT="$HOME/.local/share/virtual-slack-$NUM"
VAR_BIN="$VAR_ROOT/bin"
LAUNCHER="$VAR_ROOT/launch-slack-$NUM.sh"
URL_LAUNCHER="$VAR_ROOT/open-slack-$NUM-url.sh"
CONFIG_DIR="$HOME/.config-$NUM"
DESKTOP_FILE="$APP_DIR/slack-$NUM.desktop"
echo "[1/6] Copying Slack binary to $DST_BIN ..."
sudo cp -rp "$SRC_BIN" "$DST_BIN"
echo "[2/6] Patching window class string ..."
if sudo grep -aq $'\x00browser-window\x00' "$DST_BIN"; then
sudo sed -i 's/\x00browser-window\x00/\x00browser-windox\x00/g' "$DST_BIN"
else
echo "Note: 'browser-window' marker not found; skipping patch."
fi
echo "[3/6] Creating per-variant shims ..."
mkdir -p "$VAR_BIN"
SYS_XDG_OPEN="$(command -v xdg-open || true)"
SYS_GIO="$(command -v gio || true)"
[ -z "$SYS_XDG_OPEN" ] && SYS_XDG_OPEN="/usr/bin/xdg-open"
[ -z "$SYS_GIO" ] && SYS_GIO="/usr/bin/gio"
cat > "$VAR_BIN/xdg-open" <<'EOF'
#!/bin/sh
# Ensure child apps (browsers etc.) use normal user config, not the virtual Slack's config.
unset XDG_CONFIG_HOME
exec /usr/bin/xdg-open "$@"
EOF
chmod +x "$VAR_BIN/xdg-open"
cat > "$VAR_BIN/gio" <<'EOF'
#!/bin/sh
unset XDG_CONFIG_HOME
exec /usr/bin/gio "$@"
EOF
chmod +x "$VAR_BIN/gio"
[ "$SYS_XDG_OPEN" != "/usr/bin/xdg-open" ] && sed -i "s|/usr/bin/xdg-open|$SYS_XDG_OPEN|g" "$VAR_BIN/xdg-open"
[ "$SYS_GIO" != "/usr/bin/gio" ] && sed -i "s|/usr/bin/gio|$SYS_GIO|g" "$VAR_BIN/gio"
echo "[4/6] Creating launcher ..."
mkdir -p "$VAR_ROOT"
cat > "$LAUNCHER" <<EOF
#!/bin/sh
export PATH="$VAR_BIN:\$PATH"
export XDG_CONFIG_HOME="$CONFIG_DIR"
exec "$DST_BIN" --gtk-version=3 -s "\$@"
EOF
chmod +x "$LAUNCHER"
cat > "$URL_LAUNCHER" <<EOF
#!/bin/sh
URL="\${1:-}"
# Avoid the primary Slack instance hijacking slack:// deep links via single-instance IPC.
# Kill only the original Slack launcher path, not this variant.
for slack_cmd in "$SRC_BIN" /usr/bin/slack; do
[ -n "\$slack_cmd" ] || continue
pkill -f "^\$slack_cmd( |$)" >/dev/null 2>&1 || true
done
for _ in 1 2 3 4 5 6 7 8 9 10; do
still_running=0
for slack_cmd in "$SRC_BIN" /usr/bin/slack; do
[ -n "\$slack_cmd" ] || continue
if pgrep -f "^\$slack_cmd( |$)" >/dev/null 2>&1; then
still_running=1
break
fi
done
[ "\$still_running" -eq 0 ] && break
sleep 0.2
done
exec "$LAUNCHER" "\$URL"
EOF
chmod +x "$URL_LAUNCHER"
echo "[5/6] Writing desktop entry ..."
mkdir -p "$APP_DIR"
cat > "$DESKTOP_FILE" <<EOF
[Desktop Entry]
Name=Slack $NUM
Comment=Slack using alternate config directory $NUM
Exec=$URL_LAUNCHER %u
Icon=slack
Type=Application
Terminal=false
Categories=Network;Chat;
StartupWMClass=browser-windox
MimeType=x-scheme-handler/slack;
EOF
echo "[6/6] Refreshing menus ..."
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$APP_DIR" || true
command -v kbuildsycoca5 >/dev/null 2>&1 && kbuildsycoca5 || true
echo "✅ Slack $NUM installed (virtual-slack-$NUM)."
echo ""
echo "Tip: To login into workspaces using this slack instance,"
echo " you need to make this instance to be able to capture"
echo " slack:// links by runnning:"
echo ""
echo " virtual-slack make-default $NUM"
echo ""
echo " Once you have logged in successfully, you need to restore the"
echo " original slack handler by running:"
echo ""
echo " virtual-slack restore-default"
echo ""
}
remove_slack() {
NUM="$1"
require_variant_number "$NUM"
require_src_bin
DST_BIN_DIR="$(dirname "$SRC_BIN")"
DST_BIN="$DST_BIN_DIR/slack-$NUM"
VAR_ROOT="$HOME/.local/share/virtual-slack-$NUM"
DESKTOP_FILE="$APP_DIR/slack-$NUM.desktop"
CONFIG_DIR="$HOME/.config-$NUM"
echo "[1/4] Removing cloned binary..."
[ -f "$DST_BIN" ] && sudo rm -f "$DST_BIN" && echo "Deleted $DST_BIN" || echo "Not found: $DST_BIN"
echo "[2/4] Removing virtual-slack runtime dir..."
[ -d "$VAR_ROOT" ] && rm -rf "$VAR_ROOT" && echo "Deleted $VAR_ROOT" || echo "Not found: $VAR_ROOT"
echo "[3/4] Removing desktop entry..."
[ -f "$DESKTOP_FILE" ] && rm -f "$DESKTOP_FILE" && echo "Deleted $DESKTOP_FILE" || echo "Not found: $DESKTOP_FILE"
echo "[4/4] Removing config dir..."
[ -d "$CONFIG_DIR" ] && rm -rf "$CONFIG_DIR" && echo "Deleted $CONFIG_DIR" || echo "Not found: $CONFIG_DIR"
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$APP_DIR" || true
command -v kbuildsycoca5 >/dev/null 2>&1 && kbuildsycoca5 || true
echo "✅ Slack $NUM fully removed."
}
make_default() {
NUM="$1"
require_variant_number "$NUM"
DESKTOP_FILE="slack-$NUM.desktop"
FULL_DESKTOP_FILE="$APP_DIR/$DESKTOP_FILE"
if [ ! -f "$FULL_DESKTOP_FILE" ]; then
echo "Error: desktop file not found: $FULL_DESKTOP_FILE"
exit 1
fi
echo "Setting $DESKTOP_FILE as default handler for slack:// ..."
xdg-mime default "$DESKTOP_FILE" x-scheme-handler/slack
# Force-write common user mimeapps locations used by gio/desktop environments.
set_mime_default "$DESKTOP_FILE" "$HOME/.config/mimeapps.list"
set_mime_default "$DESKTOP_FILE" "$HOME/.local/share/applications/mimeapps.list"
command -v gio >/dev/null 2>&1 && gio mime x-scheme-handler/slack "$DESKTOP_FILE" >/dev/null || true
write_local_slack_desktop_variant "$NUM"
# Some desktop/browser combinations consult xdg-settings for URL handlers.
command -v xdg-settings >/dev/null 2>&1 && xdg-settings set default-url-scheme-handler slack "$DESKTOP_FILE" || true
update_desktop_cache
echo "Current handler checks:"
echo " xdg-mime: $(xdg-mime query default x-scheme-handler/slack 2>/dev/null || echo unknown)"
echo " xdg-settings: $(xdg-settings get default-url-scheme-handler slack 2>/dev/null || echo unknown)"
echo "✅ Now, browser OAuth deep links (slack://) should open in Slack $NUM."
echo "If they still open another instance, that is usually Slack single-instance routing from an already running process."
}
restore_default() {
echo "Restoring original Slack as default handler for slack:// ..."
xdg-mime default slack.desktop x-scheme-handler/slack
set_mime_default "slack.desktop" "$HOME/.config/mimeapps.list"
set_mime_default "slack.desktop" "$HOME/.local/share/applications/mimeapps.list"
command -v gio >/dev/null 2>&1 && gio mime x-scheme-handler/slack slack.desktop >/dev/null || true
write_local_slack_desktop_original
command -v xdg-settings >/dev/null 2>&1 && xdg-settings set default-url-scheme-handler slack "slack.desktop" || true
update_desktop_cache
echo "✅ Restored original Slack as the handler."
}
update_desktop_cache() {
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$APP_DIR" || true
command -v kbuildsycoca5 >/dev/null 2>&1 && kbuildsycoca5 || true
}
# --- Main ---
if [ $# -lt 1 ]; then
echo "Usage: $0 {create|remove|make-default|restore-default} [number]"
exit 1
fi
CMD="$1"
case "$CMD" in
create)
[ $# -lt 2 ] && { echo "Usage: $0 create <number>"; exit 1; }
create_slack "$2"
;;
remove)
[ $# -lt 2 ] && { echo "Usage: $0 remove <number>"; exit 1; }
remove_slack "$2"
;;
make-default)
[ $# -lt 2 ] && { echo "Usage: $0 make-default <number>"; exit 1; }
make_default "$2"
;;
restore-default)
restore_default
;;
*)
echo "Unknown command: $CMD"
echo "Usage: $0 {create|remove|make-default|restore-default} [number]"
exit 1
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment