Skip to content

Commit 51ac667

Browse files
committed
Fix sudo SFTP handshake and enable auto-release workflow
1 parent 1d3745e commit 51ac667

13 files changed

Lines changed: 359 additions & 53 deletions

File tree

.github/workflows/build.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ on:
88
type: boolean
99
default: false
1010
push:
11+
branches:
12+
- "main"
1113
tags:
1214
- "v*"
1315

@@ -85,7 +87,7 @@ jobs:
8587
name: release
8688
runs-on: ubuntu-latest
8789
needs: build
88-
if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.publish_release)
90+
if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.publish_release) || (github.event_name == 'push' && github.ref == 'refs/heads/main')
8991
permissions:
9092
contents: write
9193
steps:
@@ -101,9 +103,21 @@ jobs:
101103
- name: List artifacts
102104
run: ls -la artifacts/
103105

106+
- name: Compute release tag
107+
id: release_tag
108+
if: "!startsWith(github.ref, 'refs/tags/')"
109+
shell: bash
110+
run: |
111+
TS="$(date -u +'%Y%m%d-%H%M')"
112+
SHORT_SHA="${GITHUB_SHA::7}"
113+
echo "tag=dev-${TS}-${SHORT_SHA}" >> "$GITHUB_OUTPUT"
114+
104115
- name: Create GitHub Release
105116
uses: softprops/action-gh-release@v2
106117
with:
118+
tag_name: ${{ steps.release_tag.outputs.tag || github.ref_name }}
119+
name: ${{ steps.release_tag.outputs.tag || github.ref_name }}
120+
prerelease: ${{ !startsWith(github.ref, 'refs/tags/') }}
107121
files: |
108122
artifacts/*.dmg
109123
artifacts/*.zip

application/i18n/locales/en.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,9 @@ const en: Messages = {
610610
'hostDetails.section.address': 'Address',
611611
'hostDetails.hostname.placeholder': 'IP or Hostname',
612612
'hostDetails.section.general': 'General',
613+
'hostDetails.section.sftp': 'SFTP Settings',
614+
'hostDetails.sftp.sudo': 'Sudo Mode',
615+
'hostDetails.sftp.sudo.desc': 'Automatically acquire Root privileges using stored password',
613616
'hostDetails.label.placeholder': 'Label (e.g., Production Server)',
614617
'hostDetails.group.placeholder': 'Parent Group',
615618
'hostDetails.section.credentials': 'Credentials',

application/i18n/locales/zh-CN.ts

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,6 @@ const zhCN: Messages = {
264264

265265
// SFTP
266266
'sftp.newFolder': '新建文件夹',
267-
'sftp.newFile': '新建文件',
268267
'sftp.filter': '筛选',
269268
'sftp.filter.placeholder': '按文件名筛选...',
270269
'sftp.columns.name': '名称',
@@ -299,8 +298,6 @@ const zhCN: Messages = {
299298
'sftp.goHome': '返回主目录',
300299
'sftp.folderName': '文件夹名称',
301300
'sftp.folderName.placeholder': '输入文件夹名称',
302-
'sftp.fileName': '文件名称',
303-
'sftp.fileName.placeholder': '输入文件名称',
304301
'sftp.prompt.newFolderName': '新建文件夹名称?',
305302
'sftp.rename.title': '重命名',
306303
'sftp.rename.newName': '新名称',
@@ -313,12 +310,6 @@ const zhCN: Messages = {
313310
'sftp.error.uploadFailed': '上传失败',
314311
'sftp.error.deleteFailed': '删除失败',
315312
'sftp.error.createFolderFailed': '创建文件夹失败',
316-
'sftp.error.createFileFailed': '创建文件失败',
317-
'sftp.error.invalidFileName': '文件名包含非法字符:{chars}',
318-
'sftp.error.reservedName': '此文件名是系统保留名称',
319-
'sftp.overwrite.title': '文件已存在',
320-
'sftp.overwrite.desc': '名为"{name}"的文件已存在。是否要替换它?',
321-
'sftp.overwrite.confirm': '替换',
322313
'sftp.error.renameFailed': '重命名失败',
323314
'sftp.picker.title': '选择主机',
324315
'sftp.picker.desc': '为{side}窗格选择主机',
@@ -388,13 +379,12 @@ const zhCN: Messages = {
388379
'hostDetails.keys.empty': '暂无密钥',
389380
'hostDetails.certs.search': '搜索证书…',
390381
'hostDetails.certs.empty': '暂无证书',
391-
'hostDetails.agentForwarding': '转发 SSH 密钥',
392-
'hostDetails.agentForwarding.desc': '允许远程服务器使用本地 SSH 密钥(例如用于 git 操作)',
393-
'hostDetails.jumpHosts': '通过主机代理',
382+
'hostDetails.agentForwarding': '代理转发',
383+
'hostDetails.jumpHosts': '跳板主机',
394384
'hostDetails.jumpHosts.hops': '{count} 跳',
395385
'hostDetails.jumpHosts.direct': '直连',
396-
'hostDetails.jumpHosts.configure': '配置代理主机',
397-
'hostDetails.proxy': '通过 HTTP/SOCKS5 代理',
386+
'hostDetails.jumpHosts.configure': '配置跳板主机',
387+
'hostDetails.proxy': '代理',
398388
'hostDetails.proxy.none': '无',
399389
'hostDetails.proxy.edit': '编辑代理',
400390
'hostDetails.proxy.configure': '配置代理',
@@ -790,20 +780,11 @@ const zhCN: Messages = {
790780
'sftp.autoSync.success': '文件已同步到远程:{fileName}',
791781
'sftp.autoSync.error': '同步文件失败:{error}',
792782

793-
// SFTP Folder Upload Progress
794-
'sftp.upload.progress': '正在上传 {current}/{total} 个文件...',
795-
'sftp.upload.currentFile': '当前: {fileName}',
796-
'sftp.upload.cancelled': '上传已取消',
797-
'sftp.upload.cancel': '取消',
798-
799783
// SFTP Reconnecting
800784
'sftp.reconnecting.title': '正在重连...',
801785
'sftp.reconnecting.desc': '连接已断开,正在尝试重新连接',
802786
'sftp.reconnected': '连接已恢复',
803787
'sftp.error.reconnectFailed': '重连失败,请重试。',
804-
'sftp.error.connectionLostManual': '连接已断开,请手动重新连接。',
805-
'sftp.error.connectionLostReconnecting': '连接已断开,正在重连...',
806-
'sftp.error.sessionLost': 'SFTP 会话已断开,请重新连接。',
807788

808789
// Settings > SFTP Show Hidden Files
809790
'settings.sftp.showHiddenFiles': '显示隐藏文件',
@@ -1118,16 +1099,9 @@ const zhCN: Messages = {
11181099
'serial.field.configLabelPlaceholder': '例如 Arduino Uno',
11191100
'serial.connectAndSave': '连接并保存',
11201101
'serial.edit.title': '串口设置',
1121-
1122-
// Keyboard Interactive Authentication (2FA/MFA)
1123-
'keyboard.interactive.title': '需要验证',
1124-
'keyboard.interactive.desc': '服务器需要额外的身份验证。',
1125-
'keyboard.interactive.descWithHost': '服务器 {hostname} 需要额外的身份验证。',
1126-
'keyboard.interactive.response': '响应',
1127-
'keyboard.interactive.enterCode': '输入验证码',
1128-
'keyboard.interactive.enterResponse': '输入响应',
1129-
'keyboard.interactive.submit': '提交',
1130-
'keyboard.interactive.verifying': '验证中...',
1102+
'hostDetails.section.sftp': 'SFTP 设置',
1103+
'hostDetails.sftp.sudo': 'Sudo 提权模式',
1104+
'hostDetails.sftp.sudo.desc': '使用保存的密码自动获取 Root 权限',
11311105
};
11321106

11331107
export default zhCN;

application/state/useSftpState.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ export const useSftpState = (
669669
keySource: key?.source,
670670
proxy: proxyConfig,
671671
jumpHosts: jumpHosts && jumpHosts.length > 0 ? jumpHosts : undefined,
672+
sudo: host.sftpSudo,
672673
};
673674
},
674675
[hosts, identities, keys],
@@ -874,15 +875,20 @@ export const useSftpState = (
874875
const hasKey = !!credentials.privateKey;
875876
const hasPassword = !!credentials.password;
876877

878+
<<<<<<< Updated upstream
877879
let sftpId: string | undefined;
878880
if (hasKey) {
879881
try {
880882
// Prefer trying key/cert first when both are present.
881-
sftpId = await openSftp({
883+
const keyFirstCredentials = {
882884
sessionId: `sftp-${connectionId}`,
883885
...credentials,
884-
password: undefined,
885-
});
886+
};
887+
// Preserve password for sudo when enabled (still prefer key auth).
888+
if (!credentials.sudo) {
889+
keyFirstCredentials.password = undefined;
890+
}
891+
sftpId = await openSftp(keyFirstCredentials);
886892
} catch (err) {
887893
if (hasPassword && isAuthError(err)) {
888894
sftpId = await openSftp({
@@ -3196,4 +3202,3 @@ export const useSftpState = (
31963202
stableMethods,
31973203
]);
31983204
};
3199-

components/HostDetailsPanel.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
} from "./ui/aside-panel";
2929
import { Badge } from "./ui/badge";
3030
import { Button } from "./ui/button";
31+
import { Switch } from "./ui/switch";
3132
import { Card } from "./ui/card";
3233
import { Combobox, ComboboxOption, MultiCombobox } from "./ui/combobox";
3334
import { Input } from "./ui/input";
@@ -925,6 +926,26 @@ const HostDetailsPanel: React.FC<HostDetailsPanelProps> = ({
925926
</div>
926927
</Card>
927928

929+
<Card className="p-3 space-y-3 bg-card border-border/80">
930+
<p className="text-xs font-semibold">
931+
{t("hostDetails.section.sftp")}
932+
</p>
933+
<div className="flex items-center justify-between">
934+
<div className="space-y-0.5">
935+
<div className="text-sm font-medium">
936+
{t("hostDetails.sftp.sudo")}
937+
</div>
938+
<div className="text-xs text-muted-foreground">
939+
{t("hostDetails.sftp.sudo.desc")}
940+
</div>
941+
</div>
942+
<Switch
943+
checked={form.sftpSudo || false}
944+
onCheckedChange={(val) => update("sftpSudo", val)}
945+
/>
946+
</div>
947+
</Card>
948+
928949
<Card className="p-3 space-y-3 bg-card border-border/80">
929950
<p className="text-xs font-semibold">
930951
{t("hostDetails.section.appearance")}

components/HostForm.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from "./ui/dialog";
1515
import { Input } from "./ui/input";
1616
import { Label } from "./ui/label";
17+
import { Switch } from "./ui/switch";
1718
import {
1819
Select,
1920
SelectContent,
@@ -257,6 +258,24 @@ const HostForm: React.FC<HostFormProps> = ({
257258
</div>
258259

259260
<div className="space-y-3 pt-2">
261+
<div className="flex items-center justify-between space-x-2 border rounded-md p-3">
262+
<div className="space-y-0.5">
263+
<Label htmlFor="sftp-sudo" className="text-base">
264+
{t("hostDetails.sftp.sudo")}
265+
</Label>
266+
<p className="text-xs text-muted-foreground">
267+
{t("hostDetails.sftp.sudo.desc")}
268+
</p>
269+
</div>
270+
<Switch
271+
id="sftp-sudo"
272+
checked={formData.sftpSudo || false}
273+
onCheckedChange={(checked) =>
274+
setFormData({ ...formData, sftpSudo: checked })
275+
}
276+
/>
277+
</div>
278+
260279
<Label>{t("hostForm.auth.method")}</Label>
261280
<div className="grid grid-cols-2 gap-4">
262281
<div

components/SFTPModal.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ interface SFTPModalProps {
254254
keySource?: 'generated' | 'imported';
255255
proxy?: NetcattyProxyConfig;
256256
jumpHosts?: NetcattyJumpHost[];
257+
sftpSudo?: boolean;
257258
};
258259
open: boolean;
259260
onClose: () => void;
@@ -521,6 +522,7 @@ const SFTPModal: React.FC<SFTPModalProps> = ({
521522
keySource: credentials.keySource,
522523
proxy: credentials.proxy,
523524
jumpHosts: credentials.jumpHosts,
525+
sudo: credentials.sftpSudo,
524526
});
525527
sftpIdRef.current = sftpId;
526528
return sftpId;
@@ -539,6 +541,7 @@ const SFTPModal: React.FC<SFTPModalProps> = ({
539541
credentials.keySource,
540542
credentials.proxy,
541543
credentials.jumpHosts,
544+
credentials.sftpSudo,
542545
openSftp,
543546
]);
544547

@@ -689,6 +692,7 @@ const SFTPModal: React.FC<SFTPModalProps> = ({
689692
keySource: credentials.keySource,
690693
proxy: credentials.proxy,
691694
jumpHosts: credentials.jumpHosts,
695+
sudo: credentials.sftpSudo,
692696
});
693697
sftpIdRef.current = sftpId;
694698

components/Terminal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,7 @@ const TerminalComponent: React.FC<TerminalProps> = ({
10791079
keySource: resolvedAuth.key?.source,
10801080
proxy: proxyConfig,
10811081
jumpHosts: jumpHosts && jumpHosts.length > 0 ? jumpHosts : undefined,
1082+
sftpSudo: host.sftpSudo,
10821083
};
10831084
})()}
10841085
open={showSFTP && status === "connected"}

domain/models.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ export interface Host {
9090
telnetPassword?: string; // Telnet-specific password
9191
// Serial-specific configuration (for protocol='serial' hosts)
9292
serialConfig?: SerialConfig;
93+
// SFTP specific configuration
94+
sftpSudo?: boolean; // Use sudo for SFTP operations (requires password)
9395
}
9496

9597
export type KeyType = 'RSA' | 'ECDSA' | 'ED25519';

electron-builder.config.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module.exports = {
99
productName: 'Netcatty',
1010
artifactName: '${productName}-${version}-${os}-${arch}.${ext}',
1111
icon: 'public/icon.png',
12+
npmRebuild: false,
1213
directories: {
1314
buildResources: 'build',
1415
output: 'release'

0 commit comments

Comments
 (0)