Add --account N flag and CSV credential resolution to C++, D, V, and Kotlin SDKs
Each implementation gains: - loadAccountsCSV / load_accounts_csv: reads public_key,secret_key rows from a CSV file, skipping # comments and blank lines - Full 5-tier credential resolution: 1. Explicit -p flag (public key override) 2. --account N → ~/.unsandbox/accounts.csv row N (bypasses env vars) 3. UNSANDBOX_PUBLIC_KEY / UNSANDBOX_SECRET_KEY env vars 4. UNSANDBOX_ACCOUNT env var selects row from ~/.unsandbox/accounts.csv 5. ~/.unsandbox/accounts.csv row 0, then ./accounts.csv row 0 - --account N CLI flag recognised in all arg-parsing loops
This commit is contained in:
parent
6860871128
commit
a839ffcd12
4 changed files with 338 additions and 22 deletions
|
|
@ -103,6 +103,27 @@ string read_file(const string& filename) {
|
|||
return buf.str();
|
||||
}
|
||||
|
||||
// Load a row from an accounts.csv file (format: public_key,secret_key per line).
|
||||
// Lines starting with '#' and blank lines are skipped. Returns the Nth data row.
|
||||
pair<string,string> loadAccountsCSV(const string& path, int index) {
|
||||
ifstream f(path);
|
||||
if (!f) return {"", ""};
|
||||
string line;
|
||||
int row = 0;
|
||||
while (getline(f, line)) {
|
||||
// Trim trailing carriage return
|
||||
if (!line.empty() && line.back() == '\r') line.pop_back();
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
if (row == index) {
|
||||
size_t comma = line.find(',');
|
||||
if (comma == string::npos) return {"", ""};
|
||||
return {line.substr(0, comma), line.substr(comma + 1)};
|
||||
}
|
||||
row++;
|
||||
}
|
||||
return {"", ""};
|
||||
}
|
||||
|
||||
string escape_json(const string& s) {
|
||||
ostringstream o;
|
||||
for (char c : s) {
|
||||
|
|
@ -1781,12 +1802,62 @@ void cmd_validate_key(bool extend, const string& public_key, const string& secre
|
|||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
string public_key = getenv("UNSANDBOX_PUBLIC_KEY") ? getenv("UNSANDBOX_PUBLIC_KEY") : "";
|
||||
string secret_key = getenv("UNSANDBOX_SECRET_KEY") ? getenv("UNSANDBOX_SECRET_KEY") : "";
|
||||
string public_key;
|
||||
string secret_key;
|
||||
int account_index = -1; // -1 = not set
|
||||
|
||||
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
|
||||
if (public_key.empty()) {
|
||||
public_key = getenv("UNSANDBOX_API_KEY") ? getenv("UNSANDBOX_API_KEY") : "";
|
||||
// First pass: scan for --account N and -p/-k flags before full arg parsing
|
||||
for (int i = 1; i < argc; i++) {
|
||||
string a = argv[i];
|
||||
if (a == "--account" && i+1 < argc) {
|
||||
account_index = atoi(argv[++i]);
|
||||
} else if (a == "-p" && i+1 < argc) {
|
||||
public_key = argv[++i];
|
||||
}
|
||||
}
|
||||
|
||||
if (account_index >= 0) {
|
||||
// --account N: load from ~/.unsandbox/accounts.csv, bypassing env vars
|
||||
const char* home = getenv("HOME");
|
||||
string csv_path = string(home ? home : ".") + "/.unsandbox/accounts.csv";
|
||||
auto creds = loadAccountsCSV(csv_path, account_index);
|
||||
if (creds.first.empty()) {
|
||||
// fall back to ./accounts.csv
|
||||
creds = loadAccountsCSV("accounts.csv", account_index);
|
||||
}
|
||||
if (!creds.first.empty()) {
|
||||
if (public_key.empty()) public_key = creds.first;
|
||||
secret_key = creds.second;
|
||||
}
|
||||
} else {
|
||||
// Priority: env vars, then ~/.unsandbox/accounts.csv row 0, then ./accounts.csv row 0
|
||||
if (public_key.empty()) {
|
||||
public_key = getenv("UNSANDBOX_PUBLIC_KEY") ? getenv("UNSANDBOX_PUBLIC_KEY") : "";
|
||||
}
|
||||
secret_key = getenv("UNSANDBOX_SECRET_KEY") ? getenv("UNSANDBOX_SECRET_KEY") : "";
|
||||
|
||||
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
|
||||
if (public_key.empty()) {
|
||||
public_key = getenv("UNSANDBOX_API_KEY") ? getenv("UNSANDBOX_API_KEY") : "";
|
||||
}
|
||||
|
||||
// Try UNSANDBOX_ACCOUNT env var to pick a row
|
||||
int env_account = -1;
|
||||
const char* env_acct = getenv("UNSANDBOX_ACCOUNT");
|
||||
if (env_acct) env_account = atoi(env_acct);
|
||||
|
||||
if (public_key.empty()) {
|
||||
const char* home = getenv("HOME");
|
||||
string csv_path = string(home ? home : ".") + "/.unsandbox/accounts.csv";
|
||||
auto creds = loadAccountsCSV(csv_path, env_account >= 0 ? env_account : 0);
|
||||
if (creds.first.empty()) {
|
||||
creds = loadAccountsCSV("accounts.csv", env_account >= 0 ? env_account : 0);
|
||||
}
|
||||
if (!creds.first.empty()) {
|
||||
public_key = creds.first;
|
||||
secret_key = creds.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (argc < 2) {
|
||||
|
|
@ -1824,6 +1895,7 @@ int main(int argc, char* argv[]) {
|
|||
else if (arg == "--name" && i+1 < argc) name = argv[++i];
|
||||
else if (arg == "--ports" && i+1 < argc) ports = argv[++i];
|
||||
else if (arg == "-k" && i+1 < argc) public_key = argv[++i];
|
||||
else if (arg == "--account" && i+1 < argc) i++; // already handled in first pass
|
||||
}
|
||||
|
||||
cmd_image(list, info, del, lock, unlock, publish, source_type, visibility_id, visibility, spawn, clone, name, ports, public_key, secret_key);
|
||||
|
|
@ -1848,6 +1920,7 @@ int main(int argc, char* argv[]) {
|
|||
else if (arg == "--tmux") tmux = true;
|
||||
else if (arg == "--screen") screen = true;
|
||||
else if (arg == "-k" && i+1 < argc) public_key = argv[++i];
|
||||
else if (arg == "--account" && i+1 < argc) i++; // already handled in first pass
|
||||
}
|
||||
|
||||
cmd_session(list, kill, shell, network, vcpu, tmux, screen, files, public_key, secret_key);
|
||||
|
|
@ -1910,6 +1983,7 @@ int main(int argc, char* argv[]) {
|
|||
unfreeze_on_demand = (val == "true") ? 1 : 0;
|
||||
}
|
||||
else if (arg == "-k" && i+1 < argc) public_key = argv[++i];
|
||||
else if (arg == "--account" && i+1 < argc) i++; // already handled in first pass
|
||||
}
|
||||
|
||||
cmd_service(name, ports, type, bootstrap, bootstrap_file, files, list, info, logs, tail, sleep, wake, destroy, resize, execute, command, dump_bootstrap, dump_file, redeploy, network, vcpu, envs, env_file, env_action, env_target, set_unfreeze_on_demand_id, set_unfreeze_on_demand_enabled, unfreeze_on_demand, public_key, secret_key);
|
||||
|
|
@ -1923,6 +1997,7 @@ int main(int argc, char* argv[]) {
|
|||
string arg = argv[i];
|
||||
if (arg == "--extend") extend = true;
|
||||
else if (arg == "-k" && i+1 < argc) public_key = argv[++i];
|
||||
else if (arg == "--account" && i+1 < argc) i++; // already handled in first pass
|
||||
}
|
||||
|
||||
cmd_validate_key(extend, public_key, secret_key);
|
||||
|
|
@ -1936,6 +2011,7 @@ int main(int argc, char* argv[]) {
|
|||
string arg = argv[i];
|
||||
if (arg == "--json") json_output = true;
|
||||
else if (arg == "-k" && i+1 < argc) public_key = argv[++i];
|
||||
else if (arg == "--account" && i+1 < argc) i++; // already handled in first pass
|
||||
}
|
||||
|
||||
cmd_languages(json_output, public_key, secret_key);
|
||||
|
|
@ -1956,6 +2032,7 @@ int main(int argc, char* argv[]) {
|
|||
else if (arg == "-n" && i+1 < argc) network = argv[++i];
|
||||
else if (arg == "-v" && i+1 < argc) vcpu = stoi(argv[++i]);
|
||||
else if (arg == "-k" && i+1 < argc) public_key = argv[++i];
|
||||
else if (arg == "--account" && i+1 < argc) i++; // already handled in first pass
|
||||
else if (arg[0] == '-') {
|
||||
cerr << RED << "Unknown option: " << arg << RESET << endl;
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import std.string;
|
|||
import std.conv;
|
||||
import std.array;
|
||||
import std.algorithm;
|
||||
import std.typecons;
|
||||
|
||||
immutable string API_BASE = "https://api.unsandbox.com";
|
||||
immutable string PORTAL_BASE = "https://unsandbox.com";
|
||||
|
|
@ -1555,13 +1556,82 @@ void validateKey(string publicKey, string secretKey, bool extend) {
|
|||
}
|
||||
}
|
||||
|
||||
int main(string[] args) {
|
||||
string publicKey = environment.get("UNSANDBOX_PUBLIC_KEY", "");
|
||||
string secretKey = environment.get("UNSANDBOX_SECRET_KEY", "");
|
||||
// Load a row from an accounts.csv file (format: public_key,secret_key per line).
|
||||
// Lines starting with '#' and blank lines are skipped. Returns the Nth data row.
|
||||
Tuple!(string, string) loadAccountsCSV(string path, int index) {
|
||||
import std.file : exists, readText;
|
||||
import std.range : empty;
|
||||
if (!exists(path)) return tuple("", "");
|
||||
string content = readText(path);
|
||||
int row = 0;
|
||||
foreach (line; content.splitLines()) {
|
||||
string stripped = line.strip();
|
||||
if (stripped.empty || stripped[0] == '#') continue;
|
||||
if (row == index) {
|
||||
auto parts = stripped.findSplit(",");
|
||||
if (!parts[1].empty) return tuple(parts[0], parts[2]);
|
||||
return tuple("", "");
|
||||
}
|
||||
row++;
|
||||
}
|
||||
return tuple("", "");
|
||||
}
|
||||
|
||||
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
|
||||
if (publicKey.empty) {
|
||||
publicKey = environment.get("UNSANDBOX_API_KEY", "");
|
||||
int main(string[] args) {
|
||||
string publicKey;
|
||||
string secretKey;
|
||||
int accountIndex = -1; // -1 = not set
|
||||
string explicitPublicKey;
|
||||
|
||||
// First pass: scan for --account N and -p flags
|
||||
for (size_t i = 1; i < args.length; i++) {
|
||||
if (args[i] == "--account" && i+1 < args.length) {
|
||||
accountIndex = to!int(args[++i]);
|
||||
} else if (args[i] == "-p" && i+1 < args.length) {
|
||||
explicitPublicKey = args[++i];
|
||||
}
|
||||
}
|
||||
|
||||
if (accountIndex >= 0) {
|
||||
// --account N: load from ~/.unsandbox/accounts.csv, bypassing env vars
|
||||
string home = environment.get("HOME", ".");
|
||||
string csvPath = home ~ "/.unsandbox/accounts.csv";
|
||||
auto creds = loadAccountsCSV(csvPath, accountIndex);
|
||||
if (creds[0].empty) {
|
||||
creds = loadAccountsCSV("accounts.csv", accountIndex);
|
||||
}
|
||||
if (!creds[0].empty) {
|
||||
publicKey = explicitPublicKey.empty ? creds[0] : explicitPublicKey;
|
||||
secretKey = creds[1];
|
||||
}
|
||||
} else {
|
||||
publicKey = explicitPublicKey.empty
|
||||
? environment.get("UNSANDBOX_PUBLIC_KEY", "")
|
||||
: explicitPublicKey;
|
||||
secretKey = environment.get("UNSANDBOX_SECRET_KEY", "");
|
||||
|
||||
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
|
||||
if (publicKey.empty) {
|
||||
publicKey = environment.get("UNSANDBOX_API_KEY", "");
|
||||
}
|
||||
|
||||
// Try UNSANDBOX_ACCOUNT env var to pick a row
|
||||
int envAccount = -1;
|
||||
string envAcct = environment.get("UNSANDBOX_ACCOUNT", "");
|
||||
if (!envAcct.empty) envAccount = to!int(envAcct);
|
||||
|
||||
if (publicKey.empty) {
|
||||
string home = environment.get("HOME", ".");
|
||||
string csvPath = home ~ "/.unsandbox/accounts.csv";
|
||||
auto creds = loadAccountsCSV(csvPath, envAccount >= 0 ? envAccount : 0);
|
||||
if (creds[0].empty) {
|
||||
creds = loadAccountsCSV("accounts.csv", envAccount >= 0 ? envAccount : 0);
|
||||
}
|
||||
if (!creds[0].empty) {
|
||||
publicKey = creds[0];
|
||||
secretKey = creds[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (args.length < 2) {
|
||||
|
|
@ -1598,6 +1668,7 @@ int main(string[] args) {
|
|||
else if (args[i] == "--screen") screen = true;
|
||||
else if (args[i] == "-f" && i+1 < args.length) inputFiles ~= args[++i];
|
||||
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
|
||||
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
|
||||
}
|
||||
|
||||
cmdSession(list, kill, shell, network, vcpu, tmux, screen, inputFiles, publicKey, secretKey);
|
||||
|
|
@ -1625,6 +1696,7 @@ int main(string[] args) {
|
|||
if (args[i] == "-e" && i+1 < args.length) svcEnvs ~= args[++i];
|
||||
else if (args[i] == "--env-file" && i+1 < args.length) svcEnvFile = args[++i];
|
||||
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
|
||||
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
|
||||
}
|
||||
cmdService(name, ports, bootstrap, bootstrapFile, type, list, info, logs, tail, sleep, wake, destroy, resize, resizeVcpu, execute, command, dumpBootstrap, dumpFile, unfreezeOnDemand, unfreezeOnDemandEnabled, createUnfreezeOnDemand, network, vcpu, inputFiles, svcEnvs, svcEnvFile, envAction, envTarget, publicKey, secretKey);
|
||||
return 0;
|
||||
|
|
@ -1658,6 +1730,7 @@ int main(string[] args) {
|
|||
else if (args[i] == "-e" && i+1 < args.length) svcEnvs ~= args[++i];
|
||||
else if (args[i] == "--env-file" && i+1 < args.length) svcEnvFile = args[++i];
|
||||
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
|
||||
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
|
||||
}
|
||||
|
||||
cmdService(name, ports, bootstrap, bootstrapFile, type, list, info, logs, tail, sleep, wake, destroy, resize, resizeVcpu, execute, command, dumpBootstrap, dumpFile, unfreezeOnDemand, unfreezeOnDemandEnabled, createUnfreezeOnDemand, network, vcpu, inputFiles, svcEnvs, svcEnvFile, envAction, envTarget, publicKey, secretKey);
|
||||
|
|
@ -1670,6 +1743,7 @@ int main(string[] args) {
|
|||
for (size_t i = 2; i < args.length; i++) {
|
||||
if (args[i] == "--extend") extend = true;
|
||||
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
|
||||
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
|
||||
}
|
||||
|
||||
if (publicKey.empty) {
|
||||
|
|
@ -1687,6 +1761,7 @@ int main(string[] args) {
|
|||
for (size_t i = 2; i < args.length; i++) {
|
||||
if (args[i] == "--json") jsonOutput = true;
|
||||
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
|
||||
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
|
||||
}
|
||||
|
||||
if (publicKey.empty) {
|
||||
|
|
@ -1720,6 +1795,7 @@ int main(string[] args) {
|
|||
else if (args[i] == "--name" && i+1 < args.length) name = args[++i];
|
||||
else if (args[i] == "--ports" && i+1 < args.length) ports = args[++i];
|
||||
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
|
||||
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
|
||||
}
|
||||
|
||||
if (publicKey.empty) {
|
||||
|
|
@ -1743,6 +1819,7 @@ int main(string[] args) {
|
|||
else if (args[i] == "-n" && i+1 < args.length) network = args[++i];
|
||||
else if (args[i] == "-v" && i+1 < args.length) vcpu = to!int(args[++i]);
|
||||
else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i];
|
||||
else if (args[i] == "--account" && i+1 < args.length) i++; // already handled in first pass
|
||||
else if (args[i].startsWith("-")) {
|
||||
stderr.writefln("%sUnknown option: %s%s", RED, args[i], RESET);
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -124,7 +124,8 @@ data class Args(
|
|||
var imageSpawn: String? = null,
|
||||
var imageClone: String? = null,
|
||||
var imageName: String? = null,
|
||||
var imagePorts: String? = null
|
||||
var imagePorts: String? = null,
|
||||
var accountIndex: Int = -1
|
||||
)
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
|
|
@ -151,7 +152,7 @@ fun main(args: Array<String>) {
|
|||
}
|
||||
|
||||
fun cmdExecute(args: Args) {
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
|
||||
val code = File(args.sourceFile!!).readText()
|
||||
val language = detectLanguage(args.sourceFile!!)
|
||||
|
||||
|
|
@ -226,7 +227,7 @@ fun cmdExecute(args: Args) {
|
|||
}
|
||||
|
||||
fun cmdSession(args: Args) {
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
|
||||
|
||||
if (args.sessionList) {
|
||||
val result = apiRequest("/sessions", "GET", null, publicKey, secretKey)
|
||||
|
|
@ -287,7 +288,7 @@ fun cmdSession(args: Args) {
|
|||
}
|
||||
|
||||
fun cmdService(args: Args) {
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
|
||||
|
||||
// Handle env subcommand
|
||||
if (args.envAction != null) {
|
||||
|
|
@ -541,7 +542,7 @@ fun saveLanguagesCache(languages: List<String>) {
|
|||
}
|
||||
|
||||
fun cmdLanguages(args: Args) {
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
|
||||
|
||||
// Try cache first
|
||||
var languages = loadLanguagesCache()
|
||||
|
|
@ -563,7 +564,7 @@ fun cmdLanguages(args: Args) {
|
|||
}
|
||||
|
||||
fun cmdImage(args: Args) {
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
|
||||
|
||||
if (args.imageList) {
|
||||
val result = apiRequest("/images", "GET", null, publicKey, secretKey)
|
||||
|
|
@ -654,7 +655,7 @@ fun cmdImage(args: Args) {
|
|||
}
|
||||
|
||||
fun cmdKey(args: Args) {
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
|
||||
|
||||
val result = validateKey(publicKey, secretKey)
|
||||
val valid = result["valid"] as? Boolean ?: false
|
||||
|
|
@ -733,11 +734,41 @@ fun validateKey(publicKey: String?, secretKey: String): Map<String, Any> {
|
|||
return parseJson(response)
|
||||
}
|
||||
|
||||
fun getApiKeys(argsKey: String?): Pair<String?, String> {
|
||||
// Load a row from an accounts.csv file (format: public_key,secret_key per line).
|
||||
// Lines starting with '#' and blank lines are skipped. Returns the Nth data row, or null.
|
||||
fun loadAccountsCSV(path: String, index: Int): Pair<String, String>? {
|
||||
val file = java.io.File(path)
|
||||
if (!file.exists()) return null
|
||||
var row = 0
|
||||
for (line in file.readLines()) {
|
||||
val stripped = line.trim()
|
||||
if (stripped.isEmpty() || stripped.startsWith("#")) continue
|
||||
if (row == index) {
|
||||
val comma = stripped.indexOf(',')
|
||||
if (comma < 0) return null
|
||||
return Pair(stripped.substring(0, comma), stripped.substring(comma + 1))
|
||||
}
|
||||
row++
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getApiKeys(argsKey: String?, accountIndex: Int = -1): Pair<String?, String> {
|
||||
var publicKey: String? = null
|
||||
var secretKey: String? = null
|
||||
|
||||
if (argsKey != null) {
|
||||
if (accountIndex >= 0) {
|
||||
// --account N: load from ~/.unsandbox/accounts.csv, bypassing env vars
|
||||
val home = System.getenv("HOME") ?: System.getProperty("user.home") ?: "."
|
||||
var creds = loadAccountsCSV("$home/.unsandbox/accounts.csv", accountIndex)
|
||||
if (creds == null) {
|
||||
creds = loadAccountsCSV("accounts.csv", accountIndex)
|
||||
}
|
||||
if (creds != null) {
|
||||
publicKey = if (argsKey != null) argsKey else creds.first
|
||||
secretKey = creds.second
|
||||
}
|
||||
} else if (argsKey != null) {
|
||||
secretKey = argsKey
|
||||
publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
} else {
|
||||
|
|
@ -750,6 +781,22 @@ fun getApiKeys(argsKey: String?): Pair<String?, String> {
|
|||
secretKey = apiKey
|
||||
}
|
||||
}
|
||||
|
||||
// Try UNSANDBOX_ACCOUNT env var to pick a row
|
||||
val envAcct = System.getenv("UNSANDBOX_ACCOUNT")
|
||||
val envAccount = envAcct?.toIntOrNull() ?: -1
|
||||
|
||||
if (publicKey.isNullOrEmpty()) {
|
||||
val home = System.getenv("HOME") ?: System.getProperty("user.home") ?: "."
|
||||
var creds = loadAccountsCSV("$home/.unsandbox/accounts.csv", if (envAccount >= 0) envAccount else 0)
|
||||
if (creds == null) {
|
||||
creds = loadAccountsCSV("accounts.csv", if (envAccount >= 0) envAccount else 0)
|
||||
}
|
||||
if (creds != null) {
|
||||
publicKey = creds.first
|
||||
secretKey = creds.second
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (secretKey.isNullOrEmpty()) {
|
||||
|
|
@ -1005,7 +1052,7 @@ fun serviceEnvDelete(serviceId: String, publicKey: String?, secretKey: String):
|
|||
}
|
||||
|
||||
fun cmdServiceEnv(args: Args) {
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
val (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex)
|
||||
val action = args.envAction
|
||||
val target = args.envTarget
|
||||
|
||||
|
|
@ -1260,6 +1307,7 @@ fun parseArgs(args: Array<String>): Args {
|
|||
"--dump-bootstrap" -> result.serviceDumpBootstrap = args[++i]
|
||||
"--dump-file" -> result.serviceDumpFile = args[++i]
|
||||
"--extend" -> result.keyExtend = true
|
||||
"--account" -> result.accountIndex = args[++i].toInt()
|
||||
"--env-file" -> result.envFile = args[++i]
|
||||
"--info" -> {
|
||||
when (result.command) {
|
||||
|
|
|
|||
|
|
@ -687,6 +687,26 @@ fn cmd_service(name string, ports string, service_type string, bootstrap string,
|
|||
exit(1)
|
||||
}
|
||||
|
||||
// Load a row from an accounts.csv file (format: public_key,secret_key per line).
|
||||
// Lines starting with '#' and blank lines are skipped. Returns the Nth data row.
|
||||
fn load_accounts_csv(path string, index int) ?(string, string) {
|
||||
content := os.read_file(path) or { return none }
|
||||
lines := content.split('\n')
|
||||
mut row := 0
|
||||
for raw_line in lines {
|
||||
line := raw_line.trim_space()
|
||||
if line == '' || line.starts_with('#') {
|
||||
continue
|
||||
}
|
||||
if row == index {
|
||||
idx := line.index(',') or { return none }
|
||||
return line[..idx], line[idx + 1..]
|
||||
}
|
||||
row++
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn get_public_key() string {
|
||||
pub_key := os.getenv('UNSANDBOX_PUBLIC_KEY')
|
||||
if pub_key != '' {
|
||||
|
|
@ -1171,8 +1191,82 @@ fn cmd_languages(json_output bool, api_key string) {
|
|||
}
|
||||
}
|
||||
|
||||
fn resolve_credentials(account_index int, explicit_pub_key string) (string, string) {
|
||||
if account_index >= 0 {
|
||||
// --account N: load from ~/.unsandbox/accounts.csv, bypassing env vars
|
||||
home := os.getenv('HOME')
|
||||
csv_path := (if home != '' { home } else { '.' }) + '/.unsandbox/accounts.csv'
|
||||
if creds := load_accounts_csv(csv_path, account_index) {
|
||||
pk := if explicit_pub_key != '' { explicit_pub_key } else { creds.0 }
|
||||
return pk, creds.1
|
||||
}
|
||||
// fall back to ./accounts.csv
|
||||
if creds := load_accounts_csv('accounts.csv', account_index) {
|
||||
pk := if explicit_pub_key != '' { explicit_pub_key } else { creds.0 }
|
||||
return pk, creds.1
|
||||
}
|
||||
return explicit_pub_key, ''
|
||||
}
|
||||
|
||||
mut pub_key := if explicit_pub_key != '' { explicit_pub_key } else { os.getenv('UNSANDBOX_PUBLIC_KEY') }
|
||||
mut sec_key := os.getenv('UNSANDBOX_SECRET_KEY')
|
||||
|
||||
if pub_key == '' {
|
||||
api_key := os.getenv('UNSANDBOX_API_KEY')
|
||||
if api_key != '' {
|
||||
pub_key = api_key
|
||||
}
|
||||
}
|
||||
|
||||
// Try UNSANDBOX_ACCOUNT env var to pick a row
|
||||
mut env_account := -1
|
||||
env_acct := os.getenv('UNSANDBOX_ACCOUNT')
|
||||
if env_acct != '' {
|
||||
env_account = env_acct.int()
|
||||
}
|
||||
|
||||
if pub_key == '' {
|
||||
home := os.getenv('HOME')
|
||||
csv_path := (if home != '' { home } else { '.' }) + '/.unsandbox/accounts.csv'
|
||||
row := if env_account >= 0 { env_account } else { 0 }
|
||||
if creds := load_accounts_csv(csv_path, row) {
|
||||
pub_key = creds.0
|
||||
sec_key = creds.1
|
||||
}
|
||||
if pub_key == '' {
|
||||
if creds2 := load_accounts_csv('accounts.csv', row) {
|
||||
pub_key = creds2.0
|
||||
sec_key = creds2.1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pub_key == '' {
|
||||
eprintln('${red}Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY environment variable not set${reset}')
|
||||
exit(1)
|
||||
}
|
||||
return pub_key, sec_key
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut api_key := get_public_key()
|
||||
// First pass: scan for --account N and -p flags
|
||||
mut account_index := -1
|
||||
mut explicit_pub_key := ''
|
||||
for i := 1; i < os.args.len; i++ {
|
||||
if os.args[i] == '--account' && i + 1 < os.args.len {
|
||||
account_index = os.args[i + 1].int()
|
||||
i++
|
||||
} else if os.args[i] == '-p' && i + 1 < os.args.len {
|
||||
explicit_pub_key = os.args[i + 1]
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
pub_key, sec_key := resolve_credentials(account_index, explicit_pub_key)
|
||||
// Inject resolved credentials into env so get_public_key/get_secret_key pick them up
|
||||
os.setenv('UNSANDBOX_PUBLIC_KEY', pub_key, true)
|
||||
os.setenv('UNSANDBOX_SECRET_KEY', sec_key, true)
|
||||
mut api_key := pub_key
|
||||
|
||||
if os.args.len < 2 {
|
||||
eprintln('Usage: ${os.args[0]} [options] <source_file>')
|
||||
|
|
@ -1238,6 +1332,9 @@ fn main() {
|
|||
i++
|
||||
api_key = os.args[i]
|
||||
}
|
||||
'--account' {
|
||||
i++ // already handled in first pass
|
||||
}
|
||||
'-f' {
|
||||
i++
|
||||
f := os.args[i]
|
||||
|
|
@ -1392,6 +1489,9 @@ fn main() {
|
|||
i++
|
||||
api_key = os.args[i]
|
||||
}
|
||||
'--account' {
|
||||
i++ // already handled in first pass
|
||||
}
|
||||
'-f' {
|
||||
i++
|
||||
f := os.args[i]
|
||||
|
|
@ -1429,6 +1529,9 @@ fn main() {
|
|||
i++
|
||||
api_key = os.args[i]
|
||||
}
|
||||
'--account' {
|
||||
i++ // already handled in first pass
|
||||
}
|
||||
else {}
|
||||
}
|
||||
i++
|
||||
|
|
@ -1449,6 +1552,9 @@ fn main() {
|
|||
i++
|
||||
api_key = os.args[i]
|
||||
}
|
||||
'--account' {
|
||||
i++ // already handled in first pass
|
||||
}
|
||||
else {}
|
||||
}
|
||||
i++
|
||||
|
|
@ -1559,6 +1665,9 @@ fn main() {
|
|||
i++
|
||||
api_key = os.args[i]
|
||||
}
|
||||
'--account' {
|
||||
i++ // already handled in first pass
|
||||
}
|
||||
else {}
|
||||
}
|
||||
i++
|
||||
|
|
@ -1600,6 +1709,7 @@ fn main() {
|
|||
'--ports' { i++; ports = os.args[i] }
|
||||
'--hot' { hot = true }
|
||||
'-k' { i++; api_key = os.args[i] }
|
||||
'--account' { i++ /* already handled in first pass */ }
|
||||
else {}
|
||||
}
|
||||
i++
|
||||
|
|
@ -1625,6 +1735,7 @@ fn main() {
|
|||
'--grep' { i++; grep = os.args[i] }
|
||||
'--follow', '-f' { follow = true }
|
||||
'-k' { i++; api_key = os.args[i] }
|
||||
'--account' { i++ /* already handled in first pass */ }
|
||||
else {}
|
||||
}
|
||||
i++
|
||||
|
|
@ -1671,6 +1782,9 @@ fn main() {
|
|||
i++
|
||||
api_key = os.args[i]
|
||||
}
|
||||
'--account' {
|
||||
i++ // already handled in first pass
|
||||
}
|
||||
else {
|
||||
if os.args[i].starts_with('-') {
|
||||
eprintln('${red}Unknown option: ${os.args[i]}${reset}')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue