Fix assumption that is_err always means the local site doesn't exist, which may cause the local site's keypair to be regenerated (#5724)

* Fix assumption that is_err always means the local site doesn't exst, which may cause things like keypairs to be overwritten

* remove use of unstable feature

* fix map_err

* exists query and transaction
This commit is contained in:
dullbananas 2025-06-13 16:12:12 -07:00 committed by GitHub
parent ba0099e7a2
commit 38e87f6dc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 104 additions and 84 deletions

1
Cargo.lock generated
View file

@ -3794,6 +3794,7 @@ dependencies = [
name = "lemmy_db_views_site"
version = "1.0.0-alpha.5"
dependencies = [
"anyhow",
"chrono",
"diesel",
"diesel-async",

View file

@ -25,6 +25,7 @@ full = [
"lemmy_db_views_api_misc/full",
"lemmy_db_views_person/full",
"lemmy_db_views_readable_federation_state/full",
"anyhow",
]
ts-rs = ["dep:ts-rs"]
@ -42,3 +43,4 @@ serde = { workspace = true }
serde_with = { workspace = true }
ts-rs = { workspace = true, optional = true }
url = { workspace = true }
anyhow = { workspace = true, optional = true }

View file

@ -40,7 +40,7 @@ impl SiteView {
Ok(local_site)
})
.await
.map_err(|_e: Arc<LemmyError>| LemmyErrorType::LocalSiteNotSetup.into())
.map_err(|e: Arc<LemmyError>| anyhow::anyhow!("err getting local site: {e:?}").into())
}
}

View file

