mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 21:47:39 +02:00
Replace SpoolBuddy self-update with SSH-based updates from Bambuddy
The daemon's self-update mechanism (git fetch/reset on its own code) was fragile: .git permission errors, self-modifying code mid-run, hardcoded main branch. Bambuddy now SSHes into the SpoolBuddy Pi and drives the update remotely — matching its own branch, with step-by-step progress via WebSocket. Install script updated with SSH access, sudoers entry, and --ssh-pubkey flag for pairing.
This commit is contained in:
@@ -5035,6 +5035,9 @@ export const spoolbuddyApi = {
|
||||
body: '{}',
|
||||
}),
|
||||
|
||||
getSSHPublicKey: () =>
|
||||
request<{ public_key: string }>('/spoolbuddy/ssh/public-key'),
|
||||
|
||||
writeTag: (deviceId: string, spoolId: number) =>
|
||||
request<{ status: string }>('/spoolbuddy/nfc/write-tag', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -504,6 +504,8 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) {
|
||||
const { t } = useTranslation();
|
||||
const [applying, setApplying] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [sshExpanded, setSSHExpanded] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const isUpdating = device.update_status === 'pending' || device.update_status === 'updating';
|
||||
|
||||
@@ -513,6 +515,13 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) {
|
||||
staleTime: 4 * 60 * 1000,
|
||||
});
|
||||
|
||||
const { data: sshKeyData } = useQuery({
|
||||
queryKey: ['spoolbuddy-ssh-key'],
|
||||
queryFn: () => spoolbuddyApi.getSSHPublicKey(),
|
||||
enabled: sshExpanded,
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
const applyUpdate = async () => {
|
||||
setApplying(true);
|
||||
setError(null);
|
||||
@@ -525,6 +534,14 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) {
|
||||
}
|
||||
};
|
||||
|
||||
const copyKey = () => {
|
||||
if (sshKeyData?.public_key) {
|
||||
navigator.clipboard.writeText(sshKeyData.public_key);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
const displayVersion = device.firmware_version
|
||||
|| (updateResult?.current_version && updateResult.current_version !== '0.0.0' ? updateResult.current_version : null);
|
||||
|
||||
@@ -641,17 +658,72 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) {
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="w-4 h-4 text-green-400 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<p className="text-zinc-300">{t('spoolbuddy.settings.upToDate', 'Up to date')}</p>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="w-4 h-4 text-green-400 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<p className="text-zinc-300">{t('spoolbuddy.settings.upToDate', 'Up to date')}</p>
|
||||
</div>
|
||||
{/* Force update button — re-deploy even when versions match */}
|
||||
<button
|
||||
onClick={applyUpdate}
|
||||
disabled={applying || isUpdating || !device.online}
|
||||
className="w-full px-4 py-2 rounded-lg text-xs font-medium bg-zinc-700 text-zinc-400 hover:bg-zinc-600 hover:text-zinc-200 disabled:opacity-40 transition-colors min-h-[36px]"
|
||||
>
|
||||
{!device.online
|
||||
? t('spoolbuddy.settings.deviceOffline', 'Device Offline')
|
||||
: t('spoolbuddy.settings.forceUpdate', 'Force Update')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{/* SSH Setup */}
|
||||
<div className="bg-zinc-800 rounded-lg p-4">
|
||||
<button
|
||||
onClick={() => setSSHExpanded(!sshExpanded)}
|
||||
className="w-full flex justify-between items-center text-sm"
|
||||
>
|
||||
<span className="font-semibold text-zinc-300">
|
||||
{t('spoolbuddy.settings.sshSetup', 'SSH Setup')}
|
||||
</span>
|
||||
<svg
|
||||
className={`w-4 h-4 text-zinc-400 transition-transform ${sshExpanded ? 'rotate-180' : ''}`}
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{sshExpanded && (
|
||||
<div className="mt-3 space-y-3">
|
||||
<p className="text-xs text-zinc-400">
|
||||
{t('spoolbuddy.settings.sshDescription', 'Add this public key to your SpoolBuddy device to enable remote updates. Run the install script with --ssh-pubkey or add it to ~/.ssh/authorized_keys on the device.')}
|
||||
</p>
|
||||
{sshKeyData?.public_key ? (
|
||||
<div className="relative">
|
||||
<pre className="bg-zinc-900 rounded p-3 text-xs text-zinc-300 font-mono break-all whitespace-pre-wrap">
|
||||
{sshKeyData.public_key}
|
||||
</pre>
|
||||
<button
|
||||
onClick={copyKey}
|
||||
className="absolute top-2 right-2 px-2 py-1 rounded text-xs bg-zinc-700 text-zinc-300 hover:bg-zinc-600 transition-colors"
|
||||
>
|
||||
{copied ? t('common.copied', 'Copied!') : t('common.copy', 'Copy')}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs text-zinc-500 italic">
|
||||
{t('spoolbuddy.settings.sshKeyLoading', 'Loading SSH key...')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user