cfg.ml 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. (*
  2. * _ _ ____ _
  3. * _| || |_/ ___| ___ _ __ _ __ ___ | |
  4. * |_ .. _\___ \ / _ \ '_ \| '_ \ / _ \| |
  5. * |_ _|___) | __/ |_) | |_) | (_) |_|
  6. * |_||_| |____/ \___| .__/| .__/ \___/(_)
  7. * |_| |_|
  8. *
  9. * Personal Social Web.
  10. *
  11. * Copyright (C) The #Seppo contributors. All rights reserved.
  12. *
  13. * This program is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU General Public License as published by
  15. * the Free Software Foundation, either version 3 of the License, or
  16. * (at your option) any later version.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU General Public License
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. *)
  26. let cgi = "seppo.cgi"
  27. let random_pwd () =
  28. (* 12*8 bits of entropy packed into 16 legible characters *)
  29. Random0.random_buffer 12
  30. |> Cstruct.to_string
  31. |> Base64.encode_string ~alphabet:Base64.uri_safe_alphabet
  32. module Base = struct
  33. let fn = "app/etc/baseurl.s"
  34. let to_file fn u : (string,string) result =
  35. File.out_channel fn (fun oc ->
  36. assert (u |> Uri.path |> St.starts_with ~prefix:"/");
  37. assert (u |> Uri.path |> St.ends_with ~suffix:"/");
  38. assert (not (u |> Uri.path |> St.ends_with ~suffix:"//"));
  39. Csexp.Atom (u
  40. |> Uri.to_string)
  41. |> Csexp.to_channel oc;
  42. Ok fn)
  43. let from_file =
  44. File.in_channel' (fun ic ->
  45. match ic |> Csexp.input with
  46. | Error _ as e -> e
  47. | Ok Csexp.Atom b ->
  48. let b = b |> Uri.of_string in
  49. assert (match b |> Uri.scheme with
  50. | Some "http"
  51. | Some "https" -> true
  52. | _ -> false);
  53. assert (b |> Uri.host |> Option.is_some);
  54. assert (b |> Uri.to_string |> St.ends_with ~suffix:"/");
  55. assert (not (b |> Uri.path |> St.ends_with ~suffix:"//"));
  56. assert (b |> Uri.fragment |> Option.is_none);
  57. assert (b |> Uri.query |> List.length = 0);
  58. Ok b
  59. | _ -> Error __LOC__ )
  60. end
  61. module CookieSecret = struct
  62. let fn = "app/var/run/cookie.s"
  63. let l32 = 32
  64. let from_file fn =
  65. (* Logr.debug (fun m -> m "CookieSecret.from_file: %s" fn); *)
  66. let ( let* ) = Result.bind in
  67. File.in_channel fn (fun ic ->
  68. let* l = match Csexp.input ic with
  69. | Error _ as e -> e
  70. | Ok Csexp.Atom s -> Ok s
  71. | _ -> Error "expected string atom" in
  72. let l = l |> Cstruct.of_string in
  73. assert (l32 = (l |> Cstruct.length));
  74. Ok l)
  75. let rule : Make.t = {
  76. target = fn;
  77. prerequisites = [];
  78. fresh = Make.Missing;
  79. command = fun _ _ _ ->
  80. File.out_channel' (fun oc ->
  81. Logr.debug (fun m -> m "Cfg.CookieSecret.rule: %d bytes of entropy, see Mirage_crypto.Chacha20" l32);
  82. Csexp.Atom (Random0.random_buffer l32
  83. |> Cstruct.to_string)
  84. |> Csexp.to_channel oc;
  85. Ok "")
  86. }
  87. let make pre = Make.make ~pre [rule] fn
  88. end
  89. module Profile = struct
  90. type t = {
  91. title : string; (* similar atom:subtitle *)
  92. bio : string; (* similar atom:description *)
  93. language : Rfc4287.rfc4646;
  94. timezone : Timedesc.Time_zone.t;
  95. posts_per_page : int;
  96. }
  97. let validate p : (t, 'a) result =
  98. Ok p
  99. let encode p =
  100. let Rfc4287.Rfc4646 language = p.language in
  101. let tz : string = p.timezone |> Timedesc.Time_zone.name in
  102. let ppp : string = p.posts_per_page |> string_of_int in
  103. Csexp.(List [
  104. List [ Atom "title"; Atom p.title ] ;
  105. List [ Atom "bio"; Atom p.bio ] ;
  106. List [ Atom "language"; Atom language ] ;
  107. List [ Atom "timezone"; Atom tz ] ;
  108. List [ Atom "posts-per-page"; Atom ppp ] ;
  109. ])
  110. let decode = function
  111. | Ok Csexp.(List [
  112. List [ Atom "title"; Atom title ] ;
  113. List [ Atom "bio"; Atom bio ] ;
  114. List [ Atom "language"; Atom language ] ;
  115. List [ Atom "timezone"; Atom timezone ] ;
  116. List [ Atom "posts-per-page"; Atom posts_per_page ] ;
  117. ]) ->
  118. {
  119. title;
  120. bio;
  121. language = Rfc4287.Rfc4646 language;
  122. timezone = Timedesc.Time_zone.(timezone |> make |> Option.value ~default:Rfc3339.fallback);
  123. posts_per_page = posts_per_page |> int_of_string;
  124. }
  125. |> validate
  126. | Ok _ -> Error "profile field expectation failure"
  127. | Error _ as e -> e
  128. let from_file fn =
  129. try fn |> File.in_channel' Csexp.input
  130. |> decode
  131. with
  132. | e ->
  133. Logr.err (fun m -> m "%s %s" __LOC__ (Printexc.to_string e));
  134. Error "failed to load profile from file"
  135. let to_file fn (p : t) =
  136. Logr.debug (fun m -> m "to_file '%s' ('%s')" fn p.title);
  137. File.out_channel fn (fun oc ->
  138. p
  139. |> encode
  140. |> Csexp.to_channel oc;
  141. Ok fn )
  142. let fn = "app/etc/profile.s"
  143. let load
  144. ?(tz = Rfc3339.fallback)
  145. fn : t =
  146. let defa posts_per_page timezone : t =
  147. let language = Rfc4287.Rfc4646 "en"
  148. and title = "Yet Another #Seppo! 🌻"
  149. and bio = {|#Seppo — Personal Social Web. For you!
  150. Hooray! You successfully put the file seppo.cgi from https://Seppo.Social/en/support/#installation on your webspace, visited it and are now enjoying networking in the fediverse!
  151. #Seppo is an https://W3.org/TR/ActivityPub fediverse server software of unsurpassed sustainability and respects the https://permacomputing.net/Principles/. It has a minimal resource and carbon footprint and is built to work for decades without maintenance. Shared webspace is sufficient, no privileged access ('root') required. The seppo.cgi is active only in the moments you are sending and receiving posts. Your casual visitors won't ever use it. They get static files from your webspace. By renting that, your provider cares for security and you may sleep untroubled!
  152. CGIs entered the stage 1997 and drove the dotcom boom. Later on they got a bad name, mostly because they don't scale well to big numbers and can't serve millions of users at a time. Many younger developers are unaware of them. However, you are not a million users, you are just one! A CGI can very well serve one, it even has favourable security properties in this case. And remember, your visitors won't use the CGI.|}
  153. in {title;bio;language;timezone;posts_per_page}
  154. in
  155. match from_file fn with
  156. | Ok p -> p
  157. | Error e ->
  158. Logr.warn (fun m -> m "%s.%s: %s" "Cfg.Profile" "load" e);
  159. defa
  160. 50
  161. tz
  162. let ban = "app/var/lib/me-banner.jpg" |> Make.copy "me-banner.jpg"
  163. let ava = "app/var/lib/me-avatar.jpg" |> Make.copy "me-avatar.jpg"
  164. end
  165. module Urlcleaner = struct
  166. let fn = "app/etc/url-cleaner.s"
  167. type t = {
  168. rex : string;
  169. rep : string;
  170. }
  171. let is_valid v : (t, 'a) result = Ok v
  172. let of_file _fn =
  173. Error "not implemented yet"
  174. let apply' _c _s =
  175. Error "not implemented yet"
  176. let apply _l _s =
  177. Error "not implemented yet"
  178. end