Add declarative attic cache

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2025-09-11 11:08:22 +01:00
parent ffafc81ed1
commit 1727785180
13 changed files with 194 additions and 967 deletions

8
flake.lock generated
View File

@@ -511,11 +511,11 @@
"secrets": {
"flake": false,
"locked": {
"lastModified": 1757519344,
"narHash": "sha256-wLwVbKDPkFCPh9UYLDqCPb62hp6mHBAgjn3Dech54YU=",
"lastModified": 1757583391,
"narHash": "sha256-q5ZXkTv0SJw7OMbu2K3b03Fbb+1Hz6ZafqdqGneyX9A=",
"ref": "refs/heads/main",
"rev": "8ae051ad0936cb8fbf10b3ab2130f09a07ca1ce6",
"revCount": 39,
"rev": "42df461dac05dccd22df0c36007174dd73aa0aea",
"revCount": 40,
"type": "git",
"url": "ssh://git@karaolidis.com/karaolidis/nix-secrets.git"
},

View File

@@ -43,7 +43,9 @@
];
download-buffer-size = 524288000;
substituters = lib.mkBefore [ "https://nix.karaolidis.com/main" ];
trusted-public-keys = lib.mkBefore [ "main:nJVRBnv73MDkwuV5sgm52m4E2ImOhWHvY12qzjPegAk=" ];
trusted-public-keys = lib.mkBefore [
"nix.karaolidis.com:1yz1tIVLGDEOFC1p/uYtR4Sx+nIbdYDqsDv4kkV0uyk="
];
netrc-file = config.sops.templates.nix-netrc.path;
};

View File