@ -1,5 +1,10 @@
use activitypub_federation::http_signatures::generate_actor_keypair;
use chrono::Utc;
use diesel::{
dsl::{exists, not, select},
query_builder::AsQuery,
};
use diesel_async::{scoped_futures::ScopedFutureExt, RunQueryDsl};
use lemmy_api_utils::utils::generate_inbox_url;
use lemmy_db_schema::{
source::{
@ -11,8 +16,9 @@ use lemmy_db_schema::{
site::{Site, SiteInsertForm},
},
traits::{ApubActor, Crud},
utils::DbPool,
utils::{get_conn, DbPool},
};
use lemmy_db_schema_file::schema::local_site;
use lemmy_db_views_site::SiteView;
use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
@ -22,89 +28,100 @@ use tracing::info;
use url::Url;
pub async fn setup_local_site(pool: &mut DbPool<'_>, settings: &Settings) -> LemmyResult<SiteView> {
// Check to see if local_site exists
if let Ok(site_view) = SiteView::read_local(pool).await {
return Ok(site_view);
let conn = &mut get_conn(pool).await?;
// Check to see if local_site exists, without the cache wrapper
if select(not(exists(local_site::table.as_query())))
.get_result(conn)
.await?
{
info!("No Local Site found, creating it.");
let domain = settings
.get_hostname_without_port()
.with_lemmy_type(LemmyErrorType::Unknown("must have domain".into()))?;
conn
.run_transaction(|conn| {
async move {
// Upsert this to the instance table
let instance = Instance::read_or_create(&mut conn.into(), domain).await?;
if let Some(setup) = &settings.setup {
let person_keypair = generate_actor_keypair()?;
let person_ap_id = Person::generate_local_actor_url(&setup.admin_username, settings)?;
// Register the user if there's a site setup
let person_form = PersonInsertForm {
ap_id: Some(person_ap_id.clone()),
inbox_url: Some(generate_inbox_url()?),
private_key: Some(person_keypair.private_key.into()),
..PersonInsertForm::new(
setup.admin_username.clone(),
person_keypair.public_key,
instance.id,
)
};
let person_inserted = Person::create(&mut conn.into(), &person_form).await?;
let local_user_form = LocalUserInsertForm {
email: setup.admin_email.clone(),
admin: Some(true),
..LocalUserInsertForm::new(person_inserted.id, Some(setup.admin_password.clone()))
};
LocalUser::create(&mut conn.into(), &local_user_form, vec![]).await?;
};
// Add an entry for the site table
let site_key_pair = generate_actor_keypair()?;
let site_ap_id = Url::parse(&settings.get_protocol_and_hostname())?;
let name = settings
.setup
.clone()
.map(|s| s.site_name)
.unwrap_or_else(|| "New Site".to_string());
let site_form = SiteInsertForm {
ap_id: Some(site_ap_id.clone().into()),
last_refreshed_at: Some(Utc::now()),
inbox_url: Some(generate_inbox_url()?),
private_key: Some(site_key_pair.private_key),
public_key: Some(site_key_pair.public_key),
..SiteInsertForm::new(name, instance.id)
};
let site = Site::create(&mut conn.into(), &site_form).await?;
// Finally create the local_site row
let local_site_form = LocalSiteInsertForm {
site_setup: Some(settings.setup.is_some()),
..LocalSiteInsertForm::new(site.id)
};
let local_site = LocalSite::create(&mut conn.into(), &local_site_form).await?;
// Create the rate limit table
let local_site_rate_limit_form = if cfg!(debug_assertions) {
LocalSiteRateLimitInsertForm {
message: Some(999),
post: Some(999),
register: Some(999),
image: Some(999),
comment: Some(999),
search: Some(999),
..LocalSiteRateLimitInsertForm::new(local_site.id)
}
} else {
LocalSiteRateLimitInsertForm::new(local_site.id)
};
// TODO these have to be set, because the database defaults are too low for the federation
// tests to pass, and there's no way to live update the rate limits without restarting the
// server.
// This can be removed once live rate limits are enabled.
LocalSiteRateLimit::create(&mut conn.into(), &local_site_rate_limit_form).await?;
Ok(())
}
.scope_boxed()
})
.await?;
}
info!("No Local Site found, creating it.");
let domain = settings
.get_hostname_without_port()
.with_lemmy_type(LemmyErrorType::Unknown("must have domain".into()))?;
// Upsert this to the instance table
let instance = Instance::read_or_create(pool, domain).await?;
if let Some(setup) = &settings.setup {
let person_keypair = generate_actor_keypair()?;
let person_ap_id = Person::generate_local_actor_url(&setup.admin_username, settings)?;
// Register the user if there's a site setup
let person_form = PersonInsertForm {
ap_id: Some(person_ap_id.clone()),
inbox_url: Some(generate_inbox_url()?),
private_key: Some(person_keypair.private_key.into()),
..PersonInsertForm::new(
setup.admin_username.clone(),
person_keypair.public_key,
instance.id,
)
};
let person_inserted = Person::create(pool, &person_form).await?;
let local_user_form = LocalUserInsertForm {
email: setup.admin_email.clone(),
admin: Some(true),
..LocalUserInsertForm::new(person_inserted.id, Some(setup.admin_password.clone()))
};
LocalUser::create(pool, &local_user_form, vec![]).await?;
};
// Add an entry for the site table
let site_key_pair = generate_actor_keypair()?;
let site_ap_id = Url::parse(&settings.get_protocol_and_hostname())?;
let name = settings
.setup
.clone()
.map(|s| s.site_name)
.unwrap_or_else(|| "New Site".to_string());
let site_form = SiteInsertForm {
ap_id: Some(site_ap_id.clone().into()),
last_refreshed_at: Some(Utc::now()),
inbox_url: Some(generate_inbox_url()?),
private_key: Some(site_key_pair.private_key),
public_key: Some(site_key_pair.public_key),
..SiteInsertForm::new(name, instance.id)
};
let site = Site::create(pool, &site_form).await?;
// Finally create the local_site row
let local_site_form = LocalSiteInsertForm {
site_setup: Some(settings.setup.is_some()),
..LocalSiteInsertForm::new(site.id)
};
let local_site = LocalSite::create(pool, &local_site_form).await?;
// Create the rate limit table
let local_site_rate_limit_form = if cfg!(debug_assertions) {
LocalSiteRateLimitInsertForm {
message: Some(999),
post: Some(999),
register: Some(999),
image: Some(999),
comment: Some(999),
search: Some(999),
..LocalSiteRateLimitInsertForm::new(local_site.id)
}
} else {
LocalSiteRateLimitInsertForm::new(local_site.id)
};
// TODO these have to be set, because the database defaults are too low for the federation
// tests to pass, and there's no way to live update the rate limits without restarting the
// server.
// This can be removed once live rate limits are enabled.
LocalSiteRateLimit::create(pool, &local_site_rate_limit_form).await?;
SiteView::read_local(pool).await
}