Skip to content

Commit 38fe397

Browse files
committed
Adopt the real brand, and add the curl installer
The logos landed in the previous commit, and they are blue — the green palette I had invented is gone from both the site and the desktop app. Replaced with colours sampled from logo.png itself: #0054fc-#006cfc primary, #009cfc cyan, #001830 for the wordmark navy. The accent is lifted to #3d86ff for text on the dark ground, with the logo blue kept as accent-strong behind white, because a saturated blue needs white on it where the green took near-black. The icon set is wired through Next's metadata API rather than as raw <link> tags, so every route inherits it. The provided manifest.json was a generator template — "short_name": "App", "Your app description", white theme — and is now filled in. Added a 1200x630 social card; there was no og:image at all. The logo variant does NOT switch on Tailwind's `dark:`. That matches an explicit dark preference, while this palette treats dark as the default and light as the override, so a visitor with no preference set would have got a dark page with the dark-text wordmark on it. Plain CSS mirrors the palette. scripts/install.sh is the `curl -fsSL https://diskpush.com/install.sh | sh` installer. It installs the CLI always and the desktop app only when there is a desktop session to run it on — downloading 100MB of Electron onto a server reached over SSH is not a kindness. It is user-local and never asks for root: a piped script requesting sudo is a habit not worth teaching, and the .deb and AppImage remain manual downloads for system integration. A desktop install carries the CLI inside it and runs it on Electron's own Node, so no system Node is needed — verified by inspection that libsql's binding exports napi_register_module_v1 with no V8 symbols, making it ABI-stable across Node and Electron. A CLI-only install is a plain Node program and does need Node 24. The GUI executable is renamed diskpush-desktop so `diskpush` on PATH is unambiguously the CLI, and artifactName is set because the default derives from the scoped package name and produced "@diskpush/desktop-0.1.0.tar.gz". The .deb carries the house after-install.sh, which fixes electron-builder's chrome-sandbox permissions on Ubuntu 24.04+. 205 tests pass.
1 parent f79412c commit 38fe397

45 files changed

