model/credentials: support legacy env vars; support .env-style credential files

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I231c5f28d7313ed44c252aaedab2becc6a6a6964
This commit is contained in:
raf 2026-05-01 23:32:49 +03:00
commit d824da52df
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF

View file

@ -132,10 +132,34 @@ impl PakkerCompatCredentialsFile {
))
})?;
serde_json::from_str(&content).map_err(|e| {
PakkerError::InternalError(format!(
"Failed to parse Pakku credentials file: {e}"
))
// Try JSON first (for compatibility with future format changes)
if let Ok(parsed) = serde_json::from_str::<Self>(&content) {
return Ok(parsed);
}
// Fall back to .env-style key=value parsing (format written by pakker init)
let mut curseforge_api_key = None;
let mut github_access_token = None;
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((key, value)) = line.split_once('=') {
let key = key.trim();
let value = value.trim().to_string();
match key {
"CURSEFORGE_API_KEY" => curseforge_api_key = Some(value),
"GITHUB_TOKEN" => github_access_token = Some(value),
_ => {},
}
}
}
Ok(Self {
curseforge_api_key,
github_access_token,
})
}
}
@ -160,8 +184,9 @@ impl ResolvedCredentials {
let pakku_file = PakkerCompatCredentialsFile::load().ok();
Self {
curseforge_api_key: resolve_secret(
curseforge_api_key: resolve_secret_with_fallback(
"PAKKER_CURSEFORGE_API_KEY",
"CURSEFORGE_API_KEY",
"curseforge_api_key",
pakker_file
.as_ref()
@ -176,8 +201,9 @@ impl ResolvedCredentials {
pakker_file.as_ref().and_then(|f| f.modrinth_token.clone()),
None,
),
github_access_token: resolve_secret(
github_access_token: resolve_secret_with_fallback(
"PAKKER_GITHUB_TOKEN",
"GITHUB_TOKEN",
"github_access_token",
pakker_file
.as_ref()
@ -248,6 +274,40 @@ fn resolve_secret(
.map(|v| (v, CredentialsSource::PakkerFile))
}
fn resolve_secret_with_fallback(
env_key: &str,
fallback_env_key: &str,
keyring_entry: &str,
pakker_file_value: Option<String>,
pakku_file_value: Option<String>,
) -> Option<(String, CredentialsSource)> {
if let Ok(v) = std::env::var(env_key)
&& !v.trim().is_empty()
{
return Some((v.trim().to_string(), CredentialsSource::Env));
}
if let Ok(v) = std::env::var(fallback_env_key)
&& !v.trim().is_empty()
{
return Some((v.trim().to_string(), CredentialsSource::Env));
}
if let Ok(v) = get_keyring_secret(keyring_entry)
&& !v.trim().is_empty()
{
return Some((v.trim().to_string(), CredentialsSource::Keyring));
}
if let Some(v) = pakker_file_value.filter(|v| !v.trim().is_empty()) {
return Some((v, CredentialsSource::PakkerFile));
}
pakku_file_value
.filter(|v| !v.trim().is_empty())
.map(|v| (v, CredentialsSource::PakkerFile))
}
fn get_keyring_secret(
entry: &str,
) -> std::result::Result<String, keyring::Error> {