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::lemmy_db_views::structs::SiteView;
13 generate_followers_url,
15 generate_local_apub_endpoint,
16 generate_shared_inbox_url,
17 generate_site_inbox_url,
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::*;
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::*;
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.to_owned()))
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::*;
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::*;
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::*;
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::*;
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::*;
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::*;
297 let communities = community
298 .filter(inbox_url.like("http://changeme%"))
299 .load::<Community>(conn)
302 for c in &communities {
303 let followers_url_ = generate_followers_url(&c.actor_id)?;
304 let inbox_url_ = generate_inbox_url(&c.actor_id)?;
305 let shared_inbox_url_ = generate_shared_inbox_url(&c.actor_id)?;
306 diesel::update(community.find(c.id))
308 followers_url.eq(followers_url_),
309 inbox_url.eq(inbox_url_),
310 shared_inbox_url.eq(shared_inbox_url_),
312 .get_result::<Community>(conn)
320 /// Site object turns into an actor, so that things like instance description can be federated. This
321 /// means we need to add actor columns to the site table, and initialize them with correct values.
322 /// Before this point, there is only a single value in the site table which refers to the local
323 /// Lemmy instance, so thats all we need to update.
324 async fn instance_actor_2022_01_28(
326 protocol_and_hostname: &str,
327 ) -> Result<(), LemmyError> {
328 info!("Running instance_actor_2021_09_29");
329 if let Ok(site_view) = SiteView::read_local(pool).await {
330 let site = site_view.site;
331 // if site already has public key, we dont need to do anything here
332 if !site.public_key.is_empty() {
335 let key_pair = generate_actor_keypair()?;
336 let actor_id = Url::parse(protocol_and_hostname)?;
337 let site_form = SiteUpdateForm::builder()
338 .actor_id(Some(actor_id.clone().into()))
339 .last_refreshed_at(Some(naive_now()))
340 .inbox_url(Some(generate_site_inbox_url(&actor_id.into())?))
341 .private_key(Some(Some(key_pair.private_key)))
342 .public_key(Some(key_pair.public_key))
344 Site::update(pool, site.id, &site_form).await?;
349 /// Fix for bug #2347, which can result in community/person public keys being overwritten with
350 /// empty string when the database value is updated. We go through all actors, and if the public
351 /// key field is empty, generate a new keypair. It would be possible to regenerate only the pubkey,
352 /// but thats more complicated and has no benefit, as federation is already broken for these actors.
353 /// https://github.com/LemmyNet/lemmy/issues/2347
354 async fn regenerate_public_keys_2022_07_05(pool: &DbPool) -> Result<(), LemmyError> {
355 let conn = &mut get_conn(pool).await?;
356 info!("Running regenerate_public_keys_2022_07_05");
359 // update communities with empty pubkey
360 use lemmy_db_schema::schema::community::dsl::*;
361 let communities: Vec<Community> = community
362 .filter(local.eq(true))
363 .filter(public_key.eq(""))
364 .load::<Community>(conn)
366 for community_ in communities {
368 "local community {} has empty public key field, regenerating key",
371 let key_pair = generate_actor_keypair()?;
372 let form = CommunityUpdateForm::builder()
373 .public_key(Some(key_pair.public_key))
374 .private_key(Some(Some(key_pair.private_key)))
376 Community::update(pool, community_.id, &form).await?;
381 // update persons with empty pubkey
382 use lemmy_db_schema::schema::person::dsl::*;
384 .filter(local.eq(true))
385 .filter(public_key.eq(""))
386 .load::<Person>(conn)
388 for person_ in persons {
390 "local user {} has empty public key field, regenerating key",
393 let key_pair = generate_actor_keypair()?;
394 let form = PersonUpdateForm::builder()
395 .public_key(Some(key_pair.public_key))
396 .private_key(Some(Some(key_pair.private_key)))
398 Person::update(pool, person_.id, &form).await?;
404 /// This ensures that your local site is initialized and exists.
406 /// If a site already exists, the DB migration should generate a local_site row.
407 /// This will only be run for brand new sites.
408 async fn initialize_local_site_2022_10_10(
411 ) -> Result<(), LemmyError> {
412 info!("Running initialize_local_site_2022_10_10");
414 // Check to see if local_site exists
415 if LocalSite::read(pool).await.is_ok() {
418 info!("No Local Site found, creating it.");
420 let domain = settings
421 .get_hostname_without_port()
422 .expect("must have domain");
424 // Upsert this to the instance table
425 let instance = Instance::create(pool, &domain).await?;
427 if let Some(setup) = &settings.setup {
428 let person_keypair = generate_actor_keypair()?;
429 let person_actor_id = generate_local_apub_endpoint(
430 EndpointType::Person,
431 &setup.admin_username,
432 &settings.get_protocol_and_hostname(),
435 // Register the user if there's a site setup
436 let person_form = PersonInsertForm::builder()
437 .name(setup.admin_username.to_owned())
439 .instance_id(instance.id)
440 .actor_id(Some(person_actor_id.clone()))
441 .private_key(Some(person_keypair.private_key))
442 .public_key(person_keypair.public_key)
443 .inbox_url(Some(generate_inbox_url(&person_actor_id)?))
444 .shared_inbox_url(Some(generate_shared_inbox_url(&person_actor_id)?))
446 let person_inserted = Person::create(pool, &person_form).await?;
448 let local_user_form = LocalUserInsertForm::builder()
449 .person_id(person_inserted.id)
450 .password_encrypted(setup.admin_password.to_owned())
451 .email(setup.admin_email.to_owned())
453 LocalUser::create(pool, &local_user_form).await?;
456 // Add an entry for the site table
457 let site_key_pair = generate_actor_keypair()?;
458 let site_actor_id = Url::parse(&settings.get_protocol_and_hostname())?;
460 let site_form = SiteInsertForm::builder()
465 .map(|s| s.site_name)
466 .unwrap_or_else(|| "New Site".to_string()),
468 .instance_id(instance.id)
469 .actor_id(Some(site_actor_id.clone().into()))
470 .last_refreshed_at(Some(naive_now()))
471 .inbox_url(Some(generate_site_inbox_url(&site_actor_id.into())?))
472 .private_key(Some(site_key_pair.private_key))
473 .public_key(Some(site_key_pair.public_key))
475 let site = Site::create(pool, &site_form).await?;
477 // Finally create the local_site row
478 let local_site_form = LocalSiteInsertForm::builder()
480 .site_setup(Some(settings.setup.is_some()))
482 let local_site = LocalSite::create(pool, &local_site_form).await?;
484 // Create the rate limit table
485 let local_site_rate_limit_form = LocalSiteRateLimitInsertForm::builder()
486 // TODO these have to be set, because the database defaults are too low for the federation
487 // tests to pass, and there's no way to live update the rate limits without restarting the
489 // This can be removed once live rate limits are enabled.
496 .local_site_id(local_site.id)
498 LocalSiteRateLimit::create(pool, &local_site_rate_limit_form).await?;