Lines changed: 816 additions & 42 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/desktop/package.json‎

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,87 @@
3535
"build": {
3636
"appId": "com.profullstack.diskpush",
3737
"productName": "DiskPush",
38-
"files": ["dist-electron/**/*", "out/**/*", "package.json"],
39-
"linux": { "category": "Utility", "target": ["AppImage", "deb"] },
40-
"mac": { "category": "public.app-category.utilities", "target": ["dmg"] }
38+
"executableName": "diskpush-desktop",
39+
"copyright": "Copyright (c) 2026 Profullstack, Inc.",
40+
"directories": {
41+
"output": "../../release",
42+
"buildResources": "resources"
43+
},
44+
"files": [
45+
"dist-electron/**/*",
46+
"out/**/*",
47+
"package.json"
48+
],
49+
"extraResources": [
50+
{
51+
"from": "resources/cli",
52+
"to": "cli"
53+
}
54+
],
55+
"linux": {
56+
"category": "Utility;FileTransfer;Network",
57+
"synopsis": "Push files fast. Sync only what changed.",
58+
"description": "A desktop app and CLI for rsync. Browse servers like FileZilla and transfer with rsync, with resumable incremental sync and direct server-to-server transfers.",
59+
"target": [
60+
"AppImage",
61+
"deb",
62+
"tar.gz"
63+
],
64+
"icon": "resources/icon.png",
65+
"desktop": {
66+
"entry": {
67+
"Keywords": "rsync;sftp;ssh;sync;transfer;backup;"
68+
}
69+
},
70+
"artifactName": "${productName}-${version}-linux-${arch}.${ext}"
71+
},
72+
"deb": {
73+
"depends": [
74+
"libgtk-3-0",
75+
"libnotify4",
76+
"libnss3",
77+
"libxss1",
78+
"libxtst6",
79+
"xdg-utils",
80+
"libatspi2.0-0",
81+
"libsecret-1-0",
82+
"rsync",
83+
"openssh-client"
84+
],
85+
"afterInstall": "resources/after-install.sh",
86+
"afterRemove": "resources/after-remove.sh",
87+
"artifactName": "${productName}-${version}-linux-${arch}.${ext}"
88+
},
89+
"mac": {
90+
"category": "public.app-category.utilities",
91+
"target": [
92+
"dmg",
93+
"zip"
94+
],
95+
"icon": "resources/icon.png",
96+
"artifactName": "${productName}-${version}-mac-${arch}.${ext}"
97+
},
98+
"win": {
99+
"target": [
100+
"nsis"
101+
],
102+
"icon": "resources/icon.png"
103+
},
104+
"npmRebuild": false,
105+
"asarUnpack": [
106+
"**/*.node",
107+
"**/node_modules/@libsql/**"
108+
],
109+
"artifactName": "${productName}-${version}-${os}-${arch}.${ext}"
110+
},
111+
"author": {
112+
"name": "Profullstack, Inc.",
113+
"email": "hello@profullstack.com"
114+
},
115+
"description": "Push files fast. Sync only what changed.",
116+
"homepage": "https://diskpush.com",
117+
"repository": {
118+
"type": "git",
119+
"url": "https://github.com/profullstack/diskpush.git"
41120
}
42121
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/bin/bash
2+
# Replaces electron-builder's stock deb postinst. Naming `deb.afterInstall` in
3+
# electron-builder.yml *overrides* the default template rather than appending
4+
# to it, so the update-alternatives / mime / desktop-database blocks below are
5+
# carried over verbatim from app-builder-lib's `templates/linux/after-install.tpl`.
6+
# Only the sandbox section is ours. Keep the rest in sync when bumping
7+
# electron-builder.
8+
9+
if type update-alternatives 2>/dev/null >&1; then
10+
# Remove previous link if it doesn't use update-alternatives
11+
if [ -L '/usr/bin/${executable}' -a -e '/usr/bin/${executable}' -a "`readlink '/usr/bin/${executable}'`" != '/etc/alternatives/${executable}' ]; then
12+
rm -f '/usr/bin/${executable}'
13+
fi
14+
update-alternatives --install '/usr/bin/${executable}' '${executable}' '/opt/${sanitizedProductName}/${executable}' 100 || ln -sf '/opt/${sanitizedProductName}/${executable}' '/usr/bin/${executable}'
15+
else
16+
ln -sf '/opt/${sanitizedProductName}/${executable}' '/usr/bin/${executable}'
17+
fi
18+
19+
# ── Sandbox ──────────────────────────────────────────────────────────────────
20+
#
21+
# The stock template picks between Chromium's two sandboxes like this:
22+
#
23+
# if ! { [[ -L /proc/self/ns/user ]] && unshare --user true; }; then
24+
# chmod 4755 chrome-sandbox # no user namespaces -> SUID sandbox
25+
# else
26+
# chmod 0755 chrome-sandbox # namespaces work -> SUID not needed
27+
# fi
28+
#
29+
# That asks the right question as the wrong user, at the wrong time. A postinst
30+
# runs as root, and Ubuntu 24.04+'s `kernel.apparmor_restrict_unprivileged_userns`
31+
# restricts *unprivileged* user namespaces only — root is exempt. So the probe
32+
# succeeds during install, the else branch runs, chrome-sandbox lands as 0755,
33+
# and the first ordinary launch dies with:
34+
#
35+
# The SUID sandbox helper binary was found, but is not configured
36+
# correctly. Rather than run without sandboxing I'm aborting now.
37+
#
38+
# Two things are done about it, in order of preference.
39+
#
40+
# First, an AppArmor profile granting this binary `userns create`. That restores
41+
# the *namespace* sandbox, which is the one upstream Chromium actually develops
42+
# against; the SUID helper is a compatibility path for kernels without userns.
43+
# Shipping the profile is what Chrome, Chromium and the other Electron apps in
44+
# the archive settled on for this exact regression.
45+
#
46+
# Second, the SUID bit, as a fallback — decided by probing as an unprivileged
47+
# user rather than as root, so the answer reflects the conditions the app will
48+
# actually run under. `nobody` is the probe subject because it is the one
49+
# account guaranteed to exist and guaranteed not to be privileged.
50+
CHROME_SANDBOX='/opt/${sanitizedProductName}/chrome-sandbox'
51+
APPARMOR_PROFILE='/etc/apparmor.d/${executable}'
52+
53+
userns_available_unprivileged() {
54+
# No unprivileged probe subject means no trustworthy answer. Report "not
55+
# available", which selects the SUID sandbox — a sandbox that is merely
56+
# unnecessary costs nothing, while wrongly skipping it aborts the app.
57+
command -v runuser >/dev/null 2>&1 || return 1
58+
id nobody >/dev/null 2>&1 || return 1
59+
runuser -u nobody -- unshare --user true >/dev/null 2>&1
60+
}
61+
62+
if [ "$(cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns 2>/dev/null)" = "1" ] \
63+
&& [ -d /etc/apparmor.d ]; then
64+
cat > "$APPARMOR_PROFILE" <<'APPARMOR_EOF'
65+
# Grants the namespace sandbox to DiskPush on kernels where
66+
# unprivileged user namespaces are restricted by AppArmor (Ubuntu 24.04+).
67+
# flags=(unconfined) keeps this a permission grant, not a confinement policy:
68+
# it does not restrict the application beyond the system default.
69+
abi <abi/4.0>,
70+
include <tunables/global>
71+
72+
profile ${executable} "/opt/${sanitizedProductName}/${executable}" flags=(unconfined) {
73+
userns,
74+
include if exists <local/${executable}>
75+
}
76+
APPARMOR_EOF
77+
78+
# Best-effort. A parser that rejects the profile (an older abi, say) must
79+
# not fail the package install — the SUID fallback below still applies.
80+
if command -v apparmor_parser >/dev/null 2>&1; then
81+
apparmor_parser -r -W "$APPARMOR_PROFILE" >/dev/null 2>&1 \
82+
|| echo 'diskpush: could not load AppArmor profile; falling back to the SUID sandbox' >&2
83+
fi
84+
fi
85+
86+
if userns_available_unprivileged; then
87+
chmod 0755 "$CHROME_SANDBOX" || true
88+
else
89+
chmod 4755 "$CHROME_SANDBOX" || true
90+
fi
91+
92+
if hash update-mime-database 2>/dev/null; then
93+
update-mime-database /usr/share/mime || true
94+
fi
95+
96+
if hash update-desktop-database 2>/dev/null; then
97+
update-desktop-database /usr/share/applications || true
98+
fi
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
# Replaces electron-builder's stock deb postrm. The update-alternatives block is
3+
# carried over verbatim from app-builder-lib's `templates/linux/after-remove.tpl`;
4+
# only the AppArmor cleanup is ours. See after-install.sh.
5+
6+
# Delete the link to the binary
7+
if type update-alternatives >/dev/null 2>&1; then
8+
update-alternatives --remove '${executable}' '/usr/bin/${executable}'
9+
else
10+
rm -f '/usr/bin/${executable}'
11+
fi
12+
13+
# Remove the profile installed by after-install.sh. Unloading before deleting,
14+
# because a profile removed from disk while still loaded stays in the kernel
15+
# until the next reboot — and it names a path that no longer has a binary at it.
16+
APPARMOR_PROFILE='/etc/apparmor.d/${executable}'
17+
if [ -f "$APPARMOR_PROFILE" ]; then
18+
if command -v apparmor_parser >/dev/null 2>&1; then
19+
apparmor_parser -R "$APPARMOR_PROFILE" >/dev/null 2>&1 || true
20+
fi
21+
rm -f "$APPARMOR_PROFILE"
22+
fi

