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 lemmy_db_views::structs::SiteView,
14 generate_followers_url,
16 generate_local_apub_endpoint,
17 generate_shared_inbox_url,
18 generate_site_inbox_url,
22 use lemmy_db_schema::{
24 comment::{Comment, CommentUpdateForm},
25 community::{Community, CommunityUpdateForm},
27 local_site::{LocalSite, LocalSiteInsertForm},
28 local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitInsertForm},
29 local_user::{LocalUser, LocalUserInsertForm},
30 person::{Person, PersonInsertForm, PersonUpdateForm},
31 post::{Post, PostUpdateForm},
32 private_message::{PrivateMessage, PrivateMessageUpdateForm},
33 site::{Site, SiteInsertForm, SiteUpdateForm},
36 utils::{get_conn, naive_now, DbPool},
38 use lemmy_utils::{error::LemmyError, settings::structs::Settings};
42 pub async fn run_advanced_migrations(pool: &DbPool, settings: &Settings) -> Result<(), LemmyError> {
43 let protocol_and_hostname = &settings.get_protocol_and_hostname();
44 user_updates_2020_04_02(pool, protocol_and_hostname).await?;
45 community_updates_2020_04_02(pool, protocol_and_hostname).await?;
46 post_updates_2020_04_03(pool, protocol_and_hostname).await?;
47 comment_updates_2020_04_03(pool, protocol_and_hostname).await?;
48 private_message_updates_2020_05_05(pool, protocol_and_hostname).await?;
49 post_thumbnail_url_updates_2020_07_27(pool, protocol_and_hostname).await?;
50 apub_columns_2021_02_02(pool).await?;
51 instance_actor_2022_01_28(pool, protocol_and_hostname).await?;
52 regenerate_public_keys_2022_07_05(pool).await?;
53 initialize_local_site_2022_10_10(pool, settings).await?;
58 async fn user_updates_2020_04_02(
60 protocol_and_hostname: &str,
61 ) -> Result<(), LemmyError> {
62 use lemmy_db_schema::schema::person::dsl::{actor_id, local, person};
63 let conn = &mut get_conn(pool).await?;
65 info!("Running user_updates_2020_04_02");
67 // Update the actor_id, private_key, and public_key, last_refreshed_at
68 let incorrect_persons = person
69 .filter(actor_id.like("http://changeme%"))
70 .filter(local.eq(true))
74 for cperson in &incorrect_persons {
75 let keypair = generate_actor_keypair()?;
77 let form = PersonUpdateForm::builder()
78 .actor_id(Some(generate_local_apub_endpoint(
81 protocol_and_hostname,
83 .private_key(Some(Some(keypair.private_key)))
84 .public_key(Some(keypair.public_key))
85 .last_refreshed_at(Some(naive_now()))
88 Person::update(pool, cperson.id, &form).await?;
91 info!("{} person rows updated.", incorrect_persons.len());
96 async fn community_updates_2020_04_02(
98 protocol_and_hostname: &str,
99 ) -> Result<(), LemmyError> {
100 use lemmy_db_schema::schema::community::dsl::{actor_id, community, local};
101 let conn = &mut get_conn(pool).await?;
103 info!("Running community_updates_2020_04_02");
105 // Update the actor_id, private_key, and public_key, last_refreshed_at
106 let incorrect_communities = community
107 .filter(actor_id.like("http://changeme%"))
108 .filter(local.eq(true))
109 .load::<Community>(conn)
112 for ccommunity in &incorrect_communities {
113 let keypair = generate_actor_keypair()?;
114 let community_actor_id = generate_local_apub_endpoint(
115 EndpointType::Community,
117 protocol_and_hostname,
120 let form = CommunityUpdateForm::builder()
121 .actor_id(Some(community_actor_id.clone()))
122 .private_key(Some(Some(keypair.private_key)))
123 .public_key(Some(keypair.public_key))
124 .last_refreshed_at(Some(naive_now()))
127 Community::update(pool, ccommunity.id, &form).await?;
130 info!("{} community rows updated.", incorrect_communities.len());
135 async fn post_updates_2020_04_03(
137 protocol_and_hostname: &str,
138 ) -> Result<(), LemmyError> {
139 use lemmy_db_schema::schema::post::dsl::{ap_id, local, post};
140 let conn = &mut get_conn(pool).await?;
142 info!("Running post_updates_2020_04_03");
145 let incorrect_posts = post
146 .filter(ap_id.like("http://changeme%"))
147 .filter(local.eq(true))
151 for cpost in &incorrect_posts {
152 let apub_id = generate_local_apub_endpoint(
154 &cpost.id.to_string(),
155 protocol_and_hostname,
160 &PostUpdateForm::builder().ap_id(Some(apub_id)).build(),
165 info!("{} post rows updated.", incorrect_posts.len());
170 async fn comment_updates_2020_04_03(
172 protocol_and_hostname: &str,
173 ) -> Result<(), LemmyError> {
174 use lemmy_db_schema::schema::comment::dsl::{ap_id, comment, local};
175 let conn = &mut get_conn(pool).await?;
177 info!("Running comment_updates_2020_04_03");
180 let incorrect_comments = comment
181 .filter(ap_id.like("http://changeme%"))
182 .filter(local.eq(true))
183 .load::<Comment>(conn)
186 for ccomment in &incorrect_comments {
187 let apub_id = generate_local_apub_endpoint(
188 EndpointType::Comment,
189 &ccomment.id.to_string(),
190 protocol_and_hostname,
195 &CommentUpdateForm::builder().ap_id(Some(apub_id)).build(),
200 info!("{} comment rows updated.", incorrect_comments.len());
205 async fn private_message_updates_2020_05_05(
207 protocol_and_hostname: &str,
208 ) -> Result<(), LemmyError> {
209 use lemmy_db_schema::schema::private_message::dsl::{ap_id, local, private_message};
210 let conn = &mut get_conn(pool).await?;
212 info!("Running private_message_updates_2020_05_05");
215 let incorrect_pms = private_message
216 .filter(ap_id.like("http://changeme%"))
217 .filter(local.eq(true))
218 .load::<PrivateMessage>(conn)
221 for cpm in &incorrect_pms {
222 let apub_id = generate_local_apub_endpoint(
223 EndpointType::PrivateMessage,
225 protocol_and_hostname,
227 PrivateMessage::update(
230 &PrivateMessageUpdateForm::builder()
231 .ap_id(Some(apub_id))
237 info!("{} private message rows updated.", incorrect_pms.len());
242 async fn post_thumbnail_url_updates_2020_07_27(
244 protocol_and_hostname: &str,
245 ) -> Result<(), LemmyError> {
246 use lemmy_db_schema::schema::post::dsl::{post, thumbnail_url};
247 let conn = &mut get_conn(pool).await?;
249 info!("Running post_thumbnail_url_updates_2020_07_27");
251 let domain_prefix = format!("{}/pictrs/image/", protocol_and_hostname,);
253 let incorrect_thumbnails = post.filter(thumbnail_url.not_like("http%"));
255 // Prepend the rows with the update
256 let res = diesel::update(incorrect_thumbnails)
260 .into_sql::<Nullable<Text>>()
261 .concat(thumbnail_url),
264 .get_results::<Post>(conn)
267 info!("{} Post thumbnail_url rows updated.", res.len());
272 /// We are setting inbox and follower URLs for local and remote actors alike, because for now
273 /// all federated instances are also Lemmy and use the same URL scheme.
274 async fn apub_columns_2021_02_02(pool: &DbPool) -> Result<(), LemmyError> {
275 let conn = &mut get_conn(pool).await?;
276 info!("Running apub_columns_2021_02_02");
278 use lemmy_db_schema::schema::person::dsl::{inbox_url, person, shared_inbox_url};
280 .filter(inbox_url.like("http://changeme%"))
281 .load::<Person>(conn)
285 let inbox_url_ = generate_inbox_url(&p.actor_id)?;
286 let shared_inbox_url_ = generate_shared_inbox_url(&p.actor_id)?;
287 diesel::update(person.find(p.id))
289 inbox_url.eq(inbox_url_),
290 shared_inbox_url.eq(shared_inbox_url_),
292 .get_result::<Person>(conn)
298 use lemmy_db_schema::schema::community::dsl::{
304 let communities = community
305 .filter(inbox_url.like("http://changeme%"))
306 .load::<Community>(conn)
309 for c in &communities {
310 let followers_url_ = generate_followers_url(&c.actor_id)?;
311 let inbox_url_ = generate_inbox_url(&c.actor_id)?;
312 let shared_inbox_url_ = generate_shared_inbox_url(&c.actor_id)?;
313 diesel::update(community.find(c.id))
315 followers_url.eq(followers_url_),
316 inbox_url.eq(inbox_url_),
317 shared_inbox_url.eq(shared_inbox_url_),
319 .get_result::<Community>(conn)
327 /// Site object turns into an actor, so that things like instance description can be federated. This
328 /// means we need to add actor columns to the site table, and initialize them with correct values.
329 /// Before this point, there is only a single value in the site table which refers to the local
330 /// Lemmy instance, so thats all we need to update.
331 async fn instance_actor_2022_01_28(
333 protocol_and_hostname: &str,
334 ) -> Result<(), LemmyError> {
335 info!("Running instance_actor_2021_09_29");
336 if let Ok(site_view) = SiteView::read_local(pool).await {
337 let site = site_view.site;
338 // if site already has public key, we dont need to do anything here
339 if !site.public_key.is_empty() {
342 let key_pair = generate_actor_keypair()?;
343 let actor_id = Url::parse(protocol_and_hostname)?;
344 let site_form = SiteUpdateForm::builder()
345 .actor_id(Some(actor_id.clone().into()))
346 .last_refreshed_at(Some(naive_now()))
347 .inbox_url(Some(generate_site_inbox_url(&actor_id.into())?))
348 .private_key(Some(Some(key_pair.private_key)))
349 .public_key(Some(key_pair.public_key))
351 Site::update(pool, site.id, &site_form).await?;
356 /// Fix for bug #2347, which can result in community/person public keys being overwritten with
357 /// empty string when the database value is updated. We go through all actors, and if the public
358 /// key field is empty, generate a new keypair. It would be possible to regenerate only the pubkey,
359 /// but thats more complicated and has no benefit, as federation is already broken for these actors.
360 /// https://github.com/LemmyNet/lemmy/issues/2347
361 async fn regenerate_public_keys_2022_07_05(pool: &DbPool) -> Result<(), LemmyError> {
362 let conn = &mut get_conn(pool).await?;
363 info!("Running regenerate_public_keys_2022_07_05");
366 // update communities with empty pubkey
367 use lemmy_db_schema::schema::community::dsl::{community, local, public_key};
368 let communities: Vec<Community> = community
369 .filter(local.eq(true))
370 .filter(public_key.eq(""))
371 .load::<Community>(conn)
373 for community_ in communities {
375 "local community {} has empty public key field, regenerating key",
378 let key_pair = generate_actor_keypair()?;
379 let form = CommunityUpdateForm::builder()
380 .public_key(Some(key_pair.public_key))
381 .private_key(Some(Some(key_pair.private_key)))
383 Community::update(pool, community_.id, &form).await?;
388 // update persons with empty pubkey
389 use lemmy_db_schema::schema::person::dsl::{local, person, public_key};
391 .filter(local.eq(true))
392 .filter(public_key.eq(""))
393 .load::<Person>(conn)
395 for person_ in persons {
397 "local user {} has empty public key field, regenerating key",
400 let key_pair = generate_actor_keypair()?;
401 let form = PersonUpdateForm::builder()
402 .public_key(Some(key_pair.public_key))
403 .private_key(Some(Some(key_pair.private_key)))
405 Person::update(pool, person_.id, &form).await?;
411 /// This ensures that your local site is initialized and exists.
413 /// If a site already exists, the DB migration should generate a local_site row.
414 /// This will only be run for brand new sites.
415 async fn initialize_local_site_2022_10_10(
418 ) -> Result<(), LemmyError> {
419 info!("Running initialize_local_site_2022_10_10");
421 // Check to see if local_site exists
422 if LocalSite::read(pool).await.is_ok() {
425 info!("No Local Site found, creating it.");
427 let domain = settings
428 .get_hostname_without_port()
429 .expect("must have domain");
431 // Upsert this to the instance table
432 let instance = Instance::create(pool, &domain).await?;
434 if let Some(setup) = &settings.setup {
435 let person_keypair = generate_actor_keypair()?;
436 let person_actor_id = generate_local_apub_endpoint(
437 EndpointType::Person,
438 &setup.admin_username,
439 &settings.get_protocol_and_hostname(),
442 // Register the user if there's a site setup
443 let person_form = PersonInsertForm::builder()
444 .name(setup.admin_username.clone())
446 .instance_id(instance.id)
447 .actor_id(Some(person_actor_id.clone()))
448 .private_key(Some(person_keypair.private_key))
449 .public_key(person_keypair.public_key)
450 .inbox_url(Some(generate_inbox_url(&person_actor_id)?))
451 .shared_inbox_url(Some(generate_shared_inbox_url(&person_actor_id)?))
453 let person_inserted = Person::create(pool, &person_form).await?;
455 let local_user_form = LocalUserInsertForm::builder()
456 .person_id(person_inserted.id)
457 .password_encrypted(setup.admin_password.clone())
458 .email(setup.admin_email.clone())
460 LocalUser::create(pool, &local_user_form).await?;
463 // Add an entry for the site table
464 let site_key_pair = generate_actor_keypair()?;
465 let site_actor_id = Url::parse(&settings.get_protocol_and_hostname())?;
467 let site_form = SiteInsertForm::builder()
472 .map(|s| s.site_name)
473 .unwrap_or_else(|| "New Site".to_string()),
475 .instance_id(instance.id)
476 .actor_id(Some(site_actor_id.clone().into()))
477 .last_refreshed_at(Some(naive_now()))
478 .inbox_url(Some(generate_site_inbox_url(&site_actor_id.into())?))
479 .private_key(Some(site_key_pair.private_key))
480 .public_key(Some(site_key_pair.public_key))
482 let site = Site::create(pool, &site_form).await?;
484 // Finally create the local_site row
485 let local_site_form = LocalSiteInsertForm::builder()
487 .site_setup(Some(settings.setup.is_some()))
489 let local_site = LocalSite::create(pool, &local_site_form).await?;
491 // Create the rate limit table
492 let local_site_rate_limit_form = LocalSiteRateLimitInsertForm::builder()
493 // TODO these have to be set, because the database defaults are too low for the federation
494 // tests to pass, and there's no way to live update the rate limits without restarting the
496 // This can be removed once live rate limits are enabled.
503 .local_site_id(local_site.id)
505 LocalSiteRateLimit::create(pool, &local_site_rate_limit_form).await?;