commit cf9443753e603740dd598586cd8d38a9f21b4e92 Author: Шурупов Илья Викторович Date: Sat Sep 12 19:13:36 2026 +0300 Add one-command Arch DSH setup Package the portable DSH profiles, local plugins, desktop integration, Firefox wrapper, and ownership-aware installer lifecycle. Bundle checksum-pinned OpenVSCode, editor extensions, translation assets, and cloudflared so unreliable upstream artifact downloads cannot break a clean installation. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e533029 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto eol=lf +*.png binary +*.patch whitespace=-space-before-tab +vendor/dsh-ui-translate/lib/opus-worker.js whitespace=-trailing-space +vendor/dsh-ui-translate/lib/ort-wasm-simd-threaded.jsep.mjs whitespace=-trailing-space diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a23dc0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +**/node_modules/ +plugins/dsh-touched-git/runtime/ +.test-home/ +*.log +*.tmp diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f7049a4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 ilusha + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..77969b0 --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +# DSH Arch Setup + +One-command installation of a customized DeepSeek Harness environment on x86-64 Arch Linux. + +## Install + +```bash +git clone https://git.ilusha-basic.online/ilusha/DSH-Arch-Setup.git +cd DSH-Arch-Setup +./install.sh +``` + +The installer may request `sudo` once to install missing Arch packages. It then: + +- installs `@deepseek-ai/dsh@0.1.1-rc.2` under `~/.local`; +- links every repository-owned plugin into `~/.dsh/plugins`; +- installs all external Web-profile plugins from the committed pnpm lockfile; +- installs checksum-pinned cloudflared 2026.8.3 locally instead of running its unpinned network postinstall; +- verifies and extracts the bundled OpenVSCode Server 1.109.5 runtime without starting it; +- checksum-verifies and installs the bundled local VSIX artifacts listed in `manifests/vscode-extension-artifacts.tsv`; +- applies narrow compatibility patches required by `dsh-touched-git`; +- installs the Firefox app profile, launcher, desktop entry, and icons; +- installs and starts `dsh-web.service` as a systemd user service. + +Open **DSH** from the desktop application menu or run: + +```bash +dsh-app +``` + +Provider credentials are intentionally not included. Configure accounts on first launch. Sublime Merge is optional; install it separately if you want the `dsh-open-in-smerge` action. + +### Installer options + +```bash +./install.sh --skip-system-packages +./install.sh --skip-network --no-start +``` + +The second form is intended for local layout tests; it does not install DSH packages, editor components, or start the service. + +## Update + +```bash +git pull --ff-only +./install.sh +``` + +The installer is idempotent. Before replacing a path it does not already manage, it moves the old path under `~/.local/state/dsh-arch-setup/backups/`. + +## Validate + +```bash +./test/run.sh +``` + +Tests use temporary homes and never start or stop the live DSH service. + +## Uninstall + +```bash +./uninstall.sh +``` + +The uninstaller stops the user service, removes unchanged managed files and exact repository links, restores backups, and removes generated installations only when they did not predate the first setup run. Credentials, sessions, attachments, settings, and user-modified files are preserved. + +## Included local plugins + +- `dsh-compact-sidebar` +- `dsh-fleet-control` +- `dsh-fullwidth` +- `dsh-header-menu` +- `dsh-open-in-smerge` +- `dsh-project-tree` +- `dsh-sidebar-match-bg` +- `dsh-touched-git` +- `dsh-ui-tweaks` +- `dsh-web-hotkeys` + +External DSH plugins are declared in `profile/web/package.json` and pinned by `profile/web/pnpm-lock.yaml`. `dsh-ui-translate@0.4.0` is vendored with its MIT license and notices because its upstream Git package cannot build on a clean install; the remaining external plugins are downloaded by pnpm. OpenVSCode extensions are bundled as license-retaining VSIX files and checksum-pinned in `manifests/vscode-extension-artifacts.tsv`, avoiding Open VSX availability during installation. Upstream extension sources and licenses are listed in `THIRD_PARTY_EXTENSIONS.md`; bundled binary provenance is in `THIRD_PARTY_BINARIES.md`. + +## Deliberately excluded + +The repository never contains: + +- DSH credentials, OAuth state, sessions, attachments, or caches; +- Firefox cookies, logins, history, session state, or generated preferences; +- repository-path history from Sublime Merge integration; +- OpenVSCode editor state or downloaded `node_modules`; +- machine-specific PID files, logs, backups, or window geometry. + +## Browser limitations + +OpenVSCode runs as a web application. Firefox retains hard-reserved shortcuts such as `Ctrl+W`, `Ctrl+T`, and `Ctrl+L`. The included dedicated profile enables web shortcut handling and the Firefox context-menu mouse-up workaround, but Chromium remains more compatible with VS Code Web input handling. diff --git a/SOURCE_REVISIONS.md b/SOURCE_REVISIONS.md new file mode 100644 index 0000000..addd1b4 --- /dev/null +++ b/SOURCE_REVISIONS.md @@ -0,0 +1,12 @@ +# Source revisions + +| Component | Source | +|---|---| +| `dsh-touched-git` | Local repository `main` at `c33e67bdb43620b9a65fd5684971ade1caa14153`; one test-only absolute-home fallback was made portable with `homedir()` | +| Other local plugins | Imported from non-Git directories under `~/.dsh/plugins` | +| DSH CLI | npm `@deepseek-ai/dsh@0.1.1-rc.2` | +| OpenVSCode Server | Exact 1.109.5 runtime bundled and SHA-256 pinned by `manifests/editor-runtime.tsv`; upstream release metadata retained in `plugins/dsh-touched-git/editor-release.json` | +| cloudflared | Exact 2026.8.3 Linux x86-64 binary bundled and SHA-256 pinned by `manifests/cloudflared.tsv` | +| `dsh-ui-translate` | Vendored built package `0.4.0` from commit `260083f023ec8494fbf59db7c4b9a237cf8e9648`, retaining MIT and third-party notices | +| Other external DSH plugins | Pinned by `profile/web/pnpm-lock.yaml` | +| OpenVSCode extensions | Exact installed artifacts bundled as VSIX files and SHA-256 pinned by `manifests/vscode-extension-artifacts.tsv` | diff --git a/THIRD_PARTY_BINARIES.md b/THIRD_PARTY_BINARIES.md new file mode 100644 index 0000000..180d0d2 --- /dev/null +++ b/THIRD_PARTY_BINARIES.md @@ -0,0 +1,17 @@ +# Third-party binaries + +## OpenVSCode Server + +- Version: 1.109.5 +- Source: https://github.com/gitpod-io/openvscode-server/tree/openvscode-server-v1.109.5 +- License: MIT, retained at `editor-runtime/LICENSE.openvscode-server.txt` +- Integrity: `manifests/editor-runtime.tsv` + +## cloudflared + +- Version: 2026.8.3 +- Source: https://github.com/cloudflare/cloudflared/tree/2026.8.3 +- License: Apache-2.0, retained at `vendor/dsh-ui-translate/THIRD_PARTY_LICENSES/Apache-2.0.txt` +- Integrity: `manifests/cloudflared.tsv` + +Both binaries are unmodified x86-64 Linux release artifacts. The installer verifies their SHA-256 digests before use. diff --git a/THIRD_PARTY_EXTENSIONS.md b/THIRD_PARTY_EXTENSIONS.md new file mode 100644 index 0000000..68fe6a6 --- /dev/null +++ b/THIRD_PARTY_EXTENSIONS.md @@ -0,0 +1,14 @@ +# Third-party editor extensions + +The repository redistributes the exact installed extension payloads as VSIX archives. Each archive retains its upstream license file and package metadata. Versions and SHA-256 digests are authoritative in `manifests/vscode-extension-artifacts.tsv`. + +| Extension | Version | License | Source | +|---|---:|---|---| +| `asispts.neo-git-graph` | 0.5.0 | MIT | https://github.com/asispts/neo-git-graph | +| `llvm-vs-code-extensions.vscode-clangd` | 0.6.0 | MIT | https://github.com/clangd/vscode-clangd | +| `ms-vscode.cmake-tools` | 1.23.52 | MIT | https://github.com/microsoft/vscode-cmake-tools | +| `rionoir.gitcharm` | 0.4.7 | GPL-3.0-only | https://github.com/RioNoir/GitCharm | +| `skippingpetticoatconvent.git-graph-zh` | 1.0.8 | bundled `LICENSE` | https://github.com/SKIPPINGpetticoatconvent/vscode-git-graph-zh | +| `tofilagman.git-branch-compare` | 0.4.0 | MIT | https://github.com/tofilagman/vscode-ext-gitlab-compare | + +The corresponding source is available at the linked upstream repositories under the recorded release version. Do not remove the license files from redistributed VSIX archives. diff --git a/assets/icons/128x128/apps/dsh.png b/assets/icons/128x128/apps/dsh.png new file mode 100644 index 0000000..4388d1f Binary files /dev/null and b/assets/icons/128x128/apps/dsh.png differ diff --git a/assets/icons/256x256/apps/dsh.png b/assets/icons/256x256/apps/dsh.png new file mode 100644 index 0000000..713feed Binary files /dev/null and b/assets/icons/256x256/apps/dsh.png differ diff --git a/assets/icons/48x48/apps/dsh.png b/assets/icons/48x48/apps/dsh.png new file mode 100644 index 0000000..9623635 Binary files /dev/null and b/assets/icons/48x48/apps/dsh.png differ diff --git a/assets/icons/512x512/apps/dsh.png b/assets/icons/512x512/apps/dsh.png new file mode 100644 index 0000000..322ce95 Binary files /dev/null and b/assets/icons/512x512/apps/dsh.png differ diff --git a/assets/icons/64x64/apps/dsh.png b/assets/icons/64x64/apps/dsh.png new file mode 100644 index 0000000..77e8649 Binary files /dev/null and b/assets/icons/64x64/apps/dsh.png differ diff --git a/bin/dsh-app b/bin/dsh-app new file mode 100755 index 0000000..3332b87 --- /dev/null +++ b/bin/dsh-app @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +url="${DSH_WEB_URL:-http://127.0.0.1:3080/}" +profile="${DSH_FIREFOX_PROFILE:-$HOME/.local/share/dsh-app-firefox}" +log="$profile/dsh-web.log" +mkdir -p "$profile" + +kiosk=0 +[[ "${1:-}" == "--kiosk" ]] && kiosk=1 + +if ! curl --fail --silent --output /dev/null "$url" 2>/dev/null; then + if systemctl --user cat dsh-web.service >/dev/null 2>&1; then + systemctl --user start dsh-web.service + else + nohup dsh web --no-open --port 3080 >"$log" 2>&1 & + fi + + for _ in $(seq 1 60); do + curl --fail --silent --output /dev/null "$url" 2>/dev/null && break + sleep 1 + done +fi + +export MOZ_ENABLE_WAYLAND=1 +unset GDK_BACKEND +arguments=(--no-remote --new-instance --profile "$profile" --name dsh-app) +if [[ "$kiosk" == 1 ]]; then + exec firefox "${arguments[@]}" --kiosk "$url" +fi +exec firefox "${arguments[@]}" --new-window "$url" diff --git a/bin/free-port-3080.sh b/bin/free-port-3080.sh new file mode 100755 index 0000000..8f5beb8 --- /dev/null +++ b/bin/free-port-3080.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -u + +port=3080 +pids=$(ss -ltnpH "sport = :${port}" 2>/dev/null | grep -oP 'pid=\K[0-9]+' | sort -u) +for pid in $pids; do + kill "$pid" 2>/dev/null || true +done + +for _ in $(seq 1 20); do + ss -ltnH "sport = :${port}" 2>/dev/null | grep -q . || break + sleep 0.5 +done +exit 0 diff --git a/desktop/dsh-app.desktop.in b/desktop/dsh-app.desktop.in new file mode 100644 index 0000000..3542112 --- /dev/null +++ b/desktop/dsh-app.desktop.in @@ -0,0 +1,14 @@ +[Desktop Entry] +Type=Application +Name=DSH +Comment=DSH web UI in a dedicated Firefox window +Exec=@HOME@/.local/bin/dsh-app +Icon=dsh +Terminal=false +Categories=Development; +StartupNotify=true +Actions=kiosk; + +[Desktop Action kiosk] +Name=DSH (fullscreen / borderless) +Exec=@HOME@/.local/bin/dsh-app --kiosk diff --git a/editor-extensions/asispts.neo-git-graph-0.5.0.vsix b/editor-extensions/asispts.neo-git-graph-0.5.0.vsix new file mode 100644 index 0000000..b503d9a Binary files /dev/null and b/editor-extensions/asispts.neo-git-graph-0.5.0.vsix differ diff --git a/editor-extensions/dsh-local.dsh-editor-file-bridge-0.1.0.vsix b/editor-extensions/dsh-local.dsh-editor-file-bridge-0.1.0.vsix new file mode 100644 index 0000000..64b2689 Binary files /dev/null and b/editor-extensions/dsh-local.dsh-editor-file-bridge-0.1.0.vsix differ diff --git a/editor-extensions/dsh-local.dsh-editor-preferences-0.2.0.vsix b/editor-extensions/dsh-local.dsh-editor-preferences-0.2.0.vsix new file mode 100644 index 0000000..ddd5f04 Binary files /dev/null and b/editor-extensions/dsh-local.dsh-editor-preferences-0.2.0.vsix differ diff --git a/editor-extensions/llvm-vs-code-extensions.vscode-clangd-0.6.0.vsix b/editor-extensions/llvm-vs-code-extensions.vscode-clangd-0.6.0.vsix new file mode 100644 index 0000000..6447164 Binary files /dev/null and b/editor-extensions/llvm-vs-code-extensions.vscode-clangd-0.6.0.vsix differ diff --git a/editor-extensions/ms-vscode.cmake-tools-1.23.52.vsix b/editor-extensions/ms-vscode.cmake-tools-1.23.52.vsix new file mode 100644 index 0000000..796c297 Binary files /dev/null and b/editor-extensions/ms-vscode.cmake-tools-1.23.52.vsix differ diff --git a/editor-extensions/rionoir.gitcharm-0.4.7.vsix b/editor-extensions/rionoir.gitcharm-0.4.7.vsix new file mode 100644 index 0000000..d4f591d Binary files /dev/null and b/editor-extensions/rionoir.gitcharm-0.4.7.vsix differ diff --git a/editor-extensions/skippingpetticoatconvent.git-graph-zh-1.0.8.vsix b/editor-extensions/skippingpetticoatconvent.git-graph-zh-1.0.8.vsix new file mode 100644 index 0000000..79c89ad Binary files /dev/null and b/editor-extensions/skippingpetticoatconvent.git-graph-zh-1.0.8.vsix differ diff --git a/editor-extensions/tofilagman.git-branch-compare-0.4.0.vsix b/editor-extensions/tofilagman.git-branch-compare-0.4.0.vsix new file mode 100644 index 0000000..59c7dde Binary files /dev/null and b/editor-extensions/tofilagman.git-branch-compare-0.4.0.vsix differ diff --git a/editor-runtime/LICENSE.openvscode-server.txt b/editor-runtime/LICENSE.openvscode-server.txt new file mode 100644 index 0000000..0ac28ee --- /dev/null +++ b/editor-runtime/LICENSE.openvscode-server.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2015 - present Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/editor-runtime/openvscode-server-1.109.5-linux-x64.tar.gz b/editor-runtime/openvscode-server-1.109.5-linux-x64.tar.gz new file mode 100644 index 0000000..866dc08 Binary files /dev/null and b/editor-runtime/openvscode-server-1.109.5-linux-x64.tar.gz differ diff --git a/firefox/user.js b/firefox/user.js new file mode 100644 index 0000000..226e049 --- /dev/null +++ b/firefox/user.js @@ -0,0 +1,19 @@ +user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true); +user_pref("browser.shell.checkDefaultBrowser", false); +user_pref("browser.aboutwelcome.enabled", false); +user_pref("browser.startup.homepage_override.mstone", "ignore"); +user_pref("startup.homepage_welcome_url", ""); +user_pref("startup.homepage_welcome_url.additional", ""); +user_pref("datareporting.policy.dataSubmissionEnabled", false); +user_pref("datareporting.healthreport.uploadEnabled", false); +user_pref("browser.tabs.warnOnClose", false); +user_pref("browser.sessionstore.resume_from_crash", false); +user_pref("browser.startup.page", 1); +user_pref("browser.startup.homepage", "http://127.0.0.1:3080/"); +user_pref("browser.tabs.inTitlebar", 1); +user_pref("full-screen-api.warning.timeout", 0); +user_pref("full-screen-api.transition-duration.enter", "0 0"); +user_pref("full-screen-api.transition-duration.leave", "0 0"); +user_pref("permissions.default.shortcuts", 1); +user_pref("dom.event.contextmenu.enabled", true); +user_pref("ui.context_menus.after_mouseup", true); diff --git a/firefox/userChrome.css b/firefox/userChrome.css new file mode 100644 index 0000000..5d6fad9 --- /dev/null +++ b/firefox/userChrome.css @@ -0,0 +1,8 @@ +#TabsToolbar { visibility: visible !important; } +#nav-bar { visibility: collapse !important; } +#PersonalToolbar { visibility: collapse !important; } +#navigator-toolbox { min-height: 0 !important; } +#navigator-toolbox::after { display: none !important; } +.browserContainer > .browserStack > [class*="warning"] { display: none !important; } +#toolbar-menubar { visibility: collapse !important; } +.titlebar-buttonbox-container { display: none !important; } diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..39a1b68 --- /dev/null +++ b/install.sh @@ -0,0 +1,259 @@ +#!/usr/bin/env bash +set -euo pipefail + +repository_directory=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +dsh_home="$HOME/.dsh" +profile_directory="$dsh_home/profiles/web" +state_directory=${XDG_STATE_HOME:-"$HOME/.local/state"}/dsh-arch-setup +managed_manifest="$state_directory/managed.tsv" +partial_manifest="$state_directory/partial.tsv" +backup_manifest="$state_directory/backups.tsv" +new_manifest=$(mktemp) +cleanup() { + local status=$? + trap - EXIT + if [[ "$status" != 0 && -s "$new_manifest" ]]; then + mkdir -p "$state_directory" + cat "$new_manifest" >> "$partial_manifest" + fi + rm -f "$new_manifest" + exit "$status" +} +trap cleanup EXIT + +skip_system_packages=${DSH_SETUP_SKIP_SYSTEM_PACKAGES:-0} +skip_network=${DSH_SETUP_SKIP_NETWORK:-0} +start_service=${DSH_SETUP_START_SERVICE:-1} +skip_host_integration=${DSH_SETUP_SKIP_HOST_INTEGRATION:-0} + +for argument in "$@"; do + case "$argument" in + --skip-system-packages) skip_system_packages=1 ;; + --skip-network) skip_network=1 ;; + --no-start) start_service=0 ;; + --test-mode) skip_system_packages=1; skip_network=1; start_service=0; skip_host_integration=1 ;; + *) printf 'Unknown option: %s\n' "$argument" >&2; exit 2 ;; + esac +done + +[[ "$EUID" != 0 ]] || { printf 'Run this installer as a regular user, not root.\n' >&2; exit 1; } +[[ "$(uname -s)" == Linux && "$(uname -m)" == x86_64 ]] || { printf 'This release supports Linux x86-64 only.\n' >&2; exit 1; } +if [[ "$skip_system_packages" != 1 ]]; then + [[ -f /etc/arch-release ]] || { printf 'This installer supports Arch Linux only.\n' >&2; exit 1; } +fi +[[ -f "$repository_directory/profile/web/pnpm-lock.yaml" ]] || { printf 'Missing Web profile lockfile.\n' >&2; exit 1; } +if [[ -x "$HOME/.local/bin/dsh" ]]; then + installed_dsh_version=$("$HOME/.local/bin/dsh" --version) + [[ "$installed_dsh_version" == 0.1.1-rc.2 ]] || { printf 'Existing ~/.local DSH version %s conflicts with required 0.1.1-rc.2.\n' "$installed_dsh_version" >&2; exit 1; } +fi +while IFS= read -r plugin; do + [[ -n "$plugin" ]] || continue + [[ -f "$repository_directory/plugins/$plugin/package.json" ]] || { printf 'Missing local plugin: %s\n' "$plugin" >&2; exit 1; } +done < "$repository_directory/manifests/local-plugins.txt" +while IFS= read -r plugin; do + [[ -n "$plugin" ]] || continue + [[ -f "$repository_directory/vendor/$plugin/package.json" ]] || { printf 'Missing vendored plugin: %s\n' "$plugin" >&2; exit 1; } +done < "$repository_directory/manifests/vendored-plugins.txt" +while IFS=$'\t' read -r extension relative expected_hash; do + [[ -n "$extension" ]] || continue + archive="$repository_directory/$relative" + [[ -f "$archive" ]] || { printf 'Missing editor extension archive: %s\n' "$relative" >&2; exit 1; } + actual_hash=$(sha256sum "$archive" | cut -d ' ' -f 1) + [[ "$actual_hash" == "$expected_hash" ]] || { printf 'Editor extension checksum mismatch: %s\n' "$relative" >&2; exit 1; } +done < "$repository_directory/manifests/vscode-extension-artifacts.tsv" +IFS=$'\t' read -r editor_version editor_archive_relative editor_archive_hash < "$repository_directory/manifests/editor-runtime.tsv" +editor_archive="$repository_directory/$editor_archive_relative" +[[ -f "$editor_archive" ]] || { printf 'Missing OpenVSCode runtime archive: %s\n' "$editor_archive_relative" >&2; exit 1; } +actual_editor_hash=$(sha256sum "$editor_archive" | cut -d ' ' -f 1) +[[ "$actual_editor_hash" == "$editor_archive_hash" ]] || { printf 'OpenVSCode runtime checksum mismatch: %s\n' "$editor_archive_relative" >&2; exit 1; } +IFS=$'\t' read -r cloudflared_version cloudflared_relative cloudflared_hash < "$repository_directory/manifests/cloudflared.tsv" +cloudflared_source="$repository_directory/$cloudflared_relative" +[[ -x "$cloudflared_source" ]] || { printf 'Missing cloudflared binary: %s\n' "$cloudflared_relative" >&2; exit 1; } +actual_cloudflared_hash=$(sha256sum "$cloudflared_source" | cut -d ' ' -f 1) +[[ "$actual_cloudflared_hash" == "$cloudflared_hash" ]] || { printf 'cloudflared checksum mismatch: %s\n' "$cloudflared_relative" >&2; exit 1; } +actual_cloudflared_version=$("$cloudflared_source" --version | awk '{print $3}') +[[ "$actual_cloudflared_version" == "$cloudflared_version" ]] || { printf 'cloudflared version mismatch: %s\n' "$actual_cloudflared_version" >&2; exit 1; } +existing_editor="$repository_directory/plugins/dsh-touched-git/runtime/current/bin/openvscode-server" +if [[ -x "$existing_editor" ]]; then + existing_editor_version=$("$existing_editor" --version | head -n 1) + [[ "$existing_editor_version" == "$editor_version" ]] || { printf 'Existing OpenVSCode runtime version %s conflicts with required %s.\n' "$existing_editor_version" "$editor_version" >&2; exit 1; } +elif [[ -e "$repository_directory/plugins/dsh-touched-git/runtime/current" ]]; then + printf 'Existing OpenVSCode runtime is incomplete.\n' >&2 + exit 1 +fi + +mkdir -p "$state_directory" +touch "$backup_manifest" +if [[ ! -f "$managed_manifest" && ! -f "$partial_manifest" ]]; then + [[ -x "$HOME/.local/bin/dsh" ]] && touch "$state_directory/dsh-preexisting" + [[ -d "$profile_directory" ]] && touch "$state_directory/profile-preexisting" + [[ -d "$dsh_home/touched-git/editor" ]] && touch "$state_directory/editor-preexisting" + [[ -d "$repository_directory/plugins/dsh-touched-git/runtime/current" ]] && touch "$state_directory/runtime-preexisting" + [[ -d "$repository_directory/plugins/dsh-touched-git/node_modules" ]] && touch "$state_directory/plugin-dependencies-preexisting" +fi + +is_managed_unchanged() { + local destination=$1 + local record + record=$( + for manifest in "$managed_manifest" "$partial_manifest"; do + [[ ! -f "$manifest" ]] || cat "$manifest" + done | awk -F '\t' -v candidate="$destination" '$2 == candidate { record = $0 } END { print record }' + ) + [[ -n "$record" ]] || return 1 + + local kind ignored expected + IFS=$'\t' read -r kind ignored expected <<< "$record" + case "$kind" in + file) [[ -f "$destination" && "$(sha256sum "$destination" | cut -d ' ' -f 1)" == "$expected" ]] ;; + link) [[ -L "$destination" && "$(readlink -f "$destination")" == "$(readlink -f "$expected")" ]] ;; + *) return 1 ;; + esac +} + +backup_unmanaged() { + local destination=$1 + if [[ ! -e "$destination" && ! -L "$destination" ]]; then + return + fi + if is_managed_unchanged "$destination"; then + return + fi + local relative=${destination#/} + local backup="$state_directory/backups/$relative" + if [[ -e "$backup" || -L "$backup" ]]; then + backup="$backup.$(date +%s%N)" + fi + mkdir -p "$(dirname "$backup")" + mv "$destination" "$backup" + printf '%s\t%s\n' "$destination" "$backup" >> "$backup_manifest" +} + +record_file() { + local destination=$1 + printf 'file\t%s\t%s\n' "$destination" "$(sha256sum "$destination" | cut -d ' ' -f 1)" >> "$new_manifest" +} + +install_managed_file() { + local source=$1 + local destination=$2 + local mode=$3 + backup_unmanaged "$destination" + install -Dm"$mode" "$source" "$destination" + record_file "$destination" +} + +install_managed_link() { + local source=$1 + local destination=$2 + if [[ -L "$destination" && "$(readlink -f "$destination")" == "$(readlink -f "$source")" ]]; then + printf 'link\t%s\t%s\n' "$destination" "$source" >> "$new_manifest" + return + fi + backup_unmanaged "$destination" + mkdir -p "$(dirname "$destination")" + rm -rf "$destination" + ln -s "$source" "$destination" + printf 'link\t%s\t%s\n' "$destination" "$source" >> "$new_manifest" +} + +if [[ "$skip_system_packages" != 1 ]]; then + packages=(nodejs npm pnpm git curl tar firefox desktop-file-utils hicolor-icon-theme iproute2 python clang cmake ninja) + missing=() + for package in "${packages[@]}"; do + pacman -Q "$package" >/dev/null 2>&1 || missing+=("$package") + done + if ((${#missing[@]})); then + sudo pacman -S --needed --noconfirm "${missing[@]}" + fi +fi + +mkdir -p "$dsh_home/plugins" "$profile_directory" "$dsh_home/profiles/headless" +while IFS= read -r plugin; do + [[ -n "$plugin" ]] || continue + install_managed_link "$repository_directory/plugins/$plugin" "$dsh_home/plugins/$plugin" +done < "$repository_directory/manifests/local-plugins.txt" +while IFS= read -r plugin; do + [[ -n "$plugin" ]] || continue + install_managed_link "$repository_directory/vendor/$plugin" "$dsh_home/vendor/$plugin" +done < "$repository_directory/manifests/vendored-plugins.txt" + +profile_files=(package.json pnpm-workspace.yaml pnpm-lock.yaml cordis.yml cordis.patch.yml patches/dsh-codex-subscription@1.10.0.patch) +for relative in "${profile_files[@]}"; do + install_managed_file "$repository_directory/profile/web/$relative" "$profile_directory/$relative" 0644 +done + +headless_files=(package.json pnpm-workspace.yaml cordis.yml cordis.patch.yml) +for relative in "${headless_files[@]}"; do + install_managed_file "$repository_directory/profile/headless/$relative" "$dsh_home/profiles/headless/$relative" 0644 +done + +install_managed_file "$repository_directory/bin/dsh-app" "$HOME/.local/bin/dsh-app" 0755 +install_managed_file "$repository_directory/bin/free-port-3080.sh" "$dsh_home/free-port-3080.sh" 0755 +install_managed_file "$repository_directory/systemd/dsh-web.service" "$HOME/.config/systemd/user/dsh-web.service" 0644 +install_managed_file "$repository_directory/firefox/user.js" "$HOME/.local/share/dsh-app-firefox/user.js" 0644 +install_managed_file "$repository_directory/firefox/userChrome.css" "$HOME/.local/share/dsh-app-firefox/chrome/userChrome.css" 0644 + +desktop_temporary=$(mktemp) +sed "s|@HOME@|$HOME|g" "$repository_directory/desktop/dsh-app.desktop.in" > "$desktop_temporary" +install_managed_file "$desktop_temporary" "$HOME/.local/share/applications/dsh-app.desktop" 0644 +rm -f "$desktop_temporary" + +while IFS= read -r icon; do + relative=${icon#"$repository_directory/assets/icons/"} + install_managed_file "$icon" "$HOME/.local/share/icons/hicolor/$relative" 0644 +done < <(find "$repository_directory/assets/icons" -type f -name '*.png' | sort) + +if [[ "$skip_network" != 1 ]]; then + if [[ ! -x "$HOME/.local/bin/dsh" ]]; then + npm install --global --prefix "$HOME/.local" --allow-scripts='@deepseek-ai/dsh-subprocess-local,koffi,node-pty,@google/genai,protobufjs' '@deepseek-ai/dsh@0.1.1-rc.2' + fi + touched_git_directory="$repository_directory/plugins/dsh-touched-git" + pnpm --dir "$touched_git_directory" install --frozen-lockfile + pnpm --dir "$profile_directory" install --frozen-lockfile + [[ -f "$profile_directory/node_modules/cloudflared/package.json" ]] || { printf 'cloudflared npm wrapper is missing after profile installation.\n' >&2; exit 1; } + install_managed_file "$cloudflared_source" "$profile_directory/node_modules/cloudflared/bin/cloudflared" 0755 + + runtime_directory="$touched_git_directory/runtime" + editor_binary="$runtime_directory/current/bin/openvscode-server" + if [[ ! -x "$editor_binary" ]]; then + mkdir -p "$runtime_directory" + runtime_staging=$(mktemp -d "$runtime_directory/.install-XXXXXX") + if ! tar -xzf "$editor_archive" -C "$runtime_staging"; then + rm -rf "$runtime_staging" + printf 'Failed to extract bundled OpenVSCode runtime.\n' >&2 + exit 1 + fi + [[ -x "$runtime_staging/current/bin/openvscode-server" ]] || { rm -rf "$runtime_staging"; printf 'Bundled OpenVSCode runtime is incomplete.\n' >&2; exit 1; } + mv "$runtime_staging/current" "$runtime_directory/current" + rmdir "$runtime_staging" + fi + printf 'Installed bundled OpenVSCode Server %s. No server started.\n' "$editor_version" + + while IFS=$'\t' read -r extension relative expected_hash; do + [[ -n "$extension" ]] || continue + archive="$repository_directory/$relative" + timeout --signal=TERM 180 "$editor_binary" --extensions-dir "$dsh_home/touched-git/editor/extensions" --install-extension "$archive" --force + done < "$repository_directory/manifests/vscode-extension-artifacts.tsv" + + DSH_PROFILE_DIRECTORY="$profile_directory" \ + DSH_EDITOR_EXTENSIONS_DIRECTORY="$dsh_home/touched-git/editor/extensions" \ + node "$repository_directory/scripts/apply-compatibility-patches.mjs" +fi + +mv "$new_manifest" "$managed_manifest" +rm -f "$partial_manifest" +printf '%s\n' "$repository_directory" > "$state_directory/repository" + +if [[ "$skip_host_integration" != 1 ]]; then + command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$HOME/.local/share/applications" + command -v gtk-update-icon-cache >/dev/null 2>&1 && gtk-update-icon-cache --force "$HOME/.local/share/icons/hicolor" >/dev/null 2>&1 || true + systemctl --user daemon-reload + if [[ "$start_service" == 1 ]]; then + systemctl --user enable dsh-web.service + systemctl --user restart dsh-web.service + fi +fi + +printf 'DSH installation complete. Open DSH from the application menu or run dsh-app.\n' +printf 'Configure provider credentials on first launch; no credentials are included here.\n' diff --git a/manifests/cloudflared.tsv b/manifests/cloudflared.tsv new file mode 100644 index 0000000..1da96f2 --- /dev/null +++ b/manifests/cloudflared.tsv @@ -0,0 +1 @@ +2026.8.3 third-party-binaries/cloudflared-2026.8.3-linux-amd64 f29324fe934d1e100617484c78deef803c4dc2cd351d645bbde42e96b4fccc5e diff --git a/manifests/editor-runtime.tsv b/manifests/editor-runtime.tsv new file mode 100644 index 0000000..bca93f9 --- /dev/null +++ b/manifests/editor-runtime.tsv @@ -0,0 +1 @@ +1.109.5 editor-runtime/openvscode-server-1.109.5-linux-x64.tar.gz 89bfa2171c8fbf9ce987b2a0731c5ddd73f9cca476c47717e0854cf0aefafec2 diff --git a/manifests/local-plugins.txt b/manifests/local-plugins.txt new file mode 100644 index 0000000..b352ee3 --- /dev/null +++ b/manifests/local-plugins.txt @@ -0,0 +1,10 @@ +dsh-compact-sidebar +dsh-fleet-control +dsh-fullwidth +dsh-header-menu +dsh-open-in-smerge +dsh-project-tree +dsh-sidebar-match-bg +dsh-touched-git +dsh-ui-tweaks +dsh-web-hotkeys diff --git a/manifests/vendored-plugins.txt b/manifests/vendored-plugins.txt new file mode 100644 index 0000000..43fdb8d --- /dev/null +++ b/manifests/vendored-plugins.txt @@ -0,0 +1 @@ +dsh-ui-translate diff --git a/manifests/vscode-extension-artifacts.tsv b/manifests/vscode-extension-artifacts.tsv new file mode 100644 index 0000000..1c2174f --- /dev/null +++ b/manifests/vscode-extension-artifacts.tsv @@ -0,0 +1,8 @@ +asispts.neo-git-graph@0.5.0 editor-extensions/asispts.neo-git-graph-0.5.0.vsix 78570de7941ee888047b31732cf55016ee797bece06c8476f1270ad1e24632df +dsh-local.dsh-editor-file-bridge@0.1.0 editor-extensions/dsh-local.dsh-editor-file-bridge-0.1.0.vsix a7666a83e9d547d22c85fb1ecac9446888ef98dfb15c01a649be302c7d90bdbb +dsh-local.dsh-editor-preferences@0.2.0 editor-extensions/dsh-local.dsh-editor-preferences-0.2.0.vsix 8d02775587aeeeed0cee4ba21735efc7e07ecf00fc7f2de860d562472502dae1 +llvm-vs-code-extensions.vscode-clangd@0.6.0 editor-extensions/llvm-vs-code-extensions.vscode-clangd-0.6.0.vsix 97fb4e4a94b75e9b7ef14995f4d5d2f441758424d2fdf6d60f8f2107eeef6b86 +ms-vscode.cmake-tools@1.23.52 editor-extensions/ms-vscode.cmake-tools-1.23.52.vsix de4b52a2588e8e634edbf7b1fb8d25f98e3b86f63c85303d2b4eebfd37809a45 +rionoir.gitcharm@0.4.7 editor-extensions/rionoir.gitcharm-0.4.7.vsix 337f9ef135ac227527a1932390f8f03a9847de06c7e1e3864902338a77251003 +skippingpetticoatconvent.git-graph-zh@1.0.8 editor-extensions/skippingpetticoatconvent.git-graph-zh-1.0.8.vsix 54ae65c1a8cfdfd377b6766ae9310bb80745f32cab08faaf22f7d792b2878926 +tofilagman.git-branch-compare@0.4.0 editor-extensions/tofilagman.git-branch-compare-0.4.0.vsix 23d063380071245fae3d6180598a092f0db4fc49b863f14489604d8f4b2b3bc5 diff --git a/plugins/dsh-compact-sidebar/cordis.patch.yml b/plugins/dsh-compact-sidebar/cordis.patch.yml new file mode 100644 index 0000000..70b44b7 --- /dev/null +++ b/plugins/dsh-compact-sidebar/cordis.patch.yml @@ -0,0 +1,7 @@ +# dsh-compact-sidebar bundle patch: inserts the plugin row into the profile +# roster. The node half (exports ".") is a no-op; all behavior lives in the +# browser half (exports "./client"), which injects a