Skip to content
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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
app/dist/app.shellcanvas.json -text
dist/adapter-windows-x86_64/bin/*.exe -text
dist/adapter-windows-x86_64/** -text
dist/adapter-linux-x86_64/bin/* -text
dist/adapter-macos-x86_64/bin/* -text
dist/adapter-macos-aarch64/bin/* -text
34 changes: 34 additions & 0 deletions .github/workflows/build-linux-connector.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Verify Linux Database connector

# A focused Linux build allows retrying a runner-library failure without
# repeating the paid macOS matrix jobs.
on:
workflow_dispatch:

permissions:
contents: read

jobs:
package:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Linux native libraries
run: |
sudo apt-get update
sudo apt-get install -y libwayland-dev libxkbcommon-dev libgtk-3-dev libsqlite3-dev libdbus-1-dev pkg-config
- name: Install Rust
run: |
rustup toolchain install stable --profile minimal
rustup default stable
rustc --version
- name: Build and validate native connector
run: |
cargo run --locked --manifest-path vendor/shellcanvas-adapter-sdk/Cargo.toml --bin shellcanvas-adapter -- build adapter dist/adapter-linux-x86_64
cargo run --locked --manifest-path vendor/shellcanvas-adapter-sdk/Cargo.toml --bin shellcanvas-adapter -- validate dist/adapter-linux-x86_64/adapter.json
- uses: actions/upload-artifact@v4
with:
name: database-adapter-linux-x86_64
path: dist/adapter-linux-x86_64/
if-no-files-found: error
retention-days: 7
55 changes: 55 additions & 0 deletions .github/workflows/build-macos-connector.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Verify macOS Database connectors

# Focused retry after the first matrix used the updater's "darwin" name rather
# than the Rust/native-adapter platform name "macos".
on:
workflow_dispatch:

permissions:
contents: read

jobs:
package:
strategy:
fail-fast: false
matrix:
include:
- platform: macos-x86_64
arch: x86_64
runner: macos-15-intel
- platform: macos-aarch64
arch: arm64
runner: macos-15
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Install Rust
run: |
rustup toolchain install stable --profile minimal
rustup default stable
rustc --version
- name: Build and validate native connector
run: |
cargo run --locked --manifest-path vendor/shellcanvas-adapter-sdk/Cargo.toml --bin shellcanvas-adapter -- build adapter dist/adapter-${{ matrix.platform }}
cargo run --locked --manifest-path vendor/shellcanvas-adapter-sdk/Cargo.toml --bin shellcanvas-adapter -- validate dist/adapter-${{ matrix.platform }}/adapter.json
- name: Check binary architecture and package identity
env:
PLATFORM: ${{ matrix.platform }}
ARCH: ${{ matrix.arch }}
run: |
python3 - <<'PY'
import json, os, pathlib, subprocess
root = pathlib.Path('dist') / ('adapter-' + os.environ['PLATFORM'])
manifest = json.loads((root / 'adapter.json').read_text())
assert manifest['platform'] == os.environ['PLATFORM']
assert manifest['version'] == '0.1.1'
binary = root / manifest['entrypoint']
assert binary.is_file()
assert subprocess.check_output(['lipo', '-archs', binary], text=True).strip() == os.environ['ARCH']
PY
- uses: actions/upload-artifact@v4
with:
name: database-adapter-${{ matrix.platform }}
path: dist/adapter-${{ matrix.platform }}/
if-no-files-found: error
retention-days: 7
57 changes: 57 additions & 0 deletions .github/workflows/build-native-platforms.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Build Database native connectors

# CI keeps these build artifacts for review. Release only after all platform
# packages and their hashes have been checked together.
on:
workflow_dispatch:

permissions:
contents: read

jobs:
package:
strategy:
fail-fast: false
matrix:
include:
- platform: linux-x86_64
runner: ubuntu-22.04
- platform: macos-x86_64
runner: macos-15-intel
- platform: macos-aarch64
runner: macos-15
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Linux native libraries
if: matrix.platform == 'linux-x86_64'
run: |
sudo apt-get update
sudo apt-get install -y libwayland-dev libxkbcommon-dev libgtk-3-dev libsqlite3-dev libdbus-1-dev pkg-config
- name: Install Rust
run: |
rustup toolchain install stable --profile minimal
rustup default stable
rustc --version
- name: Build and validate native connector
run: |
cargo run --locked --manifest-path vendor/shellcanvas-adapter-sdk/Cargo.toml --bin shellcanvas-adapter -- build adapter dist/adapter-${{ matrix.platform }}
cargo run --locked --manifest-path vendor/shellcanvas-adapter-sdk/Cargo.toml --bin shellcanvas-adapter -- validate dist/adapter-${{ matrix.platform }}/adapter.json
- name: Check platform identity
env:
PLATFORM: ${{ matrix.platform }}
run: |
python3 - <<'PY'
import json, os, pathlib
root = pathlib.Path('dist') / ('adapter-' + os.environ['PLATFORM'])
manifest = json.loads((root / 'adapter.json').read_text())
assert manifest['platform'] == os.environ['PLATFORM']
assert manifest['version'] == '0.1.1'
assert (root / manifest['entrypoint']).is_file()
PY
- uses: actions/upload-artifact@v4
with:
name: database-adapter-${{ matrix.platform }}
path: dist/adapter-${{ matrix.platform }}/
if-no-files-found: error
retention-days: 7
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["adapter"]
resolver = "2"

[workspace.package]
version = "0.1.0"
version = "0.1.1"
edition = "2024"
license = "MPL-2.0"

Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,30 @@

A first-party companion app for [ShellCanvas](https://github.com/techartdev/ShellCanvas) that connects directly to PostgreSQL, MySQL/MariaDB, Microsoft SQL Server, and SQLite.

Version 0.1.0 browses schemas and tables, runs SQL, displays bounded typed results, pages through the retained result, and saves the full retained result as CSV. It does not provide grid editing or a DDL designer.
Version 0.1.1 browses schemas and tables, runs SQL, displays bounded typed results, pages through the retained result, and saves the full retained result as CSV. It does not provide grid editing or a DDL designer.

> **SQL is not filtered into a read-only subset.** Statements run with the configured database account's privileges. Use database grants appropriate for your work. The app repeats this warning at run time.

## Install the initial Windows build
## Install

With ShellCanvas 0.1.12 or later, open **App Manager → Add apps → From GitHub** and enter `techartdev/ShellCanvas-Database` with reference `main` (or `v0.1.0` for this release). Review and install the app and its Windows connector together.
Open **App Manager → Add apps → From GitHub** and enter `techartdev/ShellCanvas-Database` with reference `main` (or `v0.1.1` for this release). Review and install the app and its connector together. The repository provides native connectors for Windows x86_64, Linux x86_64, macOS Intel, and macOS Apple Silicon. ShellCanvas selects the package matching the client machine.

Windows requires ShellCanvas 0.1.12 or later. Linux and macOS require a ShellCanvas release with native installed-app frames enabled; the 0.1.12 desktop build still blocks those frames.

Open Database, choose **Connect database**, then select SQL Server, PostgreSQL, MySQL, MariaDB or SQLite. Enter the host, TCP port, database and credentials, or an existing SQLite file path. The app shows the endpoint and actual connection state. No database connection starts until you press Connect. Passwords are not saved.

Each app window owns its connection; Disconnect or closing the window releases it. You can still use a database service already configured on an accepted workspace. The app follows the desktop's dark/light theme and updates when it changes.

There is no automatic network or SSH-host instance discovery. Network connections default to the connected SSH host: 127.0.0.1 means that remote host. The SSH server must allow TCP forwarding. Choose **This PC (direct connection)** explicitly for local access. SQL Server named instances require their TCP port. SQLite remains a local-file option; remote SQLite execution is not implemented. Schema/table enumeration starts after a connection succeeds.

ShellCanvas 0.1.12 is required for the combined installation and SSH-host routing.
ShellCanvas 0.1.12 introduced combined installation and SSH-host routing. The Linux and macOS app-frame gate is addressed separately in the desktop repository.

ShellCanvas 0.1.11 and older require the existing manual order: install
`dist/adapter-windows-x86_64/adapter.json` under **Connection adapters**, add and
configure that source, then install `app/dist/app.shellcanvas.json` or the app
from GitHub and grant `services.dev.shellcanvas.database`.

This app is intentionally not in ShellCanvas's recommended catalog yet. The checked-in Windows package is the initial test build. PostgreSQL, MySQL/MariaDB, and SQL Server paths compile but still need disposable-server and user acceptance testing; SQLite has an automated real-database integration test.
This app is intentionally not in ShellCanvas's recommended catalog yet. The native packages have automated build and integrity checks; Linux and macOS still need manual app testing. PostgreSQL, MySQL/MariaDB, and SQL Server paths compile but still need disposable-server and user acceptance testing; SQLite has an automated real-database integration test.

## Connection fields

Expand All @@ -34,7 +36,7 @@ Set `provider` to `sqlite`, `postgresql`, `mysql`, `mariadb`, or `sqlserver`.
- TLS verifies the certificate and host identity by default. Trusting a server certificate without identity verification is an explicit insecure opt-in for private/self-signed deployments.
- Password fields are not retained in saved ShellCanvas profiles; supply the password when connecting.

The app requires ShellCanvas 0.1.12 or later and the `host.tcp` permission to use SSH routing. The desktop pins the accepted host connection, opens an SSH direct-tcpip channel, and supplies a loopback transport to this app’s private connector. UI and database drivers run on the desktop; SQL runs on the remote database. Closing/replacing the host retires its tunnels without reconnecting or falling back to the PC. Database TLS hostname verification remains unchanged. See `vendor/sqlx-core/SHELLCANVAS.md` for the transport-only SQLx patch.
The app requires `host.tcp` permission to use SSH routing. The desktop pins the accepted host connection, opens an SSH direct-tcpip channel, and supplies a loopback transport to this app’s private connector. UI and database drivers run on the desktop; SQL runs on the remote database. Closing/replacing the host retires its tunnels without reconnecting or falling back to the PC. Database TLS hostname verification remains unchanged. See `vendor/sqlx-core/SHELLCANVAS.md` for the transport-only SQLx patch.

## Results and CSV

Expand All @@ -61,10 +63,10 @@ The repository command uses the pinned 0.1.11 app SDK for the app artifact, then
validates and reattaches the native dependency with a freshly computed adapter
manifest hash. Regeneration therefore cannot silently drop or stale the pin.

Build/package the native adapter with the vendored ShellCanvas 0.1.11 SDK:
Build/package the native adapter on each target OS and architecture with the vendored ShellCanvas 0.1.11 SDK:

```powershell
cargo run --manifest-path vendor/shellcanvas-adapter-sdk/Cargo.toml --bin shellcanvas-adapter -- build adapter dist/adapter-windows-x86_64
cargo run --locked --manifest-path vendor/shellcanvas-adapter-sdk/Cargo.toml --bin shellcanvas-adapter -- build adapter dist/adapter-<native-platform>
```

Use a fresh output directory for each package. The source pins SQLx 0.8.6 and uses concrete PostgreSQL, MySQL, and SQLite decoders rather than SQLx `Any`. SQL Server uses Tiberius 0.12.3. Optional live-provider credentials are never stored here; future disposable-server tests will be explicitly gated. Never point development tests at production databases.
Expand Down
2 changes: 1 addition & 1 deletion adapter/adapter.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "dev.shellcanvas.database",
"name": "ShellCanvas Database",
"version": "0.1.0",
"version": "0.1.1",
"description": "Database connections through the connected SSH host, or directly from this PC.",
"platform": "current",
"entrypoint": "bin/shellcanvas-device{exe}",
Expand Down
2 changes: 1 addition & 1 deletion app/dist/app.shellcanvas.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dev.shellcanvas.database.app",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"type": "module",
"scripts": {
Expand Down
52 changes: 29 additions & 23 deletions app/scripts/repository.mjs
Original file line number Diff line number Diff line change
@@ -1,34 +1,46 @@
// SPDX-License-Identifier: MPL-2.0
import { createHash, randomUUID } from "node:crypto";
import { readFile, rename, unlink, writeFile } from "node:fs/promises";
import { readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { parseAppPackage } from "../node_modules/@techartdev/shellcanvas-app-sdk/dist/package.js";
import { parseAppRepository } from "../node_modules/@techartdev/shellcanvas-app-sdk/dist/repository.js";

const root = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
const packagePath = "app/dist/app.shellcanvas.json";
const adapterPath = "dist/adapter-windows-x86_64/adapter.json";
const description =
"Query SQL Server, PostgreSQL, MySQL/MariaDB and SQLite from ShellCanvas.";

const packageBytes = await readFile(join(root, packagePath));
const app = parseAppPackage(
new TextDecoder("utf-8", { fatal: true }).decode(packageBytes),
);
const adapterBytes = await readFile(join(root, adapterPath));
let adapter;
try {
adapter = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(adapterBytes));
} catch {
throw new Error("The packaged native adapter manifest is not valid UTF-8 JSON.");
const platforms = ["windows-x86_64", "linux-x86_64", "macos-x86_64", "macos-aarch64"];
const packages = [];
for (const platform of platforms) {
const path = `dist/adapter-${platform}/adapter.json`;
const bytes = await readFile(join(root, path));
let adapter;
try {
adapter = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
} catch {
throw new Error(`${platform} adapter manifest is not valid UTF-8 JSON.`);
}
if (
adapter?.id !== "dev.shellcanvas.database" ||
adapter?.version !== app.version ||
adapter?.platform !== platform
)
throw new Error(`${platform} adapter identity, version, or platform changed.`);
const names = await readdir(join(root, `dist/adapter-${platform}/bin`));
if (names.length !== 1 || adapter.entrypoint !== `bin/${names[0]}`)
throw new Error(`${platform} adapter entrypoint is missing or unexpected.`);
const executable = await readFile(join(root, `dist/adapter-${platform}`, adapter.entrypoint));
const entry = adapter.files.find((file) => file.path === adapter.entrypoint);
if (entry?.size !== executable.length || entry.sha256 !== createHash("sha256").update(executable).digest("hex"))
throw new Error(`${platform} adapter executable does not match its manifest.`);
packages.push({ platform, path, sha256: createHash("sha256").update(bytes).digest("hex") });
}
if (
adapter?.id !== "dev.shellcanvas.database" ||
adapter?.version !== "0.1.0" ||
adapter?.platform !== "windows-x86_64"
)
throw new Error("The packaged native adapter identity, version, or platform changed.");

const descriptorPath = join(root, "shellcanvas.repo.json");
const descriptor = {
Expand All @@ -45,15 +57,9 @@ const descriptor = {
},
})),
nativeAdapter: {
id: adapter.id,
version: adapter.version,
packages: [
{
platform: adapter.platform,
path: adapterPath,
sha256: createHash("sha256").update(adapterBytes).digest("hex"),
},
],
id: app.id,
version: app.version,
packages,
},
};
const temporary = join(root, `.repository-native-${randomUUID()}.tmp`);
Expand Down
9 changes: 6 additions & 3 deletions app/scripts/verify-repository.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ if (app.id !== descriptor.id || app.version !== descriptor.version)
throw new Error("App package identity/version does not match the descriptor.");

const dependency = descriptor.nativeAdapter;
if (!dependency || dependency.packages.length !== 1)
throw new Error("Expected one pinned native adapter package.");
const expected = ["windows-x86_64", "linux-x86_64", "macos-x86_64", "macos-aarch64"];
if (!dependency || dependency.packages.length !== expected.length)
throw new Error("Expected four pinned native adapter packages.");
if (dependency.packages.some((entry, index) => entry.platform !== expected[index]))
throw new Error("Native adapter platform set or order changed.");
for (const entry of dependency.packages) {
const manifestBytes = readTree(entry.path);
expectHash(manifestBytes, entry.sha256, `${entry.platform} adapter manifest`);
Expand All @@ -52,4 +55,4 @@ for (const entry of dependency.packages) {
expectHash(bytes, file.sha256, file.path);
}
}
console.log(`Verified committed app, ${dependency.packages.length} adapter manifest, and all native assets.`);
console.log(`Verified committed app, ${dependency.packages.length} adapter manifests, and all native assets.`);
2 changes: 1 addition & 1 deletion app/shellcanvas.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"format": 1,
"kind": "app",
"id": "dev.shellcanvas.database",
"version": "0.1.0",
"version": "0.1.1",
"title": "ShellCanvas Database",
"description": "Browse database schemas, run SQL, inspect typed results and export CSV.",
"permissions": [
Expand Down
Loading