1 // This is for db migrations that require code
2 use activitypub_federation::core::signatures::generate_actor_keypair;
4 sql_types::{Nullable, Text},
10 use diesel_async::RunQueryDsl;
11 use lemmy_api_common::{
12 generate_followers_url,
14 generate_local_apub_endpoint,
15 generate_shared_inbox_url,
16 generate_site_inbox_url,
17 lemmy_db_views::structs::SiteView,
20 use lemmy_db_schema::{
22 comment::{Comment, CommentUpdateForm},
23 community::{Community, CommunityUpdateForm},
25 local_site::{LocalSite, LocalSiteInsertForm},
26 local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitInsertForm},
27 local_user::{LocalUser, LocalUserInsertForm},
28 person::{Person, PersonInsertForm, PersonUpdateForm},
29 post::{Post, PostUpdateForm},
30 private_message::{PrivateMessage, PrivateMessageUpdateForm},
31 site::{Site, SiteInsertForm, SiteUpdateForm},
34 utils::{get_conn, naive_now, DbPool},
36 use lemmy_utils::{error::LemmyError, settings::structs::Settings};
40 pub async fn run_advanced_migrations(pool: &DbPool, settings: &Settings) -> Result<(), LemmyError> {
41 let protocol_and_hostname = &settings.get_protocol_and_hostname();
42 user_updates_2020_04_02(pool, protocol_and_hostname).await?;
43 community_updates_2020_04_02(pool, protocol_and_hostname).await?;
44 post_updates_2020_04_03(pool, protocol_and_hostname).await?;
45 comment_updates_2020_04_03(pool, protocol_and_hostname).await?;
46 private_message_updates_2020_05_05(pool, protocol_and_hostname).await?;
47 post_thumbnail_url_updates_2020_07_27(pool, protocol_and_hostname).await?;
48 apub_columns_2021_02_02(pool).await?;
49 instance_actor_2022_01_28(pool, protocol_and_hostname).await?;
50 regenerate_public_keys_2022_07_05(pool).await?;
51 initialize_local_site_2022_10_10(pool, settings).await?;
56 async fn user_updates_2020_04_02(
58 protocol_and_hostname: &str,
59 ) -> Result<(), LemmyError> {
60 use lemmy_db_schema::schema::person::dsl::{actor_id, local, person};
61 let conn = &mut get_conn(pool).await?;
63 info!("Running user_updates_2020_04_02");
65 // Update the actor_id, private_key, and public_key, last_refreshed_at
66 let incorrect_persons = person
67 .filter(actor_id.like("http://changeme%"))
68 .filter(local.eq(true))
72 for cperson in &incorrect_persons {
73 let keypair = generate_actor_keypair()?;
75 let form = PersonUpdateForm::builder()
76 .actor_id(Some(generate_local_apub_endpoint(
79 protocol_and_hostname,
81 .private_key(Some(Some(keypair.private_key)))
82 .public_key(Some(keypair.public_key))
83 .last_refreshed_at(Some(naive_now()))
86 Person::update(pool, cperson.id, &form).await?;
89 info!("{} person rows updated.", incorrect_persons.len());
94 async fn community_updates_2020_04_02(
96 protocol_and_hostname: &str,
97 ) -> Result<(), LemmyError> {
98 use lemmy_db_schema::schema::community::dsl::{actor_id, community, local};
99 let conn = &mut get_conn(pool).await?;
101 info!("Running community_updates_2020_04_02");
103 // Update the actor_id, private_key, and public_key, last_refreshed_at
104 let incorrect_communities = community
105 .filter(actor_id.like("http://changeme%"))
106 .filter(local.eq(true))
107 .load::<Community>(conn)
110 for ccommunity in &incorrect_communities {
111 let keypair = generate_actor_keypair()?;
112 let community_actor_id = generate_local_apub_endpoint(
113 EndpointType::Community,
115 protocol_and_hostname,
118 let form = CommunityUpdateForm::builder()
119 .actor_id(Some(community_actor_id.clone()))
120 .private_key(Some(Some(keypair.private_key)))
121 .public_key(Some(keypair.public_key))
122 .last_refreshed_at(Some(naive_now()))
125 Community::update(pool, ccommunity.id, &form).await?;
128 info!("{} community rows updated.", incorrect_communities.len());
133 async fn post_updates_2020_04_03(
135 protocol_and_hostname: &str,
136 ) -> Result<(), LemmyError> {
137 use lemmy_db_schema::schema::post::dsl::{ap_id, local, post};
138 let conn = &mut get_conn(pool).await?;
140 info!("Running post_updates_2020_04_03");
143 let incorrect_posts = post
144 .filter(ap_id.like("http://changeme%"))
145 .filter(local.eq(true))
149 for cpost in &incorrect_posts {
150 let apub_id = generate_local_apub_endpoint(
152 &cpost.id.to_string(),
153 protocol_and_hostname,
158 &PostUpdateForm::builder().ap_id(Some(apub_id)).build(),
163 info!("{} post rows updated.", incorrect_posts.len());
168 async fn comment_updates_2020_04_03(
170 protocol_and_hostname: &str,
171 ) -> Result<(), LemmyError> {
172 use lemmy_db_schema::schema::comment::dsl::{ap_id, comment, local};
173 let conn = &mut get_conn(pool).await?;
175 info!("Running comment_updates_2020_04_03");
178 let incorrect_comments = comment
179 .filter(ap_id.like("http://changeme%"))
180 .filter(local.eq(true))
181 .load::<Comment>(conn)
184 for ccomment in &incorrect_comments {
185 let apub_id = generate_local_apub_endpoint(
186 EndpointType::Comment,
187 &ccomment.id.to_string(),
188 protocol_and_hostname,
193 &CommentUpdateForm::builder().ap_id(Some(apub_id)).build(),
198 info!("{} comment rows updated.", incorrect_comments.len());
203 async fn private_message_updates_2020_05_05(
205 protocol_and_hostname: &str,
206 ) -> Result<(), LemmyError> {
207 use lemmy_db_schema::schema::private_message::dsl::{ap_id, local, private_message};
208 let conn = &mut get_conn(pool).await?;
210 info!("Running private_message_updates_2020_05_05");
213 let incorrect_pms = private_message
214 .filter(ap_id.like("http://changeme%"))
215 .filter(local.eq(true))
216 .load::<PrivateMessage>(conn)
219 for cpm in &incorrect_pms {
220 let apub_id = generate_local_apub_endpoint(
221 EndpointType::PrivateMessage,
223 protocol_and_hostname,
225 PrivateMessage::update(
228 &PrivateMessageUpdateForm::builder()
229 .ap_id(Some(apub_id))
235 info!("{} private message rows updated.", incorrect_pms.len());
240 async fn post_thumbnail_url_updates_2020_07_27(
242 protocol_and_hostname: &str,
243 ) -> Result<(), LemmyError> {
244 use lemmy_db_schema::schema::post::dsl::{post, thumbnail_url};
245 let conn = &mut get_conn(pool).await?;
247 info!("Running post_thumbnail_url_updates_2020_07_27");
249 let domain_prefix = format!("{}/pictrs/image/", protocol_and_hostname,);
251 let incorrect_thumbnails = post.filter(thumbnail_url.not_like("http%"));
253 // Prepend the rows with the update
254 let res = diesel::update(incorrect_thumbnails)
258 .into_sql::<Nullable<Text>>()
259 .concat(thumbnail_url),
262 .get_results::<Post>(conn)
265 info!("{} Post thumbnail_url rows updated.", res.len());
270 /// We are setting inbox and follower URLs for local and remote actors alike, because for now
271 /// all federated instances are also Lemmy and use the same URL scheme.
272 async fn apub_columns_2021_02_02(pool: &DbPool) -> Result<(), LemmyError> {
273 let conn = &mut get_conn(pool).await?;
274 info!("Running apub_columns_2021_02_02");
276 use lemmy_db_schema::schema::person::dsl::{inbox_url, person, shared_inbox_url};
278 .filter(inbox_url.like("http://changeme%"))
279 .load::<Person>(conn)
283 let inbox_url_ = generate_inbox_url(&p.actor_id)?;
284 let shared_inbox_url_ = generate_shared_inbox_url(&p.actor_id)?;
285 diesel::update(person.find(p.id))
287 inbox_url.eq(inbox_url_),
288 shared_inbox_url.eq(shared_inbox_url_),
290 .get_result::<Person>(conn)
296 use lemmy_db_schema::schema::community::dsl::{
302 let communities = community
303 .filter(inbox_url.like("http://changeme%"))
304 .load::<Community>(conn)
307 for c in &communities {
308 let followers_url_ = generate_followers_url(&c.actor_id)?;
309 let inbox_url_ = generate_inbox_url(&c.actor_id)?;
310 let shared_inbox_url_ = generate_shared_inbox_url(&c.actor_id)?;
311 diesel::update(community.find(c.id))
313 followers_url.eq(followers_url_),
314 inbox_url.eq(inbox_url_),
315 shared_inbox_url.eq(shared_inbox_url_),
317 .get_result::<Community>(conn)
325 /// Site object turns into an actor, so that things like instance description can be federated. This
326 /// means we need to add actor columns to the site table, and initialize them with correct values.
327 /// Before this point, there is only a single value in the site table which refers to the local
328 /// Lemmy instance, so thats all we need to update.
329 async fn instance_actor_2022_01_28(
331 protocol_and_hostname: &str,
332 ) -> Result<(), LemmyError> {
333 info!("Running instance_actor_2021_09_29");
334 if let Ok(site_view) = SiteView::read_local(pool).await {
335 let site = site_view.site;
336 // if site already has public key, we dont need to do anything here
337 if !site.public_key.is_empty() {
340 let key_pair = generate_actor_keypair()?;
341 let actor_id = Url::parse(protocol_and_hostname)?;
342 let site_form = SiteUpdateForm::builder()
343 .actor_id(Some(actor_id.clone().into()))
344 .last_refreshed_at(Some(naive_now()))
345 .inbox_url(Some(generate_site_inbox_url(&actor_id.into())?))
346 .private_key(Some(Some(key_pair.private_key)))
347 .public_key(Some(key_pair.public_key))
349 Site::update(pool, site.id, &site_form).await?;
354 /// Fix for bug #2347, which can result in community/person public keys being overwritten with
355 /// empty string when the database value is updated. We go through all actors, and if the public
356 /// key field is empty, generate a new keypair. It would be possible to regenerate only the pubkey,
357 /// but thats more complicated and has no benefit, as federation is already broken for these actors.
358 /// https://github.com/LemmyNet/lemmy/issues/2347
359 async fn regenerate_public_keys_2022_07_05(pool: &DbPool) -> Result<(), LemmyError> {
360 let conn = &mut get_conn(pool).await?;
361 info!("Running regenerate_public_keys_2022_07_05");
364 // update communities with empty pubkey
365 use lemmy_db_schema::schema::community::dsl::{community, local, public_key};
366 let communities: Vec<Community> = community
367 .filter(local.eq(true))
368 .filter(public_key.eq(""))
369 .load::<Community>(conn)
371 for community_ in communities {
373 "local community {} has empty public key field, regenerating key",
376 let key_pair = generate_actor_keypair()?;
377 let form = CommunityUpdateForm::builder()
378 .public_key(Some(key_pair.public_key))
379 .private_key(Some(Some(key_pair.private_key)))
381 Community::update(pool, community_.id, &form).await?;
386 // update persons with empty pubkey
387 use lemmy_db_schema::schema::person::dsl::{local, person, public_key};
389 .filter(local.eq(true))
390 .filter(public_key.eq(""))
391 .load::<Person>(conn)
393 for person_ in persons {
395 "local user {} has empty public key field, regenerating key",
398 let key_pair = generate_actor_keypair()?;
399 let form = PersonUpdateForm::builder()
400 .public_key(Some(key_pair.public_key))
401 .private_key(Some(Some(key_pair.private_key)))
403 Person::update(pool, person_.id, &form).await?;
409 /// This ensures that your local site is initialized and exists.
411 /// If a site already exists, the DB migration should generate a local_site row.
412 /// This will only be run for brand new sites.
413 async fn initialize_local_site_2022_10_10(
416 ) -> Result<(), LemmyError> {
417 info!("Running initialize_local_site_2022_10_10");
419 // Check to see if local_site exists
420 if LocalSite::read(pool).await.is_ok() {
423 info!("No Local Site found, creating it.");
425 let domain = settings
426 .get_hostname_without_port()
427 .expect("must have domain");
429 // Upsert this to the instance table
430 let instance = Instance::create(pool, &domain).await?;
432 if let Some(setup) = &settings.setup {
433 let person_keypair = generate_actor_keypair()?;
434 let person_actor_id = generate_local_apub_endpoint(
435 EndpointType::Person,
436 &setup.admin_username,
437 &settings.get_protocol_and_hostname(),
440 // Register the user if there's a site setup
441 let person_form = PersonInsertForm::builder()
442 .name(setup.admin_username.clone())
444 .instance_id(instance.id)
445 .actor_id(Some(person_actor_id.clone()))
446 .private_key(Some(person_keypair.private_key))
447 .public_key(person_keypair.public_key)
448 .inbox_url(Some(generate_inbox_url(&person_actor_id)?))
449 .shared_inbox_url(Some(generate_shared_inbox_url(&person_actor_id)?))
451 let person_inserted = Person::create(pool, &person_form).await?;
453 let local_user_form = LocalUserInsertForm::builder()
454 .person_id(person_inserted.id)
455 .password_encrypted(setup.admin_password.clone())
456 .email(setup.admin_email.clone())
458 LocalUser::create(pool, &local_user_form).await?;
461 // Add an entry for the site table
462 let site_key_pair = generate_actor_keypair()?;
463 let site_actor_id = Url::parse(&settings.get_protocol_and_hostname())?;
465 let site_form = SiteInsertForm::builder()
470 .map(|s| s.site_name)
471 .unwrap_or_else(|| "New Site".to_string()),
473 .instance_id(instance.id)
474 .actor_id(Some(site_actor_id.clone().into()))
475 .last_refreshed_at(Some(naive_now()))
476 .inbox_url(Some(generate_site_inbox_url(&site_actor_id.into())?))
477 .private_key(Some(site_key_pair.private_key))
478 .public_key(Some(site_key_pair.public_key))
480 let site = Site::create(pool, &site_form).await?;
482 // Finally create the local_site row
483 let local_site_form = LocalSiteInsertForm::builder()
485 .site_setup(Some(settings.setup.is_some()))
487 let local_site = LocalSite::create(pool, &local_site_form).await?;
489 // Create the rate limit table
490 let local_site_rate_limit_form = LocalSiteRateLimitInsertForm::builder()
491 // TODO these have to be set, because the database defaults are too low for the federation
492 // tests to pass, and there's no way to live update the rate limits without restarting the
494 // This can be removed once live rate limits are enabled.
501 .local_site_id(local_site.id)
503 LocalSiteRateLimit::create(pool, &local_site_rate_limit_form).await?;