@@ -16,6 +16,7 @@ in
"attic/postgresql".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"attic/rs256".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"attic/admin".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"attic/keypairs/main".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
};
templates = {
@@ -94,6 +95,7 @@ in
[
"/mnt/storage/private/storm/containers/storage/volumes/attic/_data:/var/lib/attic"
"${hmConfig.sops.templates.attic-server.path}:/etc/attic/server.toml:ro"
"${hmConfig.sops.secrets."attic/keypairs/main".path}:/etc/attic/keypairs/main:ro"
"${postStart}:/etc/attic/post-start.sh:ro"
];
environmentFiles = [ hmConfig.sops.templates.attic-env.path ];

View File

@@ -11,9 +11,11 @@ while true; do
set -o errexit
if [ $status -eq 0 ]; then
attic cache configure "$CACHE_NAME" --keypair-path "/etc/attic/keypairs/$CACHE_NAME"
break
elif echo "$out" | grep -q "NoSuchCache"; then
attic cache create "$CACHE_NAME"
attic cache create "$CACHE_NAME" --keypair-path "/etc/attic/keypairs/$CACHE_NAME"
break
elif echo "$out" | grep -q "404"; then
sleep 0.1
else

View File

@@ -0,0 +1,162 @@
diff --git a/client/src/cli.rs b/client/src/cli.rs
index ee86783..e96b1b6 100644
--- a/client/src/cli.rs
+++ b/client/src/cli.rs
@@ -9,6 +9,7 @@ use enum_as_inner::EnumAsInner;
use crate::command::cache::{self, Cache};
use crate::command::get_closure::{self, GetClosure};
+use crate::command::key::{self, Key};
use crate::command::login::{self, Login};
use crate::command::push::{self, Push};
use crate::command::r#use::{self, Use};
@@ -30,6 +31,7 @@ pub enum Command {
Push(Push),
Cache(Cache),
WatchStore(WatchStore),
+ Key(Key),
#[clap(hide = true)]
GetClosure(GetClosure),
@@ -57,6 +59,7 @@ pub async fn run() -> Result<()> {
Command::Cache(_) => cache::run(opts).await,
Command::WatchStore(_) => watch_store::run(opts).await,
Command::GetClosure(_) => get_closure::run(opts).await,
+ Command::Key(_) => key::run(opts).await,
}
}
diff --git a/client/src/command/cache.rs b/client/src/command/cache.rs
index af01378..af24d8c 100644
--- a/client/src/command/cache.rs
+++ b/client/src/command/cache.rs
@@ -7,8 +7,11 @@ use crate::api::ApiClient;
use crate::cache::CacheRef;
use crate::cli::Opts;
use crate::config::Config;
-use attic::api::v1::cache_config::{
- CacheConfig, CreateCacheRequest, KeypairConfig, RetentionPeriodConfig,
+use attic::{
+ api::v1::cache_config::{
+ CacheConfig, CreateCacheRequest, KeypairConfig, RetentionPeriodConfig,
+ },
+ signing::NixKeypair,
};
/// Manage caches on an Attic server.
@@ -72,6 +75,12 @@ struct Create {
default_value = "cache.nixos.org-1"
)]
upstream_cache_key_names: Vec<String>,
+
+ /// The signing keypair to use for the cache.
+ ///
+ /// If not specified, a new keypair will be generated.
+ #[clap(long)]
+ keypair_path: Option<String>,
}
/// Configure a cache.
@@ -91,6 +100,14 @@ struct Configure {
#[clap(long)]
regenerate_keypair: bool,
+ /// Set a keypair for the cache.
+ ///
+ /// The server-side signing key will be set to the
+ /// specified keypair. This is useful for setting up
+ /// a cache with a pre-existing keypair.
+ #[clap(long, conflicts_with = "regenerate_keypair")]
+ keypair_path: Option<String>,
+
/// Make the cache public.
///
/// Use `--private` to make it private.
@@ -179,9 +196,15 @@ async fn create_cache(sub: Create) -> Result<()> {
let (server_name, server, cache) = config.resolve_cache(&sub.cache)?;
let api = ApiClient::from_server_config(server.clone())?;
+ let keypair = if let Some(keypair_path) = &sub.keypair_path {
+ let contents = std::fs::read_to_string(keypair_path)?;
+ KeypairConfig::Keypair(NixKeypair::from_str(&contents)?)
+ } else {
+ KeypairConfig::Generate
+ };
+
let request = CreateCacheRequest {
- // TODO: Make this configurable?
- keypair: KeypairConfig::Generate,
+ keypair,
is_public: sub.public,
priority: sub.priority,
store_dir: sub.store_dir,
@@ -230,6 +253,10 @@ async fn configure_cache(sub: Configure) -> Result<()> {
if sub.regenerate_keypair {
patch.keypair = Some(KeypairConfig::Generate);
+ } else if let Some(keypair_path) = &sub.keypair_path {
+ let contents = std::fs::read_to_string(keypair_path)?;
+ let keypair = KeypairConfig::Keypair(NixKeypair::from_str(&contents)?);
+ patch.keypair = Some(keypair);
}
patch.store_dir = sub.store_dir;
diff --git a/client/src/command/key.rs b/client/src/command/key.rs
new file mode 100644
index 0000000..807d8a7
--- /dev/null
+++ b/client/src/command/key.rs
@@ -0,0 +1,42 @@
+use anyhow::Result;
+use clap::{Parser, Subcommand};
+
+use crate::cli::Opts;
+use attic::signing::NixKeypair;
+
+/// Manage signing keys.
+#[derive(Debug, Parser)]
+pub struct Key {
+ #[clap(subcommand)]
+ command: KeyCommand,
+}
+
+#[derive(Debug, Subcommand)]
+enum KeyCommand {
+ Generate(Generate),
+}
+
+/// Generate a key.
+#[derive(Debug, Clone, Parser)]
+pub struct Generate {
+ /// Name of the key (must not contain colons).
+ name: String,
+}
+
+pub async fn run(opts: Opts) -> Result<()> {
+ let sub = opts.command.as_key().unwrap();
+ match &sub.command {
+ KeyCommand::Generate(sub) => generate_key(sub).await,
+ }
+}
+
+async fn generate_key(sub: &Generate) -> Result<()> {
+ let keypair = NixKeypair::generate(&sub.name)?;
+
+ println!("🔑 Generated keypair \"{}\"", sub.name);
+ println!();
+ println!(" Private key: {}", keypair.export_keypair());
+ println!(" Public key: {}", keypair.export_public_key());
+
+ Ok(())
+}
diff --git a/client/src/command/mod.rs b/client/src/command/mod.rs
index cca423f..26b105a 100644
--- a/client/src/command/mod.rs
+++ b/client/src/command/mod.rs
@@ -1,5 +1,6 @@
pub mod cache;
pub mod get_closure;
+pub mod key;
pub mod login;
pub mod push;
pub mod r#use;

View File

@@ -1,5 +1,11 @@
final: prev:
# FIXME: https://github.com/zhaofengli/attic/pull/280
prev.attic-client.overrideAttrs (oldAttrs: {
patches = oldAttrs.patches or [ ] ++ [ ./stdout-logging.patch ];
patches = oldAttrs.patches or [ ] ++ [
# fix: log non-errors to stdout
(builtins.fetchurl {
url = "https://github.com/zhaofengli/attic/pull/280.patch";
sha256 = "sha256:0j6ay6d9is7053sq5njakjmlpwk24db296rma694jggpl19ibxjv";
})
./declarative-key-pair.patch
];
})

View File

@@ -1,321 +0,0 @@
diff --git a/client/src/command/cache.rs b/client/src/command/cache.rs
index af01378..0602b3b 100644
--- a/client/src/command/cache.rs
+++ b/client/src/command/cache.rs
@@ -189,7 +189,7 @@ async fn create_cache(sub: Create) -> Result<()> {
};
api.create_cache(cache, request).await?;
- eprintln!(
+ println!(
"✨ Created cache \"{}\" on \"{}\"",
cache.as_str(),
server_name.as_str()
@@ -239,7 +239,7 @@ async fn configure_cache(sub: Configure) -> Result<()> {
let api = ApiClient::from_server_config(server.clone())?;
api.configure_cache(cache, &patch).await?;
- eprintln!(
+ println!(
"✅ Configured \"{}\" on \"{}\"",
cache.as_str(),
server_name.as_str()
@@ -254,12 +254,12 @@ async fn destroy_cache(sub: Destroy) -> Result<()> {
let (server_name, server, cache) = config.resolve_cache(&sub.cache)?;
if !sub.no_confirm {
- eprintln!("When you destory a cache:");
- eprintln!();
- eprintln!("1. Everyone will lose access.");
- eprintln!("2. The underlying data won't be deleted immediately.");
- eprintln!("3. You may not be able to create a cache of the same name.");
- eprintln!();
+ println!("When you destory a cache:");
+ println!();
+ println!("1. Everyone will lose access.");
+ println!("2. The underlying data won't be deleted immediately.");
+ println!("3. You may not be able to create a cache of the same name.");
+ println!();
let answer: String = Input::new()
.with_prompt(format!(
@@ -278,7 +278,7 @@ async fn destroy_cache(sub: Destroy) -> Result<()> {
let api = ApiClient::from_server_config(server.clone())?;
api.destroy_cache(cache).await?;
- eprintln!("🗑️ The cache was destroyed.");
+ println!("🗑️ The cache was destroyed.");
Ok(())
}
@@ -291,40 +291,40 @@ async fn show_cache_config(sub: Info) -> Result<()> {
let cache_config = api.get_cache_config(cache).await?;
if let Some(is_public) = cache_config.is_public {
- eprintln!(" Public: {}", is_public);
+ println!(" Public: {}", is_public);
}
if let Some(public_key) = cache_config.public_key {
- eprintln!(" Public Key: {}", public_key);
+ println!(" Public Key: {}", public_key);
}
if let Some(substituter_endpoint) = cache_config.substituter_endpoint {
- eprintln!("Binary Cache Endpoint: {}", substituter_endpoint);
+ println!("Binary Cache Endpoint: {}", substituter_endpoint);
}
if let Some(api_endpoint) = cache_config.api_endpoint {
- eprintln!(" API Endpoint: {}", api_endpoint);
+ println!(" API Endpoint: {}", api_endpoint);
}
if let Some(store_dir) = cache_config.store_dir {
- eprintln!(" Store Directory: {}", store_dir);
+ println!(" Store Directory: {}", store_dir);
}
if let Some(priority) = cache_config.priority {
- eprintln!(" Priority: {}", priority);
+ println!(" Priority: {}", priority);
}
if let Some(upstream_cache_key_names) = cache_config.upstream_cache_key_names {
- eprintln!(" Upstream Cache Keys: {:?}", upstream_cache_key_names);
+ println!(" Upstream Cache Keys: {:?}", upstream_cache_key_names);
}
if let Some(retention_period) = cache_config.retention_period {
match retention_period {
RetentionPeriodConfig::Period(period) => {
- eprintln!(" Retention Period: {:?}", period);
+ println!(" Retention Period: {:?}", period);
}
RetentionPeriodConfig::Global => {
- eprintln!(" Retention Period: Global Default");
+ println!(" Retention Period: Global Default");
}
}
}
diff --git a/client/src/command/login.rs b/client/src/command/login.rs
index 9abcea7..6cadd59 100644
--- a/client/src/command/login.rs
+++ b/client/src/command/login.rs
@@ -28,7 +28,7 @@ pub async fn run(opts: Opts) -> Result<()> {
let mut config_m = config.as_mut();
if let Some(server) = config_m.servers.get_mut(&sub.name) {
- eprintln!("✍️ Overwriting server \"{}\"", sub.name.as_str());
+ println!("✍️ Overwriting server \"{}\"", sub.name.as_str());
server.endpoint = sub.endpoint.to_owned();
@@ -38,7 +38,7 @@ pub async fn run(opts: Opts) -> Result<()> {
});
}
} else {
- eprintln!("✍️ Configuring server \"{}\"", sub.name.as_str());
+ println!("✍️ Configuring server \"{}\"", sub.name.as_str());
config_m.servers.insert(
sub.name.to_owned(),
diff --git a/client/src/command/push.rs b/client/src/command/push.rs
index b2bb661..5d39549 100644
--- a/client/src/command/push.rs
+++ b/client/src/command/push.rs
@@ -91,7 +91,7 @@ impl PushContext {
return Ok(());
} else {
- eprintln!("⚙️ Pushing {num_missing_paths} paths to \"{cache}\" on \"{server}\" ({num_already_cached} already cached, {num_upstream} in upstream)...",
+ println!("⚙️ Pushing {num_missing_paths} paths to \"{cache}\" on \"{server}\" ({num_already_cached} already cached, {num_upstream} in upstream)...",
cache = self.cache_name.as_str(),
server = self.server_name.as_str(),
num_missing_paths = plan.store_path_map.len(),
diff --git a/client/src/command/use.rs b/client/src/command/use.rs
index 37d8cd6..d87f65e 100644
--- a/client/src/command/use.rs
+++ b/client/src/command/use.rs
@@ -34,15 +34,15 @@ pub async fn run(opts: Opts) -> Result<()> {
let public_key = cache_config.public_key
.ok_or_else(|| anyhow!("The server did not tell us which public key it uses. Is signing managed by the client?"))?;
- eprintln!(
+ println!(
"Configuring Nix to use \"{cache}\" on \"{server_name}\":",
cache = cache.as_str(),
server_name = server_name.as_str(),
);
// Modify nix.conf
- eprintln!("+ Substituter: {}", substituter);
- eprintln!("+ Trusted Public Key: {}", public_key);
+ println!("+ Substituter: {}", substituter);
+ println!("+ Trusted Public Key: {}", public_key);
let mut nix_config = NixConfig::load().await?;
nix_config.add_substituter(&substituter);
@@ -50,7 +50,7 @@ pub async fn run(opts: Opts) -> Result<()> {
// Modify netrc
if let Some(token) = server.token()? {
- eprintln!("+ Access Token");
+ println!("+ Access Token");
let mut nix_netrc = NixNetrc::load().await?;
let host = Url::parse(&substituter)?
diff --git a/client/src/command/watch_store.rs b/client/src/command/watch_store.rs
index 24eaf7a..aec0c33 100644
--- a/client/src/command/watch_store.rs
+++ b/client/src/command/watch_store.rs
@@ -91,7 +91,7 @@ pub async fn run(opts: Opts) -> Result<()> {
watcher.watch(&store_dir, RecursiveMode::NonRecursive)?;
- eprintln!(
+ println!(
"👀 Pushing new store paths to \"{cache}\" on \"{server}\"",
cache = cache.as_str(),
server = server_name.as_str(),
diff --git a/client/src/push.rs b/client/src/push.rs
index 309bd4b..2fea414 100644
--- a/client/src/push.rs
+++ b/client/src/push.rs
@@ -595,7 +595,7 @@ pub async fn upload_path(
};
mp.suspend(|| {
- eprintln!(
+ println!(
"✅ {} ({})",
path.as_os_str().to_string_lossy(),
info_string
diff --git a/server/src/database/migration/m20230112_000004_migrate_nar_remote_files_to_chunks.rs b/server/src/database/migration/m20230112_000004_migrate_nar_remote_files_to_chunks.rs
index 42d70a6..6bbe585 100644
--- a/server/src/database/migration/m20230112_000004_migrate_nar_remote_files_to_chunks.rs
+++ b/server/src/database/migration/m20230112_000004_migrate_nar_remote_files_to_chunks.rs
@@ -24,7 +24,7 @@ impl MigrationTrait for Migration {
// When this migration is run, we assume that there are no
// preexisting chunks.
- eprintln!("* Migrating NARs to chunks...");
+ println!("* Migrating NARs to chunks...");
// Add a temporary column into `chunk` to store the related `nar_id`.
manager
diff --git a/server/src/database/migration/m20230112_000005_drop_old_nar_columns.rs b/server/src/database/migration/m20230112_000005_drop_old_nar_columns.rs
index 9d29b66..7436b4a 100644
--- a/server/src/database/migration/m20230112_000005_drop_old_nar_columns.rs
+++ b/server/src/database/migration/m20230112_000005_drop_old_nar_columns.rs
@@ -16,7 +16,7 @@ impl MigrationName for Migration {
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
- eprintln!("* Migrating NAR schema...");
+ println!("* Migrating NAR schema...");
if manager.get_database_backend() == DatabaseBackend::Sqlite {
// Just copy all data to a new table
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 0314e69..89644e1 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -217,7 +217,7 @@ async fn fallback(_: Uri) -> ServerResult<()> {
/// Runs the API server.
pub async fn run_api_server(cli_listen: Option<SocketAddr>, config: Config) -> Result<()> {
- eprintln!("Starting API server...");
+ println!("Starting API server...");
let state = StateInner::new(config).await;
@@ -239,7 +239,7 @@ pub async fn run_api_server(cli_listen: Option<SocketAddr>, config: Config) -> R
.layer(TraceLayer::new_for_http())
.layer(CatchPanicLayer::new());
- eprintln!("Listening on {:?}...", listen);
+ println!("Listening on {:?}...", listen);
let listener = TcpListener::bind(&listen).await?;
@@ -256,7 +256,7 @@ pub async fn run_api_server(cli_listen: Option<SocketAddr>, config: Config) -> R
/// Runs database migrations.
pub async fn run_migrations(config: Config) -> Result<()> {
- eprintln!("Running migrations...");
+ println!("Running migrations...");
let state = StateInner::new(config).await;
let db = state.database().await?;
diff --git a/server/src/main.rs b/server/src/main.rs
index c5f08df..3a37c23 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -121,14 +121,14 @@ fn init_logging(tokio_console: bool) {
.init();
if tokio_console {
- eprintln!("Note: tokio-console is enabled");
+ println!("Note: tokio-console is enabled");
}
}
fn dump_version() {
#[cfg(debug_assertions)]
- eprintln!("Attic Server {} (debug)", env!("CARGO_PKG_VERSION"));
+ println!("Attic Server {} (debug)", env!("CARGO_PKG_VERSION"));
#[cfg(not(debug_assertions))]
- eprintln!("Attic Server {} (release)", env!("CARGO_PKG_VERSION"));
+ println!("Attic Server {} (release)", env!("CARGO_PKG_VERSION"));
}
diff --git a/server/src/oobe.rs b/server/src/oobe.rs
index d3d912d..98ef88c 100644
--- a/server/src/oobe.rs
+++ b/server/src/oobe.rs
@@ -77,25 +77,25 @@ pub async fn run_oobe() -> Result<()> {
token.encode(&SignatureType::RS256(key), &None, &None)?
};
- eprintln!();
- eprintln!("-----------------");
- eprintln!("Welcome to Attic!");
- eprintln!();
- eprintln!("A simple setup using SQLite and local storage has been configured for you in:");
- eprintln!();
- eprintln!(" {}", config_path.to_str().unwrap());
- eprintln!();
- eprintln!("Run the following command to log into this server:");
- eprintln!();
- eprintln!(" attic login local http://localhost:8080 {root_token}");
- eprintln!();
- eprintln!("Documentations and guides:");
- eprintln!();
- eprintln!(" https://docs.attic.rs");
- eprintln!();
- eprintln!("Enjoy!");
- eprintln!("-----------------");
- eprintln!();
+ println!();
+ println!("-----------------");
+ println!("Welcome to Attic!");
+ println!();
+ println!("A simple setup using SQLite and local storage has been configured for you in:");
+ println!();
+ println!(" {}", config_path.to_str().unwrap());
+ println!();
+ println!("Run the following command to log into this server:");
+ println!();
+ println!(" attic login local http://localhost:8080 {root_token}");
+ println!();
+ println!("Documentations and guides:");
+ println!();
+ println!(" https://docs.attic.rs");
+ println!();
+ println!("Enjoy!");
+ println!("-----------------");
+ println!();
Ok(())
}

View File

@@ -1,10 +1,15 @@
final: prev:
prev.tea.overrideAttrs (oldAttrs: {
patches = oldAttrs.patches or [ ] ++ [
# feat: add user auth via env
(builtins.fetchurl {
url = "https://gitea.com/gitea/tea/pulls/639.patch";
sha256 = "sha256:0c5gpi6aajd3h0wp7lrvj5qk9wsqhgbap7ijvl0x117v0g8mgzvs";
})
./instance-ssh-host-env.patch
# fix: evaluate env login in repo context
(builtins.fetchurl {
url = "https://gitea.com/gitea/tea/pulls/809.patch";
sha256 = "sha256:1f9cyizwmza6kg0r3q5d8h7vvph4wnh7kh3wvi5aqnbw100j7igg";
})
];
})

View File

@@ -1,174 +0,0 @@
diff --git a/modules/config/login.go b/modules/config/login.go
index 3b77fb9..94de9cd 100644
--- a/modules/config/login.go
+++ b/modules/config/login.go
@@ -13,6 +13,7 @@ import (
"net/http/cookiejar"
"net/url"
"os"
+ "strconv"
"strings"
"time"
@@ -200,6 +201,63 @@ func UpdateLogin(login *Login) error {
return saveConfig()
}
+// CreateLoginFromEnvVars returns a login based on environment variables, or nil if no login can be created
+func CreateLoginFromEnvVars() (*Login, error) {
+ var token string
+
+ giteaToken := os.Getenv("GITEA_TOKEN")
+ githubToken := os.Getenv("GH_TOKEN")
+ giteaInstanceURL := os.Getenv("GITEA_INSTANCE_URL")
+ instanceInsecure := os.Getenv("GITEA_INSTANCE_INSECURE")
+ giteaInstanceSSHHost := os.Getenv("GITEA_INSTANCE_SSH_HOST")
+ insecure := false
+ if len(instanceInsecure) > 0 {
+ insecure, _ = strconv.ParseBool(instanceInsecure)
+ }
+
+ // if no tokens are set, or no instance url for gitea fail fast
+ if len(giteaInstanceURL) == 0 || (len(giteaToken) == 0 && len(githubToken) == 0) {
+ return nil, nil
+ }
+
+ token = giteaToken
+ if len(giteaToken) == 0 {
+ token = githubToken
+ }
+
+ login := &Login{
+ Name: "GITEA_LOGIN_VIA_ENV",
+ URL: giteaInstanceURL,
+ Token: token,
+ SSHHost: giteaInstanceSSHHost,
+ Insecure: insecure,
+ SSHKey: "",
+ SSHCertPrincipal: "",
+ SSHKeyFingerprint: "",
+ SSHAgent: false,
+ VersionCheck: true,
+ Created: time.Now().Unix(),
+ }
+
+ client := login.Client()
+ u, _, err := client.GetMyUserInfo()
+ if err != nil {
+ return nil, fmt.Errorf("failed to validate token: %s", err)
+ }
+
+ login.User = u.UserName
+
+ if login.SSHHost == "" {
+ parsedURL, err := url.Parse(giteaInstanceURL)
+ if err != nil {
+ return nil, err
+ }
+ login.SSHHost = parsedURL.Host
+ }
+
+ return login, nil
+}
+
// Client returns a client to operate Gitea API. You may provide additional modifiers
// for the client like gitea.SetBasicAuth() for customization
func (l *Login) Client(options ...gitea.ClientOption) *gitea.Client {
diff --git a/modules/context/context.go b/modules/context/context.go
index aec5592..636eeec 100644
--- a/modules/context/context.go
+++ b/modules/context/context.go
@@ -9,9 +9,7 @@ import (
"log"
"os"
"path"
- "strconv"
"strings"
- "time"
"code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/git"
@@ -108,16 +106,6 @@ func InitCommand(cmd *cli.Command) *TeaContext {
c.RepoSlug = repoFlag
}
- // override config user with env variable
- envLogin := GetLoginByEnvVar()
- if envLogin != nil {
- _, err := utils.ValidateAuthenticationMethod(envLogin.URL, envLogin.Token, "", "", false, "", "")
- if err != nil {
- log.Fatal(err.Error())
- }
- c.Login = envLogin
- }
-
// override login from flag, or use default login if repo based detection failed
if len(loginFlag) != 0 {
c.Login = config.GetLoginByName(loginFlag)
@@ -196,10 +184,25 @@ func contextFromLocalRepo(repoPath, remoteValue string) (*git.TeaRepo, *config.L
return repo, nil, "", fmt.Errorf("Remote '%s' not found in this Git repository", remoteValue)
}
+ envLogin, err := config.CreateLoginFromEnvVars()
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
logins, err := config.GetLogins()
if err != nil {
return repo, nil, "", err
}
+
+ if envLogin != nil {
+ _, err := utils.ValidateAuthenticationMethod(envLogin.URL, envLogin.Token, "", "", false, "", "")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ logins = append([]config.Login{*envLogin}, logins...)
+ }
+
for _, l := range logins {
sshHost := l.GetSSHHost()
for _, u := range remoteConfig.URLs {
@@ -223,40 +226,3 @@ func contextFromLocalRepo(repoPath, remoteValue string) (*git.TeaRepo, *config.L
return repo, nil, "", errNotAGiteaRepo
}
-
-// GetLoginByEnvVar returns a login based on environment variables, or nil if no login can be created
-func GetLoginByEnvVar() *config.Login {
- var token string
-
- giteaToken := os.Getenv("GITEA_TOKEN")
- githubToken := os.Getenv("GH_TOKEN")
- giteaInstanceURL := os.Getenv("GITEA_INSTANCE_URL")
- instanceInsecure := os.Getenv("GITEA_INSTANCE_INSECURE")
- insecure := false
- if len(instanceInsecure) > 0 {
- insecure, _ = strconv.ParseBool(instanceInsecure)
- }
-
- // if no tokens are set, or no instance url for gitea fail fast
- if len(giteaInstanceURL) == 0 || (len(giteaToken) == 0 && len(githubToken) == 0) {
- return nil
- }
-
- token = giteaToken
- if len(giteaToken) == 0 {
- token = githubToken
- }
-
- return &config.Login{
- Name: "GITEA_LOGIN_VIA_ENV",
- URL: giteaInstanceURL,
- Token: token,
- Insecure: insecure,
- SSHKey: "",
- SSHCertPrincipal: "",
- SSHKeyFingerprint: "",
- SSHAgent: false,
- Created: time.Now().Unix(),
- VersionCheck: false,
- }
-}

View File

@@ -1,312 +0,0 @@
From 2ef5994a21d694bdf50d1d9d4c524c9584368917 Mon Sep 17 00:00:00 2001
From: Nikolaos Karaolidis <nick@karaolidis.com>
Date: Mon, 28 Jul 2025 15:33:55 +0100
Subject: [PATCH] fe: dynamic configuration env vars
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
---
.../dynamic/auth.emailupdate.enabled.en.md | 1 +
.../auth.login.local.maxattempts.en.md | 2 ++
.../auth.signup.confirm.commenter.en.md | 2 ++
.../dynamic/auth.signup.confirm.user.en.md | 2 ++
.../backend/dynamic/auth.signup.enabled.en.md | 2 ++
...in.defaults.comments.deletion.author.en.md | 2 ++
...defaults.comments.deletion.moderator.en.md | 2 ++
...ain.defaults.comments.editing.author.en.md | 2 ++
....defaults.comments.editing.moderator.en.md | 2 ++
...omain.defaults.comments.enablevoting.en.md | 2 ++
...domain.defaults.comments.rss.enabled.en.md | 2 ++
...domain.defaults.comments.showdeleted.en.md | 2 ++
...ain.defaults.comments.text.maxlength.en.md | 2 ++
.../domain.defaults.login.showforunauth.en.md | 2 ++
...ain.defaults.markdown.images.enabled.en.md | 2 ++
...main.defaults.markdown.links.enabled.en.md | 2 ++
...ain.defaults.markdown.tables.enabled.en.md | 4 ++-
...main.defaults.signup.enablefederated.en.md | 2 ++
.../domain.defaults.signup.enablelocal.en.md | 2 ++
.../domain.defaults.signup.enablesso.en.md | 2 ++
.../dynamic/integrations.usegravatar.en.md | 1 +
.../dynamic/operation.newowner.enabled.en.md | 2 ++
internal/data/dyn_config.go | 28 +++++++++++++++++--
23 files changed, 69 insertions(+), 3 deletions(-)
diff --git a/docs/content/configuration/backend/dynamic/auth.emailupdate.enabled.en.md b/docs/content/configuration/backend/dynamic/auth.emailupdate.enabled.en.md
index 5b9c5716..a139c953 100644
--- a/docs/content/configuration/backend/dynamic/auth.emailupdate.enabled.en.md
+++ b/docs/content/configuration/backend/dynamic/auth.emailupdate.enabled.en.md
@@ -20,3 +20,4 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter defines w
* If no operational mailer is configured, the email will be updated right away, without intermediate confirmation.
* If set to `Off`, users cannot change their emails themselves. Email can only be updated by a [superuser](/kb/permissions/superuser) (for example, at a user's request).
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_AUTH_EMAILUPDATE_ENABLED`.
diff --git a/docs/content/configuration/backend/dynamic/auth.login.local.maxattempts.en.md b/docs/content/configuration/backend/dynamic/auth.login.local.maxattempts.en.md
index bada14ec..5a3993a8 100644
--- a/docs/content/configuration/backend/dynamic/auth.login.local.maxattempts.en.md
+++ b/docs/content/configuration/backend/dynamic/auth.login.local.maxattempts.en.md
@@ -20,3 +20,5 @@ Comentario keeps a counter of failed login attempts for each user.
* The failed login attempt counter gets reset to zero as soon as the user has successfully logged in.
This mechanism is mostly intended to safeguard users against brute-force attacks on their passwords. In order to disable it, set the value to `0`.
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_AUTH_LOGIN_LOCAL_MAXATTEMPTS`.
diff --git a/docs/content/configuration/backend/dynamic/auth.signup.confirm.commenter.en.md b/docs/content/configuration/backend/dynamic/auth.signup.confirm.commenter.en.md
index 6ad25e5a..fbb38b3b 100644
--- a/docs/content/configuration/backend/dynamic/auth.signup.confirm.commenter.en.md
+++ b/docs/content/configuration/backend/dynamic/auth.signup.confirm.commenter.en.md
@@ -17,3 +17,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter configure
* If set to `Off`, commenters will be logged in immediately upon registration. This is not recommended due to security considerations; it may also render the user account unusable in the future if they misspelled their email address.
This setting applies only to websites embedding Comentario. For the Administration UI there's a [separate configuration item](auth.signup.confirm.user).
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_AUTH_SIGNUP_CONFIRM_COMMENTER`.
diff --git a/docs/content/configuration/backend/dynamic/auth.signup.confirm.user.en.md b/docs/content/configuration/backend/dynamic/auth.signup.confirm.user.en.md
index 6a14a1d3..3aee4ae6 100644
--- a/docs/content/configuration/backend/dynamic/auth.signup.confirm.user.en.md
+++ b/docs/content/configuration/backend/dynamic/auth.signup.confirm.user.en.md
@@ -17,3 +17,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter configure
* If set to `Off`, users can log in immediately upon registration. This is not recommended due to security considerations; it may also render the user account unusable in the future if they misspelled their email address.
This setting applies only to the Administration UI. For websites embedding Comentario there's a [separate configuration item](auth.signup.confirm.commenter).
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_AUTH_SIGNUP_CONFIRM_USER`.
diff --git a/docs/content/configuration/backend/dynamic/auth.signup.enabled.en.md b/docs/content/configuration/backend/dynamic/auth.signup.enabled.en.md
index 35acc2ba..d8a38804 100644
--- a/docs/content/configuration/backend/dynamic/auth.signup.enabled.en.md
+++ b/docs/content/configuration/backend/dynamic/auth.signup.enabled.en.md
@@ -20,3 +20,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter controls
* If set to `Off`, new user registrations are forbidden.
This setting applies only to the Administration UI. It can be useful for (temporarily) preventing new sign-ups.
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_AUTH_SIGNUP_ENABLED`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.comments.deletion.author.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.comments.deletion.author.en.md
index 9fef72b4..93f280cf 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.comments.deletion.author.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.comments.deletion.author.en.md
@@ -18,3 +18,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter can be us
* When set to `On`, comment authors will see a *trashcan* button on their comments, which allows them to delete a written comment.
* If set to `Off`, comments cannot be removed by their authors once submitted (but possibly can [by domain moderators](domain.defaults.comments.deletion.moderator) if enabled).
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_COMMENTS_DELETION_AUTHOR`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.comments.deletion.moderator.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.comments.deletion.moderator.en.md
index 97c3b3e0..f1c002f8 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.comments.deletion.moderator.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.comments.deletion.moderator.en.md
@@ -18,3 +18,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter can be us
* When set to `On`, users with the moderator or owner [role](/kb/permissions/roles) and [superusers](/kb/permissions/superuser) will see a *trashcan* button on every comment, allowing them to delete any comment on the domain's pages.
* If set to `Off`, comments cannot be removed by moderators (but possibly can [by their authors](domain.defaults.comments.deletion.author) if enabled).
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_COMMENTS_DELETION_MODERATOR`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.comments.editing.author.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.comments.editing.author.en.md
index 346a93b6..de989f59 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.comments.editing.author.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.comments.editing.author.en.md
@@ -17,3 +17,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter can be us
* When set to `On`, comment authors will see a *pencil* button on their comments, which allows them to edit a written comment.
* If set to `Off`, comments cannot be changed by their authors once submitted (but possibly can [by domain moderators](domain.defaults.comments.editing.moderator) if enabled).
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_COMMENTS_EDITING_AUTHOR`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.comments.editing.moderator.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.comments.editing.moderator.en.md
index 0b35d1e3..25bfcbf1 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.comments.editing.moderator.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.comments.editing.moderator.en.md
@@ -17,3 +17,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter can be us
* When set to `On`, users with the moderator or owner [role](/kb/permissions/roles) and [superusers](/kb/permissions/superuser) will see a *pencil* button on every comment, allowing them to edit any comment on the domain's pages.
* If set to `Off`, comments cannot be changed by moderators (but possibly can [by their authors](domain.defaults.comments.editing.author) if enabled).
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_COMMENTS_EDITING_MODERATOR`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.comments.enablevoting.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.comments.enablevoting.en.md
index a2e72f5b..17ec769e 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.comments.enablevoting.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.comments.enablevoting.en.md
@@ -13,3 +13,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter can be us
* When set to `On`, every comment card will contain a score and a set of upvote/downvote buttons. Also, any commenter will be able to upvote or downvote others' comments (bot not those they authored self).
* If set to `Off`, the score and the voting buttons will be hidden. It will also disable sorting comments by votes.
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_COMMENTS_ENABLEVOTING`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.comments.rss.enabled.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.comments.rss.enabled.en.md
index 3981741e..4108ed90 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.comments.rss.enabled.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.comments.rss.enabled.en.md
@@ -21,3 +21,5 @@ When RSS is enabled, the users will be able to subscribe to comment feeds using:
* `RSS` dropdown button under the [comment editor](/kb/comment-editor) (left of the sorting buttons).
* `Comment RSS feed` links in Domain properties and Domain page properties.
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_COMMENTS_RSS_ENABLED`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.comments.showdeleted.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.comments.showdeleted.en.md
index 89b26859..f2293dba 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.comments.showdeleted.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.comments.showdeleted.en.md
@@ -18,3 +18,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter controls
* If set to `Off`, deleted comments will be hidden in the comment tree immediately, as well as all its child comments.
This setting doesn't affect comment display in the Administration UI (it has a separate switch for hiding deleted comments).
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_COMMENTS_SHOWDELETED`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.comments.text.maxlength.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.comments.text.maxlength.en.md
index a87b2fff..9e068c5a 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.comments.text.maxlength.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.comments.text.maxlength.en.md
@@ -21,3 +21,5 @@ It defines how long comment texts can be on a specific domain, representing the
* The lowest possible value for this setting is the "Twitter limit" of `140` characters.
* The top (physical) limit is `1048576` (1 MiB).
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_COMMENTS_TEXT_MAXLENGTH`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.login.showforunauth.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.login.showforunauth.en.md
index 85b264e6..65a29ea4 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.login.showforunauth.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.login.showforunauth.en.md
@@ -19,3 +19,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter allows to
* If set to `Off`, the login dialog will be skipped if [commenting without registration](/configuration/frontend/domain/authentication) is enabled for this domain. Otherwise the dialog will be shown and user will be requested to log in or sign up.
When this setting is `Off`, the user can still authenticate explicitly using the `Sign in` button above the comment editor.
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_LOGIN_SHOWFORUNAUTH`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.markdown.images.enabled.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.markdown.images.enabled.en.md
index f229294c..6af7d884 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.markdown.images.enabled.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.markdown.images.enabled.en.md
@@ -18,3 +18,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter configure
* If set to `Off`, commenters won't be able to insert images, and the corresponding markup will be removed from the resulting text.
This setting only applies to newly written comments and does not affect images in existing comments.
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_MARKDOWN_IMAGES_ENABLED`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.markdown.links.enabled.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.markdown.links.enabled.en.md
index cfaaddcb..bb171b1e 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.markdown.links.enabled.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.markdown.links.enabled.en.md
@@ -18,3 +18,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter configure
* If set to `Off`, commenters won't be able to insert links, and the corresponding string will be rendered as plain, non-clickable text.
This setting only applies to newly written comments and does not affect links in existing comments.
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_MARKDOWN_LINKS_ENABLED`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.markdown.tables.enabled.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.markdown.tables.enabled.en.md
index 243affed..f38c097e 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.markdown.tables.enabled.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.markdown.tables.enabled.en.md
@@ -16,5 +16,7 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter configure
* If set to `On`, commenters can insert [tables](/kb/markdown#tables) in comments.
* If set to `Off`, commenters won't be able to insert tables, and the corresponding markup will be removed from the resulting text.
-
+
This setting only applies to newly written comments and does not affect tables in existing comments.
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_MARKDOWN_TABLES_ENABLED`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.signup.enablefederated.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.signup.enablefederated.en.md
index 7a5d92a9..c6015fdb 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.signup.enablefederated.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.signup.enablefederated.en.md
@@ -21,3 +21,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter controls
This setting doesn't apply to the Administration UI, which uses a [separate configuration item](auth.signup.enabled) for that.
It also doesn't affect *existing users*, i.e. those already registered. They will be able to log in even when this setting is disabled. To disable federated login completely, switch off undesirable providers in the domain's [authentication settings](/configuration/frontend/domain/authentication).
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_SIGNUP_ENABLEFEDERATED`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.signup.enablelocal.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.signup.enablelocal.en.md
index 874d6dc4..f2dff20e 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.signup.enablelocal.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.signup.enablelocal.en.md
@@ -23,3 +23,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter controls
This setting doesn't apply to the Administration UI, which uses a [separate configuration item](auth.signup.enabled) for that.
It also doesn't affect *existing users*. They will be able to log in even when this setting is disabled. To disable login with email/password completely, switch it off in the domain's [authentication settings](/configuration/frontend/domain/authentication).
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_SIGNUP_ENABLELOCAL`.
diff --git a/docs/content/configuration/backend/dynamic/domain.defaults.signup.enablesso.en.md b/docs/content/configuration/backend/dynamic/domain.defaults.signup.enablesso.en.md
index 6da28064..86bc6826 100644
--- a/docs/content/configuration/backend/dynamic/domain.defaults.signup.enablesso.en.md
+++ b/docs/content/configuration/backend/dynamic/domain.defaults.signup.enablesso.en.md
@@ -22,3 +22,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter controls
* If set to `Off`, SSO registration on websites embedding comments will be forbidden.
This setting doesn't affect *SSO users who are already registered*. They will be able to log in even when this setting is disabled. To disable SSO login completely, switch it off in the domain's [authentication settings](/configuration/frontend/domain/authentication).
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_DOMAIN_DEFAULTS_SIGNUP_ENABLESSO`.
diff --git a/docs/content/configuration/backend/dynamic/integrations.usegravatar.en.md b/docs/content/configuration/backend/dynamic/integrations.usegravatar.en.md
index 8509944d..b81d1766 100644
--- a/docs/content/configuration/backend/dynamic/integrations.usegravatar.en.md
+++ b/docs/content/configuration/backend/dynamic/integrations.usegravatar.en.md
@@ -16,3 +16,4 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter configure
It also enables the use of Gravatar during import (e.g. from [Commento](/installation/migration/commento) or [WordPress](/installation/migration/wordpress)).
* If set to `Off`, Gravatar won't be contacted.
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_INTEGRATIONS_USEGRAVATAR`.
diff --git a/docs/content/configuration/backend/dynamic/operation.newowner.enabled.en.md b/docs/content/configuration/backend/dynamic/operation.newowner.enabled.en.md
index a3025d96..d2a27b15 100644
--- a/docs/content/configuration/backend/dynamic/operation.newowner.enabled.en.md
+++ b/docs/content/configuration/backend/dynamic/operation.newowner.enabled.en.md
@@ -15,3 +15,5 @@ This [dynamic configuration](/configuration/backend/dynamic) parameter configure
* If set to `Off`, users without domains (for example, commenters) won't be able to register their own domains in Comentario, and thus become domain owners. Most likely, this is what you want, therefore `Off` is the default.
This setting doesn't affect users who already own at least one domain.
+
+This default value of this setting can be overridden at startup using the environment variable `DYN_DEFAULT_OPERATION_NEWOWNER_ENABLED`.
diff --git a/internal/data/dyn_config.go b/internal/data/dyn_config.go
index 8595ea2a..ad5b9894 100644
--- a/internal/data/dyn_config.go
+++ b/internal/data/dyn_config.go
@@ -2,14 +2,21 @@ package data
import (
"fmt"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/google/uuid"
+ "github.com/op/go-logging"
"gitlab.com/comentario/comentario/internal/api/models"
- "strconv"
- "time"
)
+// logger represents a package-wide logger instance
+var logger = logging.MustGetLogger("data")
+
// DynConfigItemSectionKey is a key of dynamic configuration item section
type DynConfigItemSectionKey string
@@ -196,6 +203,11 @@ const (
// ConfigKeyDomainDefaultsPrefix is a prefix given to domain setting keys that turn them into global domain defaults keys
const ConfigKeyDomainDefaultsPrefix = "domain.defaults."
+func getInitialDynConfigItemEnvVar(key DynConfigItemKey) (string, bool) {
+ name := "DYN_DEFAULT_" + strings.ToUpper(strings.ReplaceAll(string(key), ".", "_"))
+ return os.LookupEnv(name)
+}
+
// DefaultDynInstanceConfig is the default dynamic instance configuration
var DefaultDynInstanceConfig = DynConfigMap{
ConfigKeyAuthEmailUpdateEnabled: {DefaultValue: "false", Datatype: ConfigDatatypeBool, Section: DynConfigItemSectionAuth},
@@ -221,3 +233,15 @@ var DefaultDynInstanceConfig = DynConfigMap{
ConfigKeyDomainDefaultsPrefix + DomainConfigKeyFederatedSignupEnabled: {DefaultValue: "true", Datatype: ConfigDatatypeBool, Section: DynConfigItemSectionAuth},
ConfigKeyDomainDefaultsPrefix + DomainConfigKeySsoSignupEnabled: {DefaultValue: "true", Datatype: ConfigDatatypeBool, Section: DynConfigItemSectionAuth},
}
+
+func init() {
+ for key, item := range DefaultDynInstanceConfig {
+ value, override := getInitialDynConfigItemEnvVar(key)
+ if override {
+ if err := item.ValidateValue(value); err != nil {
+ logger.Fatalf("bad override for %q: %v", key, err)
+ }
+ item.DefaultValue = value
+ }
+ }
+}
--
GitLab

View File

@@ -12,8 +12,12 @@ pkgs.stdenv.mkDerivation (finalAttrs: {
};
patches = [
# Fix variety typo
(builtins.fetchurl {
url = "https://patch-diff.githubusercontent.com/raw/Clemens-E/obsidian-languagetool-plugin/pull/132.patch";
sha256 = "sha256:13lzp0920h1kc9yl3r04r505zb417hlciyiwii0fm5b12g35av8v";
})
./settings-notification.patch
./variety-typo.patch
];
offlineCache = pkgs.fetchYarnDeps {

View File

@@ -1,149 +0,0 @@
diff --git a/src/SettingsTab.ts b/src/SettingsTab.ts
index d0d65bb..2a211b0 100644
--- a/src/SettingsTab.ts
+++ b/src/SettingsTab.ts
@@ -33,10 +33,10 @@ export interface LanguageToolPluginSettings {
staticLanguage?: string;
motherTongue?: string;
- englishVeriety?: undefined | 'en-US' | 'en-GB' | 'en-CA' | 'en-AU' | 'en-ZA' | 'en-NZ';
- germanVeriety?: undefined | 'de-DE' | 'de-AT' | 'de-CH';
- portugueseVeriety?: undefined | 'pt-BR' | 'pt-PT' | 'pt-AO' | 'pt-MZ';
- catalanVeriety?: undefined | 'ca-ES' | 'ca-ES-valencia';
+ englishVariety?: undefined | 'en-US' | 'en-GB' | 'en-CA' | 'en-AU' | 'en-ZA' | 'en-NZ';
+ germanVariety?: undefined | 'de-DE' | 'de-AT' | 'de-CH';
+ portugueseVariety?: undefined | 'pt-BR' | 'pt-PT' | 'pt-AO' | 'pt-MZ';
+ catalanVariety?: undefined | 'ca-ES' | 'ca-ES-valencia';
pickyMode: boolean;
@@ -286,13 +286,13 @@ export class LanguageToolSettingsTab extends PluginSettingTab {
component.onChange(async value => {
this.plugin.settings.staticLanguage = value;
if (value !== 'auto') {
- this.plugin.settings.englishVeriety = undefined;
+ this.plugin.settings.englishVariety = undefined;
englishVarietyDropdown?.setValue('default');
- this.plugin.settings.germanVeriety = undefined;
+ this.plugin.settings.germanVariety = undefined;
germanVarietyDropdown?.setValue('default');
- this.plugin.settings.portugueseVeriety = undefined;
+ this.plugin.settings.portugueseVariety = undefined;
portugueseVarietyDropdown?.setValue('default');
- this.plugin.settings.catalanVeriety = undefined;
+ this.plugin.settings.catalanVariety = undefined;
catalanVarietyDropdown?.setValue('default');
}
await this.plugin.saveSettings();
@@ -338,14 +338,14 @@ export class LanguageToolSettingsTab extends PluginSettingTab {
'en-ZA': 'English (South Africa)',
'en-NZ': 'English (New Zealand)',
})
- .setValue(this.plugin.settings.englishVeriety ?? 'default')
+ .setValue(this.plugin.settings.englishVariety ?? 'default')
.onChange(async value => {
if (value === 'default') {
- this.plugin.settings.englishVeriety = undefined;
+ this.plugin.settings.englishVariety = undefined;
} else {
this.plugin.settings.staticLanguage = 'auto';
staticLanguageComponent?.setValue('auto');
- this.plugin.settings.englishVeriety = value as 'en-US' | 'en-GB' | 'en-CA' | 'en-AU' | 'en-ZA' | 'en-NZ';
+ this.plugin.settings.englishVariety = value as 'en-US' | 'en-GB' | 'en-CA' | 'en-AU' | 'en-ZA' | 'en-NZ';
}
await this.plugin.saveSettings();
});
@@ -360,14 +360,14 @@ export class LanguageToolSettingsTab extends PluginSettingTab {
'de-CH': 'German (Switzerland)',
'de-AT': 'German (Austria)',
})
- .setValue(this.plugin.settings.germanVeriety ?? 'default')
+ .setValue(this.plugin.settings.germanVariety ?? 'default')
.onChange(async value => {
if (value === 'default') {
- this.plugin.settings.germanVeriety = undefined;
+ this.plugin.settings.germanVariety = undefined;
} else {
this.plugin.settings.staticLanguage = 'auto';
staticLanguageComponent?.setValue('auto');
- this.plugin.settings.germanVeriety = value as 'de-DE' | 'de-CH' | 'de-AT';
+ this.plugin.settings.germanVariety = value as 'de-DE' | 'de-CH' | 'de-AT';
}
await this.plugin.saveSettings();
});
@@ -383,14 +383,14 @@ export class LanguageToolSettingsTab extends PluginSettingTab {
'pt-AO': 'Portuguese (Angola)',
'pt-MZ': 'Portuguese (Mozambique)',
})
- .setValue(this.plugin.settings.portugueseVeriety ?? 'default')
+ .setValue(this.plugin.settings.portugueseVariety ?? 'default')
.onChange(async value => {
if (value === 'default') {
- this.plugin.settings.portugueseVeriety = undefined;
+ this.plugin.settings.portugueseVariety = undefined;
} else {
this.plugin.settings.staticLanguage = 'auto';
staticLanguageComponent?.setValue('auto');
- this.plugin.settings.portugueseVeriety = value as 'pt-BR' | 'pt-PT' | 'pt-AO' | 'pt-MZ';
+ this.plugin.settings.portugueseVariety = value as 'pt-BR' | 'pt-PT' | 'pt-AO' | 'pt-MZ';
}
await this.plugin.saveSettings();
});
@@ -404,14 +404,14 @@ export class LanguageToolSettingsTab extends PluginSettingTab {
'ca-ES': 'Catalan',
'ca-ES-valencia': 'Catalan (Valencian)',
})
- .setValue(this.plugin.settings.catalanVeriety ?? 'default')
+ .setValue(this.plugin.settings.catalanVariety ?? 'default')
.onChange(async value => {
if (value === 'default') {
- this.plugin.settings.catalanVeriety = undefined;
+ this.plugin.settings.catalanVariety = undefined;
} else {
this.plugin.settings.staticLanguage = 'auto';
staticLanguageComponent?.setValue('auto');
- this.plugin.settings.catalanVeriety = value as 'ca-ES' | 'ca-ES-valencia';
+ this.plugin.settings.catalanVariety = value as 'ca-ES' | 'ca-ES-valencia';
}
await this.plugin.saveSettings();
});
diff --git a/src/api.ts b/src/api.ts
index 109984c..4cd1700 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -58,27 +58,27 @@ export async function getDetectionResult(
params.disabledRules = settings.ruleOtherDisabledRules;
}
- if (settings.englishVeriety) {
+ if (settings.englishVariety) {
params.preferredVariants = `${params.preferredVariants ? `${params.preferredVariants},` : ''}${
- settings.englishVeriety
+ settings.englishVariety
}`;
}
- if (settings.germanVeriety) {
+ if (settings.germanVariety) {
params.preferredVariants = `${params.preferredVariants ? `${params.preferredVariants},` : ''}${
- settings.germanVeriety
+ settings.germanVariety
}`;
}
- if (settings.portugueseVeriety) {
+ if (settings.portugueseVariety) {
params.preferredVariants = `${params.preferredVariants ? `${params.preferredVariants},` : ''}${
- settings.portugueseVeriety
+ settings.portugueseVariety
}`;
}
- if (settings.catalanVeriety) {
+ if (settings.catalanVariety) {
params.preferredVariants = `${params.preferredVariants ? `${params.preferredVariants},` : ''}${
- settings.catalanVeriety
+ settings.catalanVariety
}`;
}