‎apps/desktop/resources/icon.png‎

542 KB
Loading

‎apps/desktop/src/app/globals.css‎

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
@import 'tailwindcss';
22

33
@theme {
4-
--color-ink: #0a0c10;
5-
--color-surface: #11141b;
6-
--color-raised: #171b24;
7-
--color-line: #232936;
8-
--color-text: #e7ebf2;
9-
--color-muted: #8e99ae;
10-
--color-accent: #4ade80;
4+
--color-ink: #070c16;
5+
--color-surface: #0e1522;
6+
--color-raised: #141d2c;
7+
--color-line: #1f2a3c;
8+
--color-text: #e8edf6;
9+
--color-muted: #8d9bb3;
10+
--color-accent: #3d86ff;
11+
--color-accent-strong: #0b62fd;
1112
--color-danger: #f87171;
1213
--color-warn: #fbbf24;
1314
--font-mono: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
@@ -35,11 +36,12 @@ body {
3536
:root {
3637
color-scheme: light;
3738
--color-ink: #ffffff;
38-
--color-surface: #f4f6f8;
39+
--color-surface: #f5f7fa;
3940
--color-raised: #ffffff;
40-
--color-line: #dfe3ea;
41-
--color-text: #11141a;
42-
--color-muted: #5d6779;
43-
--color-accent: #16a34a;
41+
--color-line: #dfe4ed;
42+
--color-text: #001830;
43+
--color-muted: #55627a;
44+
--color-accent: #0b62fd;
45+
--color-accent-strong: #0b62fd;
4446
}
4547
}

‎apps/desktop/src/components/ui.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function Button({
2525
}) {
2626
const styles = {
2727
default: 'border border-line bg-raised hover:bg-surface',
28-
primary: 'bg-accent text-ink font-medium hover:opacity-90',
28+
primary: 'bg-accent-strong text-white font-medium hover:opacity-90',
2929
danger: 'border border-danger/50 text-danger hover:bg-danger/10',
3030
ghost: 'text-muted hover:bg-surface hover:text-text',
3131
}[variant]

‎apps/web/app/download/page.tsx‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,36 @@ export default async function DownloadPage() {
2525
: 'Builds are published to GitHub Releases. Until the first tagged release, install from source.'}
2626
</p>
2727

28+
<section className="mt-10">
29+
<h2 className="text-lg font-semibold">Install</h2>
30+
<p className="mt-2 text-sm text-muted">
31+
Installs the CLI, and the desktop app when this machine has a desktop to run it on.
32+
Everything lands under your home directory: no root, no package manager, no system files
33+
touched.
34+
</p>
35+
<div className="mt-4">
36+
<Code label="linux and macos">{`curl -fsSL https://diskpush.com/install.sh | sh`}</Code>
37+
</div>
38+
<ul className="mt-4 space-y-1.5 text-sm text-muted">
39+
<li>
40+
<code className="font-mono text-xs">--cli-only</code> skips the desktop app; a server
41+
gets the CLI and nothing else.
42+
</li>
43+
<li>
44+
<code className="font-mono text-xs">diskpush update</code> upgrades in place,{' '}
45+
<code className="font-mono text-xs">diskpush uninstall</code> removes it and keeps your
46+
connections.
47+
</li>
48+
<li>
49+
Piping a script into a shell is a real decision. It is short enough to read first:{' '}
50+
<a className="text-accent hover:underline" href="/install.sh">
51+
read install.sh
52+
</a>
53+
.
54+
</li>
55+
</ul>
56+
</section>
57+
2858
<section className="mt-10">
2959
<h2 className="text-lg font-semibold">Requirements</h2>
3060
<p className="mt-2 text-sm leading-relaxed text-muted">
@@ -63,9 +93,9 @@ pnpm build
6393
node apps/cli/dist/bin.js --help`}</Code>
6494
</div>
6595
<p className="mt-3 text-xs text-muted">
66-
A shell installer will be offered once binaries are published. It will be short enough to
67-
read, will verify checksums, and manual instructions will always be documented alongside
68-
it, because nobody should have to pipe an unread script into a shell.
96+
A desktop install already contains the CLI and runs it on the Node inside Electron, so
97+
nothing else is needed. A CLI-only install is a plain Node program and needs Node 24 or
98+
newer.
6999
</p>
70100
</section>
71101

‎apps/web/app/globals.css‎

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
@import 'tailwindcss';
22

3+
/* Sampled from the logo: primary blue #0054fc-#006cfc, cyan #009cfc, and the
4+
navy #001830 of the wordmark. */
35
@theme {
4-
--color-ink: #0a0c10;
5-
--color-surface: #11141b;
6-
--color-raised: #171b24;
7-
--color-line: #232936;
8-
--color-text: #e7ebf2;
9-
--color-muted: #96a0b5;
10-
--color-accent: #4ade80;
11-
--color-accent-dim: #22c55e;
6+
--color-ink: #070c16;
7+
--color-surface: #0e1522;
8+
--color-raised: #141d2c;
9+
--color-line: #1f2a3c;
10+
--color-text: #e8edf6;
11+
--color-muted: #93a1b8;
12+
/* Lifted from the logo's #0054fc so it stays legible as text on the dark
13+
ground; --color-accent-strong is the logo blue itself, used behind white. */
14+
--color-accent: #3d86ff;
15+
--color-accent-strong: #0b62fd;
16+
--color-cyan: #21b9fb;
1217
--color-warn: #fbbf24;
1318
--font-mono: ui-monospace, 'SFMono-Regular', 'JetBrains Mono', Menlo, Consolas, monospace;
1419
}
@@ -33,13 +38,13 @@ body {
3338
:root {
3439
color-scheme: light;
3540
--color-ink: #ffffff;
36-
--color-surface: #f6f7f9;
41+
--color-surface: #f5f7fa;
3742
--color-raised: #ffffff;
38-
--color-line: #e3e6ec;
39-
--color-text: #10131a;
40-
--color-muted: #5b6577;
41-
--color-accent: #16a34a;
42-
--color-accent-dim: #15803d;
43+
--color-line: #dfe4ed;
44+
--color-text: #001830;
45+
--color-muted: #55627a;
46+
--color-accent: #0b62fd;
47+
--color-accent-strong: #0b62fd;
4348
}
4449
}
4550

@@ -82,3 +87,13 @@ body {
8287
.prose-docs th, .prose-docs td { border: 1px solid var(--color-line); padding: 0.5rem 0.75rem; text-align: left; vertical-align: top; }
8388
.prose-docs th { background: var(--color-surface); font-weight: 600; }
8489
.prose-docs hr { border: 0; border-top: 1px solid var(--color-line); margin: 2rem 0; }
90+
91+
/* Logo variant switching, mirroring the palette above: dark by default, the
92+
light wordmark only when the visitor actually asks for a light scheme. */
93+
.logo-dark { display: block; }
94+
.logo-light { display: none; }
95+
96+
@media (prefers-color-scheme: light) {
97+
.logo-dark { display: none; }
98+
.logo-light { display: block; }
99+
}

0 commit comments

Comments
 (0)