]> Untitled Git - lemmy.git/blob - src/code_migrations.rs
Moving settings to Database. (#2492)
[lemmy.git] / src / code_migrations.rs
1 // This is for db migrations that require code
2 use activitypub_federation::core::signatures::generate_actor_keypair;
3 use diesel::{
4   sql_types::{Nullable, Text},
5   *,
6 };
7 use lemmy_api_common::lemmy_db_views::structs::SiteView;
8 use lemmy_apub::{
9   generate_followers_url,
10   generate_inbox_url,
11   generate_local_apub_endpoint,
12   generate_shared_inbox_url,
13   generate_site_inbox_url,
14   EndpointType,
15 };
16 use lemmy_db_schema::{
17   source::{
18     comment::{Comment, CommentUpdateForm},
19     community::{Community, CommunityUpdateForm},
20     instance::Instance,
21     local_site::{LocalSite, LocalSiteInsertForm},
22     local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitInsertForm},
23     local_user::{LocalUser, LocalUserInsertForm},
24     person::{Person, PersonInsertForm, PersonUpdateForm},
25     post::{Post, PostUpdateForm},
26     private_message::{PrivateMessage, PrivateMessageUpdateForm},
27     site::{Site, SiteInsertForm, SiteUpdateForm},
28   },
29   traits::Crud,
30   utils::naive_now,
31 };
32 use lemmy_utils::{error::LemmyError, settings::structs::Settings};
33 use tracing::info;
34 use url::Url;
35
36 pub fn run_advanced_migrations(
37   conn: &mut PgConnection,
38   settings: &Settings,
39 ) -> Result<(), LemmyError> {
40   let protocol_and_hostname = &settings.get_protocol_and_hostname();
41   user_updates_2020_04_02(conn, protocol_and_hostname)?;
42   community_updates_2020_04_02(conn, protocol_and_hostname)?;
43   post_updates_2020_04_03(conn, protocol_and_hostname)?;
44   comment_updates_2020_04_03(conn, protocol_and_hostname)?;
45   private_message_updates_2020_05_05(conn, protocol_and_hostname)?;
46   post_thumbnail_url_updates_2020_07_27(conn, protocol_and_hostname)?;
47   apub_columns_2021_02_02(conn)?;
48   instance_actor_2022_01_28(conn, protocol_and_hostname)?;
49   regenerate_public_keys_2022_07_05(conn)?;
50   initialize_local_site_2022_10_10(conn, settings)?;
51
52   Ok(())
53 }
54
55 fn user_updates_2020_04_02(
56   conn: &mut PgConnection,
57   protocol_and_hostname: &str,
58 ) -> Result<(), LemmyError> {
59   use lemmy_db_schema::schema::person::dsl::*;
60
61   info!("Running user_updates_2020_04_02");
62
63   // Update the actor_id, private_key, and public_key, last_refreshed_at
64   let incorrect_persons = person
65     .filter(actor_id.like("http://changeme%"))
66     .filter(local.eq(true))
67     .load::<Person>(conn)?;
68
69   for cperson in &incorrect_persons {
70     let keypair = generate_actor_keypair()?;
71
72     let form = PersonUpdateForm::builder()
73       .actor_id(Some(generate_local_apub_endpoint(
74         EndpointType::Person,
75         &cperson.name,
76         protocol_and_hostname,
77       )?))
78       .private_key(Some(Some(keypair.private_key)))
79       .public_key(Some(keypair.public_key))
80       .last_refreshed_at(Some(naive_now()))
81       .build();
82
83     Person::update(conn, cperson.id, &form)?;
84   }
85
86   info!("{} person rows updated.", incorrect_persons.len());
87
88   Ok(())
89 }
90
91 fn community_updates_2020_04_02(
92   conn: &mut PgConnection,
93   protocol_and_hostname: &str,
94 ) -> Result<(), LemmyError> {
95   use lemmy_db_schema::schema::community::dsl::*;
96
97   info!("Running community_updates_2020_04_02");
98
99   // Update the actor_id, private_key, and public_key, last_refreshed_at
100   let incorrect_communities = community
101     .filter(actor_id.like("http://changeme%"))
102     .filter(local.eq(true))
103     .load::<Community>(conn)?;
104
105   for ccommunity in &incorrect_communities {
106     let keypair = generate_actor_keypair()?;
107     let community_actor_id = generate_local_apub_endpoint(
108       EndpointType::Community,
109       &ccommunity.name,
110       protocol_and_hostname,
111     )?;
112
113     let form = CommunityUpdateForm::builder()
114       .actor_id(Some(community_actor_id.to_owned()))
115       .private_key(Some(Some(keypair.private_key)))
116       .public_key(Some(keypair.public_key))
117       .last_refreshed_at(Some(naive_now()))
118       .build();
119
120     Community::update(conn, ccommunity.id, &form)?;
121   }
122
123   info!("{} community rows updated.", incorrect_communities.len());
124
125   Ok(())
126 }
127
128 fn post_updates_2020_04_03(
129   conn: &mut PgConnection,
130   protocol_and_hostname: &str,
131 ) -> Result<(), LemmyError> {
132   use lemmy_db_schema::schema::post::dsl::*;
133
134   info!("Running post_updates_2020_04_03");
135
136   // Update the ap_id
137   let incorrect_posts = post
138     .filter(ap_id.like("http://changeme%"))
139     .filter(local.eq(true))
140     .load::<Post>(conn)?;
141
142   for cpost in &incorrect_posts {
143     let apub_id = generate_local_apub_endpoint(
144       EndpointType::Post,
145       &cpost.id.to_string(),
146       protocol_and_hostname,
147     )?;
148     Post::update(
149       conn,
150       cpost.id,
151       &PostUpdateForm::builder().ap_id(Some(apub_id)).build(),
152     )?;
153   }
154
155   info!("{} post rows updated.", incorrect_posts.len());
156
157   Ok(())
158 }
159
160 fn comment_updates_2020_04_03(
161   conn: &mut PgConnection,
162   protocol_and_hostname: &str,
163 ) -> Result<(), LemmyError> {
164   use lemmy_db_schema::schema::comment::dsl::*;
165
166   info!("Running comment_updates_2020_04_03");
167
168   // Update the ap_id
169   let incorrect_comments = comment
170     .filter(ap_id.like("http://changeme%"))
171     .filter(local.eq(true))
172     .load::<Comment>(conn)?;
173
174   for ccomment in &incorrect_comments {
175     let apub_id = generate_local_apub_endpoint(
176       EndpointType::Comment,
177       &ccomment.id.to_string(),
178       protocol_and_hostname,
179     )?;
180     Comment::update(
181       conn,
182       ccomment.id,
183       &CommentUpdateForm::builder().ap_id(Some(apub_id)).build(),
184     )?;
185   }
186
187   info!("{} comment rows updated.", incorrect_comments.len());
188
189   Ok(())
190 }
191
192 fn private_message_updates_2020_05_05(
193   conn: &mut PgConnection,
194   protocol_and_hostname: &str,
195 ) -> Result<(), LemmyError> {
196   use lemmy_db_schema::schema::private_message::dsl::*;
197
198   info!("Running private_message_updates_2020_05_05");
199
200   // Update the ap_id
201   let incorrect_pms = private_message
202     .filter(ap_id.like("http://changeme%"))
203     .filter(local.eq(true))
204     .load::<PrivateMessage>(conn)?;
205
206   for cpm in &incorrect_pms {
207     let apub_id = generate_local_apub_endpoint(
208       EndpointType::PrivateMessage,
209       &cpm.id.to_string(),
210       protocol_and_hostname,
211     )?;
212     PrivateMessage::update(
213       conn,
214       cpm.id,
215       &PrivateMessageUpdateForm::builder()
216         .ap_id(Some(apub_id))
217         .build(),
218     )?;
219   }
220
221   info!("{} private message rows updated.", incorrect_pms.len());
222
223   Ok(())
224 }
225
226 fn post_thumbnail_url_updates_2020_07_27(
227   conn: &mut PgConnection,
228   protocol_and_hostname: &str,
229 ) -> Result<(), LemmyError> {
230   use lemmy_db_schema::schema::post::dsl::*;
231
232   info!("Running post_thumbnail_url_updates_2020_07_27");
233
234   let domain_prefix = format!("{}/pictrs/image/", protocol_and_hostname,);
235
236   let incorrect_thumbnails = post.filter(thumbnail_url.not_like("http%"));
237
238   // Prepend the rows with the update
239   let res = diesel::update(incorrect_thumbnails)
240     .set(
241       thumbnail_url.eq(
242         domain_prefix
243           .into_sql::<Nullable<Text>>()
244           .concat(thumbnail_url),
245       ),
246     )
247     .get_results::<Post>(conn)?;
248
249   info!("{} Post thumbnail_url rows updated.", res.len());
250
251   Ok(())
252 }
253
254 /// We are setting inbox and follower URLs for local and remote actors alike, because for now
255 /// all federated instances are also Lemmy and use the same URL scheme.
256 fn apub_columns_2021_02_02(conn: &mut PgConnection) -> Result<(), LemmyError> {
257   info!("Running apub_columns_2021_02_02");
258   {
259     use lemmy_db_schema::schema::person::dsl::*;
260     let persons = person
261       .filter(inbox_url.like("http://changeme%"))
262       .load::<Person>(conn)?;
263
264     for p in &persons {
265       let inbox_url_ = generate_inbox_url(&p.actor_id)?;
266       let shared_inbox_url_ = generate_shared_inbox_url(&p.actor_id)?;
267       diesel::update(person.find(p.id))
268         .set((
269           inbox_url.eq(inbox_url_),
270           shared_inbox_url.eq(shared_inbox_url_),
271         ))
272         .get_result::<Person>(conn)?;
273     }
274   }
275
276   {
277     use lemmy_db_schema::schema::community::dsl::*;
278     let communities = community
279       .filter(inbox_url.like("http://changeme%"))
280       .load::<Community>(conn)?;
281
282     for c in &communities {
283       let followers_url_ = generate_followers_url(&c.actor_id)?;
284       let inbox_url_ = generate_inbox_url(&c.actor_id)?;
285       let shared_inbox_url_ = generate_shared_inbox_url(&c.actor_id)?;
286       diesel::update(community.find(c.id))
287         .set((
288           followers_url.eq(followers_url_),
289           inbox_url.eq(inbox_url_),
290           shared_inbox_url.eq(shared_inbox_url_),
291         ))
292         .get_result::<Community>(conn)?;
293     }
294   }
295
296   Ok(())
297 }
298
299 /// Site object turns into an actor, so that things like instance description can be federated. This
300 /// means we need to add actor columns to the site table, and initialize them with correct values.
301 /// Before this point, there is only a single value in the site table which refers to the local
302 /// Lemmy instance, so thats all we need to update.
303 fn instance_actor_2022_01_28(
304   conn: &mut PgConnection,
305   protocol_and_hostname: &str,
306 ) -> Result<(), LemmyError> {
307   info!("Running instance_actor_2021_09_29");
308   if let Ok(site_view) = SiteView::read_local(conn) {
309     let site = site_view.site;
310     // if site already has public key, we dont need to do anything here
311     if !site.public_key.is_empty() {
312       return Ok(());
313     }
314     let key_pair = generate_actor_keypair()?;
315     let actor_id = Url::parse(protocol_and_hostname)?;
316     let site_form = SiteUpdateForm::builder()
317       .actor_id(Some(actor_id.clone().into()))
318       .last_refreshed_at(Some(naive_now()))
319       .inbox_url(Some(generate_site_inbox_url(&actor_id.into())?))
320       .private_key(Some(Some(key_pair.private_key)))
321       .public_key(Some(key_pair.public_key))
322       .build();
323     Site::update(conn, site.id, &site_form)?;
324   }
325   Ok(())
326 }
327
328 /// Fix for bug #2347, which can result in community/person public keys being overwritten with
329 /// empty string when the database value is updated. We go through all actors, and if the public
330 /// key field is empty, generate a new keypair. It would be possible to regenerate only the pubkey,
331 /// but thats more complicated and has no benefit, as federation is already broken for these actors.
332 /// https://github.com/LemmyNet/lemmy/issues/2347
333 fn regenerate_public_keys_2022_07_05(conn: &mut PgConnection) -> Result<(), LemmyError> {
334   info!("Running regenerate_public_keys_2022_07_05");
335
336   {
337     // update communities with empty pubkey
338     use lemmy_db_schema::schema::community::dsl::*;
339     let communities: Vec<Community> = community
340       .filter(local.eq(true))
341       .filter(public_key.eq(""))
342       .load::<Community>(conn)?;
343     for community_ in communities {
344       info!(
345         "local community {} has empty public key field, regenerating key",
346         community_.name
347       );
348       let key_pair = generate_actor_keypair()?;
349       let form = CommunityUpdateForm::builder()
350         .public_key(Some(key_pair.public_key))
351         .private_key(Some(Some(key_pair.private_key)))
352         .build();
353       Community::update(conn, community_.id, &form)?;
354     }
355   }
356
357   {
358     // update persons with empty pubkey
359     use lemmy_db_schema::schema::person::dsl::*;
360     let persons = person
361       .filter(local.eq(true))
362       .filter(public_key.eq(""))
363       .load::<Person>(conn)?;
364     for person_ in persons {
365       info!(
366         "local user {} has empty public key field, regenerating key",
367         person_.name
368       );
369       let key_pair = generate_actor_keypair()?;
370       let form = PersonUpdateForm::builder()
371         .public_key(Some(key_pair.public_key))
372         .private_key(Some(Some(key_pair.private_key)))
373         .build();
374       Person::update(conn, person_.id, &form)?;
375     }
376   }
377   Ok(())
378 }
379
380 /// This ensures that your local site is initialized and exists.
381 ///
382 /// If a site already exists, the DB migration should generate a local_site row.
383 /// This will only be run for brand new sites.
384 fn initialize_local_site_2022_10_10(
385   conn: &mut PgConnection,
386   settings: &Settings,
387 ) -> Result<(), LemmyError> {
388   info!("Running initialize_local_site_2022_10_10");
389
390   // Check to see if local_site exists
391   if LocalSite::read(conn).is_ok() {
392     return Ok(());
393   }
394   info!("No Local Site found, creating it.");
395
396   let domain = settings
397     .get_hostname_without_port()
398     .expect("must have domain");
399
400   // Upsert this to the instance table
401   let instance = Instance::create(conn, &domain)?;
402
403   if let Some(setup) = &settings.setup {
404     let person_keypair = generate_actor_keypair()?;
405     let person_actor_id = generate_local_apub_endpoint(
406       EndpointType::Person,
407       &setup.admin_username,
408       &settings.get_protocol_and_hostname(),
409     )?;
410
411     // Register the user if there's a site setup
412     let person_form = PersonInsertForm::builder()
413       .name(setup.admin_username.to_owned())
414       .admin(Some(true))
415       .instance_id(instance.id)
416       .actor_id(Some(person_actor_id.clone()))
417       .private_key(Some(person_keypair.private_key))
418       .public_key(person_keypair.public_key)
419       .inbox_url(Some(generate_inbox_url(&person_actor_id)?))
420       .shared_inbox_url(Some(generate_shared_inbox_url(&person_actor_id)?))
421       .build();
422     let person_inserted = Person::create(conn, &person_form)?;
423
424     let local_user_form = LocalUserInsertForm::builder()
425       .person_id(person_inserted.id)
426       .password_encrypted(setup.admin_password.to_owned())
427       .email(setup.admin_email.to_owned())
428       .build();
429     LocalUser::create(conn, &local_user_form)?;
430   };
431
432   // Add an entry for the site table
433   let site_key_pair = generate_actor_keypair()?;
434   let site_actor_id = Url::parse(&settings.get_protocol_and_hostname())?;
435
436   let site_form = SiteInsertForm::builder()
437     .name(
438       settings
439         .setup
440         .to_owned()
441         .map(|s| s.site_name)
442         .unwrap_or_else(|| "New Site".to_string()),
443     )
444     .instance_id(instance.id)
445     .actor_id(Some(site_actor_id.clone().into()))
446     .last_refreshed_at(Some(naive_now()))
447     .inbox_url(Some(generate_site_inbox_url(&site_actor_id.into())?))
448     .private_key(Some(site_key_pair.private_key))
449     .public_key(Some(site_key_pair.public_key))
450     .build();
451   let site = Site::create(conn, &site_form)?;
452
453   // Finally create the local_site row
454   let local_site_form = LocalSiteInsertForm::builder()
455     .site_id(site.id)
456     .site_setup(Some(settings.setup.is_some()))
457     .build();
458   let local_site = LocalSite::create(conn, &local_site_form)?;
459
460   // Create the rate limit table
461   let local_site_rate_limit_form = LocalSiteRateLimitInsertForm::builder()
462     // TODO these have to be set, because the database defaults are too low for the federation
463     // tests to pass, and there's no way to live update the rate limits without restarting the
464     // server.
465     // This can be removed once live rate limits are enabled.
466     .message(Some(999))
467     .post(Some(999))
468     .register(Some(999))
469     .image(Some(999))
470     .comment(Some(999))
471     .search(Some(999))
472     .local_site_id(local_site.id)
473     .build();
474   LocalSiteRateLimit::create(conn, &local_site_rate_limit_form)?;
475
476   Ok(())
477 }