Skip to content

Commit 3cc2533

Browse files
authored
Merge pull request #368 from pfitaxel/fix-static-deploy
Fix & Document static deployment CLI support
2 parents 28eedb3 + 73f56f0 commit 3cc2533

13 files changed

+132
-34
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
How to deploy learn-ocaml statically
2+
====================================
3+
4+
This section explains how to deploy a static version of learn-ocaml on
5+
an HTTP server.
6+
7+
## Using pre-built docker images
8+
9+
You will just need to:
10+
11+
- install Docker Engine (<https://docs.docker.com/get-docker/>)
12+
- build the `www` folder by using the commands below
13+
- use an HTTP server to serve learn-ocaml statically.
14+
15+
Assuming your exercise repository is in directory `$REPOSITORY` and
16+
you want to generate the website contents in directory `$TARGET/www`
17+
to serve it at `$URL` (the base URL **without trailing slash**), then
18+
you can run:
19+
20+
```bash
21+
# Remove old version
22+
cd "$TARGET"
23+
rm -fr www
24+
# Get the last (dev) version of learn-ocaml
25+
sudo docker pull ocamlsf/learn-ocaml:master
26+
# Build the site within a Docker container
27+
sudo docker run --rm -i --entrypoint="" \
28+
-v "$REPOSITORY:/repository" -v "$TARGET:/home/learn-ocaml/target" \
29+
ocamlsf/learn-ocaml:master \
30+
sh -c "learn-ocaml build --repo=/repository --base-url $URL && mv www target/"
31+
```
32+
33+
Regarding the `--base-url` option, if you plan to deploy the `www`
34+
directory with **GitHub Pages**, assuming the underlying GitHub repo
35+
(either public or private) is `https://github.com/user-name/repo-name`
36+
then you should first run:
37+
38+
```bash
39+
export URL=https://user-name.github.io/repo-name
40+
```
41+
42+
For a comprehensive example of one such deployment, you may take a
43+
look at the following repository:
44+
- <https://github.com/pfitaxel/pfitaxel-demo>
45+
- deployed to <https://pfitaxel.github.io/pfitaxel-demo>
46+
- thanks to this [`deploy` script](https://github.com/pfitaxel/pfitaxel-demo/blob/master/deploy).
47+
48+
## Manual compilation
49+
50+
Note: you need a working `opam` environment (version 2.0+) as well as
51+
an `opam switch` and a compiled version of `learn-ocaml` (see
52+
[How to deploy a learn-ocaml instance](howto-deploy-a-learn-ocaml-instance.md#manual-compilation)
53+
for details).
54+
55+
Assuming your exercise repository is in directory `$REPOSITORY` and
56+
you want to generate the website contents in directory `$TARGET/www`
57+
to serve it at `$URL` (the base URL **without trailing slash**), then
58+
you can run:
59+
60+
```bash
61+
rm -fr "$TARGET/www"
62+
cd .../learn-ocaml # go to the learn-ocaml git repo
63+
learn-ocaml build --repo=$REPOSITORY --base-url $URL
64+
mv www "$TARGET/www"
65+
```

src/app/learnocaml_config.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ class type learnocaml_config = object
1313
method enablePlayground: bool Js.optdef_prop
1414
method txtLoginWelcome: Js.js_string Js.t Js.optdef_prop
1515
method txtNickname: Js.js_string Js.t Js.optdef_prop
16-
method root: Js.js_string Js.t Js.optdef_prop
16+
method baseUrl: Js.js_string Js.t Js.optdef_prop
1717
end
1818

1919
let config : learnocaml_config Js.t = Js.Unsafe.js_expr "learnocaml_config"
20-
let api_server = Js.(to_string (Optdef.get config##.root (fun () -> string "")))
20+
let api_server = Js.(to_string (Optdef.get config##.baseUrl (fun () -> string "")))

src/app/learnocaml_config.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class type learnocaml_config = object
1717
method enablePlayground: bool Js.optdef_prop
1818
method txtLoginWelcome: Js.js_string Js.t Js.optdef_prop
1919
method txtNickname: Js.js_string Js.t Js.optdef_prop
20-
method root: Js.js_string Js.t Js.optdef_prop
20+
method baseUrl: Js.js_string Js.t Js.optdef_prop
2121
end
2222

2323
val config : learnocaml_config Js.t

src/main/learnocaml_main.ml

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ module Args = struct
4747
"Directory where the app should be generated for the $(i,build) command, \
4848
and from where it is served by the $(i,serve) command."
4949

50+
let base_url =
51+
value & opt string "" &
52+
info ["base-url"] ~docv:"BASE_URL" ~env:(Arg.env_var "LEARNOCAML_BASE_URL") ~doc:
53+
"Set the base URL of the website. \
54+
Should not end with a trailing slash. \
55+
Currently, this has no effect on the backend - '$(b,learn-ocaml serve)'. \
56+
Mandatory for '$(b,learn-ocaml build)' if the site is not hosted in path '/', \
57+
which typically occurs for static deployment."
58+
5059
module Grader = struct
5160
let info = info ~docs:"GRADER OPTIONS"
5261

@@ -180,27 +189,22 @@ module Args = struct
180189
value & opt int 1 & info ["jobs";"j"] ~docv:"INT" ~doc:
181190
"Number of building jobs to run in parallel"
182191

183-
let root =
184-
value & opt string "" & info ["root"] ~docv:"ROOT" ~doc:
185-
"Set the root of all documents. Use only for static deployment.\
186-
Should not end with a trailing slash."
187-
188192
type t = {
189193
contents_dir: string;
190194
try_ocaml: bool option;
191195
lessons: bool option;
192196
exercises: bool option;
193197
playground: bool option;
194198
toplevel: bool option;
195-
root: string
199+
base_url: string
196200
}
197201

198202
let builder_conf =
199203
let apply
200-
contents_dir try_ocaml lessons exercises playground toplevel root
201-
= { contents_dir; try_ocaml; lessons; exercises; playground; toplevel; root }
204+
contents_dir try_ocaml lessons exercises playground toplevel base_url
205+
= { contents_dir; try_ocaml; lessons; exercises; playground; toplevel; base_url }
202206
in
203-
Term.(const apply $contents_dir $try_ocaml $lessons $exercises $playground $toplevel $root)
207+
Term.(const apply $contents_dir $try_ocaml $lessons $exercises $playground $toplevel $base_url)
204208

205209
let repo_conf =
206210
let apply repo_dir exercises_filtered jobs =
@@ -241,16 +245,16 @@ module Args = struct
241245
{ commands; app_dir; repo_dir; grader; builder; server }
242246
in
243247
Term.(const apply $commands $app_dir $repo_dir
244-
$Grader.term $Builder.term $Server.term app_dir)
248+
$Grader.term $Builder.term $Server.term app_dir base_url)
245249
end
246250

247251
open Args
248252

249-
let process_html_file orig_file dest_file root =
253+
let process_html_file orig_file dest_file base_url =
250254
let transform_tag e tag attrs attr =
251255
let attr_pair = ("", attr) in
252256
match List.assoc_opt attr_pair attrs with
253-
| Some url -> `Start_element ((e, tag), (attr_pair, root ^ url) :: (List.remove_assoc attr_pair attrs))
257+
| Some url -> `Start_element ((e, tag), (attr_pair, base_url ^ url) :: (List.remove_assoc attr_pair attrs))
254258
| None -> `Start_element ((e, tag), attrs) in
255259
Lwt_io.open_file ~mode:Lwt_io.Input orig_file >>= fun ofile ->
256260
Lwt_io.open_file ~mode:Lwt_io.Output dest_file >>= fun wfile ->
@@ -322,11 +326,13 @@ let main o =
322326
let json_config = ServerData.build_config preconfig in
323327
Learnocaml_store.write_to_file ServerData.config_enc json_config www_server_config
324328
>>= fun () ->
329+
if o.builder.Builder.base_url <> "" then
330+
Printf.printf "Base URL: %s\n%!" o.builder.Builder.base_url;
325331
Lwt_unix.files_of_directory o.builder.Builder.contents_dir
326332
|> Lwt_stream.iter_s (fun file ->
327333
if Filename.extension file = ".html" then
328334
process_html_file (o.builder.Builder.contents_dir/file)
329-
(o.app_dir/file) o.builder.Builder.root
335+
(o.app_dir/file) o.builder.Builder.base_url
330336
else
331337
Lwt.return_unit) >>= fun () ->
332338
let if_enabled opt dir f = (match opt with
@@ -363,14 +369,14 @@ let main o =
363369
\ enableLessons: %b,\n\
364370
\ enableExercises: %b,\n\
365371
\ enableToplevel: %b,\n\
366-
\ root: \"%s\"\n\
372+
\ baseUrl: \"%s\"\n\
367373
}\n"
368374
(tutorials_ret <> None)
369375
(playground_ret <> None)
370376
(lessons_ret <> None)
371377
(exercises_ret <> None)
372378
(o.builder.Builder.toplevel <> Some false)
373-
o.builder.Builder.root >>= fun () ->
379+
o.builder.Builder.base_url >>= fun () ->
374380
Lwt.return (tutorials_ret <> Some false && exercises_ret <> Some false)))
375381
else
376382
Lwt.return true
@@ -383,14 +389,18 @@ let main o =
383389
let open Server in
384390
("--app-dir="^o.app_dir) ::
385391
("--sync-dir="^o.server.sync_dir) ::
392+
("--base-url="^o.builder.Builder.base_url) ::
386393
("--port="^string_of_int o.server.port) ::
387394
(match o.server.cert with None -> [] | Some c -> ["--cert="^c])
388395
in
389396
Unix.execv native_server (Array.of_list (native_server::server_args))
390-
else
391-
Printf.printf "Starting server on port %d\n%!"
392-
!Learnocaml_server.port;
393-
Learnocaml_server.launch ()
397+
else begin
398+
Printf.printf "Starting server on port %d\n%!"
399+
!Learnocaml_server.port;
400+
if o.builder.Builder.base_url <> "" then
401+
Printf.printf "Base URL: %s\n%!" o.builder.Builder.base_url;
402+
Learnocaml_server.launch ()
403+
end
394404
else
395405
Lwt.return true
396406
in

src/main/learnocaml_server_args.ml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@ let port =
3434

3535
type t = {
3636
sync_dir: string;
37-
cert: string option;
37+
base_url: string;
3838
port: int;
39+
cert: string option;
3940
}
4041

41-
let term app_dir =
42-
let apply app_dir sync_dir port cert =
42+
let term app_dir base_url =
43+
let apply app_dir sync_dir base_url port cert =
4344
Learnocaml_store.static_dir := app_dir;
4445
Learnocaml_store.sync_dir := sync_dir;
4546
let port = match port, cert with
@@ -52,8 +53,9 @@ let term app_dir =
5253
| Some base -> Some (base ^ ".pem", base ^ ".key");
5354
| None -> None);
5455
Learnocaml_server.port := port;
55-
{ sync_dir; port; cert }
56+
Learnocaml_server.base_url := base_url;
57+
{ sync_dir; base_url; port; cert }
5658
in
5759
(* warning: if you add any options here, remember to pass them through when
5860
calling the native server from learn-ocaml main *)
59-
Term.(const apply $app_dir $sync_dir $port $cert)
61+
Term.(const apply $ app_dir $ sync_dir $ base_url $ port $ cert)

src/main/learnocaml_server_args.mli

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88

99
type t = {
1010
sync_dir: string;
11-
cert: string option;
11+
base_url: string;
1212
port: int;
13+
cert: string option;
1314
}
1415

15-
val term: string Cmdliner.Term.t -> t Cmdliner.Term.t
16+
val term: string Cmdliner.Term.t -> string Cmdliner.Term.t -> t Cmdliner.Term.t

src/main/learnocaml_server_main.ml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ let signal_waiter =
2424
let main o =
2525
Printf.printf "Learnocaml server v.%s starting on port %d\n%!"
2626
Learnocaml_api.version o.port;
27+
if o.base_url <> "" then
28+
Printf.printf "Base URL: %s\n%!" o.base_url;
2729
let rec run () =
2830
let minimum_duration = 15. in
2931
let t0 = Unix.time () in
@@ -68,9 +70,18 @@ let app_dir =
6870
"Directory where the app has been generated by the $(b,learn-ocaml build) \
6971
command, and from where it will be served."
7072

73+
let base_url =
74+
let open Cmdliner.Arg in
75+
value & opt string "" &
76+
info ["base-url"] ~docv:"BASE_URL" ~env:(env_var "LEARNOCAML_BASE_URL") ~doc:
77+
"Set the base URL of the website. \
78+
Should not end with a trailing slash. \
79+
Currently, this has no effect on the backend - '$(b,learn-ocaml serve)'. \
80+
Mandatory for '$(b,learn-ocaml build)' if the site is not hosted in path '/', \
81+
which typically occurs for static deployment."
7182

7283
let main_cmd =
73-
Cmdliner.Term.(const main $ Learnocaml_server_args.term app_dir),
84+
Cmdliner.Term.(const main $ Learnocaml_server_args.term app_dir base_url),
7485
Cmdliner.Term.info
7586
~man
7687
~doc:"Learn-ocaml web-app manager"

src/server/learnocaml_server.ml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,19 @@ let cert_key_files = ref None
1515

1616
let log_channel = ref (Some stdout)
1717

18+
let base_url = ref ""
19+
1820
let args = Arg.align @@
1921
[ "-static-dir", Arg.Set_string static_dir,
2022
"PATH where static files should be found (./www)" ;
2123
"-sync-dir", Arg.Set_string sync_dir,
2224
"PATH where sync tokens are stored (./sync)" ;
25+
"-base-url", Arg.Set_string base_url,
26+
"BASE_URL of the website. \
27+
Should not end with a trailing slash. \
28+
Currently, this has no effect on the native backend. \
29+
Mandatory for 'learn-ocaml build' if the site is not hosted in path '/', \
30+
which typically occurs for static deployment." ;
2331
"-port", Arg.Set_int port,
2432
"PORT the TCP port (8080)" ]
2533

src/server/learnocaml_server.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
val port: int ref
1212
val cert_key_files: (string * string) option ref
13+
val base_url: string ref
1314

1415
val args: (Arg.key * Arg.spec * Arg.doc) list
1516

static/exercise.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
</div>
3434
<script language="JavaScript">
3535
var n = Math.floor (Math.random () * 8.99) + 1;
36-
document.getElementById('chamo-img').src = learnocaml_config.root + '/icons/tryocaml_loading_' + n + '.gif';
36+
document.getElementById('chamo-img').src = learnocaml_config.baseUrl + '/icons/tryocaml_loading_' + n + '.gif';
3737
</script>
3838
<!-- Anything below could be recreated dynamically, but IDs must be kept. -->
3939
<div id="learnocaml-exo-toolbar">

0 commit comments

Comments
 (0)