Skip to content

Commit f071dea

Browse files
authored
fix(cli): installer keeps a shared binary readable, and the keyring hint names --server
- install-cli.sh stages the binary as 0755, as install-r2.sh does: under umask 077 `chmod +x` left 0700 and locked other users out. - The locked-keychain hint suggests `login --server <url> --no-keyring`; login refuses without --server. - Skill: a fail-branch node keeps its usual success handles and adds `fail-branch`; turning the Service API off waits until after the smoke test, which needs it on.
1 parent 5f01248 commit f071dea

6 files changed

Lines changed: 26 additions & 6 deletions

File tree

‎cli/scripts/install-cli.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ bin_dir_for() {
4141
# running difyctl keeps its old file and DEST is never half written.
4242
install_binary() {
4343
_stage="$(dirname "$2")/.difyctl.install.$$"
44-
if cp "$1" "$_stage" && chmod +x "$_stage" && mv -f "$_stage" "$2"; then
44+
if cp "$1" "$_stage" && chmod 0755 "$_stage" && mv -f "$_stage" "$2"; then
4545
return 0
4646
fi
4747
rm -f "$_stage"

‎cli/scripts/install-cli.test.ts‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { execFileSync, spawnSync } from 'node:child_process'
2-
import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
2+
import { chmodSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
33
import { tmpdir } from 'node:os'
44
import { join } from 'node:path'
55
import { fileURLToPath } from 'node:url'
@@ -447,6 +447,24 @@ describe('install-cli.sh install_binary', () => {
447447
}
448448
})
449449

450+
// A shared install must stay readable by other users, whatever the installer's umask.
451+
it.skipIf(process.platform === 'win32')(
452+
'installs a 0600 source as mode 0755 under umask 077',
453+
() => {
454+
const dir = mkdtempSync(join(tmpdir(), 'difyctl-install-'))
455+
try {
456+
const src = join(dir, 'new')
457+
const dest = join(dir, 'bin-difyctl')
458+
writeFileSync(src, 'new build', { mode: 0o600 })
459+
const r = runLib(`umask 077; install_binary "${src}" "${dest}"`)
460+
expect(r.code).toBe(0)
461+
expect(statSync(dest).mode & 0o777).toBe(0o755)
462+
} finally {
463+
rmSync(dir, { recursive: true, force: true })
464+
}
465+
},
466+
)
467+
450468
it('dies without touching the old binary when the folder is not writable', () => {
451469
const r = runLib('install_binary /nonexistent/src /nonexistent-dir/difyctl')
452470
expect(r.code).not.toBe(0)

‎cli/src/store/keychain-token-store.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,6 @@ it('a locked keychain points at login --no-keyring', async () => {
136136
getPasswordError = new Error('keyring locked')
137137
await expect(new KeychainTokenStore(SERVICE).read('h', 'e')).rejects.toMatchObject({
138138
code: ErrorCode.KeyringUnavailable,
139-
hint: expect.stringContaining('login --no-keyring'),
139+
hint: expect.stringContaining('login --server <url> --no-keyring'),
140140
})
141141
})

‎cli/src/store/token-store.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ function keyringUnavailableError(cause: unknown): BaseError {
104104
return new BaseError({
105105
code: ErrorCode.KeyringUnavailable,
106106
message: 'OS keychain is unreachable',
107-
hint: `unlock the keychain (or the login session) and retry, or run ${BINARY} login --no-keyring to keep the token in a file`,
107+
hint: `unlock the keychain (or the login session) and retry, or run ${BINARY} login --server <url> --no-keyring to keep the token in a file`,
108108
cause,
109109
})
110110
}

‎skills/difyctl/references/build-workflow/dsl.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Start from an export of the app's draft. A new Workflow app's export is empty. A
1010
- if-else: each case's `case_id`, plus `false` for else;
1111
- question classifier: each class's `id`;
1212
- human input: each `user_actions` id, plus `__timeout`;
13-
- `error_strategy: fail-branch`: `source` for success, `fail-branch` for failure.
13+
- `error_strategy: fail-branch` adds a `fail-branch` handle for failure. Success still leaves by the node's usual handles: `source`, or the branch handles above.
1414
- Give every branch an edge, including `false` and `__timeout`.
1515
- End node output names must be unique across all End nodes.
1616
- A node id is any unique string. Never change the id of an existing node.

‎skills/difyctl/references/build-workflow/handover.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Start when every slice in `plan.md` is ticked. `<mode>` is `workflow` or `advanc
2626
difyctl set webapp <mode> --app-id <app_id> --enabled --json
2727
```
2828

29-
- Service API (needs an admin). Warn the human first what turning it off costs (see Limits in `build-workflow.md`), and note the app id.
29+
- Service API (needs an admin). Warn the human first what turning it off costs (see Limits in `build-workflow.md`), and note the app id. If the human wants it off, record that and turn it off after the smoke test in step 6: `difyctl run` fails with `service_api_disabled` while it is off.
3030

3131
```bash
3232
difyctl set service_api --app-id <app_id> --enabled --json
@@ -44,4 +44,6 @@ Start when every slice in `plan.md` is ticked. `<mode>` is `workflow` or `advanc
4444
difyctl run console_app <mode> --app-id <app_id> --inputs '{"<var>": "<value>"}' --json
4545
```
4646

47+
Then, if the human chose to turn the Service API off in step 5, turn it off now with `--enabled=false`.
48+
4749
7. Close. Mark `plan.md` as published with the `version_id`, the date and the url. Give the human a short summary that lists every change from the spec and plan, and when the human approved it.

0 commit comments

Comments
 (0)