diff --git a/clients/awk/sync/src/un.awk b/clients/awk/sync/src/un.awk index 805a6ff..511396f 100644 --- a/clients/awk/sync/src/un.awk +++ b/clients/awk/sync/src/un.awk @@ -64,6 +64,9 @@ BEGIN { GREEN = "\033[32m" YELLOW = "\033[33m" RESET = "\033[0m" + + # Global credential state + GLOBAL_ACCOUNT_INDEX = -1 } # ============================================================================ @@ -106,32 +109,84 @@ function health_check( cmd, result) { return (result == "200") } -function get_api_keys( public_key, secret_key, cmd) { - # Get public key - cmd = "echo -n $UNSANDBOX_PUBLIC_KEY" - cmd | getline public_key - close(cmd) - - # Get secret key - cmd = "echo -n $UNSANDBOX_SECRET_KEY" - cmd | getline secret_key - close(cmd) - - # Fallback to old UNSANDBOX_API_KEY for backwards compat - if (public_key == "") { - cmd = "echo -n $UNSANDBOX_API_KEY" - cmd | getline public_key - close(cmd) - secret_key = "" +function load_accounts_csv(index , home, path, line, fields, count, pk, sk) { + home = ENVIRON["HOME"] + count = -1 + pk = "" + sk = "" + # Try ~/.unsandbox/accounts.csv first + path = home "/.unsandbox/accounts.csv" + while ((getline line < path) > 0) { + if (line ~ /^[[:space:]]*$/ || line ~ /^[[:space:]]*#/) continue + count++ + if (count == index) { + split(line, fields, ",") + pk = fields[1]; sk = fields[2] + gsub(/^[[:space:]]+|[[:space:]]+$/, "", pk) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", sk) + close(path) + GLOBAL_PUBLIC_KEY = pk; GLOBAL_SECRET_KEY = sk + return 1 + } } + close(path) + # Try ./accounts.csv as fallback + count = -1; path = "accounts.csv" + while ((getline line < path) > 0) { + if (line ~ /^[[:space:]]*$/ || line ~ /^[[:space:]]*#/) continue + count++ + if (count == index) { + split(line, fields, ",") + pk = fields[1]; sk = fields[2] + gsub(/^[[:space:]]+|[[:space:]]+$/, "", pk) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", sk) + close(path) + GLOBAL_PUBLIC_KEY = pk; GLOBAL_SECRET_KEY = sk + return 1 + } + } + close(path) + return 0 +} - if (public_key == "") { - print RED "Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY not set" RESET > "/dev/stderr" +function get_api_keys( public_key, secret_key, cmd, default_index) { + # Priority 1: already set via -p/-k flags + if (GLOBAL_PUBLIC_KEY != "" && GLOBAL_SECRET_KEY != "") { return } + + # Priority 2: --account N bypasses env vars + if (GLOBAL_ACCOUNT_INDEX >= 0) { + if (load_accounts_csv(GLOBAL_ACCOUNT_INDEX)) { + if (GLOBAL_PUBLIC_KEY != "") return + } + print RED "Error: Account index " GLOBAL_ACCOUNT_INDEX " not found in accounts.csv" RESET > "/dev/stderr" exit 1 } - GLOBAL_PUBLIC_KEY = public_key - GLOBAL_SECRET_KEY = secret_key + # Priority 3: env vars UNSANDBOX_PUBLIC_KEY / UNSANDBOX_SECRET_KEY + cmd = "echo -n $UNSANDBOX_PUBLIC_KEY"; cmd | getline public_key; close(cmd) + cmd = "echo -n $UNSANDBOX_SECRET_KEY"; cmd | getline secret_key; close(cmd) + if (public_key != "" && secret_key != "") { + GLOBAL_PUBLIC_KEY = public_key; GLOBAL_SECRET_KEY = secret_key; return + } + + # Fallback to legacy UNSANDBOX_API_KEY for backwards compat + if (public_key == "") { + cmd = "echo -n $UNSANDBOX_API_KEY"; cmd | getline public_key; close(cmd) + secret_key = "" + } + if (public_key != "") { + GLOBAL_PUBLIC_KEY = public_key; GLOBAL_SECRET_KEY = secret_key; return + } + + # Priority 4/5: accounts.csv with UNSANDBOX_ACCOUNT env var or row 0 + cmd = "echo -n $UNSANDBOX_ACCOUNT"; cmd | getline default_index; close(cmd) + if (default_index == "") default_index = 0 + if (load_accounts_csv(default_index + 0)) { + if (GLOBAL_PUBLIC_KEY != "") return + } + + print RED "Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY not set" RESET > "/dev/stderr" + exit 1 } function get_extension(filename) { @@ -2359,6 +2414,20 @@ END { exit 0 } + # Pre-scan ARGV for global flags: --account N, -p KEY, -k KEY + for (_gi = 1; _gi < ARGC; _gi++) { + if (ARGV[_gi] == "--account" && _gi + 1 < ARGC) { + GLOBAL_ACCOUNT_INDEX = ARGV[_gi + 1] + 0 + _gi++ + } else if (ARGV[_gi] == "-p" && _gi + 1 < ARGC) { + GLOBAL_PUBLIC_KEY = ARGV[_gi + 1] + _gi++ + } else if (ARGV[_gi] == "-k" && _gi + 1 < ARGC) { + GLOBAL_SECRET_KEY = ARGV[_gi + 1] + _gi++ + } + } + if (ARGV[1] == "session") { if (ARGC >= 3 && ARGV[2] == "--list") { session_list() diff --git a/clients/cobol/sync/src/un.cob b/clients/cobol/sync/src/un.cob index 6a3401f..8aae80a 100644 --- a/clients/cobol/sync/src/un.cob +++ b/clients/cobol/sync/src/un.cob @@ -45,15 +45,21 @@ SELECT SOURCE-FILE ASSIGN TO WS-FILENAME ORGANIZATION IS LINE SEQUENTIAL FILE STATUS IS WS-FILE-STATUS. + SELECT CRED-FILE ASSIGN TO "/tmp/unsb_creds.txt" + ORGANIZATION IS LINE SEQUENTIAL + FILE STATUS IS WS-CRED-STATUS. DATA DIVISION. FILE SECTION. FD SOURCE-FILE. 01 SOURCE-LINE PIC X(1024). + FD CRED-FILE. + 01 CRED-LINE PIC X(512). WORKING-STORAGE SECTION. 01 WS-FILENAME PIC X(256). 01 WS-FILE-STATUS PIC XX. + 01 WS-CRED-STATUS PIC XX. 01 WS-API-KEY PIC X(256). 01 WS-PUBLIC-KEY PIC X(256). 01 WS-SECRET-KEY PIC X(256). @@ -97,12 +103,23 @@ 01 WS-UOD-ENABLED PIC X(8). 01 WS-TYPE PIC X(32). 01 WS-SHELL PIC X(32). + 01 WS-ACCOUNT-INDEX PIC S9(4) VALUE -1. + 01 WS-ACCOUNT-STR PIC X(16). + 01 WS-ACCT-POS PIC 9(4) VALUE 0. + 01 WS-ERROR-MSG PIC X(256). PROCEDURE DIVISION. MAIN-PROCEDURE. - * Get command line argument (first argument) + * Get first command line argument ACCEPT WS-ARG1 FROM COMMAND-LINE. + * Pre-scan: handle --account N global flag before subcommand + IF WS-ARG1 = "--account" + ACCEPT WS-ACCOUNT-STR FROM ARGUMENT-VALUE + MOVE FUNCTION NUMVAL(WS-ACCOUNT-STR) TO WS-ACCOUNT-INDEX + ACCEPT WS-ARG1 FROM ARGUMENT-VALUE + END-IF. + IF WS-ARG1 = SPACES DISPLAY "Usage: un.cob " UPON SYSERR DISPLAY " un.cob session [options]" UPON SYSERR @@ -147,7 +164,94 @@ PERFORM HANDLE-EXECUTE. STOP RUN. + GET-CREDENTIALS. + * If already set, return immediately + IF WS-PUBLIC-KEY NOT = SPACES AND WS-SECRET-KEY NOT = SPACES + EXIT PARAGRAPH + END-IF. + + * Build shell script to resolve credentials with full priority + IF WS-ACCOUNT-INDEX >= 0 + MOVE WS-ACCOUNT-INDEX TO WS-ACCOUNT-STR + STRING "IDX=" FUNCTION TRIM(WS-ACCOUNT-STR) "; " + "PK=''; SK=''; CNT=-1; " + "for CSV in \"$HOME/.unsandbox/accounts.csv\" " + "\"./accounts.csv\"; do " + "[ -f \"$CSV\" ] || continue; " + "while IFS= read -r line || [ -n \"$line\" ]; do " + "case \"$line\" in \"#\"*|\"\"|\" \"*) continue ;; esac; " + "CNT=$((CNT+1)); " + "if [ \"$CNT\" -eq \"$IDX\" ]; then " + "PK=$(echo \"$line\" | cut -d',' -f1 | tr -d ' '); " + "SK=$(echo \"$line\" | cut -d',' -f2 | tr -d ' '); " + "break 2; fi; " + "done < \"$CSV\"; done; " + "if [ -z \"$PK\" ]; then " + "echo -e '\\x1b[31mError: Account index " + FUNCTION TRIM(WS-ACCOUNT-STR) + " not found in accounts.csv\\x1b[0m' >&2; exit 1; fi; " + "printf '%s\\n%s\\n' \"$PK\" \"$SK\" " + "> /tmp/unsb_creds.txt" + DELIMITED BY SIZE INTO WS-CURL-CMD + END-STRING + ELSE + STRING "PK=\"$UNSANDBOX_PUBLIC_KEY\"; " + "SK=\"$UNSANDBOX_SECRET_KEY\"; " + "if [ -z \"$PK\" ]; then PK=\"$UNSANDBOX_API_KEY\"; SK=''; fi; " + "if [ -z \"$PK\" ]; then " + "IDX=\"${UNSANDBOX_ACCOUNT:-0}\"; " + "CNT=-1; " + "for CSV in \"$HOME/.unsandbox/accounts.csv\" " + "\"./accounts.csv\"; do " + "[ -f \"$CSV\" ] || continue; " + "while IFS= read -r line || [ -n \"$line\" ]; do " + "case \"$line\" in \"#\"*|\"\"|\" \"*) continue ;; esac; " + "CNT=$((CNT+1)); " + "if [ \"$CNT\" -eq \"$IDX\" ]; then " + "PK=$(echo \"$line\" | cut -d',' -f1 | tr -d ' '); " + "SK=$(echo \"$line\" | cut -d',' -f2 | tr -d ' '); " + "break 2; fi; " + "done < \"$CSV\"; done; fi; " + "if [ -z \"$PK\" ]; then " + "echo -e '\\x1b[31mError: No credentials found\\x1b[0m' " + ">&2; exit 1; fi; " + "printf '%s\\n%s\\n' \"$PK\" \"$SK\" " + "> /tmp/unsb_creds.txt" + DELIMITED BY SIZE INTO WS-CURL-CMD + END-STRING + END-IF. + + CALL "SYSTEM" USING WS-CURL-CMD + RETURNING WS-EXIT-CODE. + IF WS-EXIT-CODE NOT = 0 + MOVE WS-EXIT-CODE TO RETURN-CODE + STOP RUN + END-IF. + + * Read resolved credentials from temp file + MOVE SPACES TO WS-PUBLIC-KEY. + MOVE SPACES TO WS-SECRET-KEY. + OPEN INPUT CRED-FILE. + IF WS-CRED-STATUS = "00" + READ CRED-FILE INTO WS-PUBLIC-KEY + READ CRED-FILE INTO WS-SECRET-KEY + CLOSE CRED-FILE + END-IF. + + IF WS-PUBLIC-KEY = SPACES + DISPLAY "Error: Could not resolve credentials" + UPON SYSERR + MOVE 1 TO RETURN-CODE + STOP RUN + END-IF. + HANDLE-EXECUTE. + * Get credentials + PERFORM GET-CREDENTIALS. + IF WS-API-KEY = SPACES + MOVE WS-PUBLIC-KEY TO WS-API-KEY + END-IF. + * Check if file exists OPEN INPUT SOURCE-FILE. IF WS-FILE-STATUS NOT = "00" @@ -168,25 +272,14 @@ STOP RUN END-IF. - * Get API key from environment - ACCEPT WS-API-KEY FROM ENVIRONMENT "UNSANDBOX_API_KEY". - - IF WS-API-KEY = SPACES - DISPLAY "Error: UNSANDBOX_API_KEY not set" UPON SYSERR - MOVE 1 TO RETURN-CODE - STOP RUN - END-IF. - * Use curl to make request PERFORM MAKE-EXECUTE-REQUEST. HANDLE-SESSION. - * Get API key - ACCEPT WS-API-KEY FROM ENVIRONMENT "UNSANDBOX_API_KEY". + * Get credentials + PERFORM GET-CREDENTIALS. IF WS-API-KEY = SPACES - DISPLAY "Error: UNSANDBOX_API_KEY not set" UPON SYSERR - MOVE 1 TO RETURN-CODE - STOP RUN + MOVE WS-PUBLIC-KEY TO WS-API-KEY END-IF. * Initialize session parameters @@ -209,27 +302,8 @@ END-IF. HANDLE-SERVICE. - * Get API keys (try new format first, fall back to old) - ACCEPT WS-PUBLIC-KEY FROM ENVIRONMENT "UNSANDBOX_PUBLIC_KEY". - IF WS-PUBLIC-KEY NOT = SPACES - ACCEPT WS-SECRET-KEY FROM ENVIRONMENT "UNSANDBOX_SECRET_KEY" - IF WS-SECRET-KEY = SPACES - DISPLAY "Error: UNSANDBOX_SECRET_KEY not set" - UPON SYSERR - MOVE 1 TO RETURN-CODE - STOP RUN - END-IF - ELSE - ACCEPT WS-API-KEY FROM ENVIRONMENT "UNSANDBOX_API_KEY" - IF WS-API-KEY = SPACES - DISPLAY "Error: UNSANDBOX_PUBLIC_KEY/SECRET_KEY or " - "UNSANDBOX_API_KEY not set" UPON SYSERR - MOVE 1 TO RETURN-CODE - STOP RUN - END-IF - MOVE WS-API-KEY TO WS-PUBLIC-KEY - MOVE WS-API-KEY TO WS-SECRET-KEY - END-IF. + * Get credentials + PERFORM GET-CREDENTIALS. * Initialize service parameters MOVE SPACES TO WS-NAME. @@ -332,26 +406,7 @@ END-IF. MAKE-EXECUTE-REQUEST. - * Get public/secret keys with fallback - ACCEPT WS-PUBLIC-KEY FROM ENVIRONMENT "UNSANDBOX_PUBLIC_KEY". - IF WS-PUBLIC-KEY NOT = SPACES - ACCEPT WS-SECRET-KEY FROM ENVIRONMENT "UNSANDBOX_SECRET_KEY" - IF WS-SECRET-KEY = SPACES - DISPLAY "Error: UNSANDBOX_SECRET_KEY not set" - UPON SYSERR - MOVE 1 TO RETURN-CODE - STOP RUN - END-IF - ELSE - ACCEPT WS-PUBLIC-KEY FROM ENVIRONMENT "UNSANDBOX_API_KEY" - IF WS-PUBLIC-KEY = SPACES - DISPLAY "Error: UNSANDBOX_PUBLIC_KEY/SECRET_KEY or " - "UNSANDBOX_API_KEY not set" UPON SYSERR - MOVE 1 TO RETURN-CODE - STOP RUN - END-IF - MOVE WS-PUBLIC-KEY TO WS-SECRET-KEY - END-IF. + * Credentials already resolved by caller (GET-CREDENTIALS) * Build curl command using shell with HMAC signature STRING "TS=$(date +%s); " @@ -943,12 +998,10 @@ CALL "SYSTEM" USING WS-CURL-CMD. HANDLE-KEY. - * Get API key - ACCEPT WS-API-KEY FROM ENVIRONMENT "UNSANDBOX_API_KEY". + * Get credentials + PERFORM GET-CREDENTIALS. IF WS-API-KEY = SPACES - DISPLAY "Error: UNSANDBOX_API_KEY not set" UPON SYSERR - MOVE 1 TO RETURN-CODE - STOP RUN + MOVE WS-PUBLIC-KEY TO WS-API-KEY END-IF. * Parse key arguments @@ -1124,27 +1177,8 @@ CALL "SYSTEM" USING WS-CURL-CMD. HANDLE-LANGUAGES. - * Get API keys - ACCEPT WS-PUBLIC-KEY FROM ENVIRONMENT "UNSANDBOX_PUBLIC_KEY". - IF WS-PUBLIC-KEY NOT = SPACES - ACCEPT WS-SECRET-KEY FROM ENVIRONMENT "UNSANDBOX_SECRET_KEY" - IF WS-SECRET-KEY = SPACES - DISPLAY "Error: UNSANDBOX_SECRET_KEY not set" - UPON SYSERR - MOVE 1 TO RETURN-CODE - STOP RUN - END-IF - ELSE - ACCEPT WS-API-KEY FROM ENVIRONMENT "UNSANDBOX_API_KEY" - IF WS-API-KEY = SPACES - DISPLAY "Error: UNSANDBOX_PUBLIC_KEY/SECRET_KEY or " - "UNSANDBOX_API_KEY not set" UPON SYSERR - MOVE 1 TO RETURN-CODE - STOP RUN - END-IF - MOVE WS-API-KEY TO WS-PUBLIC-KEY - MOVE WS-API-KEY TO WS-SECRET-KEY - END-IF. + * Get credentials + PERFORM GET-CREDENTIALS. * Parse --json flag MOVE SPACES TO WS-JSON-OUTPUT. @@ -1219,27 +1253,8 @@ CALL "SYSTEM" USING WS-CURL-CMD. HANDLE-IMAGE. - * Get API keys (try new format first, fall back to old) - ACCEPT WS-PUBLIC-KEY FROM ENVIRONMENT "UNSANDBOX_PUBLIC_KEY". - IF WS-PUBLIC-KEY NOT = SPACES - ACCEPT WS-SECRET-KEY FROM ENVIRONMENT "UNSANDBOX_SECRET_KEY" - IF WS-SECRET-KEY = SPACES - DISPLAY "Error: UNSANDBOX_SECRET_KEY not set" - UPON SYSERR - MOVE 1 TO RETURN-CODE - STOP RUN - END-IF - ELSE - ACCEPT WS-API-KEY FROM ENVIRONMENT "UNSANDBOX_API_KEY" - IF WS-API-KEY = SPACES - DISPLAY "Error: UNSANDBOX_PUBLIC_KEY/SECRET_KEY or " - "UNSANDBOX_API_KEY not set" UPON SYSERR - MOVE 1 TO RETURN-CODE - STOP RUN - END-IF - MOVE WS-API-KEY TO WS-PUBLIC-KEY - MOVE WS-API-KEY TO WS-SECRET-KEY - END-IF. + * Get credentials + PERFORM GET-CREDENTIALS. * Initialize image parameters MOVE SPACES TO WS-ID. @@ -1676,15 +1691,7 @@ HANDLE-SNAPSHOT. * Get credentials - ACCEPT WS-PUBLIC-KEY FROM ENVIRONMENT - "UNSANDBOX_PUBLIC_KEY". - ACCEPT WS-SECRET-KEY FROM ENVIRONMENT - "UNSANDBOX_SECRET_KEY". - IF WS-PUBLIC-KEY = SPACES OR WS-SECRET-KEY = SPACES - DISPLAY "Error: API keys not set" UPON SYSERR - MOVE 1 TO RETURN-CODE - STOP RUN - END-IF. + PERFORM GET-CREDENTIALS. * Get second argument (operation or --list) ACCEPT WS-ARG2 FROM ARGUMENT-VALUE. diff --git a/clients/forth/sync/src/un.forth b/clients/forth/sync/src/un.forth index 82b1d99..7b10612 100644 --- a/clients/forth/sync/src/un.forth +++ b/clients/forth/sync/src/un.forth @@ -97,8 +97,84 @@ find-ext ext-lang ; +\ Account index for --account N flag (-1 = not set) +variable account-index +-1 account-index ! + +\ Argument shift: 0 normally, 2 when --account N is prepended +variable arg-shift +0 arg-shift ! + +\ Shifted arg accessor - applies arg-shift to all handler arg accesses +: sarg ( n -- addr len ) + arg-shift @ + arg +; + +\ Buffer for credentials loaded from CSV +256 constant MAX-KEY-LEN +create csv-pk-buf MAX-KEY-LEN allot +variable csv-pk-len +create csv-sk-buf MAX-KEY-LEN allot +variable csv-sk-len + +\ Load credentials from accounts.csv at given index (n) +\ Writes PK/SK to /tmp/unsb_creds.txt; returns true if PK found +: load-accounts-csv-index ( n -- flag ) + dup 0< if drop 0 exit then + \ Write a shell script with the index embedded + s" /tmp/unsb_cred_resolve.sh" w/o create-file throw >r + s" #!/bin/bash" r@ write-line throw + s" IDX=" r@ write-file throw + dup 0 <# #s #> r@ write-file throw + s" " r@ write-line throw + s" CNT=-1; PK=''; SK=''" r@ write-line throw + s" for CSV in \"$HOME/.unsandbox/accounts.csv\" \"./accounts.csv\"; do" r@ write-line throw + s" [ -f \"$CSV\" ] || continue" r@ write-line throw + s" while IFS= read -r line || [ -n \"$line\" ]; do" r@ write-line throw + s" case \"$line\" in '#'*|'') continue ;; esac" r@ write-line throw + s" CNT=$((CNT+1))" r@ write-line throw + s" if [ \"$CNT\" -eq \"$IDX\" ]; then" r@ write-line throw + s" PK=$(echo \"$line\" | cut -d',' -f1 | tr -d ' ')" r@ write-line throw + s" SK=$(echo \"$line\" | cut -d',' -f2 | tr -d ' ')" r@ write-line throw + s" break 2" r@ write-line throw + s" fi" r@ write-line throw + s" done < \"$CSV\"" r@ write-line throw + s" done" r@ write-line throw + s" printf '%s\\n%s\\n' \"$PK\" \"$SK\" > /tmp/unsb_creds.txt" r@ write-line throw + s" [ -n \"$PK\" ]" r@ write-line throw + r> close-file throw + drop \ drop index + s" bash /tmp/unsb_cred_resolve.sh" system + 0= if + \ Script exited 0: PK was found; read results + s" /tmp/unsb_creds.txt" r/o open-file + 0= if + >r + csv-pk-buf MAX-KEY-LEN r@ read-line throw + drop csv-pk-len ! + csv-sk-buf MAX-KEY-LEN r@ read-line throw + drop csv-sk-len ! + r> close-file throw + -1 + else + drop 0 + then + else + 0 + then +; + \ Get API keys from environment (HMAC or legacy) : get-public-key ( -- addr len ) + account-index @ dup 0>= if + load-accounts-csv-index if + csv-pk-buf csv-pk-len @ exit + then + s" Error: Account index not found in accounts.csv" type cr + 1 (bye) + else + drop + then s" UNSANDBOX_PUBLIC_KEY" getenv dup 0= if 2drop s" UNSANDBOX_API_KEY" getenv @@ -110,6 +186,20 @@ ; : get-secret-key ( -- addr len ) + account-index @ dup 0>= if + \ CSV already loaded if pk-buf is non-empty + csv-pk-len @ 0> if + drop csv-sk-buf csv-sk-len @ exit + then + \ Load it now + load-accounts-csv-index if + csv-sk-buf csv-sk-len @ exit + then + \ Index not found - error was printed by get-public-key; return empty + s" " exit + else + drop + then s" UNSANDBOX_SECRET_KEY" getenv dup 0= if 2drop s" UNSANDBOX_API_KEY" getenv @@ -716,7 +806,7 @@ 0 (bye) then - 2 arg 2dup s" --extend" compare 0= if + 2 sarg 2dup s" --extend" compare 0= if 2drop 1 validate-key 0 (bye) @@ -779,7 +869,7 @@ 1 (bye) then - 2 arg 2dup s" --list" compare 0= if + 2 sarg 2dup s" --list" compare 0= if 2drop session-list 0 (bye) then @@ -795,7 +885,7 @@ s" Error: --kill requires session ID" type cr 1 (bye) then - 3 arg session-kill + 3 sarg session-kill 0 (bye) then @@ -835,7 +925,7 @@ 1 (bye) then - 2 arg 2dup s" --list" compare 0= if + 2 sarg 2dup s" --list" compare 0= if 2drop service-list 0 (bye) then @@ -856,7 +946,7 @@ s" Error: --info requires service ID" type cr 1 (bye) then - 3 arg service-info + 3 sarg service-info 0 (bye) then @@ -866,7 +956,7 @@ s" Error: --logs requires service ID" type cr 1 (bye) then - 3 arg service-logs + 3 sarg service-logs 0 (bye) then @@ -876,7 +966,7 @@ s" Error: --freeze requires service ID" type cr 1 (bye) then - 3 arg service-sleep + 3 sarg service-sleep 0 (bye) then @@ -886,7 +976,7 @@ s" Error: --unfreeze requires service ID" type cr 1 (bye) then - 3 arg service-wake + 3 sarg service-wake 0 (bye) then @@ -896,7 +986,7 @@ s" Error: --destroy requires service ID" type cr 1 (bye) then - 3 arg service-destroy + 3 sarg service-destroy 0 (bye) then @@ -911,13 +1001,13 @@ s" Error: --resize requires --vcpu N" type cr 1 (bye) then - 4 arg 2dup s" --vcpu" compare 0= if + 4 sarg 2dup s" --vcpu" compare 0= if 2drop argc @ 6 < if s" Error: --vcpu requires a value" type cr 1 (bye) then - 3 arg 5 arg service-resize + 3 sarg 5 sarg service-resize 0 (bye) then 2dup s" -v" compare 0= if @@ -926,7 +1016,7 @@ s" Error: -v requires a value" type cr 1 (bye) then - 3 arg 5 arg service-resize + 3 sarg 5 sarg service-resize 0 (bye) then 2drop @@ -940,16 +1030,16 @@ s" Error: --dump-bootstrap requires service ID" type cr 1 (bye) then - 3 arg + 3 sarg \ Check for --dump-file argc @ 5 >= if - 4 arg 2dup s" --dump-file" compare 0= if + 4 sarg 2dup s" --dump-file" compare 0= if 2drop argc @ 6 < if s" Error: --dump-file requires filename" type cr 1 (bye) then - 5 arg + 5 sarg else 2drop 0 0 then @@ -967,13 +1057,13 @@ s" Usage: un.forth service env [options]" type cr 1 (bye) then - 3 arg 2dup s" status" compare 0= if + 3 sarg 2dup s" status" compare 0= if 2drop argc @ 5 < if s" Error: status requires service ID" type cr 1 (bye) then - 4 arg service-env-status + 4 sarg service-env-status 0 (bye) then 2dup s" set" compare 0= if @@ -991,7 +1081,7 @@ s" Error: export requires service ID" type cr 1 (bye) then - 4 arg service-env-export + 4 sarg service-env-export 0 (bye) then 2dup s" delete" compare 0= if @@ -1000,7 +1090,7 @@ s" Error: delete requires service ID" type cr 1 (bye) then - 4 arg service-env-delete + 4 sarg service-env-delete 0 (bye) then 2drop @@ -1072,7 +1162,7 @@ 0 (bye) then - 2 arg 2dup s" --json" compare 0= if + 2 sarg 2dup s" --json" compare 0= if 2drop 1 languages-list 0 (bye) @@ -1670,7 +1760,7 @@ 0 (bye) then - 2 arg 2dup s" --list" compare 0= if + 2 sarg 2dup s" --list" compare 0= if 2drop snapshot-list 0 (bye) then @@ -1686,7 +1776,7 @@ s" Error: --info requires snapshot ID" type cr 1 (bye) then - 3 arg snapshot-info + 3 sarg snapshot-info 0 (bye) then @@ -1696,7 +1786,7 @@ s" Error: --restore requires snapshot ID" type cr 1 (bye) then - 3 arg snapshot-restore + 3 sarg snapshot-restore 0 (bye) then @@ -1706,7 +1796,7 @@ s" Error: --delete requires snapshot ID" type cr 1 (bye) then - 3 arg snapshot-delete + 3 sarg snapshot-delete 0 (bye) then @@ -1716,7 +1806,7 @@ s" Error: --lock requires snapshot ID" type cr 1 (bye) then - 3 arg snapshot-lock + 3 sarg snapshot-lock 0 (bye) then @@ -1726,7 +1816,7 @@ s" Error: --unlock requires snapshot ID" type cr 1 (bye) then - 3 arg snapshot-unlock + 3 sarg snapshot-unlock 0 (bye) then @@ -1736,7 +1826,7 @@ s" Error: --clone requires snapshot ID" type cr 1 (bye) then - 3 arg snapshot-clone + 3 sarg snapshot-clone 0 (bye) then @@ -1752,7 +1842,7 @@ 1 (bye) then - 2 arg 2dup s" --list" compare 0= if + 2 sarg 2dup s" --list" compare 0= if 2drop image-list 0 (bye) then @@ -1768,7 +1858,7 @@ s" Error: --info requires image ID" type cr 1 (bye) then - 3 arg image-info + 3 sarg image-info 0 (bye) then @@ -1778,7 +1868,7 @@ s" Error: --delete requires image ID" type cr 1 (bye) then - 3 arg image-delete + 3 sarg image-delete 0 (bye) then @@ -1788,7 +1878,7 @@ s" Error: --lock requires image ID" type cr 1 (bye) then - 3 arg image-lock + 3 sarg image-lock 0 (bye) then @@ -1798,7 +1888,7 @@ s" Error: --unlock requires image ID" type cr 1 (bye) then - 3 arg image-unlock + 3 sarg image-unlock 0 (bye) then @@ -1818,7 +1908,7 @@ s" Error: --visibility requires image ID and mode" type cr 1 (bye) then - 3 arg 4 arg image-visibility + 3 sarg 4 sarg image-visibility 0 (bye) then @@ -1828,7 +1918,7 @@ s" Error: --spawn requires image ID" type cr 1 (bye) then - 3 arg image-spawn + 3 sarg image-spawn 0 (bye) then @@ -1838,7 +1928,7 @@ s" Error: --clone requires image ID" type cr 1 (bye) then - 3 arg image-clone + 3 sarg image-clone 0 (bye) then @@ -1860,8 +1950,21 @@ 1 (bye) then - \ Get first argument (skip gforth and script name) - 1 arg + \ Check for --account N as first argument (before arg-shift is applied) + 1 arg 2dup s" --account" compare 0= if + 2drop + argc @ 3 < if + s" Error: --account requires a numeric argument" type cr + 1 (bye) + then + 2 arg s>number drop account-index ! + 2 arg-shift ! + else + 2drop + then + + \ Get subcommand (adjusted for arg-shift) + 1 arg-shift @ + arg \ Check for subcommands 2dup s" session" compare 0= if diff --git a/clients/prolog/sync/src/un.pro b/clients/prolog/sync/src/un.pro index c83bfa3..19e7942 100644 --- a/clients/prolog/sync/src/un.pro +++ b/clients/prolog/sync/src/un.pro @@ -39,6 +39,9 @@ :- initialization(main, main). +% Initialize global account index to -1 (not set) +:- nb_setval(account_index, -1). + % Constants portal_base('https://unsandbox.com'). languages_cache_ttl(3600). % 1 hour cache TTL @@ -85,26 +88,91 @@ read_file_content(Filename, Content) :- read_string(Stream, _, Content), close(Stream). -% Get API keys from environment (HMAC or legacy) +% Load credentials from accounts.csv at 0-indexed row +% Tries ~/.unsandbox/accounts.csv first, then ./accounts.csv +load_accounts_csv(Index, PK, SK) :- + getenv('HOME', Home), + atomic_list_concat([Home, '/.unsandbox/accounts.csv'], Path1), + ( exists_file(Path1) + -> load_accounts_csv_file(Path1, Index, PK, SK) + ; exists_file('accounts.csv') + -> load_accounts_csv_file('accounts.csv', Index, PK, SK) + ; fail + ). + +load_accounts_csv_file(Path, Index, PK, SK) :- + setup_call_cleanup( + open(Path, read, Stream), + load_accounts_csv_stream(Stream, 0, Index, PK, SK), + close(Stream) + ). + +load_accounts_csv_stream(Stream, Count, Index, PK, SK) :- + read_line_to_string(Stream, Line), + Line \= end_of_file, + !, + % Skip blank lines and comments + ( (Line = "" ; string_code(1, Line, 35)) % 35 = '#' + -> load_accounts_csv_stream(Stream, Count, Index, PK, SK) + ; Count =:= Index + -> split_string(Line, ",", " \t", [PKStr, SKStr|_]), + atom_string(PK, PKStr), + atom_string(SK, SKStr) + ; Next is Count + 1, + load_accounts_csv_stream(Stream, Next, Index, PK, SK) + ). + +% Get API keys from environment (HMAC or legacy), respecting --account N get_public_key(PublicKey) :- - ( getenv('UNSANDBOX_PUBLIC_KEY', PublicKey), - PublicKey \= '' - -> true - ; getenv('UNSANDBOX_API_KEY', PublicKey), - PublicKey \= '' - -> true - ; write(user_error, 'Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY environment variable not set\n'), - halt(1) + ( nb_getval(account_index, Idx), Idx >= 0 + -> ( load_accounts_csv(Idx, PublicKey, _) + -> true + ; format(user_error, 'Error: Account index ~w not found in accounts.csv~n', [Idx]), + halt(1) + ) + ; ( getenv('UNSANDBOX_PUBLIC_KEY', PublicKey), + PublicKey \= '' + -> true + ; getenv('UNSANDBOX_API_KEY', PublicKey), + PublicKey \= '' + -> true + ; % Try accounts.csv with UNSANDBOX_ACCOUNT or row 0 + ( getenv('UNSANDBOX_ACCOUNT', IdxStr), + atom_number(IdxStr, DefaultIdx) + -> true + ; DefaultIdx = 0 + ), + ( load_accounts_csv(DefaultIdx, PublicKey, _) + -> true + ; write(user_error, 'Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY environment variable not set\n'), + halt(1) + ) + ) ). get_secret_key(SecretKey) :- - ( getenv('UNSANDBOX_SECRET_KEY', SecretKey), - SecretKey \= '' - -> true - ; getenv('UNSANDBOX_API_KEY', SecretKey), - SecretKey \= '' - -> true - ; SecretKey = '' + ( nb_getval(account_index, Idx), Idx >= 0 + -> ( load_accounts_csv(Idx, _, SecretKey) + -> true + ; SecretKey = '' + ) + ; ( getenv('UNSANDBOX_SECRET_KEY', SecretKey), + SecretKey \= '' + -> true + ; getenv('UNSANDBOX_API_KEY', SecretKey), + SecretKey \= '' + -> true + ; % Try accounts.csv with UNSANDBOX_ACCOUNT or row 0 + ( getenv('UNSANDBOX_ACCOUNT', IdxStr), + atom_number(IdxStr, DefaultIdx) + -> true + ; DefaultIdx = 0 + ), + ( load_accounts_csv(DefaultIdx, _, SecretKey) + -> true + ; SecretKey = '' + ) + ) ). % Get API key (legacy compatibility) @@ -742,8 +810,22 @@ service_create_with_vault(Name, Ports, Bootstrap, BootstrapFile, ServiceType, In % Main program main(Argv) :- + % Initialize account_index global to -1 (not set) + nb_setval(account_index, -1), + + % Parse --account N global flag if present + ( Argv = ['--account', NStr|Rest] + -> ( atom_number(NStr, N), integer(N) + -> nb_setval(account_index, N), + ActualArgv = Rest + ; write(user_error, 'Error: --account requires an integer argument\n'), + halt(1) + ) + ; ActualArgv = Argv + ), + % Check arguments - ( Argv = [] + ( ActualArgv = [] -> write(user_error, 'Usage: un.pro [options] \n'), write(user_error, ' un.pro session [options]\n'), write(user_error, ' un.pro service [options]\n'), @@ -782,19 +864,19 @@ main(Argv) :- ), % Parse subcommands - ( Argv = ['session'|Rest] + ( ActualArgv = ['session'|Rest] -> handle_session(Rest) - ; Argv = ['service'|Rest] + ; ActualArgv = ['service'|Rest] -> handle_service(Rest) - ; Argv = ['snapshot'|Rest] + ; ActualArgv = ['snapshot'|Rest] -> handle_snapshot(Rest) - ; Argv = ['image'|Rest] + ; ActualArgv = ['image'|Rest] -> handle_image(Rest) - ; Argv = ['languages'|Rest] + ; ActualArgv = ['languages'|Rest] -> handle_languages(Rest) - ; Argv = ['key'|Rest] + ; ActualArgv = ['key'|Rest] -> handle_key(Rest) - ; Argv = [Filename|_] + ; ActualArgv = [Filename|_] -> execute_file(Filename) ; write(user_error, 'Error: Invalid arguments\n'), halt(1)