Compare commits
1599 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
574d343881 | ||
![]() |
c205385023 | ||
![]() |
9e0ac1594c | ||
![]() |
2fd434f511 | ||
![]() |
af39f39082 | ||
![]() |
ab751bda5c | ||
![]() |
f84078627f | ||
![]() |
3ec3dc5195 | ||
![]() |
73102e7aeb | ||
![]() |
b039e2985b | ||
![]() |
6d7863d56a | ||
![]() |
aba32e7200 | ||
![]() |
a71823c5ab | ||
![]() |
fcf9122519 | ||
![]() |
6c3fc41176 | ||
![]() |
0bdb1bac4d | ||
![]() |
35c76221fe | ||
![]() |
ffb092721c | ||
![]() |
8758b3af27 | ||
![]() |
5202cdff8c | ||
![]() |
ce0cb95282 | ||
![]() |
ee421f6427 | ||
![]() |
268da21bbf | ||
![]() |
4ad5f61bc7 | ||
![]() |
3df3850b3a | ||
![]() |
50733efa1b | ||
![]() |
98230ee770 | ||
![]() |
37f250b4d7 | ||
![]() |
869661bf25 | ||
![]() |
009fa955ed | ||
![]() |
7c8f7e9fcb | ||
![]() |
14539c4e0f | ||
![]() |
a11a292cd9 | ||
![]() |
5890064191 | ||
![]() |
1f30e693ad | ||
![]() |
32976f3d42 | ||
![]() |
30bc23f102 | ||
![]() |
786c7039d6 | ||
![]() |
19c3b02155 | ||
![]() |
1a80524772 | ||
![]() |
699a1cc01b | ||
![]() |
29ae04c921 | ||
![]() |
62a1652cc9 | ||
![]() |
290e031034 | ||
![]() |
7642302d17 | ||
![]() |
aebf833530 | ||
![]() |
86b51804c1 | ||
![]() |
aa12afa34d | ||
![]() |
2ff6d2b36c | ||
![]() |
e5f7aa6c2a | ||
![]() |
e3811edd87 | ||
![]() |
55cd9d806b | ||
![]() |
96789f5945 | ||
![]() |
81d481a110 | ||
![]() |
054c7f276e | ||
![]() |
1220673e61 | ||
![]() |
815274e966 | ||
![]() |
f1503b5a21 | ||
![]() |
4dcdf84d32 | ||
![]() |
dda0b611e2 | ||
![]() |
a23bfd1769 | ||
![]() |
a55ccce64e | ||
![]() |
42c5030b0e | ||
![]() |
be3df52b4f | ||
![]() |
0ca5eb4997 | ||
![]() |
9eeb84158e | ||
![]() |
37c2be778c | ||
![]() |
dc1b2c810d | ||
![]() |
88c7f188e0 | ||
![]() |
4181cc7065 | ||
![]() |
69e3fc2016 | ||
![]() |
56269f0226 | ||
![]() |
e446eff311 | ||
![]() |
00042de04c | ||
![]() |
82e0af763d | ||
![]() |
c5bfd28005 | ||
![]() |
0ffa5715fd | ||
![]() |
139312149e | ||
![]() |
29740b0af6 | ||
![]() |
9f6467be05 | ||
![]() |
caae99aa09 | ||
![]() |
8f2b14429f | ||
![]() |
af0d81436d | ||
![]() |
477ee23ad3 | ||
![]() |
27bcac5e8b | ||
![]() |
6535cc6bab | ||
![]() |
8173bbbf75 | ||
![]() |
2146eef150 | ||
![]() |
97b7ccbee4 | ||
![]() |
8eb98409d5 | ||
![]() |
a4390a1f4f | ||
![]() |
f42f7dd01f | ||
![]() |
0ca2ef68f0 | ||
![]() |
c3ca924ba8 | ||
![]() |
0155e6dc34 | ||
![]() |
727f9a0d49 | ||
![]() |
d31af27888 | ||
![]() |
9331dd13da | ||
![]() |
3c7203741f | ||
![]() |
4e79360567 | ||
![]() |
529273d105 | ||
![]() |
2e198396c1 | ||
![]() |
259c7512b8 | ||
![]() |
59b29f4c42 | ||
![]() |
bf3615aa96 | ||
![]() |
06a505f6df | ||
![]() |
c8d6c6aaa8 | ||
![]() |
cc2859a826 | ||
![]() |
26ccf6fd57 | ||
![]() |
f220bbca84 | ||
![]() |
4fb3f02870 | ||
![]() |
471d1f0a2f | ||
![]() |
1b12107c54 | ||
![]() |
b3a4adcbdd | ||
![]() |
12c69c6a94 | ||
![]() |
d3147f3fb7 | ||
![]() |
47265786e3 | ||
![]() |
1d9795c577 | ||
![]() |
e35b84b419 | ||
![]() |
5a57b03b61 | ||
![]() |
e526f36b81 | ||
![]() |
d289cd1e02 | ||
![]() |
4c3a32b51f | ||
![]() |
6c65624942 | ||
![]() |
cba22751b4 | ||
![]() |
c5d0265984 | ||
![]() |
fc772e1c39 | ||
![]() |
d70157e72a | ||
![]() |
91359bcaa7 | ||
![]() |
22fc580275 | ||
![]() |
2f304bffcc | ||
![]() |
162076c5dd | ||
![]() |
9bd97db90b | ||
![]() |
3a25b32ce6 | ||
![]() |
8fcc4b48a5 | ||
![]() |
289dee5996 | ||
![]() |
b1b7954e93 | ||
![]() |
35a55c6cbf | ||
![]() |
cd06f3fb12 | ||
![]() |
796d22d0d8 | ||
![]() |
be4357ad7a | ||
![]() |
202d6f93d4 | ||
![]() |
8b9b69ce22 | ||
![]() |
c40b3a4ad6 | ||
![]() |
c7f1b89f6c | ||
![]() |
dcff08ae13 | ||
![]() |
b0bf348908 | ||
![]() |
b73eca91ca | ||
![]() |
3db5eae9a9 | ||
![]() |
adb5f6ab2a | ||
![]() |
2a84353a51 | ||
![]() |
ca4fb3187f | ||
![]() |
8ab25e7c3d | ||
![]() |
f69ef9f846 | ||
![]() |
ba2608c643 | ||
![]() |
c3f5ad8b6d | ||
![]() |
4dbe5490f8 | ||
![]() |
711080616e | ||
![]() |
8e603e5212 | ||
![]() |
147167e589 | ||
![]() |
cebb1f3e22 | ||
![]() |
0b085a91b6 | ||
![]() |
ca3ceac4f3 | ||
![]() |
c833fae901 | ||
![]() |
8d3a7b704c | ||
![]() |
1e53fd1f8c | ||
![]() |
166b00867f | ||
![]() |
7c474396f1 | ||
![]() |
f6f6b3afa3 | ||
![]() |
a91197635a | ||
![]() |
88706d4c27 | ||
![]() |
29fac11bfe | ||
![]() |
947ef67184 | ||
![]() |
8ede924956 | ||
![]() |
55c2d3648e | ||
![]() |
2cf8e48fb5 | ||
![]() |
ae77038a64 | ||
![]() |
ffed8f67a0 | ||
![]() |
1efd7da6ee | ||
![]() |
6e161d0140 | ||
![]() |
5f4144cc98 | ||
![]() |
f866bbcf45 | ||
![]() |
ed6231d3aa | ||
![]() |
9d38259ad7 | ||
![]() |
4b254fe5ed | ||
![]() |
f8040209b0 | ||
![]() |
e59ee33a6e | ||
![]() |
ff15ced3ce | ||
![]() |
75acd6a67b | ||
![]() |
73ac6207af | ||
![]() |
e435fe66a5 | ||
![]() |
d7569d6f8e | ||
![]() |
ba6c2cf854 | ||
![]() |
970b25d017 | ||
![]() |
671ef0d5ef | ||
![]() |
77220d6662 | ||
![]() |
7e469f911d | ||
![]() |
18393ec6b4 | ||
![]() |
28fdbeb0c0 | ||
![]() |
5664e4d318 | ||
![]() |
24c83e721f | ||
![]() |
cc73ab711e | ||
![]() |
2cfe4474ac | ||
![]() |
74766e4786 | ||
![]() |
ed461ff4a7 | ||
![]() |
184d87ff2a | ||
![]() |
06ed7dc0cf | ||
![]() |
a0b229431c | ||
![]() |
2a06c8a94c | ||
![]() |
91159d08d3 | ||
![]() |
06a83f146b | ||
![]() |
7b66d1656b | ||
![]() |
40176a667f | ||
![]() |
e02345a4e8 | ||
![]() |
1408e9f5f4 | ||
![]() |
b66d204d69 | ||
![]() |
164447717f | ||
![]() |
0472ef0533 | ||
![]() |
202efae6d8 | ||
![]() |
2e043241fb | ||
![]() |
fa61f06fed | ||
![]() |
8b19413fa1 | ||
![]() |
7c2e7692b0 | ||
![]() |
ce11959b1a | ||
![]() |
097974d57d | ||
![]() |
09ff03ca4f | ||
![]() |
313f050c42 | ||
![]() |
4862831f71 | ||
![]() |
c46beb976a | ||
![]() |
11a85d1dc5 | ||
![]() |
67c4a86376 | ||
![]() |
e00ef1aef1 | ||
![]() |
fb5f98f2fa | ||
![]() |
82a1ba8402 | ||
![]() |
7f53ad52fb | ||
![]() |
73cdd687e9 | ||
![]() |
af09bc547a | ||
![]() |
3ddc796068 | ||
![]() |
3c071467bb | ||
![]() |
0c43feee1b | ||
![]() |
5bcbc8b328 | ||
![]() |
87e4f458fb | ||
![]() |
808e8711e1 | ||
![]() |
19935254a7 | ||
![]() |
a499940309 | ||
![]() |
74544009ca | ||
![]() |
665f9fa693 | ||
![]() |
24b555185a | ||
![]() |
24f4b7b6b6 | ||
![]() |
217dffa845 | ||
![]() |
a7b796fa57 | ||
![]() |
6c5fb5fe97 | ||
![]() |
20ea322e25 | ||
![]() |
4f9664cfe2 | ||
![]() |
be211a48ef | ||
![]() |
553ee26312 | ||
![]() |
7e6111448a | ||
![]() |
ccc0294f2e | ||
![]() |
3232ad61aa | ||
![]() |
202a5bf9a5 | ||
![]() |
47136f6a3c | ||
![]() |
5d3161c6ef | ||
![]() |
9da4aa236e | ||
![]() |
d581cf54cb | ||
![]() |
fca2528332 | ||
![]() |
5edd246474 | ||
![]() |
77ed2faf31 | ||
![]() |
4a17441e5a | ||
![]() |
e1166ec834 | ||
![]() |
2a1d341586 | ||
![]() |
55a59a2e43 | ||
![]() |
e019a33509 | ||
![]() |
737dcf65eb | ||
![]() |
9deaeb1fa9 | ||
![]() |
bcfc2c1b0d | ||
![]() |
f71bacc998 | ||
![]() |
ff14b1aa71 | ||
![]() |
ebbbdcb2b1 | ||
![]() |
d0fca9e56b | ||
![]() |
517737aa0b | ||
![]() |
5dadd34a87 | ||
![]() |
df134fefd0 | ||
![]() |
47cec97e63 | ||
![]() |
0b8b87d7d0 | ||
![]() |
3bf1d72905 | ||
![]() |
8cdd449cca | ||
![]() |
6fc3c19763 | ||
![]() |
265dc07c78 | ||
![]() |
1ae039ddef | ||
![]() |
378d34b213 | ||
![]() |
9657430cac | ||
![]() |
6271535f46 | ||
![]() |
2bef5ba981 | ||
![]() |
efb1f3c824 | ||
![]() |
53050a5836 | ||
![]() |
6428ad9f0b | ||
![]() |
9068ff2239 | ||
![]() |
fc6cd33ce0 | ||
![]() |
b0b8e2d058 | ||
![]() |
6bfa402bfa | ||
![]() |
b51a0bba92 | ||
![]() |
2d3f962a1d | ||
![]() |
625242136a | ||
![]() |
f92560fed0 | ||
![]() |
8249ef69f0 | ||
![]() |
c63605425f | ||
![]() |
5b57900c0b | ||
![]() |
d0afdabd4c | ||
![]() |
618746fa00 | ||
![]() |
e7bc6c2ba9 | ||
![]() |
e9f86cd602 | ||
![]() |
6e8517f795 | ||
![]() |
5fa540bea1 | ||
![]() |
99f597887c | ||
![]() |
352526c36a | ||
![]() |
cbbed04eed | ||
![]() |
b2e7b474ff | ||
![]() |
b2756fb18c | ||
![]() |
37b88029e4 | ||
![]() |
4b7413184e | ||
![]() |
41ef0da180 | ||
![]() |
a4a8b3fa2c | ||
![]() |
02e5984f34 | ||
![]() |
b91c5a489c | ||
![]() |
c47c3b2f9e | ||
![]() |
eaa1353dcd | ||
![]() |
b9a3b0a66a | ||
![]() |
929b805fae | ||
![]() |
082f6516a1 | ||
![]() |
1aa21f1d6c | ||
![]() |
cec9702796 | ||
![]() |
f8cbda9c3c | ||
![]() |
71aee05bc0 | ||
![]() |
772de55a0d | ||
![]() |
e6f92238b1 | ||
![]() |
db76b52e35 | ||
![]() |
e6e994e843 | ||
![]() |
284e379341 | ||
![]() |
3ce1cc63af | ||
![]() |
9945a7f7be | ||
![]() |
004c964cc1 | ||
![]() |
0f0d6d12d3 | ||
![]() |
c97e4d4e2f | ||
![]() |
53d496aff5 | ||
![]() |
032ae29066 | ||
![]() |
21caa57e7b | ||
![]() |
37ee104afa | ||
![]() |
dac75ff996 | ||
![]() |
67e06e5a18 | ||
![]() |
4cbc0bad34 | ||
![]() |
9f8c1decc4 | ||
![]() |
1244533387 | ||
![]() |
8c30724f17 | ||
![]() |
50868f5bb5 | ||
![]() |
e15b6ad52e | ||
![]() |
b194135a0f | ||
![]() |
5b8a7fd191 | ||
![]() |
be272ffb2a | ||
![]() |
8ee60ce0c7 | ||
![]() |
e553bcb7e2 | ||
![]() |
c0288ec6f6 | ||
![]() |
65b83f5f00 | ||
![]() |
dcd520179c | ||
![]() |
c830d964d5 | ||
![]() |
9e5993f1da | ||
![]() |
7ed3e0506b | ||
![]() |
7045e1116c | ||
![]() |
fb56fd406f | ||
![]() |
5489395272 | ||
![]() |
6ecda96dd6 | ||
![]() |
30b8bc3664 | ||
![]() |
80ad455fc7 | ||
![]() |
21eaf0dd9f | ||
![]() |
84d2524025 | ||
![]() |
959dfb145a | ||
![]() |
998c18df42 | ||
![]() |
88b10aa2f5 | ||
![]() |
d8f5758e08 | ||
![]() |
47e45a4d3f | ||
![]() |
3e31ff4ac7 | ||
![]() |
ff30396a8e | ||
![]() |
196a7fbc65 | ||
![]() |
c66e8bb4c9 | ||
![]() |
5595146fe2 | ||
![]() |
76b688e574 | ||
![]() |
f00d0be4d6 | ||
![]() |
f9d815676f | ||
![]() |
94612d09a6 | ||
![]() |
76ed65ed82 | ||
![]() |
560bab395b | ||
![]() |
c68b846eef | ||
![]() |
5896b2c9f7 | ||
![]() |
0317fd63fa | ||
![]() |
7f6886c60f | ||
![]() |
10bdca8901 | ||
![]() |
66cb2c0f3e | ||
![]() |
0152e29946 | ||
![]() |
c6f0c07931 | ||
![]() |
51ceab9f6f | ||
![]() |
46ead8cd9d | ||
![]() |
bfb3d50936 | ||
![]() |
962307475e | ||
![]() |
80f4edcd20 | ||
![]() |
1ad4035943 | ||
![]() |
5ab735fea3 | ||
![]() |
e79cb0d376 | ||
![]() |
f728cf89c6 | ||
![]() |
8f719e21d2 | ||
![]() |
29de00ee3c | ||
![]() |
52291b0012 | ||
![]() |
e58c341290 | ||
![]() |
f988a4939e | ||
![]() |
60ee2bfc35 | ||
![]() |
42601c52cc | ||
![]() |
0679586b2c | ||
![]() |
be4201f7ee | ||
![]() |
11a73b5630 | ||
![]() |
f1efac41bf | ||
![]() |
aa6921dd5a | ||
![]() |
e94da17c3c | ||
![]() |
e2ee18fa86 | ||
![]() |
c5ec8ceba3 | ||
![]() |
3458c742cb | ||
![]() |
d1a85e53dc | ||
![]() |
d915cc3ff2 | ||
![]() |
b11c02c6e0 | ||
![]() |
49f3bb53f4 | ||
![]() |
9b7a94046b | ||
![]() |
62ef5ca2fe | ||
![]() |
028e0b0b77 | ||
![]() |
d2a42a69b0 | ||
![]() |
1f21f283df | ||
![]() |
7f35158575 | ||
![]() |
d0da677813 | ||
![]() |
a0a02688c5 | ||
![]() |
2372842b8a | ||
![]() |
7e205a9751 | ||
![]() |
e7fab5c304 | ||
![]() |
8b8b512d06 | ||
![]() |
714072dbd8 | ||
![]() |
6e8f39c22d | ||
![]() |
f3c3225124 | ||
![]() |
614bfe77d8 | ||
![]() |
1beea06ce5 | ||
![]() |
42adb44153 | ||
![]() |
d5a0202106 | ||
![]() |
3d524f2092 | ||
![]() |
409835303e | ||
![]() |
acc8d15fec | ||
![]() |
608cad6404 | ||
![]() |
571a428375 | ||
![]() |
1575adf272 | ||
![]() |
4bc6d869f3 | ||
![]() |
e5a6119505 | ||
![]() |
d80dab284d | ||
![]() |
9d556728bb | ||
![]() |
4369e2cbfa | ||
![]() |
ef4455bb67 | ||
![]() |
76c9111d80 | ||
![]() |
946ed844c5 | ||
![]() |
cceb652039 | ||
![]() |
6e988bf587 | ||
![]() |
dbc6998375 | ||
![]() |
1bdc9aa297 | ||
![]() |
73f1211286 | ||
![]() |
3fece09dda | ||
![]() |
7ad4b0c7cb | ||
![]() |
252015f50d | ||
![]() |
b3cc235c8a | ||
![]() |
47d7af8f48 | ||
![]() |
8528684dc4 | ||
![]() |
d4ce3aa731 | ||
![]() |
ec710f4d90 | ||
![]() |
14378f4cc2 | ||
![]() |
cc8e780653 | ||
![]() |
5bbf584cb7 | ||
![]() |
b5defabf49 | ||
![]() |
2d1f91e527 | ||
![]() |
1653ee77ed | ||
![]() |
10f09f4f70 | ||
![]() |
b7f277147b | ||
![]() |
f3be735eeb | ||
![]() |
3e855eb1be | ||
![]() |
98dc1f71db | ||
![]() |
703703a648 | ||
![]() |
8db8df6d7a | ||
![]() |
744430ba76 | ||
![]() |
45b858c5af | ||
![]() |
d4b5373c05 | ||
![]() |
aba55cc093 | ||
![]() |
5957a37933 | ||
![]() |
d20a33a0e4 | ||
![]() |
df35268bfe | ||
![]() |
c357d02b56 | ||
![]() |
4eb22821f2 | ||
![]() |
b92ea54eda | ||
![]() |
522ef3daea | ||
![]() |
77edffd695 | ||
![]() |
a8bc4f8a4a | ||
![]() |
66c3760b02 | ||
![]() |
fd28e224f2 | ||
![]() |
da3fedb5aa | ||
![]() |
e4e4d472b8 | ||
![]() |
bcbc68dd82 | ||
![]() |
c7df0587d2 | ||
![]() |
cd36733858 | ||
![]() |
6bf4f3b2aa | ||
![]() |
12d81ac07a | ||
![]() |
d60fa9a400 | ||
![]() |
81d423d6c6 | ||
![]() |
069b477ff3 | ||
![]() |
cf9046ea47 | ||
![]() |
71a25d4514 | ||
![]() |
2ff7d05b15 | ||
![]() |
bdb29df82a | ||
![]() |
0dbad9bd99 | ||
![]() |
2991d2d1f1 | ||
![]() |
a36a56b4ff | ||
![]() |
0e59ab003a | ||
![]() |
d67b71b7ae | ||
![]() |
8859bf8842 | ||
![]() |
4e29342711 | ||
![]() |
8a3790b01f | ||
![]() |
0d245fe4e4 | ||
![]() |
da34c6cb34 | ||
![]() |
9c0e5ba9c2 | ||
![]() |
289c3bc3c1 | ||
![]() |
3adfec0693 | ||
![]() |
137591f458 | ||
![]() |
debd297494 | ||
![]() |
10bb5ef3c0 | ||
![]() |
42e7d1a3fb | ||
![]() |
5fbd2838c9 | ||
![]() |
17dde3a2a9 | ||
![]() |
8d50554849 | ||
![]() |
493eb03345 | ||
![]() |
1beac49f4a | ||
![]() |
f230be5ede | ||
![]() |
6283e7ec83 | ||
![]() |
2438766418 | ||
![]() |
6f2e409fb9 | ||
![]() |
aa459aeb39 | ||
![]() |
9d6e8e6b6f | ||
![]() |
e882e7954c | ||
![]() |
c234463a67 | ||
![]() |
391320a590 | ||
![]() |
8648285375 | ||
![]() |
485c7b72c2 | ||
![]() |
e93cc83d58 | ||
![]() |
39b9f592b6 | ||
![]() |
1f515464fe | ||
![]() |
854d0cbb86 | ||
![]() |
87212a7414 | ||
![]() |
2338035df2 | ||
![]() |
ea132ff88d | ||
![]() |
78c14c05f3 | ||
![]() |
1d2b36e9b0 | ||
![]() |
a929ff84c7 | ||
![]() |
0d5bbc16cf | ||
![]() |
ee1fd5a469 | ||
![]() |
a702f36524 | ||
![]() |
59edc6d369 | ||
![]() |
907b77788d | ||
![]() |
914a3eaba5 | ||
![]() |
b1f048f2ef | ||
![]() |
53d76ad3a2 | ||
![]() |
7af70b92e9 | ||
![]() |
3425eca4ff | ||
![]() |
9e0bf9cd9f | ||
![]() |
3118918098 | ||
![]() |
6a995c822c | ||
![]() |
a09f535e8f | ||
![]() |
a60ac53c87 | ||
![]() |
d2c81bc1d0 | ||
![]() |
3908c6d041 | ||
![]() |
c50e1f9852 | ||
![]() |
6954e03bb4 | ||
![]() |
08eee9309e | ||
![]() |
6ed41b38ed | ||
![]() |
6b521e0b86 | ||
![]() |
1bdc66c75b | ||
![]() |
e30b2ca875 | ||
![]() |
1f3ed58570 | ||
![]() |
6a31b640c1 | ||
![]() |
ed97150311 | ||
![]() |
78eb77f157 | ||
![]() |
f152288d76 | ||
![]() |
492c5072b7 | ||
![]() |
534e251f97 | ||
![]() |
cfcd85a188 | ||
![]() |
fd3b5ebbad | ||
![]() |
1a2d5913eb | ||
![]() |
8f46d89ac0 | ||
![]() |
e82c06cf93 | ||
![]() |
392525571f | ||
![]() |
53927f0490 | ||
![]() |
ede71db11a | ||
![]() |
a2e2b1d512 | ||
![]() |
cff18992ad | ||
![]() |
b2c0b5024c | ||
![]() |
996483de94 | ||
![]() |
f4b7b85b02 | ||
![]() |
b4391d0f79 | ||
![]() |
f49cc1fcf0 | ||
![]() |
18205fbf4a | ||
![]() |
2f6ea71106 | ||
![]() |
7b6ac158cc | ||
![]() |
facf52f117 | ||
![]() |
f36796dd85 | ||
![]() |
0427f8090f | ||
![]() |
da86eaad97 | ||
![]() |
3b05135f11 | ||
![]() |
76afec8adb | ||
![]() |
06da90ac76 | ||
![]() |
7e3caf7f48 | ||
![]() |
e08552eb99 | ||
![]() |
5fb403af4b | ||
![]() |
84acdd5a7f | ||
![]() |
3e6abb7a5e | ||
![]() |
0315f986db | ||
![]() |
7735c7ddd4 | ||
![]() |
239a4c63a2 | ||
![]() |
f5bd5b7751 | ||
![]() |
287b0302d9 | ||
![]() |
44e23aad78 | ||
![]() |
606775f72d | ||
![]() |
9a6308f8d9 | ||
![]() |
0c4db2d99f | ||
![]() |
938970817c | ||
![]() |
d2a1b8e349 | ||
![]() |
4477506345 | ||
![]() |
0787489e1b | ||
![]() |
436757dd55 | ||
![]() |
a0b6d8ec6f | ||
![]() |
b92efcd7b0 | ||
![]() |
3e17b47ec3 | ||
![]() |
31c0788bd9 | ||
![]() |
dec3244758 | ||
![]() |
91e385efa7 | ||
![]() |
13313abb37 | ||
![]() |
79a51dfdce | ||
![]() |
a999ac8f07 | ||
![]() |
a3e3f24d2d | ||
![]() |
b2b85eb548 | ||
![]() |
95c5ebb090 | ||
![]() |
3d0da4f25a | ||
![]() |
bc7bb5076f | ||
![]() |
a80561bfc8 | ||
![]() |
22f86ad76c | ||
![]() |
0ae9cfa42f | ||
![]() |
ff8c4ca8a3 | ||
![]() |
ed4ed4de9d | ||
![]() |
d177b99f3a | ||
![]() |
65de8c4916 | ||
![]() |
178f9d4c51 | ||
![]() |
9433564c5b | ||
![]() |
5deba0c4ba | ||
![]() |
5234d4c7ae | ||
![]() |
1bea28026e | ||
![]() |
9a5c8ff058 | ||
![]() |
2b183c9773 | ||
![]() |
5dee864afd | ||
![]() |
6fdf931515 | ||
![]() |
d126baa443 | ||
![]() |
d1e2d593ff | ||
![]() |
3663d7c8fc | ||
![]() |
a30e6b539f | ||
![]() |
800b6a6bc5 | ||
![]() |
ca3982337e | ||
![]() |
159b3553a9 | ||
![]() |
6821e63b71 | ||
![]() |
c1c13930f7 | ||
![]() |
58f18bffff | ||
![]() |
b80906b8c8 | ||
![]() |
07aa077eae | ||
![]() |
3f74c30288 | ||
![]() |
141cb04b27 | ||
![]() |
8769864f24 | ||
![]() |
8ee72dd80f | ||
![]() |
455475724a | ||
![]() |
794be0de8e | ||
![]() |
1f633e188d | ||
![]() |
df0745985b | ||
![]() |
cad027f3fc | ||
![]() |
61a844b413 | ||
![]() |
319b404ef4 | ||
![]() |
19fb7eb7cc | ||
![]() |
cb3b0ce266 | ||
![]() |
82d8e9c433 | ||
![]() |
86ee4cad59 | ||
![]() |
add9666fcd | ||
![]() |
c93687eaad | ||
![]() |
d848873685 | ||
![]() |
c27576a41f | ||
![]() |
6d3ed95b84 | ||
![]() |
ff7cd082ff | ||
![]() |
3582ecc9cc | ||
![]() |
5f626268ef | ||
![]() |
6227f92b5f | ||
![]() |
020ba08635 | ||
![]() |
2ad175816a | ||
![]() |
3d46083dcc | ||
![]() |
dad1417b23 | ||
![]() |
9a3c2409d1 | ||
![]() |
0efb16793e | ||
![]() |
68ad36e945 | ||
![]() |
989ed216a7 | ||
![]() |
319113024d | ||
![]() |
399f7e7b80 | ||
![]() |
b4a6e5c2fe | ||
![]() |
1949ab892a | ||
![]() |
1ec34b256c | ||
![]() |
3c12a99415 | ||
![]() |
a8ced3a7ad | ||
![]() |
1af7deaeb3 | ||
![]() |
861a7c5c5e | ||
![]() |
1d02915f26 | ||
![]() |
90009f3c01 | ||
![]() |
dbce653b5e | ||
![]() |
b4443b1251 | ||
![]() |
155c76b299 | ||
![]() |
553be3e1d4 | ||
![]() |
e1e0a31afc | ||
![]() |
d78466507d | ||
![]() |
d9955a052d | ||
![]() |
2e40da09ea | ||
![]() |
490cf2dd82 | ||
![]() |
b0343ef8d8 | ||
![]() |
fb64b4f0a8 | ||
![]() |
5a747baeca | ||
![]() |
c4ce7faea6 | ||
![]() |
3a810c4fc0 | ||
![]() |
abb93ad799 | ||
![]() |
f31101432e | ||
![]() |
a2c98d016e | ||
![]() |
5581a2ba7e | ||
![]() |
1fe01ae173 | ||
![]() |
24706a1759 | ||
![]() |
182ac00e93 | ||
![]() |
ca81af2ae5 | ||
![]() |
92173c6053 | ||
![]() |
33e1a090d8 | ||
![]() |
e407808f47 | ||
![]() |
7b53330b20 | ||
![]() |
da02b024d6 | ||
![]() |
5502367832 | ||
![]() |
ddc61d2b62 | ||
![]() |
dc049a88eb | ||
![]() |
2b7a02697c | ||
![]() |
4e8acc71c6 | ||
![]() |
3bc0c18974 | ||
![]() |
3004f04a34 | ||
![]() |
e3f1fd0a16 | ||
![]() |
8367606012 | ||
![]() |
6956ffd2a9 | ||
![]() |
0b3ffe1a99 | ||
![]() |
e44ee6ed8a | ||
![]() |
45a4362bb3 | ||
![]() |
8e7df7ae7b | ||
![]() |
676a0da5ff | ||
![]() |
e802df9668 | ||
![]() |
c8e4d68978 | ||
![]() |
5ee2994504 | ||
![]() |
c194cb079e | ||
![]() |
1910bfacbd | ||
![]() |
e16ca97e1c | ||
![]() |
4bcfd52bc7 | ||
![]() |
29df06f0b5 | ||
![]() |
9ec4e6d1d1 | ||
![]() |
ce34c12349 | ||
![]() |
7b5a5541cb | ||
![]() |
731faf29c8 | ||
![]() |
bef561511f | ||
![]() |
f0b5446ec3 | ||
![]() |
629e829f8a | ||
![]() |
7c434adcb2 | ||
![]() |
3641abc70f | ||
![]() |
da790617e3 | ||
![]() |
35ba762c9c | ||
![]() |
42d9c31db7 | ||
![]() |
703af1dd1e | ||
![]() |
1dd09094a5 | ||
![]() |
b8c9717862 | ||
![]() |
06f89cb5ed | ||
![]() |
b5602028e5 | ||
![]() |
b1e45cde1e | ||
![]() |
ca117c251c | ||
![]() |
e815210cc7 | ||
![]() |
f37864cfd3 | ||
![]() |
d05d92c03a | ||
![]() |
948f4c44fd | ||
![]() |
5db76e6dcd | ||
![]() |
c944c0e54a | ||
![]() |
dd7fe85770 | ||
![]() |
b9c1831183 | ||
![]() |
5bbb292ef5 | ||
![]() |
e589b5d82a | ||
![]() |
465fb0a686 | ||
![]() |
9702c1756f | ||
![]() |
9990100f89 | ||
![]() |
a611298f43 | ||
![]() |
6a872b371e | ||
![]() |
1e298fb053 | ||
![]() |
51e1a15d63 | ||
![]() |
46e6d95364 | ||
![]() |
52c099193d | ||
![]() |
9d5784efb9 | ||
![]() |
2847c3a90c | ||
![]() |
d66f0635a3 | ||
![]() |
244ad7d38c | ||
![]() |
7fbf1826ea | ||
![]() |
b4a760234e | ||
![]() |
72a38a599d | ||
![]() |
8134d3bfbc | ||
![]() |
3df4afe7af | ||
![]() |
400c64b4ef | ||
![]() |
44dccb292f | ||
![]() |
0070e68702 | ||
![]() |
f3b1b5c7a6 | ||
![]() |
175c8d0585 | ||
![]() |
bc425a78bb | ||
![]() |
e0c4f9fc23 | ||
![]() |
2cac46fdb2 | ||
![]() |
66f8d6a626 | ||
![]() |
f163559f4a | ||
![]() |
a615f783a3 | ||
![]() |
3cafc7e49f | ||
![]() |
12ee42e8ae | ||
![]() |
9e5c837d3d | ||
![]() |
91be46784e | ||
![]() |
60a1c93801 | ||
![]() |
3a0a581782 | ||
![]() |
5cbf9399b2 | ||
![]() |
d942f52eeb | ||
![]() |
8c1620e6c5 | ||
![]() |
9fdab027da | ||
![]() |
bc32450005 | ||
![]() |
cc95d30dc1 | ||
![]() |
25ef67e8e0 | ||
![]() |
2ad1159f69 | ||
![]() |
561f4d0889 | ||
![]() |
cd0b3e05e2 | ||
![]() |
cdba57e96a | ||
![]() |
f13bd59f6f | ||
![]() |
89b0c421d5 | ||
![]() |
83ddee10ed | ||
![]() |
8a03b73086 | ||
![]() |
333b62f1fc | ||
![]() |
231d14e95d | ||
![]() |
9817610dc3 | ||
![]() |
aaf365c907 | ||
![]() |
0f93571ca5 | ||
![]() |
5b13f96162 | ||
![]() |
b41a383eae | ||
![]() |
1701149fd7 | ||
![]() |
5f8664723e | ||
![]() |
18ce8eb5a6 | ||
![]() |
d51d39728a | ||
![]() |
2255de7847 | ||
![]() |
a8c0609eb9 | ||
![]() |
66f29e0f5a | ||
![]() |
ca00c0eab0 | ||
![]() |
54baa0c31a | ||
![]() |
5d3dc509bd | ||
![]() |
9cf22e4106 | ||
![]() |
898fea9fdc | ||
![]() |
f79495e6bf | ||
![]() |
f474b31c94 | ||
![]() |
fafbe86b55 | ||
![]() |
82ad2dfbc6 | ||
![]() |
ac32ae496e | ||
![]() |
949d8d0bfa | ||
![]() |
7fd3271c9b | ||
![]() |
6267b752ae | ||
![]() |
7fcd6ad450 | ||
![]() |
dcde9f6222 | ||
![]() |
2e8ddeb114 | ||
![]() |
e07aaa603a | ||
![]() |
0bcd6adde6 | ||
![]() |
444029699a | ||
![]() |
b9bdc99c1d | ||
![]() |
c896fe05fd | ||
![]() |
424803bcd7 | ||
![]() |
9024cf1614 | ||
![]() |
a239a25ae0 | ||
![]() |
36a1ad0078 | ||
![]() |
6d696758e4 | ||
![]() |
2545cd9bb3 | ||
![]() |
096b159c23 | ||
![]() |
74958d9397 | ||
![]() |
9db18439af | ||
![]() |
2b6ad596d2 | ||
![]() |
917786f2f5 | ||
![]() |
a800496f6c | ||
![]() |
a92fee8a82 | ||
![]() |
7b1c4aedcf | ||
![]() |
572e008f1d | ||
![]() |
0379727cc0 | ||
![]() |
c9d52bea43 | ||
![]() |
263c5e838e | ||
![]() |
439e4381f0 | ||
![]() |
c34bcabcb9 | ||
![]() |
2b1bfa0ba7 | ||
![]() |
aea2eefa77 | ||
![]() |
dcde4020c2 | ||
![]() |
1225ff47be | ||
![]() |
5aaa5263fa | ||
![]() |
eca4f33afc | ||
![]() |
1e578a25d3 | ||
![]() |
41b2e6e401 | ||
![]() |
ced45d101a | ||
![]() |
03693c379e | ||
![]() |
0058ed803d | ||
![]() |
7d9a93ab5f | ||
![]() |
8a61eb1738 | ||
![]() |
cbbead3780 | ||
![]() |
146aec7e0c | ||
![]() |
f7e5904c5b | ||
![]() |
077727595c | ||
![]() |
4bfc69dc80 | ||
![]() |
8d7f55ce92 | ||
![]() |
cda7f73cfa | ||
![]() |
915664ede2 | ||
![]() |
037730761c | ||
![]() |
1d1e108e09 | ||
![]() |
6e71e617ed | ||
![]() |
9e0bb5cc71 | ||
![]() |
5fa268dab1 | ||
![]() |
1a26c1fb81 | ||
![]() |
2cc0eb885a | ||
![]() |
749b9e0997 | ||
![]() |
669dbfd449 | ||
![]() |
444f0ba00c | ||
![]() |
e46e724a70 | ||
![]() |
2e67a534cf | ||
![]() |
24c0829289 | ||
![]() |
60f5ce0ff8 | ||
![]() |
0325be3e13 | ||
![]() |
b37b13a939 | ||
![]() |
37642408a4 | ||
![]() |
9d2823e84b | ||
![]() |
ae7974564c | ||
![]() |
30c69f94c8 | ||
![]() |
47cf1915ff | ||
![]() |
9f32fc1854 | ||
![]() |
8a2eba1156 | ||
![]() |
254687e841 | ||
![]() |
aa59b1fca3 | ||
![]() |
88bff9d03d | ||
![]() |
3ca0f32ad3 | ||
![]() |
6a2876a9fa | ||
![]() |
fad6900779 | ||
![]() |
d8d58b2ebd | ||
![]() |
859dc34ea6 | ||
![]() |
8a37d2daec | ||
![]() |
41db9fe116 | ||
![]() |
8dce5a87bc | ||
![]() |
266e82755a | ||
![]() |
b237ab9e7b | ||
![]() |
7c78e6c326 | ||
![]() |
f1ed6c95f0 | ||
![]() |
2f0ce2a431 | ||
![]() |
adf3779d02 | ||
![]() |
73309b5741 | ||
![]() |
2320d59bd1 | ||
![]() |
1915ecd0c2 | ||
![]() |
d050242d0f | ||
![]() |
3d6d60b64e | ||
![]() |
fc90be8424 | ||
![]() |
1555abb2bf | ||
![]() |
8c8968c2b0 | ||
![]() |
69d0a47734 | ||
![]() |
5ae1fdf621 | ||
![]() |
c24f6b0a6a | ||
![]() |
11e32588d7 | ||
![]() |
34e44f2eed | ||
![]() |
c0464b2e47 | ||
![]() |
d686ae1ae7 | ||
![]() |
0dc3593661 | ||
![]() |
dc40cfe80e | ||
![]() |
d541c17974 | ||
![]() |
09cc8569b3 | ||
![]() |
3089d441b4 | ||
![]() |
19806899f2 | ||
![]() |
553e31235e | ||
![]() |
55323ec206 | ||
![]() |
49a5f3a654 | ||
![]() |
97c27774b1 | ||
![]() |
de11909a04 | ||
![]() |
2f15d5128e | ||
![]() |
276ef26161 | ||
![]() |
d5d315df08 | ||
![]() |
f7f82b8214 | ||
![]() |
ddece49abb | ||
![]() |
02192ee2d5 | ||
![]() |
a6b7e303df | ||
![]() |
5e5a976ea6 | ||
![]() |
c20c07ec87 | ||
![]() |
bac34e394b | ||
![]() |
2ce223c811 | ||
![]() |
e107c84162 | ||
![]() |
1cea503292 | ||
![]() |
e9bc25cce0 | ||
![]() |
8f7e25f9a1 | ||
![]() |
399def182b | ||
![]() |
f830b2a417 | ||
![]() |
cab1bca6fb | ||
![]() |
5eb7a14a33 | ||
![]() |
19da170435 | ||
![]() |
30cfdcaa83 | ||
![]() |
e9c78422b5 | ||
![]() |
844817297e | ||
![]() |
b624116be7 | ||
![]() |
38cf95523f | ||
![]() |
d6d8590acb | ||
![]() |
da460064ae | ||
![]() |
8a6de3006c | ||
![]() |
9e35ba5bef | ||
![]() |
c83777ccdc | ||
![]() |
aaad55e076 | ||
![]() |
c1e359bd38 | ||
![]() |
53f5dbd902 | ||
![]() |
9e7b0c0bfd | ||
![]() |
0aca778a9e | ||
![]() |
83af28c137 | ||
![]() |
bfbf2c0521 | ||
![]() |
09edf38a35 | ||
![]() |
e4d4e059bd | ||
![]() |
2967383654 | ||
![]() |
85f5ae1a37 | ||
![]() |
ecafe4add9 | ||
![]() |
9462511aa5 | ||
![]() |
31736eea9a | ||
![]() |
f97ef7eaac | ||
![]() |
2065099338 | ||
![]() |
d4df579fa6 | ||
![]() |
4378603e83 | ||
![]() |
40db4edc6d | ||
![]() |
ccf13979e9 | ||
![]() |
76f134c393 | ||
![]() |
77d4c1f23d | ||
![]() |
5856f46e1d | ||
![]() |
edfd1eb6cf | ||
![]() |
1ae6678360 | ||
![]() |
7794eea3fb | ||
![]() |
f51e6a1ca0 | ||
![]() |
ab00a19be1 | ||
![]() |
7742bfdda5 | ||
![]() |
f3878d8216 | ||
![]() |
d17cb637fe | ||
![]() |
5b63efe63c | ||
![]() |
54816b0a7c | ||
![]() |
41fc73db42 | ||
![]() |
984d6be542 | ||
![]() |
d7d8459edb | ||
![]() |
39a7116d16 | ||
![]() |
d27c970cc4 | ||
![]() |
cf56dbb97b | ||
![]() |
a4ccfe4e11 | ||
![]() |
f1871bbe24 | ||
![]() |
1cc9153a91 | ||
![]() |
4258254c39 | ||
![]() |
f3aee9bd16 | ||
![]() |
5cb8ccf8b2 | ||
![]() |
1d63e417ca | ||
![]() |
ee0020e8fa | ||
![]() |
2d83575a24 | ||
![]() |
33c168530e | ||
![]() |
5d4d34b24d | ||
![]() |
49cc794937 | ||
![]() |
7f9e77ce5b | ||
![]() |
6fa3b429db | ||
![]() |
e89836c035 | ||
![]() |
784b5cb6f0 | ||
![]() |
daaa763c3b | ||
![]() |
2b18c64081 | ||
![]() |
785addc245 | ||
![]() |
b4758db017 | ||
![]() |
10fbfee157 | ||
![]() |
c58a251dbd | ||
![]() |
27be5e4847 | ||
![]() |
be97a0c95b | ||
![]() |
689a312756 | ||
![]() |
1484869ee3 | ||
![]() |
74a457f6b5 | ||
![]() |
137a044f96 | ||
![]() |
a090632a48 | ||
![]() |
451a16c57e | ||
![]() |
6e14e86a1a | ||
![]() |
a142f543ba | ||
![]() |
0bb3996c30 | ||
![]() |
2a23e8afea | ||
![]() |
071e375d5f | ||
![]() |
ca2d0a58b9 | ||
![]() |
1cfeee8808 | ||
![]() |
6ff421061d | ||
![]() |
2d049c39fc | ||
![]() |
5535804acb | ||
![]() |
0901fa255f | ||
![]() |
3e5b272b80 | ||
![]() |
693446dba9 | ||
![]() |
12d6a744df | ||
![]() |
45dcb3bd17 | ||
![]() |
6de9414c2f | ||
![]() |
b1f8c31c80 | ||
![]() |
8032f874af | ||
![]() |
c869bc34af | ||
![]() |
d1c06ab603 | ||
![]() |
7653f75310 | ||
![]() |
de4ea150c0 | ||
![]() |
0fdb0df176 | ||
![]() |
6cefdba515 | ||
![]() |
b3bd236e15 | ||
![]() |
79a06fd9ac | ||
![]() |
3249574744 | ||
![]() |
7e04d1d756 | ||
![]() |
d63083bc17 | ||
![]() |
b93ec84822 | ||
![]() |
b1606f21e6 | ||
![]() |
437eb18dd2 | ||
![]() |
82c889861d | ||
![]() |
6ba45ee389 | ||
![]() |
af0082a16b | ||
![]() |
4bdca83c94 | ||
![]() |
4183d45ab3 | ||
![]() |
674ae9b4fc | ||
![]() |
ff283ae636 | ||
![]() |
76eabb2de8 | ||
![]() |
2fbcb16190 | ||
![]() |
5d5ebb2583 | ||
![]() |
49b9a9f017 | ||
![]() |
aa60d948bb | ||
![]() |
37d4d0e140 | ||
![]() |
e86622b921 | ||
![]() |
0d86c4ecf5 | ||
![]() |
249f39cf46 | ||
![]() |
8f3532e191 | ||
![]() |
27d0f62cd2 | ||
![]() |
a31dadacb2 | ||
![]() |
59fa95acf4 | ||
![]() |
32c3fb01d4 | ||
![]() |
ddc852d658 | ||
![]() |
01bc8584a2 | ||
![]() |
6524f38125 | ||
![]() |
50c16239d2 | ||
![]() |
bfdec8f22e | ||
![]() |
25aa892f86 | ||
![]() |
5dedfe2629 | ||
![]() |
699b317d54 | ||
![]() |
b1622ec745 | ||
![]() |
3cbcddad83 | ||
![]() |
35d888e91e | ||
![]() |
20be7f98f7 | ||
![]() |
a39d8aca30 | ||
![]() |
453ae6e97b | ||
![]() |
89c85aca37 | ||
![]() |
87c276f425 | ||
![]() |
4ec92f9f14 | ||
![]() |
8d01b0356b | ||
![]() |
81a43a588b | ||
![]() |
8ea5a957a6 | ||
![]() |
fee81c7d33 | ||
![]() |
0dd291ae5c | ||
![]() |
db3f62b79a | ||
![]() |
f8add6ae6d | ||
![]() |
d1f115d951 | ||
![]() |
fab5c33796 | ||
![]() |
4ab525ab5f | ||
![]() |
1185619bf6 | ||
![]() |
4b1d80203e | ||
![]() |
d8cabdb90f | ||
![]() |
947b9b1a9e | ||
![]() |
6f63ac7831 | ||
![]() |
0c028c7186 | ||
![]() |
1498707ac9 | ||
![]() |
de20c3f3a7 | ||
![]() |
0df552e2a1 | ||
![]() |
b4c53a29a9 | ||
![]() |
ca67757269 | ||
![]() |
aaa4deeed0 | ||
![]() |
bda8671807 | ||
![]() |
4d75c16335 | ||
![]() |
b5f6547e64 | ||
![]() |
17aee17c5f | ||
![]() |
2f99104f57 | ||
![]() |
80519f4fd0 | ||
![]() |
1531e94cc7 | ||
![]() |
43c3ac78fc | ||
![]() |
9cc6aa9b6d | ||
![]() |
031cb6076a | ||
![]() |
5e60582ef3 | ||
![]() |
ca198e0363 | ||
![]() |
d14a4bbe2c | ||
![]() |
ada8582768 | ||
![]() |
856923b35f | ||
![]() |
39902a7140 | ||
![]() |
8524556b33 | ||
![]() |
7c36ac93ba | ||
![]() |
fec3d959f2 | ||
![]() |
52d8f74eb1 | ||
![]() |
701b93d226 | ||
![]() |
bb83bb47d8 | ||
![]() |
1ba47d4a3d | ||
![]() |
8c76f2b30c | ||
![]() |
a7c3ea0906 | ||
![]() |
fa2cb33b27 | ||
![]() |
32706963ae | ||
![]() |
fb4c920996 | ||
![]() |
370ec4f5c7 | ||
![]() |
5e77e448bd | ||
![]() |
7c46fe74a5 | ||
![]() |
dcdb8d8a89 | ||
![]() |
087dd0fcd2 | ||
![]() |
33a139861b | ||
![]() |
d8d1b6c149 | ||
![]() |
a2f5a0bea9 | ||
![]() |
0063752a7f | ||
![]() |
297f6988bd | ||
![]() |
a6d217d113 | ||
![]() |
e51ea3f2be | ||
![]() |
bf36f9fc9a | ||
![]() |
b196dd2bea | ||
![]() |
10191f43fe | ||
![]() |
342f40c8d7 | ||
![]() |
895bc378df | ||
![]() |
00cafc8392 | ||
![]() |
a6d0c36594 | ||
![]() |
71a8573fdb | ||
![]() |
2715607361 | ||
![]() |
f2bfe6cd96 | ||
![]() |
9008d5eea4 | ||
![]() |
d340fc056e | ||
![]() |
f3e1b95147 | ||
![]() |
b5aa53fe7b | ||
![]() |
96c16bfb85 | ||
![]() |
d33226f3c2 | ||
![]() |
78fe52bfb8 | ||
![]() |
383cd6e73d | ||
![]() |
25fa0f739f | ||
![]() |
919b6a8d6c | ||
![]() |
92223b1dde | ||
![]() |
9a0f7286bc | ||
![]() |
71f2b73c36 | ||
![]() |
b34bdd2846 | ||
![]() |
392e432071 | ||
![]() |
09e48546ab | ||
![]() |
77ecdbe12a | ||
![]() |
1431c5a21a | ||
![]() |
8c63f669a9 | ||
![]() |
c009b39795 | ||
![]() |
dfd808b90e | ||
![]() |
75e46fc111 | ||
![]() |
337a0118c0 | ||
![]() |
2ee355d6a4 | ||
![]() |
4fa0876d91 | ||
![]() |
46d4e2898d | ||
![]() |
4e410473cb | ||
![]() |
fdddd7d58c | ||
![]() |
563106c0d2 | ||
![]() |
b6d8db5259 | ||
![]() |
5e67bd773f | ||
![]() |
aaab44090d | ||
![]() |
7b154fcc45 | ||
![]() |
d2779061b0 | ||
![]() |
3e20642b31 | ||
![]() |
a46032b549 | ||
![]() |
8ca8225cef | ||
![]() |
0e6cf6a485 | ||
![]() |
37cdba370f | ||
![]() |
d5f87fe09f | ||
![]() |
2930fa9cc9 | ||
![]() |
53c3201c17 | ||
![]() |
4229d68d23 | ||
![]() |
8b0bdc71bc | ||
![]() |
47e66580db | ||
![]() |
c360777ee0 | ||
![]() |
05874e9f81 | ||
![]() |
c3e1d5313d | ||
![]() |
4b36dce29f | ||
![]() |
d84ad44b74 | ||
![]() |
b60468d2b6 | ||
![]() |
35d041a701 | ||
![]() |
045ba0671b | ||
![]() |
bbc2847530 | ||
![]() |
887f2a2c24 | ||
![]() |
2b265b2529 | ||
![]() |
f0da8a75b0 | ||
![]() |
9aa2110409 | ||
![]() |
047bd4e7cc | ||
![]() |
10d781c570 | ||
![]() |
c2aa7f1748 | ||
![]() |
4ace113965 | ||
![]() |
69933e240f | ||
![]() |
9ac6ed344c | ||
![]() |
c9c0d3723b | ||
![]() |
c09876cbe2 | ||
![]() |
6bb4d27a3f | ||
![]() |
48c3a3a834 | ||
![]() |
24dcb4b783 | ||
![]() |
22d6f48bb8 | ||
![]() |
df98fb012e | ||
![]() |
ea44ab0c85 | ||
![]() |
b1759c8882 | ||
![]() |
c633d87f1e | ||
![]() |
680e829824 | ||
![]() |
891a352f42 | ||
![]() |
df829e8927 | ||
![]() |
f2ae3af90e | ||
![]() |
62b991649b | ||
![]() |
767dce29f4 | ||
![]() |
7f1c91d8f4 | ||
![]() |
3a0bacde3a | ||
![]() |
7f12418e4c | ||
![]() |
40013f7292 | ||
![]() |
2070c8c102 | ||
![]() |
eb19a73044 | ||
![]() |
87ce499840 | ||
![]() |
9a3dbedc52 | ||
![]() |
0cebb4c9d7 | ||
![]() |
fc25b0e10d | ||
![]() |
006b89746a | ||
![]() |
1f7838ba5f | ||
![]() |
e5e6876cef | ||
![]() |
2686615304 | ||
![]() |
e512847652 | ||
![]() |
4fb158933e | ||
![]() |
575af23e23 | ||
![]() |
52c468d89c | ||
![]() |
80e241c86f | ||
![]() |
c8199c6303 | ||
![]() |
090f68bb21 | ||
![]() |
5b4f0d4304 | ||
![]() |
1efb8c765b | ||
![]() |
1c0d0daef8 | ||
![]() |
302573e860 | ||
![]() |
5e58fc60d4 | ||
![]() |
1322926d9b | ||
![]() |
a64fa15fee | ||
![]() |
71c620f38f | ||
![]() |
65d9ac3c61 | ||
![]() |
f752e6df1e | ||
![]() |
19bcb9cea0 | ||
![]() |
7b22330583 | ||
![]() |
1be2b3721a | ||
![]() |
e53488cd64 | ||
![]() |
fe5ca1a67e | ||
![]() |
0670423a3d | ||
![]() |
e9620df5b5 | ||
![]() |
2a425f4344 | ||
![]() |
ee63002f21 | ||
![]() |
2d94b2999f | ||
![]() |
7a055e65db | ||
![]() |
e385214121 | ||
![]() |
b0116ee539 | ||
![]() |
301fed30b2 | ||
![]() |
e449b9c193 | ||
![]() |
bafcf6bd23 | ||
![]() |
15788bec67 | ||
![]() |
e921354544 | ||
![]() |
eb7648abc2 | ||
![]() |
9a45f4a8c9 | ||
![]() |
1f3165859f | ||
![]() |
2d6e7186aa | ||
![]() |
efde40cbbd | ||
![]() |
f3c2a15e53 | ||
![]() |
d64853a6f5 | ||
![]() |
b72d887dd7 | ||
![]() |
49ebf969c1 | ||
![]() |
1a6b16d493 | ||
![]() |
6fd7e27e95 | ||
![]() |
28c6377db7 | ||
![]() |
67f21bb518 | ||
![]() |
7c0e113fbc | ||
![]() |
bc3ace60dc | ||
![]() |
ce2310b1ae | ||
![]() |
6979a11bfa | ||
![]() |
10a4ac4809 | ||
![]() |
34341e7aac | ||
![]() |
ac7ff491e1 | ||
![]() |
abd3bc13d2 | ||
![]() |
ebed5c2f4b | ||
![]() |
bcebf0ee7b | ||
![]() |
95ee2cb709 | ||
![]() |
9faecccc9c | ||
![]() |
49babdcae9 | ||
![]() |
ef3b29bc5d | ||
![]() |
a2da7a5080 | ||
![]() |
f37e44a6f7 | ||
![]() |
d45b2a7c70 | ||
![]() |
b0b7e8d25d | ||
![]() |
7eba029d1f | ||
![]() |
82d12b3eeb | ||
![]() |
dd07495624 | ||
![]() |
8783df8d8d | ||
![]() |
d4cce8cdff | ||
![]() |
8a17afb6e3 | ||
![]() |
2bbfde40f0 | ||
![]() |
7cf230ec1f | ||
![]() |
c5e2789324 | ||
![]() |
5d96076587 | ||
![]() |
2e872069fb | ||
![]() |
ae51870db5 | ||
![]() |
7409ccad66 | ||
![]() |
cff066a7be | ||
![]() |
a198124894 | ||
![]() |
58f6659e40 | ||
![]() |
bd16299ffb | ||
![]() |
7656adc8b0 | ||
![]() |
4b3f9e5f42 | ||
![]() |
febb7c32c1 | ||
![]() |
94bb9ed00d | ||
![]() |
5fbd4f2d4e | ||
![]() |
50f1decee7 | ||
![]() |
c3176b0ca3 | ||
![]() |
f29354e0f4 | ||
![]() |
67b774faca | ||
![]() |
a08a839385 | ||
![]() |
425078652e | ||
![]() |
76a6959cf0 | ||
![]() |
b7b5cf2f2d | ||
![]() |
2ff067be6d | ||
![]() |
2cd6a9e720 | ||
![]() |
ca33692459 | ||
![]() |
32bd8aa105 | ||
![]() |
080ff7043e | ||
![]() |
c5102452e4 | ||
![]() |
99f2905cab | ||
![]() |
34d59f66d9 | ||
![]() |
88b2954c90 | ||
![]() |
d1aeff7bbf | ||
![]() |
371ef6cad8 | ||
![]() |
053b038e74 | ||
![]() |
acdd9bb674 | ||
![]() |
bc4844df3f | ||
![]() |
372af86250 | ||
![]() |
a13f4197d4 | ||
![]() |
356e71709a | ||
![]() |
c48988afcb | ||
![]() |
48b0658a52 | ||
![]() |
9fa4106c04 | ||
![]() |
8a7ab7bc78 | ||
![]() |
d3ae59eea6 | ||
![]() |
6a7cb3dcc8 | ||
![]() |
7f2050b522 | ||
![]() |
3c35aeb9a8 | ||
![]() |
c02ab23b3d | ||
![]() |
3a06310d37 | ||
![]() |
22b9a5e5dc | ||
![]() |
75fd4b2525 | ||
![]() |
a78655c5a7 | ||
![]() |
fa79e233b7 | ||
![]() |
1e174e1abc | ||
![]() |
a87b2e680c | ||
![]() |
ec6123d39d | ||
![]() |
f381c2e649 | ||
![]() |
5c3530cc7f | ||
![]() |
6ca5b3aa70 | ||
![]() |
e6a5dd1273 | ||
![]() |
358b830747 | ||
![]() |
a91e94dd16 | ||
![]() |
26f31a11f7 | ||
![]() |
3dc0a8388b | ||
![]() |
acc1fe9274 | ||
![]() |
7c273296c2 | ||
![]() |
815034f0f1 | ||
![]() |
c8c39aa40d | ||
![]() |
b34119c908 | ||
![]() |
b9331dbd57 | ||
![]() |
c928d10316 | ||
![]() |
b43125e9e8 | ||
![]() |
451dccfbf4 | ||
![]() |
eb8b9c4d98 | ||
![]() |
e79b43e906 | ||
![]() |
a1dc73882a | ||
![]() |
0fb78f19ec | ||
![]() |
81a410db91 | ||
![]() |
924aeb4abb | ||
![]() |
b966258849 | ||
![]() |
9031b9aa57 | ||
![]() |
cbe4095533 | ||
![]() |
1be278779d | ||
![]() |
8c9d2f0c4f | ||
![]() |
76fc077e3b | ||
![]() |
8e6d9de536 | ||
![]() |
93045957a0 | ||
![]() |
e71d181a23 | ||
![]() |
fcbc6e06c8 | ||
![]() |
33c6e68b5e | ||
![]() |
a4d241524c | ||
![]() |
af1c71f7ff | ||
![]() |
78c57805d5 | ||
![]() |
cc324a6d4b | ||
![]() |
8437f47f36 | ||
![]() |
89bde5db17 | ||
![]() |
f43ebe8d51 | ||
![]() |
341bc42d95 | ||
![]() |
493f9ab331 | ||
![]() |
e9753fd65d | ||
![]() |
3b136339af | ||
![]() |
1821c21243 | ||
![]() |
e675ab85c7 | ||
![]() |
58f005eea2 | ||
![]() |
d34e84ae9d | ||
![]() |
981ef2ca3b | ||
![]() |
c87fcd9b71 | ||
![]() |
c69adfb506 | ||
![]() |
ac82f0f437 | ||
![]() |
c975f7eb4a | ||
![]() |
07b590e2c3 | ||
![]() |
0b98be05fd | ||
![]() |
0a54b1aa99 | ||
![]() |
e114f79e44 | ||
![]() |
3ff046affa | ||
![]() |
e26229c0b4 | ||
![]() |
6c000968c9 | ||
![]() |
8d79be7cfb | ||
![]() |
25264a43cf | ||
![]() |
4cd4fd1dff | ||
![]() |
e2a899327f | ||
![]() |
56601d93c3 | ||
![]() |
f2fa067025 | ||
![]() |
02cb5ec076 | ||
![]() |
571ca2dec6 | ||
![]() |
35a95b5f0c | ||
![]() |
ce9d9fd26d | ||
![]() |
d79a99323e | ||
![]() |
a81972067a | ||
![]() |
67f19a65b7 | ||
![]() |
a21b496d48 | ||
![]() |
7ff49705bc | ||
![]() |
6dc43dd70b | ||
![]() |
42c78a8ba7 | ||
![]() |
54449562bd | ||
![]() |
e29fad06ed | ||
![]() |
f1a5c7da55 | ||
![]() |
0239ff8646 | ||
![]() |
e4a64bd129 | ||
![]() |
a0354de3c1 | ||
![]() |
2e4e1ce82f | ||
![]() |
1f0ea679e5 | ||
![]() |
06f646099f | ||
![]() |
3360817cb6 | ||
![]() |
b84e929e8c | ||
![]() |
df74ff68ab | ||
![]() |
e042ad0b4a | ||
![]() |
246f9f9044 | ||
![]() |
03aa48a88c | ||
![]() |
de54056005 | ||
![]() |
5e2c133669 | ||
![]() |
4fc4cfe2cc | ||
![]() |
bc08f4de34 | ||
![]() |
12904ecc32 | ||
![]() |
601d371796 | ||
![]() |
30d9e09390 | ||
![]() |
ca33ccd66d | ||
![]() |
84deb1fa7a | ||
![]() |
2a0e5d90e6 | ||
![]() |
3c05033481 | ||
![]() |
7850a5d478 | ||
![]() |
f84c73eb15 | ||
![]() |
f5a3b1bc5a | ||
![]() |
b2fe8e5691 | ||
![]() |
9d4c410996 | ||
![]() |
dcae92ce4a | ||
![]() |
29957b8cd8 | ||
![]() |
6299e0368c | ||
![]() |
c862b6062d | ||
![]() |
146587ffff | ||
![]() |
077d8dec9a | ||
![]() |
af8d6086fc | ||
![]() |
18f8661d73 | ||
![]() |
bd70f66c70 | ||
![]() |
ac213fc4b5 | ||
![]() |
db33549173 | ||
![]() |
e985e2b84c | ||
![]() |
1d9abf7528 | ||
![]() |
9607edcc23 | ||
![]() |
e082b923e0 | ||
![]() |
dd4df873b4 | ||
![]() |
3adbfe315e | ||
![]() |
6000a84ffc | ||
![]() |
d429433bb2 | ||
![]() |
5de870be41 | ||
![]() |
1fc75086aa | ||
![]() |
fa3437c09a | ||
![]() |
01b27645fb | ||
![]() |
373c3f82dd | ||
![]() |
5c39325104 | ||
![]() |
0304dd0040 | ||
![]() |
a549edfd75 | ||
![]() |
25e6b31a5f | ||
![]() |
3c21e7d45b | ||
![]() |
7c6972df7e | ||
![]() |
753bd0701f | ||
![]() |
c5faf2c5ea | ||
![]() |
c50cd1ba7f | ||
![]() |
a69e906c6e | ||
![]() |
f7f4759bde | ||
![]() |
897f5f62d5 |
@@ -1,21 +0,0 @@
|
|||||||
# Python CircleCI 2.0 configuration file
|
|
||||||
# Updating CircleCI configuration from v1 to v2
|
|
||||||
# Check https://circleci.com/docs/2.0/language-python/ for more details
|
|
||||||
#
|
|
||||||
version: 2
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
machine: true
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- run:
|
|
||||||
name: build images
|
|
||||||
command: |
|
|
||||||
docker build -t jupyterhub/jupyterhub .
|
|
||||||
docker build -t jupyterhub/jupyterhub-onbuild onbuild
|
|
||||||
docker build -t jupyterhub/jupyterhub:alpine -f dockerfiles/Dockerfile.alpine .
|
|
||||||
docker build -t jupyterhub/singleuser singleuser
|
|
||||||
- run:
|
|
||||||
name: smoke test jupyterhub
|
|
||||||
command: |
|
|
||||||
docker run --rm -it jupyterhub/jupyterhub jupyterhub --help
|
|
@@ -1,4 +1,5 @@
|
|||||||
[run]
|
[run]
|
||||||
|
parallel = True
|
||||||
branch = False
|
branch = False
|
||||||
omit =
|
omit =
|
||||||
jupyterhub/tests/*
|
jupyterhub/tests/*
|
||||||
|
5
.flake8
@@ -10,13 +10,12 @@
|
|||||||
# E402: module level import not at top of file
|
# E402: module level import not at top of file
|
||||||
# I100: Import statements are in the wrong order
|
# I100: Import statements are in the wrong order
|
||||||
# I101: Imported names are in the wrong order. Should be
|
# I101: Imported names are in the wrong order. Should be
|
||||||
ignore = E, C, W, F401, F403, F811, F841, E402, I100, I101
|
ignore = E, C, W, F401, F403, F811, F841, E402, I100, I101, D400
|
||||||
|
builtins = c, get_config
|
||||||
exclude =
|
exclude =
|
||||||
.cache,
|
.cache,
|
||||||
.github,
|
.github,
|
||||||
docs,
|
docs,
|
||||||
examples,
|
|
||||||
jupyterhub/alembic*,
|
jupyterhub/alembic*,
|
||||||
onbuild,
|
onbuild,
|
||||||
scripts,
|
scripts,
|
||||||
|
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,37 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Hi! Thanks for using JupyterHub.
|
|
||||||
|
|
||||||
If you are reporting an issue with JupyterHub, please use the [GitHub issue](https://github.com/jupyterhub/jupyterhub/issues) search feature to check if your issue has been asked already. If it has, please add your comments to the existing issue.
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
|
|
||||||
- Running `jupyter troubleshoot` from the command line, if possible, and posting
|
|
||||||
its output would also be helpful.
|
|
||||||
- Running in `--debug` mode can also be helpful for troubleshooting.
|
|
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
name: Installation and configuration issues
|
|
||||||
about: Installation and configuration assistance
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
If you are having issues with installation or configuration, you may ask for help on the JupyterHub gitter channel or file an issue here.
|
|
180
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
# Build releases and (on tags) publish to PyPI
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
# always build releases (to make sure wheel-building works)
|
||||||
|
# but only publish to PyPI on tags
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-release:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: "14"
|
||||||
|
|
||||||
|
- name: install build package
|
||||||
|
run: |
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install build
|
||||||
|
pip freeze
|
||||||
|
|
||||||
|
- name: build release
|
||||||
|
run: |
|
||||||
|
python -m build --sdist --wheel .
|
||||||
|
ls -l dist
|
||||||
|
|
||||||
|
- name: verify wheel
|
||||||
|
run: |
|
||||||
|
cd dist
|
||||||
|
pip install ./*.whl
|
||||||
|
# verify data-files are installed where they are found
|
||||||
|
cat <<EOF | python
|
||||||
|
import os
|
||||||
|
from jupyterhub._data import DATA_FILES_PATH
|
||||||
|
print(f"DATA_FILES_PATH={DATA_FILES_PATH}")
|
||||||
|
assert os.path.exists(DATA_FILES_PATH), DATA_FILES_PATH
|
||||||
|
for subpath in (
|
||||||
|
"templates/page.html",
|
||||||
|
"static/css/style.min.css",
|
||||||
|
"static/components/jquery/dist/jquery.js",
|
||||||
|
):
|
||||||
|
path = os.path.join(DATA_FILES_PATH, subpath)
|
||||||
|
assert os.path.exists(path), path
|
||||||
|
print("OK")
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# ref: https://github.com/actions/upload-artifact#readme
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: jupyterhub-${{ github.sha }}
|
||||||
|
path: "dist/*"
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Publish to PyPI
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
env:
|
||||||
|
TWINE_USERNAME: __token__
|
||||||
|
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
pip install twine
|
||||||
|
twine upload --skip-existing dist/*
|
||||||
|
|
||||||
|
publish-docker:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
services:
|
||||||
|
# So that we can test this in PRs/branches
|
||||||
|
local-registry:
|
||||||
|
image: registry:2
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Should we push this image to a public registry?
|
||||||
|
run: |
|
||||||
|
if [ "${{ startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/master') }}" = "true" ]; then
|
||||||
|
# Empty => Docker Hub
|
||||||
|
echo "REGISTRY=" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "REGISTRY=localhost:5000/" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Setup docker to build for multiple platforms (requires qemu).
|
||||||
|
# See:
|
||||||
|
# https://github.com/docker/build-push-action/tree/v2.3.0#usage
|
||||||
|
# https://github.com/docker/build-push-action/blob/v2.3.0/docs/advanced/multi-platform.md
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
with:
|
||||||
|
# Allows pushing to registry on localhost:5000
|
||||||
|
driver-opts: network=host
|
||||||
|
|
||||||
|
# https://github.com/docker/login-action/tree/v1.8.0#docker-hub
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
if: env.REGISTRY != 'localhost:5000/'
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
# https://github.com/jupyterhub/action-major-minor-tag-calculator
|
||||||
|
# If this is a tagged build this will return additional parent tags.
|
||||||
|
# E.g. 1.2.3 is expanded to Docker tags
|
||||||
|
# [{prefix}:1.2.3, {prefix}:1.2, {prefix}:1, {prefix}:latest] unless
|
||||||
|
# this is a backported tag in which case the newer tags aren't updated.
|
||||||
|
# For branches this will return the branch name.
|
||||||
|
# If GITHUB_TOKEN isn't available (e.g. in PRs) returns no tags [].
|
||||||
|
- name: Get list of jupyterhub tags
|
||||||
|
id: jupyterhubtags
|
||||||
|
uses: jupyterhub/action-major-minor-tag-calculator@v1
|
||||||
|
with:
|
||||||
|
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
prefix: "${{ env.REGISTRY }}jupyterhub/jupyterhub:"
|
||||||
|
defaultTag: "${{ env.REGISTRY }}jupyterhub/jupyterhub:noref"
|
||||||
|
|
||||||
|
- name: Build and push jupyterhub
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
# tags parameter must be a string input so convert `gettags` JSON
|
||||||
|
# array into a comma separated list of tags
|
||||||
|
tags: ${{ join(fromJson(steps.jupyterhubtags.outputs.tags)) }}
|
||||||
|
|
||||||
|
# jupyterhub-onbuild
|
||||||
|
|
||||||
|
- name: Get list of jupyterhub-onbuild tags
|
||||||
|
id: onbuildtags
|
||||||
|
uses: jupyterhub/action-major-minor-tag-calculator@v1
|
||||||
|
with:
|
||||||
|
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
prefix: "${{ env.REGISTRY }}jupyterhub/jupyterhub-onbuild:"
|
||||||
|
defaultTag: "${{ env.REGISTRY }}jupyterhub/jupyterhub-onbuild:noref"
|
||||||
|
|
||||||
|
- name: Build and push jupyterhub-onbuild
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
build-args: |
|
||||||
|
BASE_IMAGE=${{ fromJson(steps.jupyterhubtags.outputs.tags)[0] }}
|
||||||
|
context: onbuild
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ join(fromJson(steps.onbuildtags.outputs.tags)) }}
|
||||||
|
|
||||||
|
# jupyterhub-demo
|
||||||
|
|
||||||
|
- name: Get list of jupyterhub-demo tags
|
||||||
|
id: demotags
|
||||||
|
uses: jupyterhub/action-major-minor-tag-calculator@v1
|
||||||
|
with:
|
||||||
|
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
prefix: "${{ env.REGISTRY }}jupyterhub/jupyterhub-demo:"
|
||||||
|
defaultTag: "${{ env.REGISTRY }}jupyterhub/jupyterhub-demo:noref"
|
||||||
|
|
||||||
|
- name: Build and push jupyterhub-demo
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
build-args: |
|
||||||
|
BASE_IMAGE=${{ fromJson(steps.onbuildtags.outputs.tags)[0] }}
|
||||||
|
context: demo-image
|
||||||
|
# linux/arm64 currently fails:
|
||||||
|
# ERROR: Could not build wheels for argon2-cffi which use PEP 517 and cannot be installed directly
|
||||||
|
# ERROR: executor failed running [/bin/sh -c python3 -m pip install notebook]: exit code: 1
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ join(fromJson(steps.demotags.outputs.tags)) }}
|
246
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
# This is a GitHub workflow defining a set of jobs with a set of steps.
|
||||||
|
# ref: https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions
|
||||||
|
#
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
# Trigger the workflow's on all PRs but only on pushed tags or commits to
|
||||||
|
# main/master branch to avoid PRs developed in a GitHub fork's dedicated branch
|
||||||
|
# to trigger.
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
# Declare bash be used by default in this workflow's "run" steps.
|
||||||
|
#
|
||||||
|
# NOTE: bash will by default run with:
|
||||||
|
# --noprofile: Ignore ~/.profile etc.
|
||||||
|
# --norc: Ignore ~/.bashrc etc.
|
||||||
|
# -e: Exit directly on errors
|
||||||
|
# -o pipefail: Don't mask errors from a command piped into another command
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
env:
|
||||||
|
# UTF-8 content may be interpreted as ascii and causes errors without this.
|
||||||
|
LANG: C.UTF-8
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Run "pre-commit run --all-files"
|
||||||
|
pre-commit:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
timeout-minutes: 2
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
|
||||||
|
# ref: https://github.com/pre-commit/action
|
||||||
|
- uses: pre-commit/action@v2.0.0
|
||||||
|
- name: Help message if pre-commit fail
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: |
|
||||||
|
echo "You can install pre-commit hooks to automatically run formatting"
|
||||||
|
echo "on each commit with:"
|
||||||
|
echo " pre-commit install"
|
||||||
|
echo "or you can run by hand on staged files with"
|
||||||
|
echo " pre-commit run"
|
||||||
|
echo "or after-the-fact on already committed files with"
|
||||||
|
echo " pre-commit run --all-files"
|
||||||
|
|
||||||
|
# Run "pytest jupyterhub/tests" in various configurations
|
||||||
|
pytest:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
# Keep running even if one variation of the job fail
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# We run this job multiple times with different parameterization
|
||||||
|
# specified below, these parameters have no meaning on their own and
|
||||||
|
# gain meaning on how job steps use them.
|
||||||
|
#
|
||||||
|
# subdomain:
|
||||||
|
# Tests everything when JupyterHub is configured to add routes for
|
||||||
|
# users with dedicated subdomains like user1.jupyter.example.com
|
||||||
|
# rather than jupyter.example.com/user/user1.
|
||||||
|
#
|
||||||
|
# db: [mysql/postgres]
|
||||||
|
# Tests everything when JupyterHub works against a dedicated mysql or
|
||||||
|
# postgresql server.
|
||||||
|
#
|
||||||
|
# jupyter_server:
|
||||||
|
# Tests everything when the user instances are started with
|
||||||
|
# jupyter_server instead of notebook.
|
||||||
|
#
|
||||||
|
# ssl:
|
||||||
|
# Tests everything using internal SSL connections instead of
|
||||||
|
# unencrypted HTTP
|
||||||
|
#
|
||||||
|
# main_dependencies:
|
||||||
|
# Tests everything when the we use the latest available dependencies
|
||||||
|
# from: ipytraitlets.
|
||||||
|
#
|
||||||
|
# NOTE: Since only the value of these parameters are presented in the
|
||||||
|
# GitHub UI when the workflow run, we avoid using true/false as
|
||||||
|
# values by instead duplicating the name to signal true.
|
||||||
|
include:
|
||||||
|
- python: "3.6"
|
||||||
|
oldest_dependencies: oldest_dependencies
|
||||||
|
- python: "3.6"
|
||||||
|
subdomain: subdomain
|
||||||
|
- python: "3.7"
|
||||||
|
db: mysql
|
||||||
|
- python: "3.7"
|
||||||
|
ssl: ssl
|
||||||
|
- python: "3.8"
|
||||||
|
db: postgres
|
||||||
|
- python: "3.8"
|
||||||
|
jupyter_server: jupyter_server
|
||||||
|
- python: "3.9"
|
||||||
|
main_dependencies: main_dependencies
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# NOTE: In GitHub workflows, environment variables are set by writing
|
||||||
|
# assignment statements to a file. They will be set in the following
|
||||||
|
# steps as if would used `export MY_ENV=my-value`.
|
||||||
|
- name: Configure environment variables
|
||||||
|
run: |
|
||||||
|
if [ "${{ matrix.subdomain }}" != "" ]; then
|
||||||
|
echo "JUPYTERHUB_TEST_SUBDOMAIN_HOST=http://localhost.jovyan.org:8000" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
if [ "${{ matrix.db }}" == "mysql" ]; then
|
||||||
|
echo "MYSQL_HOST=127.0.0.1" >> $GITHUB_ENV
|
||||||
|
echo "JUPYTERHUB_TEST_DB_URL=mysql+mysqlconnector://root@127.0.0.1:3306/jupyterhub" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
if [ "${{ matrix.ssl }}" == "ssl" ]; then
|
||||||
|
echo "SSL_ENABLED=1" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
if [ "${{ matrix.db }}" == "postgres" ]; then
|
||||||
|
echo "PGHOST=127.0.0.1" >> $GITHUB_ENV
|
||||||
|
echo "PGUSER=test_user" >> $GITHUB_ENV
|
||||||
|
echo "PGPASSWORD=hub[test/:?" >> $GITHUB_ENV
|
||||||
|
echo "JUPYTERHUB_TEST_DB_URL=postgresql://test_user:hub%5Btest%2F%3A%3F@127.0.0.1:5432/jupyterhub" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
if [ "${{ matrix.jupyter_server }}" != "" ]; then
|
||||||
|
echo "JUPYTERHUB_SINGLEUSER_APP=jupyterhub.tests.mockserverapp.MockServerApp" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
# NOTE: actions/setup-node@v1 make use of a cache within the GitHub base
|
||||||
|
# environment and setup in a fraction of a second.
|
||||||
|
- name: Install Node v14
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: "14"
|
||||||
|
- name: Install Node dependencies
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
npm install -g configurable-http-proxy
|
||||||
|
npm list
|
||||||
|
|
||||||
|
# NOTE: actions/setup-python@v2 make use of a cache within the GitHub base
|
||||||
|
# environment and setup in a fraction of a second.
|
||||||
|
- name: Install Python ${{ matrix.python }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python }}
|
||||||
|
- name: Install Python dependencies
|
||||||
|
run: |
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install --upgrade . -r dev-requirements.txt
|
||||||
|
|
||||||
|
if [ "${{ matrix.oldest_dependencies }}" != "" ]; then
|
||||||
|
# take any dependencies in requirements.txt such as tornado>=5.0
|
||||||
|
# and transform them to tornado==5.0 so we can run tests with
|
||||||
|
# the earliest-supported versions
|
||||||
|
cat requirements.txt | grep '>=' | sed -e 's@>=@==@g' > oldest-requirements.txt
|
||||||
|
pip install -r oldest-requirements.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${{ matrix.main_dependencies }}" != "" ]; then
|
||||||
|
pip install git+https://github.com/ipython/traitlets#egg=traitlets --force
|
||||||
|
fi
|
||||||
|
if [ "${{ matrix.jupyter_server }}" != "" ]; then
|
||||||
|
pip uninstall notebook --yes
|
||||||
|
pip install jupyter_server
|
||||||
|
fi
|
||||||
|
if [ "${{ matrix.db }}" == "mysql" ]; then
|
||||||
|
pip install mysql-connector-python
|
||||||
|
fi
|
||||||
|
if [ "${{ matrix.db }}" == "postgres" ]; then
|
||||||
|
pip install psycopg2-binary
|
||||||
|
fi
|
||||||
|
|
||||||
|
pip freeze
|
||||||
|
|
||||||
|
# NOTE: If you need to debug this DB setup step, consider the following.
|
||||||
|
#
|
||||||
|
# 1. mysql/postgressql are database servers we start as docker containers,
|
||||||
|
# and we use clients named mysql/psql.
|
||||||
|
#
|
||||||
|
# 2. When we start a database server we need to pass environment variables
|
||||||
|
# explicitly as part of the `docker run` command. These environment
|
||||||
|
# variables are named differently from the similarly named environment
|
||||||
|
# variables used by the clients.
|
||||||
|
#
|
||||||
|
# - mysql server ref: https://hub.docker.com/_/mysql/
|
||||||
|
# - mysql client ref: https://dev.mysql.com/doc/refman/5.7/en/environment-variables.html
|
||||||
|
# - postgres server ref: https://hub.docker.com/_/postgres/
|
||||||
|
# - psql client ref: https://www.postgresql.org/docs/9.5/libpq-envars.html
|
||||||
|
#
|
||||||
|
# 3. When we connect, they should use 127.0.0.1 rather than the
|
||||||
|
# default way of connecting which leads to errors like below both for
|
||||||
|
# mysql and postgresql unless we set MYSQL_HOST/PGHOST to 127.0.0.1.
|
||||||
|
#
|
||||||
|
# - ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
|
||||||
|
#
|
||||||
|
- name: Start a database server (${{ matrix.db }})
|
||||||
|
if: ${{ matrix.db }}
|
||||||
|
run: |
|
||||||
|
if [ "${{ matrix.db }}" == "mysql" ]; then
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y mysql-client
|
||||||
|
DB=mysql bash ci/docker-db.sh
|
||||||
|
DB=mysql bash ci/init-db.sh
|
||||||
|
fi
|
||||||
|
if [ "${{ matrix.db }}" == "postgres" ]; then
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y postgresql-client
|
||||||
|
DB=postgres bash ci/docker-db.sh
|
||||||
|
DB=postgres bash ci/init-db.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run pytest
|
||||||
|
# FIXME: --color=yes explicitly set because:
|
||||||
|
# https://github.com/actions/runner/issues/241
|
||||||
|
run: |
|
||||||
|
pytest -v --maxfail=2 --color=yes --cov=jupyterhub jupyterhub/tests
|
||||||
|
- name: Submit codecov report
|
||||||
|
run: |
|
||||||
|
codecov
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: build images
|
||||||
|
run: |
|
||||||
|
docker build -t jupyterhub/jupyterhub .
|
||||||
|
docker build -t jupyterhub/jupyterhub-onbuild onbuild
|
||||||
|
docker build -t jupyterhub/jupyterhub:alpine -f dockerfiles/Dockerfile.alpine .
|
||||||
|
docker build -t jupyterhub/singleuser singleuser
|
||||||
|
|
||||||
|
- name: smoke test jupyterhub
|
||||||
|
run: |
|
||||||
|
docker run --rm -t jupyterhub/jupyterhub jupyterhub --help
|
||||||
|
|
||||||
|
- name: verify static files
|
||||||
|
run: |
|
||||||
|
docker run --rm -t -v $PWD/dockerfiles:/io jupyterhub/jupyterhub python3 /io/test.py
|
5
.gitignore
vendored
@@ -21,6 +21,11 @@ share/jupyterhub/static/css/style.min.css.map
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
MANIFEST
|
MANIFEST
|
||||||
.coverage
|
.coverage
|
||||||
|
.coverage.*
|
||||||
htmlcov
|
htmlcov
|
||||||
.idea/
|
.idea/
|
||||||
|
.vscode/
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
|
pip-wheel-metadata
|
||||||
|
docs/source/reference/metrics.rst
|
||||||
|
oldest-requirements.txt
|
||||||
|
24
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
|
rev: v1.9.0
|
||||||
|
hooks:
|
||||||
|
- id: reorder-python-imports
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 20.8b1
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
|
rev: v2.2.1
|
||||||
|
hooks:
|
||||||
|
- id: prettier
|
||||||
|
- repo: https://gitlab.com/pycqa/flake8
|
||||||
|
rev: "3.8.4"
|
||||||
|
hooks:
|
||||||
|
- id: flake8
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v3.4.0
|
||||||
|
hooks:
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-case-conflict
|
||||||
|
- id: check-executables-have-shebangs
|
||||||
|
- id: requirements-txt-fixer
|
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
share/jupyterhub/templates/
|
68
.travis.yml
@@ -1,68 +0,0 @@
|
|||||||
language: python
|
|
||||||
sudo: false
|
|
||||||
cache:
|
|
||||||
- pip
|
|
||||||
python:
|
|
||||||
- 3.6
|
|
||||||
- 3.5
|
|
||||||
- nightly
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- ASYNC_TEST_TIMEOUT=15
|
|
||||||
- MYSQL_HOST=127.0.0.1
|
|
||||||
- MYSQL_TCP_PORT=13306
|
|
||||||
services:
|
|
||||||
- postgres
|
|
||||||
- docker
|
|
||||||
|
|
||||||
# installing dependencies
|
|
||||||
before_install:
|
|
||||||
- nvm install 6; nvm use 6
|
|
||||||
- npm install
|
|
||||||
- npm install -g configurable-http-proxy
|
|
||||||
- |
|
|
||||||
# setup database
|
|
||||||
if [[ $JUPYTERHUB_TEST_DB_URL == mysql* ]]; then
|
|
||||||
unset MYSQL_UNIX_PORT
|
|
||||||
DB=mysql bash ci/docker-db.sh
|
|
||||||
DB=mysql bash ci/init-db.sh
|
|
||||||
pip install 'mysql-connector<2.2'
|
|
||||||
elif [[ $JUPYTERHUB_TEST_DB_URL == postgresql* ]]; then
|
|
||||||
DB=postgres bash ci/init-db.sh
|
|
||||||
pip install psycopg2-binary
|
|
||||||
fi
|
|
||||||
install:
|
|
||||||
- pip install --upgrade pip
|
|
||||||
- pip install --pre -r dev-requirements.txt .
|
|
||||||
- pip freeze
|
|
||||||
|
|
||||||
# running tests
|
|
||||||
script:
|
|
||||||
- |
|
|
||||||
# run tests
|
|
||||||
set -e
|
|
||||||
pytest -v --maxfail=2 --cov=jupyterhub jupyterhub/tests
|
|
||||||
- |
|
|
||||||
# build docs
|
|
||||||
pushd docs
|
|
||||||
pip install -r requirements.txt
|
|
||||||
make html
|
|
||||||
popd
|
|
||||||
after_success:
|
|
||||||
- codecov
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
fast_finish: true
|
|
||||||
include:
|
|
||||||
- python: 3.6
|
|
||||||
env: JUPYTERHUB_TEST_SUBDOMAIN_HOST=http://localhost.jovyan.org:8000
|
|
||||||
- python: 3.6
|
|
||||||
env:
|
|
||||||
- JUPYTERHUB_TEST_DB_URL=mysql+mysqlconnector://root@127.0.0.1:$MYSQL_TCP_PORT/jupyterhub
|
|
||||||
- python: 3.6
|
|
||||||
env:
|
|
||||||
- JUPYTERHUB_TEST_DB_URL=postgresql://postgres@127.0.0.1/jupyterhub
|
|
||||||
- python: 3.7
|
|
||||||
dist: xenial
|
|
||||||
allow_failures:
|
|
||||||
- python: nightly
|
|
@@ -2,24 +2,24 @@
|
|||||||
|
|
||||||
- [ ] Upgrade Docs prior to Release
|
- [ ] Upgrade Docs prior to Release
|
||||||
|
|
||||||
- [ ] Change log
|
- [ ] Change log
|
||||||
- [ ] New features documented
|
- [ ] New features documented
|
||||||
- [ ] Update the contributor list - thank you page
|
- [ ] Update the contributor list - thank you page
|
||||||
|
|
||||||
- [ ] Upgrade and test Reference Deployments
|
- [ ] Upgrade and test Reference Deployments
|
||||||
|
|
||||||
- [ ] Release software
|
- [ ] Release software
|
||||||
|
|
||||||
- [ ] Make sure 0 issues in milestone
|
- [ ] Make sure 0 issues in milestone
|
||||||
- [ ] Follow release process steps
|
- [ ] Follow release process steps
|
||||||
- [ ] Send builds to PyPI (Warehouse) and Conda Forge
|
- [ ] Send builds to PyPI (Warehouse) and Conda Forge
|
||||||
|
|
||||||
- [ ] Blog post and/or release note
|
- [ ] Blog post and/or release note
|
||||||
|
|
||||||
- [ ] Notify users of release
|
- [ ] Notify users of release
|
||||||
|
|
||||||
- [ ] Email Jupyter and Jupyter In Education mailing lists
|
- [ ] Email Jupyter and Jupyter In Education mailing lists
|
||||||
- [ ] Tweet (optional)
|
- [ ] Tweet (optional)
|
||||||
|
|
||||||
- [ ] Increment the version number for the next release
|
- [ ] Increment the version number for the next release
|
||||||
|
|
||||||
|
181
CONTRIBUTING.md
@@ -1,98 +1,139 @@
|
|||||||
# Contributing
|
# Contributing to JupyterHub
|
||||||
|
|
||||||
Welcome! As a [Jupyter](https://jupyter.org) project, we follow the [Jupyter contributor guide](https://jupyter.readthedocs.io/en/latest/contributor/content-contributor.html).
|
Welcome! As a [Jupyter](https://jupyter.org) project,
|
||||||
|
you can follow the [Jupyter contributor guide](https://jupyter.readthedocs.io/en/latest/contributing/content-contributor.html).
|
||||||
|
|
||||||
|
Make sure to also follow [Project Jupyter's Code of Conduct](https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md)
|
||||||
|
for a friendly and welcoming collaborative environment.
|
||||||
|
|
||||||
## Set up your development system
|
## Setting up a development environment
|
||||||
|
|
||||||
For a development install, clone the [repository](https://github.com/jupyterhub/jupyterhub)
|
<!--
|
||||||
and then install from source:
|
https://jupyterhub.readthedocs.io/en/stable/contributing/setup.html
|
||||||
|
contains a lot of the same information. Should we merge the docs and
|
||||||
|
just have this page link to that one?
|
||||||
|
-->
|
||||||
|
|
||||||
|
JupyterHub requires Python >= 3.5 and nodejs.
|
||||||
|
|
||||||
|
As a Python project, a development install of JupyterHub follows standard practices for the basics (steps 1-2).
|
||||||
|
|
||||||
|
1. clone the repo
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/jupyterhub/jupyterhub
|
||||||
|
```
|
||||||
|
2. do a development install with pip
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd jupyterhub
|
||||||
|
python3 -m pip install --editable .
|
||||||
|
```
|
||||||
|
|
||||||
|
3. install the development requirements,
|
||||||
|
which include things like testing tools
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m pip install -r dev-requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
4. install configurable-http-proxy with npm:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g configurable-http-proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
5. set up pre-commit hooks for automatic code formatting, etc.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pre-commit install
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also invoke the pre-commit hook manually at any time with
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pre-commit run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
JupyterHub has adopted automatic code formatting so you shouldn't
|
||||||
|
need to worry too much about your code style.
|
||||||
|
As long as your code is valid,
|
||||||
|
the pre-commit hook should take care of how it should look.
|
||||||
|
You can invoke the pre-commit hook by hand at any time with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/jupyterhub/jupyterhub
|
pre-commit run
|
||||||
cd jupyterhub
|
|
||||||
npm install -g configurable-http-proxy
|
|
||||||
pip3 install -r dev-requirements.txt -e .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Troubleshooting a development install
|
which should run any autoformatting on your code
|
||||||
|
and tell you about any errors it couldn't fix automatically.
|
||||||
|
You may also install [black integration](https://github.com/psf/black#editor-integration)
|
||||||
|
into your text editor to format code automatically.
|
||||||
|
|
||||||
If the `pip3 install` command fails and complains about `lessc` being
|
If you have already committed files before setting up the pre-commit
|
||||||
unavailable, you may need to explicitly install some additional JavaScript
|
hook with `pre-commit install`, you can fix everything up using
|
||||||
dependencies:
|
`pre-commit run --all-files`. You need to make the fixing commit
|
||||||
|
yourself after that.
|
||||||
|
|
||||||
npm install
|
## Testing
|
||||||
|
|
||||||
This will fetch client-side JavaScript dependencies necessary to compile CSS.
|
It's a good idea to write tests to exercise any new features,
|
||||||
|
or that trigger any bugs that you have fixed to catch regressions.
|
||||||
|
|
||||||
You may also need to manually update JavaScript and CSS after some development
|
You can run the tests with:
|
||||||
updates, with:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 setup.py js # fetch updated client-side js
|
pytest -v
|
||||||
python3 setup.py css # recompile CSS from LESS sources
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running the test suite
|
in the repo directory. If you want to just run certain tests,
|
||||||
|
check out the [pytest docs](https://pytest.readthedocs.io/en/latest/usage.html)
|
||||||
We use [pytest](http://doc.pytest.org/en/latest/) for running tests.
|
for how pytest can be called.
|
||||||
|
For instance, to test only spawner-related things in the REST API:
|
||||||
1. Set up a development install as described above.
|
|
||||||
|
|
||||||
2. Set environment variable for `ASYNC_TEST_TIMEOUT` to 15 seconds:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ASYNC_TEST_TIMEOUT=15
|
pytest -v -k spawn jupyterhub/tests/test_api.py
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Run tests.
|
The tests live in `jupyterhub/tests` and are organized roughly into:
|
||||||
|
|
||||||
To run all the tests:
|
1. `test_api.py` tests the REST API
|
||||||
|
2. `test_pages.py` tests loading the HTML pages
|
||||||
|
|
||||||
```bash
|
and other collections of tests for different components.
|
||||||
pytest -v jupyterhub/tests
|
When writing a new test, there should usually be a test of
|
||||||
```
|
similar functionality already written and related tests should
|
||||||
|
be added nearby.
|
||||||
|
|
||||||
To run an individual test file (i.e. `test_api.py`):
|
The fixtures live in `jupyterhub/tests/conftest.py`. There are
|
||||||
|
fixtures that can be used for JupyterHub components, such as:
|
||||||
|
|
||||||
```bash
|
- `app`: an instance of JupyterHub with mocked parts
|
||||||
pytest -v jupyterhub/tests/test_api.py
|
- `auth_state_enabled`: enables persisting auth_state (like authentication tokens)
|
||||||
```
|
- `db`: a sqlite in-memory DB session
|
||||||
|
- `io_loop`: a Tornado event loop
|
||||||
|
- `event_loop`: a new asyncio event loop
|
||||||
|
- `user`: creates a new temporary user
|
||||||
|
- `admin_user`: creates a new temporary admin user
|
||||||
|
- single user servers
|
||||||
|
- `cleanup_after`: allows cleanup of single user servers between tests
|
||||||
|
- mocked service
|
||||||
|
- `MockServiceSpawner`: a spawner that mocks services for testing with a short poll interval
|
||||||
|
- `mockservice`: mocked service with no external service url
|
||||||
|
- `mockservice_url`: mocked service with a url to test external services
|
||||||
|
|
||||||
### Troubleshooting tests
|
And fixtures to add functionality or spawning behavior:
|
||||||
|
|
||||||
If you see test failures because of timeouts, you may wish to increase the
|
- `admin_access`: grants admin access
|
||||||
`ASYNC_TEST_TIMEOUT` used by the
|
- `no_patience`: sets slow-spawning timeouts to zero
|
||||||
[pytest-tornado-plugin](https://github.com/eugeniy/pytest-tornado/blob/c79f68de2222eb7cf84edcfe28650ebf309a4d0c/README.rst#markers)
|
- `slow_spawn`: enables the SlowSpawner (a spawner that takes a few seconds to start)
|
||||||
from the default of 5 seconds:
|
- `never_spawn`: enables the NeverSpawner (a spawner that will never start)
|
||||||
|
- `bad_spawn`: enables the BadSpawner (a spawner that fails immediately)
|
||||||
|
- `slow_bad_spawn`: enables the SlowBadSpawner (a spawner that fails after a short delay)
|
||||||
|
|
||||||
```bash
|
To read more about fixtures check out the
|
||||||
export ASYNC_TEST_TIMEOUT=15
|
[pytest docs](https://docs.pytest.org/en/latest/fixture.html)
|
||||||
```
|
for how to use the existing fixtures, and how to create new ones.
|
||||||
|
|
||||||
If you see many test errors and failures, double check that you have installed
|
When in doubt, feel free to [ask](https://gitter.im/jupyterhub/jupyterhub).
|
||||||
`configurable-http-proxy`.
|
|
||||||
|
|
||||||
## Building the Docs locally
|
|
||||||
|
|
||||||
1. Install the development system as described above.
|
|
||||||
|
|
||||||
2. Install the dependencies for documentation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 -m pip install -r docs/requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Build the docs:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd docs
|
|
||||||
make clean
|
|
||||||
make html
|
|
||||||
```
|
|
||||||
|
|
||||||
4. View the docs:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
open build/html/index.html
|
|
||||||
```
|
|
||||||
|
@@ -24,7 +24,7 @@ software without specific prior written permission.
|
|||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
@@ -46,8 +46,8 @@ Jupyter uses a shared copyright model. Each contributor maintains copyright
|
|||||||
over their contributions to Jupyter. But, it is important to note that these
|
over their contributions to Jupyter. But, it is important to note that these
|
||||||
contributions are typically only changes to the repositories. Thus, the Jupyter
|
contributions are typically only changes to the repositories. Thus, the Jupyter
|
||||||
source code, in its entirety is not the copyright of any single person or
|
source code, in its entirety is not the copyright of any single person or
|
||||||
institution. Instead, it is the collective copyright of the entire Jupyter
|
institution. Instead, it is the collective copyright of the entire Jupyter
|
||||||
Development Team. If individual contributors want to maintain a record of what
|
Development Team. If individual contributors want to maintain a record of what
|
||||||
changes/contributions they have specific copyright on, they should indicate
|
changes/contributions they have specific copyright on, they should indicate
|
||||||
their copyright in the commit message of the change, when they commit the
|
their copyright in the commit message of the change, when they commit the
|
||||||
change to one of the Jupyter repositories.
|
change to one of the Jupyter repositories.
|
||||||
|
87
Dockerfile
@@ -21,40 +21,81 @@
|
|||||||
# your jupyterhub_config.py will be added automatically
|
# your jupyterhub_config.py will be added automatically
|
||||||
# from your docker directory.
|
# from your docker directory.
|
||||||
|
|
||||||
FROM ubuntu:18.04
|
ARG BASE_IMAGE=ubuntu:focal-20200729
|
||||||
LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"
|
FROM $BASE_IMAGE AS builder
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
# install nodejs, utf8 locale, set CDN because default httpredir is unreliable
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
RUN apt-get -y update && \
|
RUN apt-get update \
|
||||||
apt-get -y upgrade && \
|
&& apt-get install -yq --no-install-recommends \
|
||||||
apt-get -y install wget git bzip2 && \
|
build-essential \
|
||||||
apt-get purge && \
|
ca-certificates \
|
||||||
apt-get clean && \
|
locales \
|
||||||
rm -rf /var/lib/apt/lists/*
|
python3-dev \
|
||||||
ENV LANG C.UTF-8
|
python3-pip \
|
||||||
|
python3-pycurl \
|
||||||
|
nodejs \
|
||||||
|
npm \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# install Python + NodeJS with conda
|
RUN python3 -m pip install --upgrade setuptools pip wheel
|
||||||
RUN wget -q https://repo.continuum.io/miniconda/Miniconda3-4.5.1-Linux-x86_64.sh -O /tmp/miniconda.sh && \
|
|
||||||
echo '0c28787e3126238df24c5d4858bd0744 */tmp/miniconda.sh' | md5sum -c - && \
|
|
||||||
bash /tmp/miniconda.sh -f -b -p /opt/conda && \
|
|
||||||
/opt/conda/bin/conda install --yes -c conda-forge \
|
|
||||||
python=3.6 sqlalchemy tornado jinja2 traitlets requests pip pycurl \
|
|
||||||
nodejs configurable-http-proxy && \
|
|
||||||
/opt/conda/bin/pip install --upgrade pip && \
|
|
||||||
rm /tmp/miniconda.sh
|
|
||||||
ENV PATH=/opt/conda/bin:$PATH
|
|
||||||
|
|
||||||
ADD . /src/jupyterhub
|
# copy everything except whats in .dockerignore, its a
|
||||||
|
# compromise between needing to rebuild and maintaining
|
||||||
|
# what needs to be part of the build
|
||||||
|
COPY . /src/jupyterhub/
|
||||||
WORKDIR /src/jupyterhub
|
WORKDIR /src/jupyterhub
|
||||||
|
|
||||||
RUN pip install . && \
|
# Build client component packages (they will be copied into ./share and
|
||||||
rm -rf $PWD ~/.cache ~/.npm
|
# packaged with the built wheel.)
|
||||||
|
RUN python3 setup.py bdist_wheel
|
||||||
|
RUN python3 -m pip wheel --wheel-dir wheelhouse dist/*.whl
|
||||||
|
|
||||||
|
|
||||||
|
FROM $BASE_IMAGE
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -yq --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
gnupg \
|
||||||
|
locales \
|
||||||
|
python3-pip \
|
||||||
|
python3-pycurl \
|
||||||
|
nodejs \
|
||||||
|
npm \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV SHELL=/bin/bash \
|
||||||
|
LC_ALL=en_US.UTF-8 \
|
||||||
|
LANG=en_US.UTF-8 \
|
||||||
|
LANGUAGE=en_US.UTF-8
|
||||||
|
|
||||||
|
RUN locale-gen $LC_ALL
|
||||||
|
|
||||||
|
# always make sure pip is up to date!
|
||||||
|
RUN python3 -m pip install --no-cache --upgrade setuptools pip
|
||||||
|
|
||||||
|
RUN npm install -g configurable-http-proxy@^4.2.0 \
|
||||||
|
&& rm -rf ~/.npm
|
||||||
|
|
||||||
|
# install the wheels we built in the first stage
|
||||||
|
COPY --from=builder /src/jupyterhub/wheelhouse /tmp/wheelhouse
|
||||||
|
RUN python3 -m pip install --no-cache /tmp/wheelhouse/*
|
||||||
|
|
||||||
RUN mkdir -p /srv/jupyterhub/
|
RUN mkdir -p /srv/jupyterhub/
|
||||||
WORKDIR /srv/jupyterhub/
|
WORKDIR /srv/jupyterhub/
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
|
LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"
|
||||||
LABEL org.jupyter.service="jupyterhub"
|
LABEL org.jupyter.service="jupyterhub"
|
||||||
|
|
||||||
CMD ["jupyterhub"]
|
CMD ["jupyterhub"]
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
|
|
54
README.md
@@ -6,17 +6,18 @@
|
|||||||
**[License](#license)** |
|
**[License](#license)** |
|
||||||
**[Help and Resources](#help-and-resources)**
|
**[Help and Resources](#help-and-resources)**
|
||||||
|
|
||||||
|
|
||||||
# [JupyterHub](https://github.com/jupyterhub/jupyterhub)
|
# [JupyterHub](https://github.com/jupyterhub/jupyterhub)
|
||||||
|
|
||||||
|
[](https://pypi.python.org/pypi/jupyterhub)
|
||||||
[](https://pypi.python.org/pypi/jupyterhub)
|
[](https://www.npmjs.com/package/jupyterhub)
|
||||||
[](https://jupyterhub.readthedocs.org/en/latest/?badge=latest)
|
[](https://jupyterhub.readthedocs.org/en/latest/)
|
||||||
[](https://jupyterhub.readthedocs.io/en/0.7.2/?badge=0.7.2)
|
[](https://github.com/jupyterhub/jupyterhub/actions)
|
||||||
[](https://travis-ci.org/jupyterhub/jupyterhub)
|
[](https://hub.docker.com/r/jupyterhub/jupyterhub/tags)
|
||||||
[](https://circleci.com/gh/jupyterhub/jupyterhub)
|
[](https://circleci.com/gh/jupyterhub/jupyterhub)<!-- CircleCI Token: b5b65862eb2617b9a8d39e79340b0a6b816da8cc -->
|
||||||
[](https://codecov.io/github/jupyterhub/jupyterhub?branch=master)
|
[](https://codecov.io/gh/jupyterhub/jupyterhub)
|
||||||
[](https://groups.google.com/forum/#!forum/jupyter)
|
[](https://github.com/jupyterhub/jupyterhub/issues)
|
||||||
|
[](https://discourse.jupyter.org/c/jupyterhub)
|
||||||
|
[](https://gitter.im/jupyterhub/jupyterhub)
|
||||||
|
|
||||||
With [JupyterHub](https://jupyterhub.readthedocs.io) you can create a
|
With [JupyterHub](https://jupyterhub.readthedocs.io) you can create a
|
||||||
**multi-user Hub** which spawns, manages, and proxies multiple instances of the
|
**multi-user Hub** which spawns, manages, and proxies multiple instances of the
|
||||||
@@ -50,17 +51,16 @@ for administration of the Hub and its users.
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
||||||
### Check prerequisites
|
### Check prerequisites
|
||||||
|
|
||||||
- A Linux/Unix based system
|
- A Linux/Unix based system
|
||||||
- [Python](https://www.python.org/downloads/) 3.5 or greater
|
- [Python](https://www.python.org/downloads/) 3.5 or greater
|
||||||
- [nodejs/npm](https://www.npmjs.com/)
|
- [nodejs/npm](https://www.npmjs.com/)
|
||||||
|
|
||||||
* If you are using **`conda`**, the nodejs and npm dependencies will be installed for
|
- If you are using **`conda`**, the nodejs and npm dependencies will be installed for
|
||||||
you by conda.
|
you by conda.
|
||||||
|
|
||||||
* If you are using **`pip`**, install a recent version of
|
- If you are using **`pip`**, install a recent version of
|
||||||
[nodejs/npm](https://docs.npmjs.com/getting-started/installing-node).
|
[nodejs/npm](https://docs.npmjs.com/getting-started/installing-node).
|
||||||
For example, install it on Linux (Debian/Ubuntu) using:
|
For example, install it on Linux (Debian/Ubuntu) using:
|
||||||
|
|
||||||
@@ -71,6 +71,7 @@ for administration of the Hub and its users.
|
|||||||
The `nodejs-legacy` package installs the `node` executable and is currently
|
The `nodejs-legacy` package installs the `node` executable and is currently
|
||||||
required for npm to work on Debian/Ubuntu.
|
required for npm to work on Debian/Ubuntu.
|
||||||
|
|
||||||
|
- If using the default PAM Authenticator, a [pluggable authentication module (PAM)](https://en.wikipedia.org/wiki/Pluggable_authentication_module).
|
||||||
- TLS certificate and key for HTTPS communication
|
- TLS certificate and key for HTTPS communication
|
||||||
- Domain name
|
- Domain name
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ JupyterHub can be installed with `pip`, and the proxy with `npm`:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install -g configurable-http-proxy
|
npm install -g configurable-http-proxy
|
||||||
python3 -m pip install jupyterhub
|
python3 -m pip install jupyterhub
|
||||||
```
|
```
|
||||||
|
|
||||||
If you plan to run notebook servers locally, you will need to install the
|
If you plan to run notebook servers locally, you will need to install the
|
||||||
@@ -116,10 +117,10 @@ To start the Hub server, run the command:
|
|||||||
Visit `https://localhost:8000` in your browser, and sign in with your unix
|
Visit `https://localhost:8000` in your browser, and sign in with your unix
|
||||||
PAM credentials.
|
PAM credentials.
|
||||||
|
|
||||||
*Note*: To allow multiple users to sign into the server, you will need to
|
_Note_: To allow multiple users to sign into the server, you will need to
|
||||||
run the `jupyterhub` command as a *privileged user*, such as root.
|
run the `jupyterhub` command as a _privileged user_, such as root.
|
||||||
The [wiki](https://github.com/jupyterhub/jupyterhub/wiki/Using-sudo-to-run-JupyterHub-without-root-privileges)
|
The [wiki](https://github.com/jupyterhub/jupyterhub/wiki/Using-sudo-to-run-JupyterHub-without-root-privileges)
|
||||||
describes how to run the server as a *less privileged user*, which requires
|
describes how to run the server as a _less privileged user_, which requires
|
||||||
more configuration of the system.
|
more configuration of the system.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -138,18 +139,18 @@ To generate a default config file with settings and descriptions:
|
|||||||
|
|
||||||
### Start the Hub
|
### Start the Hub
|
||||||
|
|
||||||
To start the Hub on a specific url and port ``10.0.1.2:443`` with **https**:
|
To start the Hub on a specific url and port `10.0.1.2:443` with **https**:
|
||||||
|
|
||||||
jupyterhub --ip 10.0.1.2 --port 443 --ssl-key my_ssl.key --ssl-cert my_ssl.cert
|
jupyterhub --ip 10.0.1.2 --port 443 --ssl-key my_ssl.key --ssl-cert my_ssl.cert
|
||||||
|
|
||||||
### Authenticators
|
### Authenticators
|
||||||
|
|
||||||
| Authenticator | Description |
|
| Authenticator | Description |
|
||||||
| --------------------------------------------------------------------------- | ------------------------------------------------- |
|
| ---------------------------------------------------------------------------- | ------------------------------------------------- |
|
||||||
| PAMAuthenticator | Default, built-in authenticator |
|
| PAMAuthenticator | Default, built-in authenticator |
|
||||||
| [OAuthenticator](https://github.com/jupyterhub/oauthenticator) | OAuth + JupyterHub Authenticator = OAuthenticator |
|
| [OAuthenticator](https://github.com/jupyterhub/oauthenticator) | OAuth + JupyterHub Authenticator = OAuthenticator |
|
||||||
| [ldapauthenticator](https://github.com/jupyterhub/ldapauthenticator) | Simple LDAP Authenticator Plugin for JupyterHub |
|
| [ldapauthenticator](https://github.com/jupyterhub/ldapauthenticator) | Simple LDAP Authenticator Plugin for JupyterHub |
|
||||||
| [kdcAuthenticator](https://github.com/bloomberg/jupyterhub-kdcauthenticator)| Kerberos Authenticator Plugin for JupyterHub |
|
| [kerberosauthenticator](https://github.com/jupyterhub/kerberosauthenticator) | Kerberos Authenticator Plugin for JupyterHub |
|
||||||
|
|
||||||
### Spawners
|
### Spawners
|
||||||
|
|
||||||
@@ -161,6 +162,7 @@ To start the Hub on a specific url and port ``10.0.1.2:443`` with **https**:
|
|||||||
| [sudospawner](https://github.com/jupyterhub/sudospawner) | Spawn single-user servers without being root |
|
| [sudospawner](https://github.com/jupyterhub/sudospawner) | Spawn single-user servers without being root |
|
||||||
| [systemdspawner](https://github.com/jupyterhub/systemdspawner) | Spawn single-user notebook servers using systemd |
|
| [systemdspawner](https://github.com/jupyterhub/systemdspawner) | Spawn single-user notebook servers using systemd |
|
||||||
| [batchspawner](https://github.com/jupyterhub/batchspawner) | Designed for clusters using batch scheduling software |
|
| [batchspawner](https://github.com/jupyterhub/batchspawner) | Designed for clusters using batch scheduling software |
|
||||||
|
| [yarnspawner](https://github.com/jupyterhub/yarnspawner) | Spawn single-user notebook servers distributed on a Hadoop cluster |
|
||||||
| [wrapspawner](https://github.com/jupyterhub/wrapspawner) | WrapSpawner and ProfilesSpawner enabling runtime configuration of spawners |
|
| [wrapspawner](https://github.com/jupyterhub/wrapspawner) | WrapSpawner and ProfilesSpawner enabling runtime configuration of spawners |
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
@@ -204,6 +206,9 @@ and the [`CONTRIBUTING.md`](CONTRIBUTING.md). The `CONTRIBUTING.md` file
|
|||||||
explains how to set up a development installation, how to run the test suite,
|
explains how to set up a development installation, how to run the test suite,
|
||||||
and how to contribute to documentation.
|
and how to contribute to documentation.
|
||||||
|
|
||||||
|
For a high-level view of the vision and next directions of the project, see the
|
||||||
|
[JupyterHub community roadmap](docs/source/contributing/roadmap.md).
|
||||||
|
|
||||||
### A note about platform support
|
### A note about platform support
|
||||||
|
|
||||||
JupyterHub is supported on Linux/Unix based systems.
|
JupyterHub is supported on Linux/Unix based systems.
|
||||||
@@ -237,6 +242,9 @@ our JupyterHub [Gitter](https://gitter.im/jupyterhub/jupyterhub) channel.
|
|||||||
- [Documentation for JupyterHub's REST API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/jupyterhub/master/docs/rest-api.yml#/default)
|
- [Documentation for JupyterHub's REST API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/jupyterhub/master/docs/rest-api.yml#/default)
|
||||||
- [Documentation for Project Jupyter](http://jupyter.readthedocs.io/en/latest/index.html) | [PDF](https://media.readthedocs.org/pdf/jupyter/latest/jupyter.pdf)
|
- [Documentation for Project Jupyter](http://jupyter.readthedocs.io/en/latest/index.html) | [PDF](https://media.readthedocs.org/pdf/jupyter/latest/jupyter.pdf)
|
||||||
- [Project Jupyter website](https://jupyter.org)
|
- [Project Jupyter website](https://jupyter.org)
|
||||||
|
- [Project Jupyter community](https://jupyter.org/community)
|
||||||
|
|
||||||
|
JupyterHub follows the Jupyter [Community Guides](https://jupyter.readthedocs.io/en/latest/community/content-community.html).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@@ -1,19 +1,16 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# Copyright (c) Jupyter Development Team.
|
# Copyright (c) Jupyter Development Team.
|
||||||
# Distributed under the terms of the Modified BSD License.
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
bower-lite
|
bower-lite
|
||||||
|
|
||||||
Since Bower's on its way out,
|
Since Bower's on its way out,
|
||||||
stage frontend dependencies from node_modules into components
|
stage frontend dependencies from node_modules into components
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from os.path import join
|
|
||||||
import shutil
|
import shutil
|
||||||
|
from os.path import join
|
||||||
|
|
||||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
@@ -1,50 +1,60 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# source this file to setup postgres and mysql
|
# The goal of this script is to start a database server as a docker container.
|
||||||
# for local testing (as similar as possible to docker)
|
#
|
||||||
|
# Required environment variables:
|
||||||
|
# - DB: The database server to start, either "postgres" or "mysql".
|
||||||
|
#
|
||||||
|
# - PGUSER/PGPASSWORD: For the creation of a postgresql user with associated
|
||||||
|
# password.
|
||||||
|
|
||||||
set -e
|
set -eu
|
||||||
|
|
||||||
export MYSQL_HOST=127.0.0.1
|
# Stop and remove any existing database container
|
||||||
export MYSQL_TCP_PORT=${MYSQL_TCP_PORT:-13306}
|
DOCKER_CONTAINER="hub-test-$DB"
|
||||||
export PGHOST=127.0.0.1
|
docker rm -f "$DOCKER_CONTAINER" 2>/dev/null || true
|
||||||
NAME="hub-test-$DB"
|
|
||||||
DOCKER_RUN="docker run -d --name $NAME"
|
|
||||||
|
|
||||||
docker rm -f "$NAME" 2>/dev/null || true
|
# Prepare environment variables to startup and await readiness of either a mysql
|
||||||
|
# or postgresql server.
|
||||||
|
if [[ "$DB" == "mysql" ]]; then
|
||||||
|
# Environment variables can influence both the mysql server in the docker
|
||||||
|
# container and the mysql client.
|
||||||
|
#
|
||||||
|
# ref server: https://hub.docker.com/_/mysql/
|
||||||
|
# ref client: https://dev.mysql.com/doc/refman/5.7/en/setting-environment-variables.html
|
||||||
|
#
|
||||||
|
DOCKER_RUN_ARGS="-p 3306:3306 --env MYSQL_ALLOW_EMPTY_PASSWORD=1 mysql:5.7"
|
||||||
|
READINESS_CHECK="mysql --user root --execute \q"
|
||||||
|
elif [[ "$DB" == "postgres" ]]; then
|
||||||
|
# Environment variables can influence both the postgresql server in the
|
||||||
|
# docker container and the postgresql client (psql).
|
||||||
|
#
|
||||||
|
# ref server: https://hub.docker.com/_/postgres/
|
||||||
|
# ref client: https://www.postgresql.org/docs/9.5/libpq-envars.html
|
||||||
|
#
|
||||||
|
# POSTGRES_USER / POSTGRES_PASSWORD will create a user on startup of the
|
||||||
|
# postgres server, but PGUSER and PGPASSWORD are the environment variables
|
||||||
|
# used by the postgresql client psql, so we configure the user based on how
|
||||||
|
# we want to connect.
|
||||||
|
#
|
||||||
|
DOCKER_RUN_ARGS="-p 5432:5432 --env "POSTGRES_USER=${PGUSER}" --env "POSTGRES_PASSWORD=${PGPASSWORD}" postgres:9.5"
|
||||||
|
READINESS_CHECK="psql --command \q"
|
||||||
|
else
|
||||||
|
echo '$DB must be mysql or postgres'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
case "$DB" in
|
# Start the database server
|
||||||
"mysql")
|
docker run --detach --name "$DOCKER_CONTAINER" $DOCKER_RUN_ARGS
|
||||||
RUN_ARGS="-e MYSQL_ALLOW_EMPTY_PASSWORD=1 -p $MYSQL_TCP_PORT:3306 mysql:5.7"
|
|
||||||
CHECK="mysql --host $MYSQL_HOST --port $MYSQL_TCP_PORT --user root -e \q"
|
|
||||||
;;
|
|
||||||
"postgres")
|
|
||||||
RUN_ARGS="-p 5432:5432 postgres:9.5"
|
|
||||||
CHECK="psql --user postgres -c \q"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo '$DB must be mysql or postgres'
|
|
||||||
exit 1
|
|
||||||
esac
|
|
||||||
|
|
||||||
$DOCKER_RUN $RUN_ARGS
|
|
||||||
|
|
||||||
|
# Wait for the database server to start
|
||||||
echo -n "waiting for $DB "
|
echo -n "waiting for $DB "
|
||||||
for i in {1..60}; do
|
for i in {1..60}; do
|
||||||
if $CHECK; then
|
if $READINESS_CHECK; then
|
||||||
echo 'done'
|
echo 'done'
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
echo -n '.'
|
echo -n '.'
|
||||||
sleep 1
|
sleep 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
$CHECK
|
$READINESS_CHECK
|
||||||
|
|
||||||
|
|
||||||
echo -e "
|
|
||||||
Set these environment variables:
|
|
||||||
|
|
||||||
export MYSQL_HOST=127.0.0.1
|
|
||||||
export MYSQL_TCP_PORT=$MYSQL_TCP_PORT
|
|
||||||
export PGHOST=127.0.0.1
|
|
||||||
"
|
|
||||||
|
@@ -1,27 +1,26 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# initialize jupyterhub databases for testing
|
# The goal of this script is to initialize a running database server with clean
|
||||||
|
# databases for use during tests.
|
||||||
|
#
|
||||||
|
# Required environment variables:
|
||||||
|
# - DB: The database server to start, either "postgres" or "mysql".
|
||||||
|
|
||||||
set -e
|
set -eu
|
||||||
|
|
||||||
MYSQL="mysql --user root --host $MYSQL_HOST --port $MYSQL_TCP_PORT -e "
|
# Prepare env vars SQL_CLIENT and EXTRA_CREATE_DATABASE_ARGS
|
||||||
PSQL="psql --user postgres -c "
|
if [[ "$DB" == "mysql" ]]; then
|
||||||
|
SQL_CLIENT="mysql --user root --execute "
|
||||||
case "$DB" in
|
EXTRA_CREATE_DATABASE_ARGS='CHARACTER SET utf8 COLLATE utf8_general_ci'
|
||||||
"mysql")
|
elif [[ "$DB" == "postgres" ]]; then
|
||||||
EXTRA_CREATE='CHARACTER SET utf8 COLLATE utf8_general_ci'
|
SQL_CLIENT="psql --command "
|
||||||
SQL="$MYSQL"
|
else
|
||||||
;;
|
echo '$DB must be mysql or postgres'
|
||||||
"postgres")
|
exit 1
|
||||||
SQL="$PSQL"
|
fi
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo '$DB must be mysql or postgres'
|
|
||||||
exit 1
|
|
||||||
esac
|
|
||||||
|
|
||||||
|
# Configure a set of databases in the database server for upgrade tests
|
||||||
set -x
|
set -x
|
||||||
|
for SUFFIX in '' _upgrade_072 _upgrade_081 _upgrade_094; do
|
||||||
for SUFFIX in '' _upgrade_072 _upgrade_081; do
|
$SQL_CLIENT "DROP DATABASE jupyterhub${SUFFIX};" 2>/dev/null || true
|
||||||
$SQL "DROP DATABASE jupyterhub${SUFFIX};" 2>/dev/null || true
|
$SQL_CLIENT "CREATE DATABASE jupyterhub${SUFFIX} ${EXTRA_CREATE_DATABASE_ARGS:-};"
|
||||||
$SQL "CREATE DATABASE jupyterhub${SUFFIX} ${EXTRA_CREATE};"
|
|
||||||
done
|
done
|
||||||
|
16
demo-image/Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Demo JupyterHub Docker image
|
||||||
|
#
|
||||||
|
# This should only be used for demo or testing and not as a base image to build on.
|
||||||
|
#
|
||||||
|
# It includes the notebook package and it uses the DummyAuthenticator and the SimpleLocalProcessSpawner.
|
||||||
|
ARG BASE_IMAGE=jupyterhub/jupyterhub-onbuild
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
|
# Install the notebook package
|
||||||
|
RUN python3 -m pip install notebook
|
||||||
|
|
||||||
|
# Create a demo user
|
||||||
|
RUN useradd --create-home demo
|
||||||
|
RUN chown demo .
|
||||||
|
|
||||||
|
USER demo
|
26
demo-image/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
## Demo Dockerfile
|
||||||
|
|
||||||
|
This is a demo JupyterHub Docker image to help you get a quick overview of what
|
||||||
|
JupyterHub is and how it works.
|
||||||
|
|
||||||
|
It uses the SimpleLocalProcessSpawner to spawn new user servers and
|
||||||
|
DummyAuthenticator for authentication.
|
||||||
|
The DummyAuthenticator allows you to log in with any username & password and the
|
||||||
|
SimpleLocalProcessSpawner allows starting servers without having to create a
|
||||||
|
local user for each JupyterHub user.
|
||||||
|
|
||||||
|
### Important!
|
||||||
|
|
||||||
|
This should only be used for demo or testing purposes!
|
||||||
|
It shouldn't be used as a base image to build on.
|
||||||
|
|
||||||
|
### Try it
|
||||||
|
|
||||||
|
1. `cd` to the root of your jupyterhub repo.
|
||||||
|
|
||||||
|
2. Build the demo image with `docker build -t jupyterhub-demo demo-image`.
|
||||||
|
|
||||||
|
3. Run the demo image with `docker run -d -p 8000:8000 jupyterhub-demo`.
|
||||||
|
|
||||||
|
4. Visit http://localhost:8000 and login with any username and password
|
||||||
|
5. Happy demo-ing :tada:!
|
7
demo-image/jupyterhub_config.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Configuration file for jupyterhub-demo
|
||||||
|
|
||||||
|
c = get_config()
|
||||||
|
|
||||||
|
# Use DummyAuthenticator and SimpleSpawner
|
||||||
|
c.JupyterHub.spawner_class = "simple"
|
||||||
|
c.JupyterHub.authenticator_class = "dummy"
|
@@ -1,13 +1,20 @@
|
|||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
mock
|
|
||||||
codecov
|
|
||||||
cryptography
|
|
||||||
pytest-cov
|
|
||||||
pytest-tornado
|
|
||||||
pytest>=3.3
|
|
||||||
notebook
|
|
||||||
requests-mock
|
|
||||||
virtualenv
|
|
||||||
# temporary pin of attrs for jsonschema 0.3.0a1
|
# temporary pin of attrs for jsonschema 0.3.0a1
|
||||||
# seems to be a pip bug
|
# seems to be a pip bug
|
||||||
attrs>=17.4.0
|
attrs>=17.4.0
|
||||||
|
beautifulsoup4
|
||||||
|
codecov
|
||||||
|
coverage
|
||||||
|
cryptography
|
||||||
|
html5lib # needed for beautifulsoup
|
||||||
|
mock
|
||||||
|
notebook
|
||||||
|
pre-commit
|
||||||
|
pytest>=3.3
|
||||||
|
pytest-asyncio
|
||||||
|
pytest-cov
|
||||||
|
requests-mock
|
||||||
|
# blacklist urllib3 releases affected by https://github.com/urllib3/urllib3/issues/1683
|
||||||
|
# I *think* this should only affect testing, not production
|
||||||
|
urllib3!=1.25.4,!=1.25.5
|
||||||
|
virtualenv
|
||||||
|
@@ -1,11 +1,14 @@
|
|||||||
FROM python:3.6.3-alpine3.6
|
FROM alpine:3.13
|
||||||
|
|
||||||
ARG JUPYTERHUB_VERSION=0.8.1
|
|
||||||
|
|
||||||
RUN pip3 install --no-cache jupyterhub==${JUPYTERHUB_VERSION}
|
|
||||||
ENV LANG=en_US.UTF-8
|
ENV LANG=en_US.UTF-8
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
python3 \
|
||||||
|
py3-pip \
|
||||||
|
py3-ruamel.yaml \
|
||||||
|
py3-cryptography \
|
||||||
|
py3-sqlalchemy
|
||||||
|
|
||||||
|
ARG JUPYTERHUB_VERSION=1.3.0
|
||||||
|
RUN pip3 install --no-cache jupyterhub==${JUPYTERHUB_VERSION}
|
||||||
|
|
||||||
USER nobody
|
USER nobody
|
||||||
CMD ["jupyterhub"]
|
CMD ["jupyterhub"]
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,21 +1,20 @@
|
|||||||
## What is Dockerfile.alpine
|
## What is Dockerfile.alpine
|
||||||
Dockerfile.alpine contains base image for jupyterhub. It does not work independently, but only as part of a full jupyterhub cluster
|
|
||||||
|
Dockerfile.alpine contains base image for jupyterhub. It does not work independently, but only as part of a full jupyterhub cluster
|
||||||
|
|
||||||
## How to use it?
|
## How to use it?
|
||||||
|
|
||||||
1. A running configurable-http-proxy, whose API is accessible.
|
1. A running configurable-http-proxy, whose API is accessible.
|
||||||
2. A jupyterhub_config file.
|
2. A jupyterhub_config file.
|
||||||
3. Authentication and other libraries required by the specific jupyterhub_config file.
|
3. Authentication and other libraries required by the specific jupyterhub_config file.
|
||||||
|
|
||||||
|
|
||||||
## Steps to test it outside a cluster
|
## Steps to test it outside a cluster
|
||||||
|
|
||||||
* start configurable-http-proxy in another container
|
- start configurable-http-proxy in another container
|
||||||
* specify CONFIGPROXY_AUTH_TOKEN env in both containers
|
- specify CONFIGPROXY_AUTH_TOKEN env in both containers
|
||||||
* put both containers on the same network (e.g. docker create network jupyterhub; docker run ... --net jupyterhub)
|
- put both containers on the same network (e.g. docker network create jupyterhub; docker run ... --net jupyterhub)
|
||||||
* tell jupyterhub where CHP is (e.g. c.ConfigurableHTTPProxy.api_url = 'http://chp:8001')
|
- tell jupyterhub where CHP is (e.g. c.ConfigurableHTTPProxy.api_url = 'http://chp:8001')
|
||||||
* tell jupyterhub not to start the proxy itself (c.ConfigurableHTTPProxy.should_start = False)
|
- tell jupyterhub not to start the proxy itself (c.ConfigurableHTTPProxy.should_start = False)
|
||||||
* Use dummy authenticator for ease of testing. Update following in jupyterhub_config file
|
- Use dummy authenticator for ease of testing. Update following in jupyterhub_config file
|
||||||
- c.JupyterHub.authenticator_class = 'dummyauthenticator.DummyAuthenticator'
|
- c.JupyterHub.authenticator_class = 'dummyauthenticator.DummyAuthenticator'
|
||||||
- c.DummyAuthenticator.password = "your strong password"
|
- c.DummyAuthenticator.password = "your strong password"
|
||||||
|
|
||||||
|
9
dockerfiles/test.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from jupyterhub._data import DATA_FILES_PATH
|
||||||
|
|
||||||
|
print(f"DATA_FILES_PATH={DATA_FILES_PATH}")
|
||||||
|
|
||||||
|
for sub_path in ("templates", "static/components", "static/css/style.min.css"):
|
||||||
|
path = os.path.join(DATA_FILES_PATH, sub_path)
|
||||||
|
assert os.path.exists(path), path
|
@@ -48,6 +48,7 @@ help:
|
|||||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||||
@echo " spelling to run spell check on documentation"
|
@echo " spelling to run spell check on documentation"
|
||||||
|
@echo " metrics to generate documentation for metrics by inspecting the source code"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BUILDDIR)/*
|
rm -rf $(BUILDDIR)/*
|
||||||
@@ -60,7 +61,12 @@ rest-api: source/_static/rest-api/index.html
|
|||||||
source/_static/rest-api/index.html: rest-api.yml node_modules
|
source/_static/rest-api/index.html: rest-api.yml node_modules
|
||||||
npm run rest-api
|
npm run rest-api
|
||||||
|
|
||||||
html: rest-api
|
metrics: source/reference/metrics.rst
|
||||||
|
|
||||||
|
source/reference/metrics.rst: generate-metrics.py
|
||||||
|
python3 generate-metrics.py
|
||||||
|
|
||||||
|
html: rest-api metrics
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
@@ -1,22 +0,0 @@
|
|||||||
# ReadTheDocs uses the `environment.yaml` so make sure to update that as well
|
|
||||||
# if you change the dependencies of JupyterHub in the various `requirements.txt`
|
|
||||||
name: jhub_docs
|
|
||||||
channels:
|
|
||||||
- conda-forge
|
|
||||||
dependencies:
|
|
||||||
- nodejs
|
|
||||||
- python=3.6
|
|
||||||
- alembic
|
|
||||||
- jinja2
|
|
||||||
- pamela
|
|
||||||
- requests
|
|
||||||
- sqlalchemy>=1
|
|
||||||
- tornado>=5.0
|
|
||||||
- traitlets>=4.1
|
|
||||||
- sphinx>=1.7
|
|
||||||
- pip:
|
|
||||||
- python-oauth2
|
|
||||||
- recommonmark==0.4.0
|
|
||||||
- async_generator
|
|
||||||
- prometheus_client
|
|
||||||
- attrs>=17.4.0
|
|
57
docs/generate-metrics.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import os
|
||||||
|
from os.path import join
|
||||||
|
|
||||||
|
from pytablewriter import RstSimpleTableWriter
|
||||||
|
from pytablewriter.style import Style
|
||||||
|
|
||||||
|
import jupyterhub.metrics
|
||||||
|
|
||||||
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
class Generator:
|
||||||
|
@classmethod
|
||||||
|
def create_writer(cls, table_name, headers, values):
|
||||||
|
writer = RstSimpleTableWriter()
|
||||||
|
writer.table_name = table_name
|
||||||
|
writer.headers = headers
|
||||||
|
writer.value_matrix = values
|
||||||
|
writer.margin = 1
|
||||||
|
[writer.set_style(header, Style(align="center")) for header in headers]
|
||||||
|
return writer
|
||||||
|
|
||||||
|
def _parse_metrics(self):
|
||||||
|
table_rows = []
|
||||||
|
for name in dir(jupyterhub.metrics):
|
||||||
|
obj = getattr(jupyterhub.metrics, name)
|
||||||
|
if obj.__class__.__module__.startswith('prometheus_client.'):
|
||||||
|
for metric in obj.describe():
|
||||||
|
table_rows.append([metric.type, metric.name, metric.documentation])
|
||||||
|
return table_rows
|
||||||
|
|
||||||
|
def prometheus_metrics(self):
|
||||||
|
generated_directory = f"{HERE}/source/reference"
|
||||||
|
if not os.path.exists(generated_directory):
|
||||||
|
os.makedirs(generated_directory)
|
||||||
|
|
||||||
|
filename = f"{generated_directory}/metrics.rst"
|
||||||
|
table_name = ""
|
||||||
|
headers = ["Type", "Name", "Description"]
|
||||||
|
values = self._parse_metrics()
|
||||||
|
writer = self.create_writer(table_name, headers, values)
|
||||||
|
|
||||||
|
title = "List of Prometheus Metrics"
|
||||||
|
underline = "============================"
|
||||||
|
content = f"{title}\n{underline}\n{writer.dumps()}"
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
print(f"Generated {filename}.")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
doc_generator = Generator()
|
||||||
|
doc_generator.prometheus_metrics()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@@ -1,5 +1,12 @@
|
|||||||
# ReadTheDocs uses the `environment.yaml` so make sure to update that as well
|
|
||||||
# if you change this file
|
|
||||||
-r ../requirements.txt
|
-r ../requirements.txt
|
||||||
|
|
||||||
|
alabaster_jupyterhub
|
||||||
|
# Temporary fix of #3021. Revert back to released autodoc-traits when
|
||||||
|
# 0.1.0 released.
|
||||||
|
https://github.com/jupyterhub/autodoc-traits/archive/75885ee24636efbfebfceed1043459715049cd84.zip
|
||||||
|
pydata-sphinx-theme
|
||||||
|
pytablewriter>=0.56
|
||||||
|
recommonmark>=0.6
|
||||||
sphinx>=1.7
|
sphinx>=1.7
|
||||||
recommonmark==0.4.0
|
sphinx-copybutton
|
||||||
|
sphinx-jsonschema
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
# see me at: http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/jupyterhub/master/docs/rest-api.yml#/default
|
# see me at: http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyterhub/jupyterhub/master/docs/rest-api.yml#/default
|
||||||
swagger: '2.0'
|
swagger: "2.0"
|
||||||
info:
|
info:
|
||||||
title: JupyterHub
|
title: JupyterHub
|
||||||
description: The REST API for JupyterHub
|
description: The REST API for JupyterHub
|
||||||
version: 0.9.0dev
|
version: 1.4.0
|
||||||
license:
|
license:
|
||||||
name: BSD-3-Clause
|
name: BSD-3-Clause
|
||||||
schemes:
|
schemes: [http, https]
|
||||||
- [http, https]
|
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
token:
|
token:
|
||||||
type: apiKey
|
type: apiKey
|
||||||
@@ -28,7 +27,7 @@ paths:
|
|||||||
This endpoint is not authenticated for the purpose of clients and user
|
This endpoint is not authenticated for the purpose of clients and user
|
||||||
to identify the JupyterHub version before setting up authentication.
|
to identify the JupyterHub version before setting up authentication.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The JupyterHub version
|
description: The JupyterHub version
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
@@ -44,7 +43,7 @@ paths:
|
|||||||
JupyterHub's version and executable path,
|
JupyterHub's version and executable path,
|
||||||
and which Authenticator and Spawner are active.
|
and which Authenticator and Spawner are active.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Detailed JupyterHub info
|
description: Detailed JupyterHub info
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
@@ -79,17 +78,32 @@ paths:
|
|||||||
/users:
|
/users:
|
||||||
get:
|
get:
|
||||||
summary: List users
|
summary: List users
|
||||||
|
parameters:
|
||||||
|
- name: state
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
enum: ["inactive", "active", "ready"]
|
||||||
|
description: |
|
||||||
|
Return only users who have servers in the given state.
|
||||||
|
If unspecified, return all users.
|
||||||
|
|
||||||
|
active: all users with any active servers (ready OR pending)
|
||||||
|
ready: all users who have any ready servers (running, not pending)
|
||||||
|
inactive: all users who have *no* active servers (complement of active)
|
||||||
|
|
||||||
|
Added in JupyterHub 1.3
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The Hub's user list
|
description: The Hub's user list
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/User'
|
$ref: "#/definitions/User"
|
||||||
post:
|
post:
|
||||||
summary: Create multiple users
|
summary: Create multiple users
|
||||||
parameters:
|
parameters:
|
||||||
- name: data
|
- name: body
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
@@ -104,13 +118,13 @@ paths:
|
|||||||
description: whether the created users should be admins
|
description: whether the created users should be admins
|
||||||
type: boolean
|
type: boolean
|
||||||
responses:
|
responses:
|
||||||
'201':
|
"201":
|
||||||
description: The users have been created
|
description: The users have been created
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
description: The created users
|
description: The created users
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/User'
|
$ref: "#/definitions/User"
|
||||||
/users/{name}:
|
/users/{name}:
|
||||||
get:
|
get:
|
||||||
summary: Get a user by name
|
summary: Get a user by name
|
||||||
@@ -121,10 +135,10 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The User model
|
description: The User model
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/User'
|
$ref: "#/definitions/User"
|
||||||
post:
|
post:
|
||||||
summary: Create a single user
|
summary: Create a single user
|
||||||
parameters:
|
parameters:
|
||||||
@@ -134,10 +148,10 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'201':
|
"201":
|
||||||
description: The user has been created
|
description: The user has been created
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/User'
|
$ref: "#/definitions/User"
|
||||||
patch:
|
patch:
|
||||||
summary: Modify a user
|
summary: Modify a user
|
||||||
description: Change a user's name or admin status
|
description: Change a user's name or admin status
|
||||||
@@ -147,7 +161,7 @@ paths:
|
|||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- name: data
|
- name: body
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
description: Updated user info. At least one key to be updated (name or admin) is required.
|
description: Updated user info. At least one key to be updated (name or admin) is required.
|
||||||
@@ -161,10 +175,10 @@ paths:
|
|||||||
type: boolean
|
type: boolean
|
||||||
description: update admin (optional, if another key is updated i.e. name)
|
description: update admin (optional, if another key is updated i.e. name)
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The updated user info
|
description: The updated user info
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/User'
|
$ref: "#/definitions/User"
|
||||||
delete:
|
delete:
|
||||||
summary: Delete a user
|
summary: Delete a user
|
||||||
parameters:
|
parameters:
|
||||||
@@ -174,8 +188,63 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'204':
|
"204":
|
||||||
description: The user has been deleted
|
description: The user has been deleted
|
||||||
|
/users/{name}/activity:
|
||||||
|
post:
|
||||||
|
summary: Notify Hub of activity for a given user.
|
||||||
|
description: Notify the Hub of activity by the user,
|
||||||
|
e.g. accessing a service or (more likely)
|
||||||
|
actively using a server.
|
||||||
|
parameters:
|
||||||
|
- name: name
|
||||||
|
description: username
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- name: body
|
||||||
|
in: body
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
last_activity:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: |
|
||||||
|
Timestamp of last-seen activity for this user.
|
||||||
|
Only needed if this is not activity associated
|
||||||
|
with using a given server.
|
||||||
|
servers:
|
||||||
|
description: |
|
||||||
|
Register activity for specific servers by name.
|
||||||
|
The keys of this dict are the names of servers.
|
||||||
|
The default server has an empty name ('').
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
"<server name>":
|
||||||
|
description: |
|
||||||
|
Activity for a single server.
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- last_activity
|
||||||
|
properties:
|
||||||
|
last_activity:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: |
|
||||||
|
Timestamp of last-seen activity on this server.
|
||||||
|
example:
|
||||||
|
last_activity: "2019-02-06T12:54:14Z"
|
||||||
|
servers:
|
||||||
|
"":
|
||||||
|
last_activity: "2019-02-06T12:54:14Z"
|
||||||
|
gpu:
|
||||||
|
last_activity: "2019-02-06T12:54:14Z"
|
||||||
|
responses:
|
||||||
|
"401":
|
||||||
|
$ref: "#/responses/Unauthorized"
|
||||||
|
"404":
|
||||||
|
description: No such user
|
||||||
/users/{name}/server:
|
/users/{name}/server:
|
||||||
post:
|
post:
|
||||||
summary: Start a user's single-user notebook server
|
summary: Start a user's single-user notebook server
|
||||||
@@ -185,10 +254,23 @@ paths:
|
|||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- name: options
|
||||||
|
description: |
|
||||||
|
Spawn options can be passed as a JSON body
|
||||||
|
when spawning via the API instead of spawn form.
|
||||||
|
The structure of the options
|
||||||
|
will depend on the Spawner's configuration.
|
||||||
|
The body itself will be available as `user_options` for the
|
||||||
|
Spawner.
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
|
||||||
responses:
|
responses:
|
||||||
'201':
|
"201":
|
||||||
description: The user's notebook server has started
|
description: The user's notebook server has started
|
||||||
'202':
|
"202":
|
||||||
description: The user's notebook server has not yet started, but has been requested
|
description: The user's notebook server has not yet started, but has been requested
|
||||||
delete:
|
delete:
|
||||||
summary: Stop a user's server
|
summary: Stop a user's server
|
||||||
@@ -199,9 +281,9 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'204':
|
"204":
|
||||||
description: The user's notebook server has stopped
|
description: The user's notebook server has stopped
|
||||||
'202':
|
"202":
|
||||||
description: The user's notebook server has not yet stopped as it is taking a while to stop
|
description: The user's notebook server has not yet stopped as it is taking a while to stop
|
||||||
/users/{name}/servers/{server_name}:
|
/users/{name}/servers/{server_name}:
|
||||||
post:
|
post:
|
||||||
@@ -213,14 +295,27 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- name: server_name
|
- name: server_name
|
||||||
description: name given to a named-server
|
description: |
|
||||||
|
name given to a named-server.
|
||||||
|
|
||||||
|
Note that depending on your JupyterHub infrastructure there are chracterter size limitation to `server_name`. Default spawner with K8s pod will not allow Jupyter Notebooks to be spawned with a name that contains more than 253 characters (keep in mind that the pod will be spawned with extra characters to identify the user and hub).
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- name: options
|
||||||
|
description: |
|
||||||
|
Spawn options can be passed as a JSON body
|
||||||
|
when spawning via the API instead of spawn form.
|
||||||
|
The structure of the options
|
||||||
|
will depend on the Spawner's configuration.
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'201':
|
"201":
|
||||||
description: The user's notebook named-server has started
|
description: The user's notebook named-server has started
|
||||||
'202':
|
"202":
|
||||||
description: The user's notebook named-server has not yet started, but has been requested
|
description: The user's notebook named-server has not yet started, but has been requested
|
||||||
delete:
|
delete:
|
||||||
summary: Stop a user's named-server
|
summary: Stop a user's named-server
|
||||||
@@ -235,69 +330,106 @@ paths:
|
|||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- name: body
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
remove:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
Whether to fully remove the server, rather than just stop it.
|
||||||
|
Removing a server deletes things like the state of the stopped server.
|
||||||
|
Default: false.
|
||||||
responses:
|
responses:
|
||||||
'204':
|
"204":
|
||||||
description: The user's notebook named-server has stopped
|
description: The user's notebook named-server has stopped
|
||||||
'202':
|
"202":
|
||||||
description: The user's notebook named-server has not yet stopped as it is taking a while to stop
|
description: The user's notebook named-server has not yet stopped as it is taking a while to stop
|
||||||
/users/{name}/tokens:
|
/users/{name}/tokens:
|
||||||
|
parameters:
|
||||||
|
- name: name
|
||||||
|
description: username
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
get:
|
get:
|
||||||
summary: List tokens for the user
|
summary: List tokens for the user
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The list of tokens
|
description: The list of tokens
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/Token'
|
$ref: "#/definitions/Token"
|
||||||
|
"401":
|
||||||
|
$ref: "#/responses/Unauthorized"
|
||||||
|
"404":
|
||||||
|
description: No such user
|
||||||
post:
|
post:
|
||||||
summary: Create a new token for the user
|
summary: Create a new token for the user
|
||||||
parameters:
|
parameters:
|
||||||
- name: expires_in
|
- name: token_params
|
||||||
type: number
|
|
||||||
required: false
|
|
||||||
in: body
|
in: body
|
||||||
description: lifetime (in seconds) after which the requested token will expire.
|
|
||||||
- name: note
|
|
||||||
type: string
|
|
||||||
required: false
|
required: false
|
||||||
in: body
|
schema:
|
||||||
description: A note attached to the token for future bookkeeping
|
type: object
|
||||||
|
properties:
|
||||||
|
expires_in:
|
||||||
|
type: number
|
||||||
|
description: lifetime (in seconds) after which the requested token will expire.
|
||||||
|
note:
|
||||||
|
type: string
|
||||||
|
description: A note attached to the token for future bookkeeping
|
||||||
responses:
|
responses:
|
||||||
'201':
|
"201":
|
||||||
description: The newly created token
|
description: The newly created token
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Token'
|
$ref: "#/definitions/Token"
|
||||||
|
"400":
|
||||||
|
description: Body must be a JSON dict or empty
|
||||||
/users/{name}/tokens/{token_id}:
|
/users/{name}/tokens/{token_id}:
|
||||||
|
parameters:
|
||||||
|
- name: name
|
||||||
|
description: username
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- name: token_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
get:
|
get:
|
||||||
summary: Get the model for a token by id
|
summary: Get the model for a token by id
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The info for the new token
|
description: The info for the new token
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Token'
|
$ref: "#/definitions/Token"
|
||||||
delete:
|
delete:
|
||||||
summary: Delete (revoke) a token by id
|
summary: Delete (revoke) a token by id
|
||||||
responses:
|
responses:
|
||||||
'204':
|
"204":
|
||||||
description: The token has been deleted
|
description: The token has been deleted
|
||||||
/user:
|
/user:
|
||||||
summary: Return authenticated user's model
|
get:
|
||||||
description:
|
summary: Return authenticated user's model
|
||||||
parameters:
|
responses:
|
||||||
responses:
|
"200":
|
||||||
'200':
|
description: The authenticated user's model is returned.
|
||||||
description: The authenticated user's model is returned.
|
schema:
|
||||||
|
$ref: "#/definitions/User"
|
||||||
/groups:
|
/groups:
|
||||||
get:
|
get:
|
||||||
summary: List groups
|
summary: List groups
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The list of groups
|
description: The list of groups
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/Group'
|
$ref: "#/definitions/Group"
|
||||||
/groups/{name}:
|
/groups/{name}:
|
||||||
get:
|
get:
|
||||||
summary: Get a group by name
|
summary: Get a group by name
|
||||||
@@ -308,10 +440,10 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The group model
|
description: The group model
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Group'
|
$ref: "#/definitions/Group"
|
||||||
post:
|
post:
|
||||||
summary: Create a group
|
summary: Create a group
|
||||||
parameters:
|
parameters:
|
||||||
@@ -321,10 +453,10 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'201':
|
"201":
|
||||||
description: The group has been created
|
description: The group has been created
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Group'
|
$ref: "#/definitions/Group"
|
||||||
delete:
|
delete:
|
||||||
summary: Delete a group
|
summary: Delete a group
|
||||||
parameters:
|
parameters:
|
||||||
@@ -334,7 +466,7 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'204':
|
"204":
|
||||||
description: The group has been deleted
|
description: The group has been deleted
|
||||||
/groups/{name}/users:
|
/groups/{name}/users:
|
||||||
post:
|
post:
|
||||||
@@ -345,7 +477,7 @@ paths:
|
|||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- name: data
|
- name: body
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
description: The users to add to the group
|
description: The users to add to the group
|
||||||
@@ -358,10 +490,10 @@ paths:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The users have been added to the group
|
description: The users have been added to the group
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Group'
|
$ref: "#/definitions/Group"
|
||||||
delete:
|
delete:
|
||||||
summary: Remove users from a group
|
summary: Remove users from a group
|
||||||
parameters:
|
parameters:
|
||||||
@@ -370,7 +502,7 @@ paths:
|
|||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- name: data
|
- name: body
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
description: The users to remove from the group
|
description: The users to remove from the group
|
||||||
@@ -383,18 +515,18 @@ paths:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The users have been removed from the group
|
description: The users have been removed from the group
|
||||||
/services:
|
/services:
|
||||||
get:
|
get:
|
||||||
summary: List services
|
summary: List services
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The service list
|
description: The service list
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/Service'
|
$ref: "#/definitions/Service"
|
||||||
/services/{name}:
|
/services/{name}:
|
||||||
get:
|
get:
|
||||||
summary: Get a service by name
|
summary: Get a service by name
|
||||||
@@ -405,16 +537,16 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The Service model
|
description: The Service model
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Service'
|
$ref: "#/definitions/Service"
|
||||||
/proxy:
|
/proxy:
|
||||||
get:
|
get:
|
||||||
summary: Get the proxy's routing table
|
summary: Get the proxy's routing table
|
||||||
description: A convenience alias for getting the routing table directly from the proxy
|
description: A convenience alias for getting the routing table directly from the proxy
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Routing table
|
description: Routing table
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
@@ -422,13 +554,13 @@ paths:
|
|||||||
post:
|
post:
|
||||||
summary: Force the Hub to sync with the proxy
|
summary: Force the Hub to sync with the proxy
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Success
|
description: Success
|
||||||
patch:
|
patch:
|
||||||
summary: Notify the Hub about a new proxy
|
summary: Notify the Hub about a new proxy
|
||||||
description: Notifies the Hub of a new proxy to use.
|
description: Notifies the Hub of a new proxy to use.
|
||||||
parameters:
|
parameters:
|
||||||
- name: data
|
- name: body
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
description: Any values that have changed for the new proxy. All keys are optional.
|
description: Any values that have changed for the new proxy. All keys are optional.
|
||||||
@@ -448,7 +580,7 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
description: CONFIGPROXY_AUTH_TOKEN for the new proxy
|
description: CONFIGPROXY_AUTH_TOKEN for the new proxy
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Success
|
description: Success
|
||||||
/authorizations/token:
|
/authorizations/token:
|
||||||
post:
|
post:
|
||||||
@@ -460,16 +592,17 @@ paths:
|
|||||||
Logging in via this method is only available when the active Authenticator
|
Logging in via this method is only available when the active Authenticator
|
||||||
accepts passwords (e.g. not OAuth).
|
accepts passwords (e.g. not OAuth).
|
||||||
parameters:
|
parameters:
|
||||||
- name: username
|
- name: credentials
|
||||||
in: body
|
in: body
|
||||||
required: false
|
schema:
|
||||||
type: string
|
type: object
|
||||||
- name: password
|
properties:
|
||||||
in: body
|
username:
|
||||||
required: false
|
type: string
|
||||||
type: string
|
password:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The new API token
|
description: The new API token
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
@@ -477,44 +610,44 @@ paths:
|
|||||||
token:
|
token:
|
||||||
type: string
|
type: string
|
||||||
description: The new API token.
|
description: The new API token.
|
||||||
'403':
|
"403":
|
||||||
description: The user can not be authenticated.
|
description: The user can not be authenticated.
|
||||||
/authorizations/token/{token}:
|
/authorizations/token/{token}:
|
||||||
get:
|
get:
|
||||||
summary: Identify a user or service from an API token
|
summary: Identify a user or service from an API token
|
||||||
parameters:
|
parameters:
|
||||||
- name: token
|
- name: token
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The user or service identified by the API token
|
description: The user or service identified by the API token
|
||||||
'404':
|
"404":
|
||||||
description: A user or service is not found.
|
description: A user or service is not found.
|
||||||
/authorizations/cookie/{cookie_name}/{cookie_value}:
|
/authorizations/cookie/{cookie_name}/{cookie_value}:
|
||||||
get:
|
get:
|
||||||
summary: Identify a user from a cookie
|
summary: Identify a user from a cookie
|
||||||
description: Used by single-user notebook servers to hand off cookie authentication to the Hub
|
description: Used by single-user notebook servers to hand off cookie authentication to the Hub
|
||||||
parameters:
|
parameters:
|
||||||
- name: cookie_name
|
- name: cookie_name
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- name: cookie_value
|
- name: cookie_value
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: The user identified by the cookie
|
description: The user identified by the cookie
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/User'
|
$ref: "#/definitions/User"
|
||||||
'404':
|
"404":
|
||||||
description: A user is not found.
|
description: A user is not found.
|
||||||
/oauth2/authorize:
|
/oauth2/authorize:
|
||||||
get:
|
get:
|
||||||
summary: 'OAuth 2.0 authorize endpoint'
|
summary: "OAuth 2.0 authorize endpoint"
|
||||||
description: |
|
description: |
|
||||||
Redirect users to this URL to begin the OAuth process.
|
Redirect users to this URL to begin the OAuth process.
|
||||||
It is not an API endpoint.
|
It is not an API endpoint.
|
||||||
@@ -539,6 +672,11 @@ paths:
|
|||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Success
|
||||||
|
"400":
|
||||||
|
description: OAuth2Error
|
||||||
/oauth2/token:
|
/oauth2/token:
|
||||||
post:
|
post:
|
||||||
summary: Request an OAuth2 token
|
summary: Request an OAuth2 token
|
||||||
@@ -550,31 +688,31 @@ paths:
|
|||||||
parameters:
|
parameters:
|
||||||
- name: client_id
|
- name: client_id
|
||||||
description: The client id
|
description: The client id
|
||||||
in: form
|
in: formData
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- name: client_secret
|
- name: client_secret
|
||||||
description: The client secret
|
description: The client secret
|
||||||
in: form
|
in: formData
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- name: grant_type
|
- name: grant_type
|
||||||
description: The grant type (always 'authorization_code')
|
description: The grant type (always 'authorization_code')
|
||||||
in: form
|
in: formData
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- name: code
|
- name: code
|
||||||
description: The code provided by the authorization redirect
|
description: The code provided by the authorization redirect
|
||||||
in: form
|
in: formData
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- name: redirect_uri
|
- name: redirect_uri
|
||||||
description: The redirect url
|
description: The redirect url
|
||||||
in: form
|
in: formData
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: JSON response including the token
|
description: JSON response including the token
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
@@ -589,14 +727,28 @@ paths:
|
|||||||
post:
|
post:
|
||||||
summary: Shutdown the Hub
|
summary: Shutdown the Hub
|
||||||
parameters:
|
parameters:
|
||||||
- name: proxy
|
- name: body
|
||||||
in: body
|
in: body
|
||||||
type: boolean
|
schema:
|
||||||
description: Whether the proxy should be shutdown as well (default from Hub config)
|
type: object
|
||||||
- name: servers
|
properties:
|
||||||
in: body
|
proxy:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Whether users' notebook servers should be shutdown as well (default from Hub config)
|
description: Whether the proxy should be shutdown as well (default from Hub config)
|
||||||
|
servers:
|
||||||
|
type: boolean
|
||||||
|
description: Whether users' notebook servers should be shutdown as well (default from Hub config)
|
||||||
|
responses:
|
||||||
|
"202":
|
||||||
|
description: Shutdown successful
|
||||||
|
"400":
|
||||||
|
description: Unexpeced value for proxy or servers
|
||||||
|
# Descriptions of common responses
|
||||||
|
responses:
|
||||||
|
NotFound:
|
||||||
|
description: The specified resource was not found
|
||||||
|
Unauthorized:
|
||||||
|
description: Authentication/Authorization error
|
||||||
definitions:
|
definitions:
|
||||||
User:
|
User:
|
||||||
type: object
|
type: object
|
||||||
@@ -624,11 +776,10 @@ definitions:
|
|||||||
format: date-time
|
format: date-time
|
||||||
description: Timestamp of last-seen activity from the user
|
description: Timestamp of last-seen activity from the user
|
||||||
servers:
|
servers:
|
||||||
type: object
|
type: array
|
||||||
description: The active servers for this user.
|
description: The active servers for this user.
|
||||||
items:
|
items:
|
||||||
schema:
|
$ref: "#/definitions/Server"
|
||||||
$ref: '#/definitions/Server'
|
|
||||||
Server:
|
Server:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -666,6 +817,9 @@ definitions:
|
|||||||
state:
|
state:
|
||||||
type: object
|
type: object
|
||||||
description: Arbitrary internal state from this server's spawner. Only available on the hub's users list or get-user-by-name method, and only if a hub admin. None otherwise.
|
description: Arbitrary internal state from this server's spawner. Only available on the hub's users list or get-user-by-name method, and only if a hub admin. None otherwise.
|
||||||
|
user_options:
|
||||||
|
type: object
|
||||||
|
description: User specified options for the user's spawned instance of a single-user server.
|
||||||
Group:
|
Group:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@@ -1,106 +1,4 @@
|
|||||||
div#helm-chart-schema h2,
|
/* Added to avoid logo being too squeezed */
|
||||||
div#helm-chart-schema h3,
|
.navbar-brand {
|
||||||
div#helm-chart-schema h4,
|
height: 4rem !important;
|
||||||
div#helm-chart-schema h5,
|
}
|
||||||
div#helm-chart-schema h6 {
|
|
||||||
font-family: courier new;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3, h3 ~ * {
|
|
||||||
margin-left: 3% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4, h4 ~ * {
|
|
||||||
margin-left: 6% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5, h5 ~ * {
|
|
||||||
margin-left: 9% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
h6, h6 ~ * {
|
|
||||||
margin-left: 12% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
h7, h7 ~ * {
|
|
||||||
margin-left: 15% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.logo {
|
|
||||||
width:100%
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-next {
|
|
||||||
float: right;
|
|
||||||
max-width: 45%;
|
|
||||||
overflow: auto;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-next::after{
|
|
||||||
content: ' »';
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-prev {
|
|
||||||
float: left;
|
|
||||||
max-width: 45%;
|
|
||||||
overflow: auto;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-prev::before{
|
|
||||||
content: '« ';
|
|
||||||
}
|
|
||||||
|
|
||||||
.prev-next-bottom {
|
|
||||||
margin-top: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prev-next-top {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar TOC and headers */
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper div {
|
|
||||||
margin-bottom: .8em;
|
|
||||||
}
|
|
||||||
div.sphinxsidebar h3 {
|
|
||||||
font-size: 1.3em;
|
|
||||||
padding-top: 0px;
|
|
||||||
font-weight: 800;
|
|
||||||
margin-left: 0px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p.caption {
|
|
||||||
font-size: 1.2em;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
margin-left: 0px !important;
|
|
||||||
font-weight: 900;
|
|
||||||
color: #767676;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul {
|
|
||||||
font-size: .8em;
|
|
||||||
margin-top: 0px;
|
|
||||||
padding-left: 3%;
|
|
||||||
margin-left: 0px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.relations ul {
|
|
||||||
font-size: 1em;
|
|
||||||
margin-left: 0px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#searchbox form {
|
|
||||||
margin-left: 0px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* body elements */
|
|
||||||
.toctree-wrapper span.caption-text {
|
|
||||||
color: #767676;
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 6.7 KiB |
@@ -1,16 +0,0 @@
|
|||||||
{# Custom template for navigation.html
|
|
||||||
|
|
||||||
alabaster theme does not provide blocks for titles to
|
|
||||||
be overridden so this custom theme handles title and
|
|
||||||
toctree for sidebar
|
|
||||||
#}
|
|
||||||
<h3>{{ _('Table of Contents') }}</h3>
|
|
||||||
{{ toctree(includehidden=theme_sidebar_includehidden, collapse=theme_sidebar_collapse) }}
|
|
||||||
{% if theme_extra_nav_links %}
|
|
||||||
<hr />
|
|
||||||
<ul>
|
|
||||||
{% for text, uri in theme_extra_nav_links.items() %}
|
|
||||||
<li class="toctree-l1"><a href="{{ uri }}">{{ text }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
@@ -1,30 +0,0 @@
|
|||||||
{% extends '!page.html' %}
|
|
||||||
|
|
||||||
{# Custom template for page.html
|
|
||||||
|
|
||||||
Alabaster theme does not provide blocks for prev/next at bottom of each page.
|
|
||||||
This is _in addition_ to the prev/next in the sidebar. The "Prev/Next" text
|
|
||||||
or symbols are handled by CSS classes in _static/custom.css
|
|
||||||
#}
|
|
||||||
|
|
||||||
{% macro prev_next(prev, next, prev_title='', next_title='') %}
|
|
||||||
{%- if prev %}
|
|
||||||
<a class='left-prev' href="{{ prev.link|e }}" title="{{ _('previous chapter')}}">{{ prev_title or prev.title }}</a>
|
|
||||||
{%- endif %}
|
|
||||||
{%- if next %}
|
|
||||||
<a class='right-next' href="{{ next.link|e }}" title="{{ _('next chapter')}}">{{ next_title or next.title }}</a>
|
|
||||||
{%- endif %}
|
|
||||||
<div style='clear:both;'></div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<div class='prev-next-top'>
|
|
||||||
{{ prev_next(prev, next, 'Previous', 'Next') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{super()}}
|
|
||||||
<div class='prev-next-bottom'>
|
|
||||||
{{ prev_next(prev, next) }}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@@ -1,17 +0,0 @@
|
|||||||
{# Custom template for relations.html
|
|
||||||
|
|
||||||
alabaster theme does not provide previous/next page by default
|
|
||||||
#}
|
|
||||||
<div class="relations">
|
|
||||||
<h3>Navigation</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="{{ pathto(master_doc) }}">Documentation Home</a><ul>
|
|
||||||
{%- if prev %}
|
|
||||||
<li><a href="{{ prev.link|e }}" title="Previous">Previous topic</a></li>
|
|
||||||
{%- endif %}
|
|
||||||
{%- if next %}
|
|
||||||
<li><a href="{{ next.link|e }}" title="Next">Next topic</a></li>
|
|
||||||
{%- endif %}
|
|
||||||
</ul>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
159
docs/source/admin/upgrading.rst
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
.. _admin/upgrading:
|
||||||
|
|
||||||
|
====================
|
||||||
|
Upgrading JupyterHub
|
||||||
|
====================
|
||||||
|
|
||||||
|
JupyterHub offers easy upgrade pathways between minor versions. This
|
||||||
|
document describes how to do these upgrades.
|
||||||
|
|
||||||
|
If you are using :ref:`a JupyterHub distribution <index/distributions>`, you
|
||||||
|
should consult the distribution's documentation on how to upgrade. This
|
||||||
|
document is if you have set up your own JupyterHub without using a
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
It is long because is pretty detailed! Most likely, upgrading
|
||||||
|
JupyterHub is painless, quick and with minimal user interruption.
|
||||||
|
|
||||||
|
Read the Changelog
|
||||||
|
==================
|
||||||
|
|
||||||
|
The `changelog <../changelog.html>`_ contains information on what has
|
||||||
|
changed with the new JupyterHub release, and any deprecation warnings.
|
||||||
|
Read these notes to familiarize yourself with the coming changes. There
|
||||||
|
might be new releases of authenticators & spawners you are using, so
|
||||||
|
read the changelogs for those too!
|
||||||
|
|
||||||
|
Notify your users
|
||||||
|
=================
|
||||||
|
|
||||||
|
If you are using the default configuration where ``configurable-http-proxy``
|
||||||
|
is managed by JupyterHub, your users will see service disruption during
|
||||||
|
the upgrade process. You should notify them, and pick a time to do the
|
||||||
|
upgrade where they will be least disrupted.
|
||||||
|
|
||||||
|
If you are using a different proxy, or running ``configurable-http-proxy``
|
||||||
|
independent of JupyterHub, your users will be able to continue using notebook
|
||||||
|
servers they had already launched, but will not be able to launch new servers
|
||||||
|
nor sign in.
|
||||||
|
|
||||||
|
|
||||||
|
Backup database & config
|
||||||
|
========================
|
||||||
|
|
||||||
|
Before doing an upgrade, it is critical to back up:
|
||||||
|
|
||||||
|
#. Your JupyterHub database (sqlite by default, or MySQL / Postgres
|
||||||
|
if you used those). If you are using sqlite (the default), you
|
||||||
|
should backup the ``jupyterhub.sqlite`` file.
|
||||||
|
#. Your ``jupyterhub_config.py`` file.
|
||||||
|
#. Your user's home directories. This is unlikely to be affected directly by
|
||||||
|
a JupyterHub upgrade, but we recommend a backup since user data is very
|
||||||
|
critical.
|
||||||
|
|
||||||
|
|
||||||
|
Shutdown JupyterHub
|
||||||
|
===================
|
||||||
|
|
||||||
|
Shutdown the JupyterHub process. This would vary depending on how you
|
||||||
|
have set up JupyterHub to run. Most likely, it is using a process
|
||||||
|
supervisor of some sort (``systemd`` or ``supervisord`` or even ``docker``).
|
||||||
|
Use the supervisor specific command to stop the JupyterHub process.
|
||||||
|
|
||||||
|
Upgrade JupyterHub packages
|
||||||
|
===========================
|
||||||
|
|
||||||
|
There are two environments where the ``jupyterhub`` package is installed:
|
||||||
|
|
||||||
|
#. The *hub environment*, which is where the JupyterHub server process
|
||||||
|
runs. This is started with the ``jupyterhub`` command, and is what
|
||||||
|
people generally think of as JupyterHub.
|
||||||
|
|
||||||
|
#. The *notebook user environments*. This is where the user notebook
|
||||||
|
servers are launched from, and is probably custom to your own
|
||||||
|
installation. This could be just one environment (different from the
|
||||||
|
hub environment) that is shared by all users, one environment
|
||||||
|
per user, or same environment as the hub environment. The hub
|
||||||
|
launched the ``jupyterhub-singleuser`` command in this environment,
|
||||||
|
which in turn starts the notebook server.
|
||||||
|
|
||||||
|
You need to make sure the version of the ``jupyterhub`` package matches
|
||||||
|
in both these environments. If you installed ``jupyterhub`` with pip,
|
||||||
|
you can upgrade it with:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python3 -m pip install --upgrade jupyterhub==<version>
|
||||||
|
|
||||||
|
Where ``<version>`` is the version of JupyterHub you are upgrading to.
|
||||||
|
|
||||||
|
If you used ``conda`` to install ``jupyterhub``, you should upgrade it
|
||||||
|
with:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
conda install -c conda-forge jupyterhub==<version>
|
||||||
|
|
||||||
|
Where ``<version>`` is the version of JupyterHub you are upgrading to.
|
||||||
|
|
||||||
|
You should also check for new releases of the authenticator & spawner you
|
||||||
|
are using. You might wish to upgrade those packages too along with JupyterHub,
|
||||||
|
or upgrade them separately.
|
||||||
|
|
||||||
|
Upgrade JupyterHub database
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Once new packages are installed, you need to upgrade the JupyterHub
|
||||||
|
database. From the hub environment, in the same directory as your
|
||||||
|
``jupyterhub_config.py`` file, you should run:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
jupyterhub upgrade-db
|
||||||
|
|
||||||
|
This should find the location of your database, and run necessary upgrades
|
||||||
|
for it.
|
||||||
|
|
||||||
|
SQLite database disadvantages
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
SQLite has some disadvantages when it comes to upgrading JupyterHub. These
|
||||||
|
are:
|
||||||
|
|
||||||
|
- ``upgrade-db`` may not work, and you may need delete your database
|
||||||
|
and start with a fresh one.
|
||||||
|
- ``downgrade-db`` **will not** work if you want to rollback to an
|
||||||
|
earlier version, so backup the ``jupyterhub.sqlite`` file before
|
||||||
|
upgrading
|
||||||
|
|
||||||
|
What happens if I delete my database?
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
Losing the Hub database is often not a big deal. Information that
|
||||||
|
resides only in the Hub database includes:
|
||||||
|
|
||||||
|
- active login tokens (user cookies, service tokens)
|
||||||
|
- users added via JupyterHub UI, instead of config files
|
||||||
|
- info about running servers
|
||||||
|
|
||||||
|
If the following conditions are true, you should be fine clearing the
|
||||||
|
Hub database and starting over:
|
||||||
|
|
||||||
|
- users specified in config file, or login using an external
|
||||||
|
authentication provider (Google, GitHub, LDAP, etc)
|
||||||
|
- user servers are stopped during upgrade
|
||||||
|
- don't mind causing users to login again after upgrade
|
||||||
|
|
||||||
|
Start JupyterHub
|
||||||
|
================
|
||||||
|
|
||||||
|
Once the database upgrade is completed, start the ``jupyterhub``
|
||||||
|
process again.
|
||||||
|
|
||||||
|
#. Log-in and start the server to make sure things work as
|
||||||
|
expected.
|
||||||
|
#. Check the logs for any errors or deprecation warnings. You
|
||||||
|
might have to update your ``jupyterhub_config.py`` file to
|
||||||
|
deal with any deprecated options.
|
||||||
|
|
||||||
|
Congratulations, your JupyterHub has been upgraded!
|
@@ -13,4 +13,3 @@ Module: :mod:`jupyterhub.app`
|
|||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
.. autoconfigurable:: JupyterHub
|
.. autoconfigurable:: JupyterHub
|
||||||
|
|
||||||
|
@@ -26,3 +26,7 @@ Module: :mod:`jupyterhub.auth`
|
|||||||
|
|
||||||
.. autoconfigurable:: PAMAuthenticator
|
.. autoconfigurable:: PAMAuthenticator
|
||||||
|
|
||||||
|
:class:`DummyAuthenticator`
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. autoconfigurable:: DummyAuthenticator
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
.. _api-index:
|
.. _api-index:
|
||||||
|
|
||||||
##################
|
##############
|
||||||
The JupyterHub API
|
JupyterHub API
|
||||||
##################
|
##############
|
||||||
|
|
||||||
:Release: |release|
|
:Release: |release|
|
||||||
:Date: |today|
|
:Date: |today|
|
||||||
|
@@ -20,4 +20,3 @@ Module: :mod:`jupyterhub.proxy`
|
|||||||
|
|
||||||
.. autoconfigurable:: ConfigurableHTTPProxy
|
.. autoconfigurable:: ConfigurableHTTPProxy
|
||||||
:members: debug, auth_token, check_running_interval, api_url, command
|
:members: debug, auth_token, check_running_interval, api_url, command
|
||||||
|
|
||||||
|
@@ -14,4 +14,3 @@ Module: :mod:`jupyterhub.services.service`
|
|||||||
|
|
||||||
.. autoconfigurable:: Service
|
.. autoconfigurable:: Service
|
||||||
:members: name, admin, url, api_token, managed, kind, command, cwd, environment, user, oauth_client_id, server, prefix, proxy_spec
|
:members: name, admin, url, api_token, managed, kind, command, cwd, environment, user, oauth_client_id, server, prefix, proxy_spec
|
||||||
|
|
||||||
|
@@ -38,4 +38,3 @@ Module: :mod:`jupyterhub.services.auth`
|
|||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
.. autoclass:: HubOAuthCallbackHandler
|
.. autoclass:: HubOAuthCallbackHandler
|
||||||
|
|
||||||
|
@@ -13,10 +13,9 @@ Module: :mod:`jupyterhub.spawner`
|
|||||||
----------------
|
----------------
|
||||||
|
|
||||||
.. autoconfigurable:: Spawner
|
.. autoconfigurable:: Spawner
|
||||||
:members: options_from_form, poll, start, stop, get_args, get_env, get_state, template_namespace, format_string
|
:members: options_from_form, poll, start, stop, get_args, get_env, get_state, template_namespace, format_string, create_certs, move_certs
|
||||||
|
|
||||||
:class:`LocalProcessSpawner`
|
:class:`LocalProcessSpawner`
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
.. autoconfigurable:: LocalProcessSpawner
|
.. autoconfigurable:: LocalProcessSpawner
|
||||||
|
|
||||||
|
@@ -34,4 +34,3 @@ Module: :mod:`jupyterhub.user`
|
|||||||
.. attribute:: spawner
|
.. attribute:: spawner
|
||||||
|
|
||||||
The user's :class:`~.Spawner` instance.
|
The user's :class:`~.Spawner` instance.
|
||||||
|
|
||||||
|
@@ -1,11 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
import shlex
|
import sys
|
||||||
|
|
||||||
# For conversion from markdown to html
|
|
||||||
import recommonmark.parser
|
|
||||||
|
|
||||||
# Set paths
|
# Set paths
|
||||||
sys.path.insert(0, os.path.abspath('.'))
|
sys.path.insert(0, os.path.abspath('.'))
|
||||||
@@ -21,10 +17,11 @@ extensions = [
|
|||||||
'sphinx.ext.intersphinx',
|
'sphinx.ext.intersphinx',
|
||||||
'sphinx.ext.napoleon',
|
'sphinx.ext.napoleon',
|
||||||
'autodoc_traits',
|
'autodoc_traits',
|
||||||
|
'sphinx_copybutton',
|
||||||
|
'sphinx-jsonschema',
|
||||||
|
'recommonmark',
|
||||||
]
|
]
|
||||||
|
|
||||||
templates_path = ['_templates']
|
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
||||||
@@ -39,7 +36,6 @@ from os.path import dirname
|
|||||||
docs = dirname(dirname(__file__))
|
docs = dirname(dirname(__file__))
|
||||||
root = dirname(docs)
|
root = dirname(docs)
|
||||||
sys.path.insert(0, root)
|
sys.path.insert(0, root)
|
||||||
sys.path.insert(0, os.path.join(docs, 'sphinxext'))
|
|
||||||
|
|
||||||
import jupyterhub
|
import jupyterhub
|
||||||
|
|
||||||
@@ -58,7 +54,69 @@ default_role = 'literal'
|
|||||||
|
|
||||||
# -- Source -------------------------------------------------------------
|
# -- Source -------------------------------------------------------------
|
||||||
|
|
||||||
source_parsers = {'.md': 'recommonmark.parser.CommonMarkParser'}
|
import recommonmark
|
||||||
|
from recommonmark.transform import AutoStructify
|
||||||
|
|
||||||
|
# -- Config -------------------------------------------------------------
|
||||||
|
from jupyterhub.app import JupyterHub
|
||||||
|
from docutils import nodes
|
||||||
|
from sphinx.directives.other import SphinxDirective
|
||||||
|
from contextlib import redirect_stdout
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
# create a temp instance of JupyterHub just to get the output of the generate-config
|
||||||
|
# and help --all commands.
|
||||||
|
jupyterhub_app = JupyterHub()
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigDirective(SphinxDirective):
|
||||||
|
"""Generate the configuration file output for use in the documentation."""
|
||||||
|
|
||||||
|
has_content = False
|
||||||
|
required_arguments = 0
|
||||||
|
optional_arguments = 0
|
||||||
|
final_argument_whitespace = False
|
||||||
|
option_spec = {}
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# The generated configuration file for this version
|
||||||
|
generated_config = jupyterhub_app.generate_config_file()
|
||||||
|
# post-process output
|
||||||
|
home_dir = os.environ['HOME']
|
||||||
|
generated_config = generated_config.replace(home_dir, '$HOME', 1)
|
||||||
|
par = nodes.literal_block(text=generated_config)
|
||||||
|
return [par]
|
||||||
|
|
||||||
|
|
||||||
|
class HelpAllDirective(SphinxDirective):
|
||||||
|
"""Print the output of jupyterhub help --all for use in the documentation."""
|
||||||
|
|
||||||
|
has_content = False
|
||||||
|
required_arguments = 0
|
||||||
|
optional_arguments = 0
|
||||||
|
final_argument_whitespace = False
|
||||||
|
option_spec = {}
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# The output of the help command for this version
|
||||||
|
buffer = StringIO()
|
||||||
|
with redirect_stdout(buffer):
|
||||||
|
jupyterhub_app.print_help('--help-all')
|
||||||
|
all_help = buffer.getvalue()
|
||||||
|
# post-process output
|
||||||
|
home_dir = os.environ['HOME']
|
||||||
|
all_help = all_help.replace(home_dir, '$HOME', 1)
|
||||||
|
par = nodes.literal_block(text=all_help)
|
||||||
|
return [par]
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
app.add_config_value('recommonmark_config', {'enable_eval_rst': True}, True)
|
||||||
|
app.add_css_file('custom.css')
|
||||||
|
app.add_transform(AutoStructify)
|
||||||
|
app.add_directive('jupyterhub-generate-config', ConfigDirective)
|
||||||
|
app.add_directive('jupyterhub-help-all', HelpAllDirective)
|
||||||
|
|
||||||
|
|
||||||
source_suffix = ['.rst', '.md']
|
source_suffix = ['.rst', '.md']
|
||||||
# source_encoding = 'utf-8-sig'
|
# source_encoding = 'utf-8-sig'
|
||||||
@@ -66,7 +124,7 @@ source_suffix = ['.rst', '.md']
|
|||||||
# -- Options for HTML output ----------------------------------------------
|
# -- Options for HTML output ----------------------------------------------
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages.
|
# The theme to use for HTML and HTML Help pages.
|
||||||
html_theme = 'alabaster'
|
html_theme = 'pydata_sphinx_theme'
|
||||||
|
|
||||||
html_logo = '_static/images/logo/logo.png'
|
html_logo = '_static/images/logo/logo.png'
|
||||||
html_favicon = '_static/images/logo/favicon.ico'
|
html_favicon = '_static/images/logo/favicon.ico'
|
||||||
@@ -74,31 +132,6 @@ html_favicon = '_static/images/logo/favicon.ico'
|
|||||||
# Paths that contain custom static files (such as style sheets)
|
# Paths that contain custom static files (such as style sheets)
|
||||||
html_static_path = ['_static']
|
html_static_path = ['_static']
|
||||||
|
|
||||||
html_theme_options = {
|
|
||||||
'show_related': True,
|
|
||||||
'description': 'Documentation for JupyterHub',
|
|
||||||
'github_user': 'jupyterhub',
|
|
||||||
'github_repo': 'jupyterhub',
|
|
||||||
'github_banner': False,
|
|
||||||
'github_button': True,
|
|
||||||
'github_type': 'star',
|
|
||||||
'show_powered_by': False,
|
|
||||||
'extra_nav_links': {
|
|
||||||
'GitHub Repo': 'http://github.com/jupyterhub/jupyterhub',
|
|
||||||
'Issue Tracker': 'http://github.com/jupyterhub/jupyterhub/issues',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
html_sidebars = {
|
|
||||||
'**': [
|
|
||||||
'about.html',
|
|
||||||
'searchbox.html',
|
|
||||||
'navigation.html',
|
|
||||||
'relations.html',
|
|
||||||
'sourcelink.html',
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlhelp_basename = 'JupyterHubdoc'
|
htmlhelp_basename = 'JupyterHubdoc'
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
@@ -181,14 +214,12 @@ intersphinx_mapping = {'https://docs.python.org/3/': None}
|
|||||||
# -- Read The Docs --------------------------------------------------------
|
# -- Read The Docs --------------------------------------------------------
|
||||||
|
|
||||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||||
if not on_rtd:
|
if on_rtd:
|
||||||
html_theme = 'alabaster'
|
|
||||||
else:
|
|
||||||
# readthedocs.org uses their theme by default, so no need to specify it
|
# readthedocs.org uses their theme by default, so no need to specify it
|
||||||
# build rest-api, since RTD doesn't run make
|
# build both metrics and rest-api, since RTD doesn't run make
|
||||||
from subprocess import check_call as sh
|
from subprocess import check_call as sh
|
||||||
|
|
||||||
sh(['make', 'rest-api'], cwd=docs)
|
sh(['make', 'metrics', 'rest-api'], cwd=docs)
|
||||||
|
|
||||||
# -- Spell checking -------------------------------------------------------
|
# -- Spell checking -------------------------------------------------------
|
||||||
|
|
||||||
|
30
docs/source/contributing/community.rst
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
.. _contributing/community:
|
||||||
|
|
||||||
|
================================
|
||||||
|
Community communication channels
|
||||||
|
================================
|
||||||
|
|
||||||
|
We use `Discourse <https://discourse.jupyter.org>` for online discussion.
|
||||||
|
Everyone in the Jupyter community is welcome to bring ideas and questions there.
|
||||||
|
In addition, we use `Gitter <https://gitter.im>`_ for online, real-time text chat,
|
||||||
|
a place for more ephemeral discussions.
|
||||||
|
The primary Gitter channel for JupyterHub is `jupyterhub/jupyterhub <https://gitter.im/jupyterhub/jupyterhub>`_.
|
||||||
|
Gitter isn't archived or searchable, so we recommend going to discourse first
|
||||||
|
to make sure that discussions are most useful and accessible to the community.
|
||||||
|
Remember that our community is distributed across the world in various
|
||||||
|
timezones, so be patient if you do not get an answer immediately!
|
||||||
|
|
||||||
|
GitHub issues are used for most long-form project discussions, bug reports
|
||||||
|
and feature requests. Issues related to a specific authenticator or
|
||||||
|
spawner should be directed to the appropriate repository for the
|
||||||
|
authenticator or spawner. If you are using a specific JupyterHub
|
||||||
|
distribution (such as `Zero to JupyterHub on Kubernetes <http://github.com/jupyterhub/zero-to-jupyterhub-k8s>`_
|
||||||
|
or `The Littlest JupyterHub <http://github.com/jupyterhub/the-littlest-jupyterhub/>`_),
|
||||||
|
you should open issues directly in their repository. If you can not
|
||||||
|
find a repository to open your issue in, do not worry! Create it in the `main
|
||||||
|
JupyterHub repository <https://github.com/jupyterhub/jupyterhub/>`_ and our
|
||||||
|
community will help you figure it out.
|
||||||
|
|
||||||
|
A `mailing list <https://groups.google.com/forum/#!forum/jupyter>`_ for all
|
||||||
|
of Project Jupyter exists, along with one for `teaching with Jupyter
|
||||||
|
<https://groups.google.com/forum/#!forum/jupyter-education>`_.
|
78
docs/source/contributing/docs.rst
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
.. _contributing/docs:
|
||||||
|
|
||||||
|
==========================
|
||||||
|
Contributing Documentation
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Documentation is often more important than code. This page helps
|
||||||
|
you get set up on how to contribute documentation to JupyterHub.
|
||||||
|
|
||||||
|
Building documentation locally
|
||||||
|
==============================
|
||||||
|
|
||||||
|
We use `sphinx <http://sphinx-doc.org>`_ to build our documentation. It takes
|
||||||
|
our documentation source files (written in `markdown
|
||||||
|
<https://daringfireball.net/projects/markdown/>`_ or `reStructuredText
|
||||||
|
<http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html>`_ &
|
||||||
|
stored under the ``docs/source`` directory) and converts it into various
|
||||||
|
formats for people to read. To make sure the documentation you write or
|
||||||
|
change renders correctly, it is good practice to test it locally.
|
||||||
|
|
||||||
|
#. Make sure you have successfuly completed :ref:`contributing/setup`.
|
||||||
|
|
||||||
|
#. Install the packages required to build the docs.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python3 -m pip install -r docs/requirements.txt
|
||||||
|
|
||||||
|
#. Build the html version of the docs. This is the most commonly used
|
||||||
|
output format, so verifying it renders as you should is usually good
|
||||||
|
enough.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
cd docs
|
||||||
|
make html
|
||||||
|
|
||||||
|
This step will display any syntax or formatting errors in the documentation,
|
||||||
|
along with the filename / line number in which they occurred. Fix them,
|
||||||
|
and re-run the ``make html`` command to re-render the documentation.
|
||||||
|
|
||||||
|
#. View the rendered documentation by opening ``build/html/index.html`` in
|
||||||
|
a web browser.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
On macOS, you can open a file from the terminal with ``open <path-to-file>``.
|
||||||
|
On Linux, you can do the same with ``xdg-open <path-to-file>``.
|
||||||
|
|
||||||
|
|
||||||
|
.. _contributing/docs/conventions:
|
||||||
|
|
||||||
|
Documentation conventions
|
||||||
|
=========================
|
||||||
|
|
||||||
|
This section lists various conventions we use in our documentation. This is a
|
||||||
|
living document that grows over time, so feel free to add to it / change it!
|
||||||
|
|
||||||
|
Our entire documentation does not yet fully conform to these conventions yet,
|
||||||
|
so help in making it so would be appreciated!
|
||||||
|
|
||||||
|
``pip`` invocation
|
||||||
|
------------------
|
||||||
|
|
||||||
|
There are many ways to invoke a ``pip`` command, we recommend the following
|
||||||
|
approach:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python3 -m pip
|
||||||
|
|
||||||
|
This invokes pip explicitly using the python3 binary that you are
|
||||||
|
currently using. This is the **recommended way** to invoke pip
|
||||||
|
in our documentation, since it is least likely to cause problems
|
||||||
|
with python3 and pip being from different environments.
|
||||||
|
|
||||||
|
For more information on how to invoke ``pip`` commands, see
|
||||||
|
`the pip documentation <https://pip.pypa.io/en/stable/>`_.
|
21
docs/source/contributing/index.rst
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
============
|
||||||
|
Contributing
|
||||||
|
============
|
||||||
|
|
||||||
|
We want you to contribute to JupyterHub in ways that are most exciting
|
||||||
|
& useful to you. We value documentation, testing, bug reporting & code equally,
|
||||||
|
and are glad to have your contributions in whatever form you wish :)
|
||||||
|
|
||||||
|
Our `Code of Conduct <https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md>`_
|
||||||
|
(`reporting guidelines <https://github.com/jupyter/governance/blob/master/conduct/reporting_online.md>`_)
|
||||||
|
helps keep our community welcoming to as many people as possible.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
community
|
||||||
|
setup
|
||||||
|
docs
|
||||||
|
tests
|
||||||
|
roadmap
|
||||||
|
security
|
95
docs/source/contributing/roadmap.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# The JupyterHub roadmap
|
||||||
|
|
||||||
|
This roadmap collects "next steps" for JupyterHub. It is about creating a
|
||||||
|
shared understanding of the project's vision and direction amongst
|
||||||
|
the community of users, contributors, and maintainers.
|
||||||
|
The goal is to communicate priorities and upcoming release plans.
|
||||||
|
It is not a aimed at limiting contributions to what is listed here.
|
||||||
|
|
||||||
|
## Using the roadmap
|
||||||
|
|
||||||
|
### Sharing Feedback on the Roadmap
|
||||||
|
|
||||||
|
All of the community is encouraged to provide feedback as well as share new
|
||||||
|
ideas with the community. Please do so by submitting an issue. If you want to
|
||||||
|
have an informal conversation first use one of the other communication channels.
|
||||||
|
After submitting the issue, others from the community will probably
|
||||||
|
respond with questions or comments they have to clarify the issue. The
|
||||||
|
maintainers will help identify what a good next step is for the issue.
|
||||||
|
|
||||||
|
### What do we mean by "next step"?
|
||||||
|
|
||||||
|
When submitting an issue, think about what "next step" category best describes
|
||||||
|
your issue:
|
||||||
|
|
||||||
|
- **now**, concrete/actionable step that is ready for someone to start work on.
|
||||||
|
These might be items that have a link to an issue or more abstract like
|
||||||
|
"decrease typos and dead links in the documentation"
|
||||||
|
- **soon**, less concrete/actionable step that is going to happen soon,
|
||||||
|
discussions around the topic are coming close to an end at which point it can
|
||||||
|
move into the "now" category
|
||||||
|
- **later**, abstract ideas or tasks, need a lot of discussion or
|
||||||
|
experimentation to shape the idea so that it can be executed. Can also
|
||||||
|
contain concrete/actionable steps that have been postponed on purpose
|
||||||
|
(these are steps that could be in "now" but the decision was taken to work on
|
||||||
|
them later)
|
||||||
|
|
||||||
|
### Reviewing and Updating the Roadmap
|
||||||
|
|
||||||
|
The roadmap will get updated as time passes (next review by 1st December) based
|
||||||
|
on discussions and ideas captured as issues.
|
||||||
|
This means this list should not be exhaustive, it should only represent
|
||||||
|
the "top of the stack" of ideas. It should
|
||||||
|
not function as a wish list, collection of feature requests or todo list.
|
||||||
|
For those please create a
|
||||||
|
[new issue](https://github.com/jupyterhub/jupyterhub/issues/new).
|
||||||
|
|
||||||
|
The roadmap should give the reader an idea of what is happening next, what needs
|
||||||
|
input and discussion before it can happen and what has been postponed.
|
||||||
|
|
||||||
|
## The roadmap proper
|
||||||
|
|
||||||
|
### Project vision
|
||||||
|
|
||||||
|
JupyterHub is a dependable tool used by humans that reduces the complexity of
|
||||||
|
creating the environment in which a piece of software can be executed.
|
||||||
|
|
||||||
|
### Now
|
||||||
|
|
||||||
|
These "Now" items are considered active areas of focus for the project:
|
||||||
|
|
||||||
|
- HubShare - a sharing service for use with JupyterHub.
|
||||||
|
- Users should be able to:
|
||||||
|
- Push a project to other users.
|
||||||
|
- Get a checkout of a project from other users.
|
||||||
|
- Push updates to a published project.
|
||||||
|
- Pull updates from a published project.
|
||||||
|
- Manage conflicts/merges by simply picking a version (our/theirs)
|
||||||
|
- Get a checkout of a project from the internet. These steps are completely different from saving notebooks/files.
|
||||||
|
- Have directories that are managed by git completely separately from our stuff.
|
||||||
|
- Look at pushed content that they have access to without an explicit pull.
|
||||||
|
- Define and manage teams of users.
|
||||||
|
- Adding/removing a user to/from a team gives/removes them access to all projects that team has access to.
|
||||||
|
- Build other services, such as static HTML publishing and dashboarding on top of these things.
|
||||||
|
|
||||||
|
### Soon
|
||||||
|
|
||||||
|
These "Soon" items are under discussion. Once an item reaches the point of an
|
||||||
|
actionable plan, the item will be moved to the "Now" section. Typically,
|
||||||
|
these will be moved at a future review of the roadmap.
|
||||||
|
|
||||||
|
- resource monitoring and management:
|
||||||
|
- (prometheus?) API for resource monitoring
|
||||||
|
- tracking activity on single-user servers instead of the proxy
|
||||||
|
- notes and activity tracking per API token
|
||||||
|
|
||||||
|
### Later
|
||||||
|
|
||||||
|
The "Later" items are things that are at the back of the project's mind. At this
|
||||||
|
time there is no active plan for an item. The project would like to find the
|
||||||
|
resources and time to discuss these ideas.
|
||||||
|
|
||||||
|
- real-time collaboration
|
||||||
|
- Enter into real-time collaboration mode for a project that starts a shared execution context.
|
||||||
|
- Once the single-user notebook package supports realtime collaboration,
|
||||||
|
implement sharing mechanism integrated into the Hub.
|
10
docs/source/contributing/security.rst
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Reporting security issues in Jupyter or JupyterHub
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
If you find a security vulnerability in Jupyter or JupyterHub,
|
||||||
|
whether it is a failure of the security model described in :doc:`../reference/websecurity`
|
||||||
|
or a failure in implementation,
|
||||||
|
please report it to security@ipython.org.
|
||||||
|
|
||||||
|
If you prefer to encrypt your security reports,
|
||||||
|
you can use :download:`this PGP public key </ipython_security.asc>`.
|
188
docs/source/contributing/setup.rst
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
.. _contributing/setup:
|
||||||
|
|
||||||
|
================================
|
||||||
|
Setting up a development install
|
||||||
|
================================
|
||||||
|
|
||||||
|
System requirements
|
||||||
|
===================
|
||||||
|
|
||||||
|
JupyterHub can only run on MacOS or Linux operating systems. If you are
|
||||||
|
using Windows, we recommend using `VirtualBox <https://virtualbox.org>`_
|
||||||
|
or a similar system to run `Ubuntu Linux <https://ubuntu.com>`_ for
|
||||||
|
development.
|
||||||
|
|
||||||
|
Install Python
|
||||||
|
--------------
|
||||||
|
|
||||||
|
JupyterHub is written in the `Python <https://python.org>`_ programming language, and
|
||||||
|
requires you have at least version 3.5 installed locally. If you haven’t
|
||||||
|
installed Python before, the recommended way to install it is to use
|
||||||
|
`miniconda <https://conda.io/miniconda.html>`_. Remember to get the ‘Python 3’ version,
|
||||||
|
and **not** the ‘Python 2’ version!
|
||||||
|
|
||||||
|
Install nodejs
|
||||||
|
--------------
|
||||||
|
|
||||||
|
``configurable-http-proxy``, the default proxy implementation for
|
||||||
|
JupyterHub, is written in Javascript to run on `NodeJS
|
||||||
|
<https://nodejs.org/en/>`_. If you have not installed nodejs before, we
|
||||||
|
recommend installing it in the ``miniconda`` environment you set up for
|
||||||
|
Python. You can do so with ``conda install nodejs``.
|
||||||
|
|
||||||
|
Install git
|
||||||
|
-----------
|
||||||
|
|
||||||
|
JupyterHub uses `git <https://git-scm.com>`_ & `GitHub <https://github.com>`_
|
||||||
|
for development & collaboration. You need to `install git
|
||||||
|
<https://git-scm.com/book/en/v2/Getting-Started-Installing-Git>`_ to work on
|
||||||
|
JupyterHub. We also recommend getting a free account on GitHub.com.
|
||||||
|
|
||||||
|
Setting up a development install
|
||||||
|
================================
|
||||||
|
|
||||||
|
When developing JupyterHub, you need to make changes to the code & see
|
||||||
|
their effects quickly. You need to do a developer install to make that
|
||||||
|
happen.
|
||||||
|
|
||||||
|
.. note:: This guide does not attempt to dictate *how* development
|
||||||
|
environements should be isolated since that is a personal preference and can
|
||||||
|
be achieved in many ways, for example `tox`, `conda`, `docker`, etc. See this
|
||||||
|
`forum thread <https://discourse.jupyter.org/t/thoughts-on-using-tox/3497>`_ for
|
||||||
|
a more detailed discussion.
|
||||||
|
|
||||||
|
1. Clone the `JupyterHub git repository <https://github.com/jupyterhub/jupyterhub>`_
|
||||||
|
to your computer.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
git clone https://github.com/jupyterhub/jupyterhub
|
||||||
|
cd jupyterhub
|
||||||
|
|
||||||
|
2. Make sure the ``python`` you installed and the ``npm`` you installed
|
||||||
|
are available to you on the command line.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
python -V
|
||||||
|
|
||||||
|
This should return a version number greater than or equal to 3.5.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
npm -v
|
||||||
|
|
||||||
|
This should return a version number greater than or equal to 5.0.
|
||||||
|
|
||||||
|
3. Install ``configurable-http-proxy``. This is required to run
|
||||||
|
JupyterHub.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
npm install -g configurable-http-proxy
|
||||||
|
|
||||||
|
If you get an error that says ``Error: EACCES: permission denied``,
|
||||||
|
you might need to prefix the command with ``sudo``. If you do not
|
||||||
|
have access to sudo, you may instead run the following commands:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
npm install configurable-http-proxy
|
||||||
|
export PATH=$PATH:$(pwd)/node_modules/.bin
|
||||||
|
|
||||||
|
The second line needs to be run every time you open a new terminal.
|
||||||
|
|
||||||
|
4. Install the python packages required for JupyterHub development.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
python3 -m pip install -r dev-requirements.txt
|
||||||
|
python3 -m pip install -r requirements.txt
|
||||||
|
|
||||||
|
5. Setup a database.
|
||||||
|
|
||||||
|
The default database engine is ``sqlite`` so if you are just trying
|
||||||
|
to get up and running quickly for local development that should be
|
||||||
|
available via `python <https://docs.python.org/3.5/library/sqlite3.html>`__.
|
||||||
|
See :doc:`/reference/database` for details on other supported databases.
|
||||||
|
|
||||||
|
6. Install the development version of JupyterHub. This lets you edit
|
||||||
|
JupyterHub code in a text editor & restart the JupyterHub process to
|
||||||
|
see your code changes immediately.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
python3 -m pip install --editable .
|
||||||
|
|
||||||
|
7. You are now ready to start JupyterHub!
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
jupyterhub
|
||||||
|
|
||||||
|
8. You can access JupyterHub from your browser at
|
||||||
|
``http://localhost:8000`` now.
|
||||||
|
|
||||||
|
Happy developing!
|
||||||
|
|
||||||
|
Using DummyAuthenticator & SimpleLocalProcessSpawner
|
||||||
|
====================================================
|
||||||
|
|
||||||
|
To simplify testing of JupyterHub, it’s helpful to use
|
||||||
|
:class:`~jupyterhub.auth.DummyAuthenticator` instead of the default JupyterHub
|
||||||
|
authenticator and SimpleLocalProcessSpawner instead of the default spawner.
|
||||||
|
|
||||||
|
There is a sample configuration file that does this in
|
||||||
|
``testing/jupyterhub_config.py``. To launch jupyterhub with this
|
||||||
|
configuration:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
jupyterhub -f testing/jupyterhub_config.py
|
||||||
|
|
||||||
|
The default JupyterHub `authenticator
|
||||||
|
<https://jupyterhub.readthedocs.io/en/stable/reference/authenticators.html#the-default-pam-authenticator>`_
|
||||||
|
& `spawner
|
||||||
|
<https://jupyterhub.readthedocs.io/en/stable/api/spawner.html#localprocessspawner>`_
|
||||||
|
require your system to have user accounts for each user you want to log in to
|
||||||
|
JupyterHub as.
|
||||||
|
|
||||||
|
DummyAuthenticator allows you to log in with any username & password,
|
||||||
|
while SimpleLocalProcessSpawner allows you to start servers without having to
|
||||||
|
create a unix user for each JupyterHub user. Together, these make it
|
||||||
|
much easier to test JupyterHub.
|
||||||
|
|
||||||
|
Tip: If you are working on parts of JupyterHub that are common to all
|
||||||
|
authenticators & spawners, we recommend using both DummyAuthenticator &
|
||||||
|
SimpleLocalProcessSpawner. If you are working on just authenticator related
|
||||||
|
parts, use only SimpleLocalProcessSpawner. Similarly, if you are working on
|
||||||
|
just spawner related parts, use only DummyAuthenticator.
|
||||||
|
|
||||||
|
Troubleshooting
|
||||||
|
===============
|
||||||
|
|
||||||
|
This section lists common ways setting up your development environment may
|
||||||
|
fail, and how to fix them. Please add to the list if you encounter yet
|
||||||
|
another way it can fail!
|
||||||
|
|
||||||
|
``lessc`` not found
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
If the ``python3 -m pip install --editable .`` command fails and complains about
|
||||||
|
``lessc`` being unavailable, you may need to explicitly install some
|
||||||
|
additional JavaScript dependencies:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
npm install
|
||||||
|
|
||||||
|
This will fetch client-side JavaScript dependencies necessary to compile
|
||||||
|
CSS.
|
||||||
|
|
||||||
|
You may also need to manually update JavaScript and CSS after some
|
||||||
|
development updates, with:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
python3 setup.py js # fetch updated client-side js
|
||||||
|
python3 setup.py css # recompile CSS from LESS sources
|
68
docs/source/contributing/tests.rst
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
.. _contributing/tests:
|
||||||
|
|
||||||
|
==================
|
||||||
|
Testing JupyterHub
|
||||||
|
==================
|
||||||
|
|
||||||
|
Unit test help validate that JupyterHub works the way we think it does,
|
||||||
|
and continues to do so when changes occur. They also help communicate
|
||||||
|
precisely what we expect our code to do.
|
||||||
|
|
||||||
|
JupyterHub uses `pytest <https://pytest.org>`_ for all our tests. You
|
||||||
|
can find them under ``jupyterhub/tests`` directory in the git repository.
|
||||||
|
|
||||||
|
Running the tests
|
||||||
|
==================
|
||||||
|
|
||||||
|
#. Make sure you have completed :ref:`contributing/setup`. You should be able
|
||||||
|
to start ``jupyterhub`` from the commandline & access it from your
|
||||||
|
web browser. This ensures that the dev environment is properly set
|
||||||
|
up for tests to run.
|
||||||
|
|
||||||
|
#. You can run all tests in JupyterHub
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pytest -v jupyterhub/tests
|
||||||
|
|
||||||
|
This should display progress as it runs all the tests, printing
|
||||||
|
information about any test failures as they occur.
|
||||||
|
|
||||||
|
If you wish to confirm test coverage the run tests with the `--cov` flag:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pytest -v --cov=jupyterhub jupyterhub/tests
|
||||||
|
|
||||||
|
#. You can also run tests in just a specific file:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pytest -v jupyterhub/tests/<test-file-name>
|
||||||
|
|
||||||
|
#. To run a specific test only, you can do:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pytest -v jupyterhub/tests/<test-file-name>::<test-name>
|
||||||
|
|
||||||
|
This runs the test with function name ``<test-name>`` defined in
|
||||||
|
``<test-file-name>``. This is very useful when you are iteratively
|
||||||
|
developing a single test.
|
||||||
|
|
||||||
|
For example, to run the test ``test_shutdown`` in the file ``test_api.py``,
|
||||||
|
you would run:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pytest -v jupyterhub/tests/test_api.py::test_shutdown
|
||||||
|
|
||||||
|
|
||||||
|
Troubleshooting Test Failures
|
||||||
|
=============================
|
||||||
|
|
||||||
|
All the tests are failing
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Make sure you have completed all the steps in :ref:`contributing/setup` successfully, and
|
||||||
|
can launch ``jupyterhub`` from the terminal.
|
46
docs/source/events/index.rst
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
Eventlogging and Telemetry
|
||||||
|
==========================
|
||||||
|
|
||||||
|
JupyterHub can be configured to record structured events from a running server using Jupyter's `Telemetry System`_. The types of events that JupyterHub emits are defined by `JSON schemas`_ listed at the bottom of this page_.
|
||||||
|
|
||||||
|
.. _logging: https://docs.python.org/3/library/logging.html
|
||||||
|
.. _`Telemetry System`: https://github.com/jupyter/telemetry
|
||||||
|
.. _`JSON schemas`: https://json-schema.org/
|
||||||
|
|
||||||
|
How to emit events
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Event logging is handled by its ``Eventlog`` object. This leverages Python's standing logging_ library to emit, filter, and collect event data.
|
||||||
|
|
||||||
|
|
||||||
|
To begin recording events, you'll need to set two configurations:
|
||||||
|
|
||||||
|
1. ``handlers``: tells the EventLog *where* to route your events. This trait is a list of Python logging handlers that route events to
|
||||||
|
2. ``allows_schemas``: tells the EventLog *which* events should be recorded. No events are emitted by default; all recorded events must be listed here.
|
||||||
|
|
||||||
|
Here's a basic example:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
c.EventLog.handlers = [
|
||||||
|
logging.FileHandler('event.log'),
|
||||||
|
]
|
||||||
|
|
||||||
|
c.EventLog.allowed_schemas = [
|
||||||
|
'hub.jupyter.org/server-action'
|
||||||
|
]
|
||||||
|
|
||||||
|
The output is a file, ``"event.log"``, with events recorded as JSON data.
|
||||||
|
|
||||||
|
|
||||||
|
.. _page:
|
||||||
|
|
||||||
|
Event schemas
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
server-actions.rst
|
1
docs/source/events/server-actions.rst
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.. jsonschema:: ../../../jupyterhub/event-schemas/server-actions/v1.yaml
|
@@ -8,23 +8,25 @@ high performance computing.
|
|||||||
|
|
||||||
Please submit pull requests to update information or to add new institutions or uses.
|
Please submit pull requests to update information or to add new institutions or uses.
|
||||||
|
|
||||||
|
|
||||||
## Academic Institutions, Research Labs, and Supercomputer Centers
|
## Academic Institutions, Research Labs, and Supercomputer Centers
|
||||||
|
|
||||||
### University of California Berkeley
|
### University of California Berkeley
|
||||||
|
|
||||||
- [BIDS - Berkeley Institute for Data Science](https://bids.berkeley.edu/)
|
- [BIDS - Berkeley Institute for Data Science](https://bids.berkeley.edu/)
|
||||||
- [Teaching with Jupyter notebooks and JupyterHub](https://bids.berkeley.edu/resources/videos/teaching-ipythonjupyter-notebooks-and-jupyterhub)
|
|
||||||
|
- [Teaching with Jupyter notebooks and JupyterHub](https://bids.berkeley.edu/resources/videos/teaching-ipythonjupyter-notebooks-and-jupyterhub)
|
||||||
|
|
||||||
- [Data 8](http://data8.org/)
|
- [Data 8](http://data8.org/)
|
||||||
- [GitHub organization](https://github.com/data-8)
|
|
||||||
|
- [GitHub organization](https://github.com/data-8)
|
||||||
|
|
||||||
- [NERSC](http://www.nersc.gov/)
|
- [NERSC](http://www.nersc.gov/)
|
||||||
- [Press release on Jupyter and Cori](http://www.nersc.gov/news-publications/nersc-news/nersc-center-news/2016/jupyter-notebooks-will-open-up-new-possibilities-on-nerscs-cori-supercomputer/)
|
|
||||||
- [Moving and sharing data](https://www.nersc.gov/assets/Uploads/03-MovingAndSharingData-Cholia.pdf)
|
- [Press release on Jupyter and Cori](http://www.nersc.gov/news-publications/nersc-news/nersc-center-news/2016/jupyter-notebooks-will-open-up-new-possibilities-on-nerscs-cori-supercomputer/)
|
||||||
|
- [Moving and sharing data](https://www.nersc.gov/assets/Uploads/03-MovingAndSharingData-Cholia.pdf)
|
||||||
|
|
||||||
- [Research IT](http://research-it.berkeley.edu)
|
- [Research IT](http://research-it.berkeley.edu)
|
||||||
- [JupyterHub server supports campus research computation](http://research-it.berkeley.edu/blog/17/01/24/free-fully-loaded-jupyterhub-server-supports-campus-research-computation)
|
- [JupyterHub server supports campus research computation](http://research-it.berkeley.edu/blog/17/01/24/free-fully-loaded-jupyterhub-server-supports-campus-research-computation)
|
||||||
|
|
||||||
### University of California Davis
|
### University of California Davis
|
||||||
|
|
||||||
@@ -62,20 +64,25 @@ easy to do with RStudio too.
|
|||||||
### Clemson University
|
### Clemson University
|
||||||
|
|
||||||
- Advanced Computing
|
- Advanced Computing
|
||||||
- [Palmetto cluster and JupyterHub](http://citi.sites.clemson.edu/2016/08/18/JupyterHub-for-Palmetto-Cluster.html)
|
- [Palmetto cluster and JupyterHub](http://citi.sites.clemson.edu/2016/08/18/JupyterHub-for-Palmetto-Cluster.html)
|
||||||
|
|
||||||
### University of Colorado Boulder
|
### University of Colorado Boulder
|
||||||
|
|
||||||
- (CU Research Computing) CURC
|
- (CU Research Computing) CURC
|
||||||
- [JupyterHub User Guide](https://www.rc.colorado.edu/support/user-guide/jupyterhub.html)
|
|
||||||
- Slurm job dispatched on Crestone compute cluster
|
- [JupyterHub User Guide](https://www.rc.colorado.edu/support/user-guide/jupyterhub.html)
|
||||||
- log troubleshooting
|
- Slurm job dispatched on Crestone compute cluster
|
||||||
- Profiles in IPython Clusters tab
|
- log troubleshooting
|
||||||
- [Parallel Processing with JupyterHub tutorial](https://www.rc.colorado.edu/support/examples-and-tutorials/parallel-processing-with-jupyterhub.html)
|
- Profiles in IPython Clusters tab
|
||||||
- [Parallel Programming with JupyterHub document](https://www.rc.colorado.edu/book/export/html/833)
|
- [Parallel Processing with JupyterHub tutorial](https://www.rc.colorado.edu/support/examples-and-tutorials/parallel-processing-with-jupyterhub.html)
|
||||||
|
- [Parallel Programming with JupyterHub document](https://www.rc.colorado.edu/book/export/html/833)
|
||||||
|
|
||||||
- Earth Lab at CU
|
- Earth Lab at CU
|
||||||
- [Tutorial on Parallel R on JupyterHub](https://earthdatascience.org/tutorials/parallel-r-on-jupyterhub/)
|
- [Tutorial on Parallel R on JupyterHub](https://earthdatascience.org/tutorials/parallel-r-on-jupyterhub/)
|
||||||
|
|
||||||
|
### George Washington University
|
||||||
|
|
||||||
|
- [Jupyter Hub](http://go.gwu.edu/jupyter) with university single-sign-on. Deployed early 2017.
|
||||||
|
|
||||||
### HTCondor
|
### HTCondor
|
||||||
|
|
||||||
@@ -83,10 +90,15 @@ easy to do with RStudio too.
|
|||||||
|
|
||||||
### University of Illinois
|
### University of Illinois
|
||||||
|
|
||||||
- https://datascience.business.illinois.edu
|
- https://datascience.business.illinois.edu (currently down; checked 04/26/19)
|
||||||
|
|
||||||
|
### IllustrisTNG Simulation Project
|
||||||
|
|
||||||
|
- [JupyterHub/Lab-based analysis platform, part of the TNG public data release](http://www.tng-project.org/data/)
|
||||||
|
|
||||||
### MIT and Lincoln Labs
|
### MIT and Lincoln Labs
|
||||||
|
|
||||||
|
- https://supercloud.mit.edu/
|
||||||
|
|
||||||
### Michigan State University
|
### Michigan State University
|
||||||
|
|
||||||
@@ -100,31 +112,44 @@ easy to do with RStudio too.
|
|||||||
|
|
||||||
- https://dsa.missouri.edu/faq/
|
- https://dsa.missouri.edu/faq/
|
||||||
|
|
||||||
### University of Rochester CIRC
|
### Paderborn University
|
||||||
|
|
||||||
|
- [Data Science (DICE) group](https://dice.cs.uni-paderborn.de/)
|
||||||
|
- [nbgraderutils](https://github.com/dice-group/nbgraderutils): Use JupyterHub + nbgrader + iJava kernel for online Java exercises. Used in lecture Statistical Natural Language Processing.
|
||||||
|
|
||||||
|
### Penn State University
|
||||||
|
|
||||||
|
- [Press release](https://news.psu.edu/story/523093/2018/05/24/new-open-source-web-apps-available-students-and-faculty): "New open-source web apps available for students and faculty" (but Hub is currently down; checked 04/26/19)
|
||||||
|
|
||||||
|
### University of Rochester CIRC
|
||||||
|
|
||||||
- [JupyterHub Userguide](https://info.circ.rochester.edu/Web_Applications/JupyterHub.html) - Slurm, beehive
|
- [JupyterHub Userguide](https://info.circ.rochester.edu/Web_Applications/JupyterHub.html) - Slurm, beehive
|
||||||
|
|
||||||
### University of California San Diego
|
### University of California San Diego
|
||||||
|
|
||||||
- San Diego Supercomputer Center - Andrea Zonca
|
- San Diego Supercomputer Center - Andrea Zonca
|
||||||
- [Deploy JupyterHub on a Supercomputer with SSH](https://zonca.github.io/2017/05/jupyterhub-hpc-batchspawner-ssh.html)
|
|
||||||
- [Run Jupyterhub on a Supercomputer](https://zonca.github.io/2015/04/jupyterhub-hpc.html)
|
- [Deploy JupyterHub on a Supercomputer with SSH](https://zonca.github.io/2017/05/jupyterhub-hpc-batchspawner-ssh.html)
|
||||||
- [Deploy JupyterHub on a VM for a Workshop](https://zonca.github.io/2016/04/jupyterhub-sdsc-cloud.html)
|
- [Run Jupyterhub on a Supercomputer](https://zonca.github.io/2015/04/jupyterhub-hpc.html)
|
||||||
- [Customize your Python environment in Jupyterhub](https://zonca.github.io/2017/02/customize-python-environment-jupyterhub.html)
|
- [Deploy JupyterHub on a VM for a Workshop](https://zonca.github.io/2016/04/jupyterhub-sdsc-cloud.html)
|
||||||
- [Jupyterhub deployment on multiple nodes with Docker Swarm](https://zonca.github.io/2016/05/jupyterhub-docker-swarm.html)
|
- [Customize your Python environment in Jupyterhub](https://zonca.github.io/2017/02/customize-python-environment-jupyterhub.html)
|
||||||
- [Sample deployment of Jupyterhub in HPC on SDSC Comet](https://zonca.github.io/2017/02/sample-deployment-jupyterhub-hpc.html)
|
- [Jupyterhub deployment on multiple nodes with Docker Swarm](https://zonca.github.io/2016/05/jupyterhub-docker-swarm.html)
|
||||||
|
- [Sample deployment of Jupyterhub in HPC on SDSC Comet](https://zonca.github.io/2017/02/sample-deployment-jupyterhub-hpc.html)
|
||||||
|
|
||||||
- Educational Technology Services - Paul Jamason
|
- Educational Technology Services - Paul Jamason
|
||||||
- [jupyterhub.ucsd.edu](https://jupyterhub.ucsd.edu)
|
- [jupyterhub.ucsd.edu](https://jupyterhub.ucsd.edu)
|
||||||
|
|
||||||
### TACC University of Texas
|
### TACC University of Texas
|
||||||
|
|
||||||
### Texas A&M
|
### Texas A&M
|
||||||
|
|
||||||
- Kristen Thyng - Oceanography
|
- Kristen Thyng - Oceanography
|
||||||
- [Teaching with JupyterHub and nbgrader](http://kristenthyng.com/blog/2016/09/07/jupyterhub+nbgrader/)
|
- [Teaching with JupyterHub and nbgrader](http://kristenthyng.com/blog/2016/09/07/jupyterhub+nbgrader/)
|
||||||
|
|
||||||
|
### Elucidata
|
||||||
|
|
||||||
|
- What's new in Jupyter Notebooks @[Elucidata](https://elucidata.io/):
|
||||||
|
- Using Jupyter Notebooks with Jupyterhub on GCP, managed by GKE - https://medium.com/elucidata/why-you-should-be-using-a-jupyter-notebook-8385a4ccd93d
|
||||||
|
|
||||||
## Service Providers
|
## Service Providers
|
||||||
|
|
||||||
@@ -141,7 +166,6 @@ easy to do with RStudio too.
|
|||||||
|
|
||||||
[Everware](https://github.com/everware) Reproducible and reusable science powered by jupyterhub and docker. Like nbviewer, but executable. CERN, Geneva [website](http://everware.xyz/)
|
[Everware](https://github.com/everware) Reproducible and reusable science powered by jupyterhub and docker. Like nbviewer, but executable. CERN, Geneva [website](http://everware.xyz/)
|
||||||
|
|
||||||
|
|
||||||
### Microsoft Azure
|
### Microsoft Azure
|
||||||
|
|
||||||
- https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-linux-dsvm-intro
|
- https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-linux-dsvm-intro
|
||||||
@@ -151,9 +175,10 @@ easy to do with RStudio too.
|
|||||||
- https://getcarina.com/blog/learning-how-to-whale/
|
- https://getcarina.com/blog/learning-how-to-whale/
|
||||||
- http://carolynvanslyck.com/talk/carina/jupyterhub/#/
|
- http://carolynvanslyck.com/talk/carina/jupyterhub/#/
|
||||||
|
|
||||||
### jcloud.io
|
### Hadoop
|
||||||
- Open to public JupyterHub server
|
|
||||||
- https://jcloud.io
|
- [Deploying JupyterHub on Hadoop](https://jupyterhub-on-hadoop.readthedocs.io)
|
||||||
|
|
||||||
## Miscellaneous
|
## Miscellaneous
|
||||||
|
|
||||||
- https://medium.com/@ybarraud/setting-up-jupyterhub-with-sudospawner-and-anaconda-844628c0dbee#.rm3yt87e1
|
- https://medium.com/@ybarraud/setting-up-jupyterhub-with-sudospawner-and-anaconda-844628c0dbee#.rm3yt87e1
|
||||||
|
@@ -4,23 +4,22 @@ The default Authenticator uses [PAM][] to authenticate system users with
|
|||||||
their username and password. With the default Authenticator, any user
|
their username and password. With the default Authenticator, any user
|
||||||
with an account and password on the system will be allowed to login.
|
with an account and password on the system will be allowed to login.
|
||||||
|
|
||||||
## Create a whitelist of users
|
## Create a set of allowed users
|
||||||
|
|
||||||
You can restrict which users are allowed to login with a whitelist,
|
|
||||||
`Authenticator.whitelist`:
|
|
||||||
|
|
||||||
|
You can restrict which users are allowed to login with a set,
|
||||||
|
`Authenticator.allowed_users`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
c.Authenticator.whitelist = {'mal', 'zoe', 'inara', 'kaylee'}
|
c.Authenticator.allowed_users = {'mal', 'zoe', 'inara', 'kaylee'}
|
||||||
```
|
```
|
||||||
|
|
||||||
Users in the whitelist are added to the Hub database when the Hub is
|
Users in the `allowed_users` set are added to the Hub database when the Hub is
|
||||||
started.
|
started.
|
||||||
|
|
||||||
## Configure admins (`admin_users`)
|
## Configure admins (`admin_users`)
|
||||||
|
|
||||||
Admin users of JupyterHub, `admin_users`, can add and remove users from
|
Admin users of JupyterHub, `admin_users`, can add and remove users from
|
||||||
the user `whitelist`. `admin_users` can take actions on other users'
|
the user `allowed_users` set. `admin_users` can take actions on other users'
|
||||||
behalf, such as stopping and restarting their servers.
|
behalf, such as stopping and restarting their servers.
|
||||||
|
|
||||||
A set of initial admin users, `admin_users` can configured be as follows:
|
A set of initial admin users, `admin_users` can configured be as follows:
|
||||||
@@ -28,15 +27,25 @@ A set of initial admin users, `admin_users` can configured be as follows:
|
|||||||
```python
|
```python
|
||||||
c.Authenticator.admin_users = {'mal', 'zoe'}
|
c.Authenticator.admin_users = {'mal', 'zoe'}
|
||||||
```
|
```
|
||||||
Users in the admin list are automatically added to the user `whitelist`,
|
|
||||||
|
Users in the admin set are automatically added to the user `allowed_users` set,
|
||||||
if they are not already present.
|
if they are not already present.
|
||||||
|
|
||||||
|
Each authenticator may have different ways of determining whether a user is an
|
||||||
|
administrator. By default JupyterHub use the PAMAuthenticator which provide the
|
||||||
|
`admin_groups` option and can determine administrator status base on a user
|
||||||
|
groups. For example we can let any users in the `wheel` group be admin:
|
||||||
|
|
||||||
|
```python
|
||||||
|
c.PAMAuthenticator.admin_groups = {'wheel'}
|
||||||
|
```
|
||||||
|
|
||||||
## Give admin access to other users' notebook servers (`admin_access`)
|
## Give admin access to other users' notebook servers (`admin_access`)
|
||||||
|
|
||||||
Since the default `JupyterHub.admin_access` setting is False, the admins
|
Since the default `JupyterHub.admin_access` setting is False, the admins
|
||||||
do not have permission to log in to the single user notebook servers
|
do not have permission to log in to the single user notebook servers
|
||||||
owned by *other users*. If `JupyterHub.admin_access` is set to True,
|
owned by _other users_. If `JupyterHub.admin_access` is set to True,
|
||||||
then admins have permission to log in *as other users* on their
|
then admins have permission to log in _as other users_ on their
|
||||||
respective machines, for debugging. **As a courtesy, you should make
|
respective machines, for debugging. **As a courtesy, you should make
|
||||||
sure your users know if admin_access is enabled.**
|
sure your users know if admin_access is enabled.**
|
||||||
|
|
||||||
@@ -44,12 +53,12 @@ sure your users know if admin_access is enabled.**
|
|||||||
|
|
||||||
Users can be added to and removed from the Hub via either the admin
|
Users can be added to and removed from the Hub via either the admin
|
||||||
panel or the REST API. When a user is **added**, the user will be
|
panel or the REST API. When a user is **added**, the user will be
|
||||||
automatically added to the whitelist and database. Restarting the Hub
|
automatically added to the allowed users set and database. Restarting the Hub
|
||||||
will not require manually updating the whitelist in your config file,
|
will not require manually updating the allowed users set in your config file,
|
||||||
as the users will be loaded from the database.
|
as the users will be loaded from the database.
|
||||||
|
|
||||||
After starting the Hub once, it is not sufficient to **remove** a user
|
After starting the Hub once, it is not sufficient to **remove** a user
|
||||||
from the whitelist in your config file. You must also remove the user
|
from the allowed users set in your config file. You must also remove the user
|
||||||
from the Hub's database, either by deleting the user from JupyterHub's
|
from the Hub's database, either by deleting the user from JupyterHub's
|
||||||
admin page, or you can clear the `jupyterhub.sqlite` database and start
|
admin page, or you can clear the `jupyterhub.sqlite` database and start
|
||||||
fresh.
|
fresh.
|
||||||
@@ -82,6 +91,7 @@ JupyterHub's [OAuthenticator][] currently supports the following
|
|||||||
popular services:
|
popular services:
|
||||||
|
|
||||||
- Auth0
|
- Auth0
|
||||||
|
- Azure AD
|
||||||
- Bitbucket
|
- Bitbucket
|
||||||
- CILogon
|
- CILogon
|
||||||
- GitHub
|
- GitHub
|
||||||
@@ -95,5 +105,16 @@ popular services:
|
|||||||
A generic implementation, which you can use for OAuth authentication
|
A generic implementation, which you can use for OAuth authentication
|
||||||
with any provider, is also available.
|
with any provider, is also available.
|
||||||
|
|
||||||
[PAM]: https://en.wikipedia.org/wiki/Pluggable_authentication_module
|
## Use DummyAuthenticator for testing
|
||||||
[OAuthenticator]: https://github.com/jupyterhub/oauthenticator
|
|
||||||
|
The :class:`~jupyterhub.auth.DummyAuthenticator` is a simple authenticator that
|
||||||
|
allows for any username/password unless if a global password has been set. If
|
||||||
|
set, it will allow for any username as long as the correct password is provided.
|
||||||
|
To set a global password, add this to the config file:
|
||||||
|
|
||||||
|
```python
|
||||||
|
c.DummyAuthenticator.password = "some_password"
|
||||||
|
```
|
||||||
|
|
||||||
|
[pam]: https://en.wikipedia.org/wiki/Pluggable_authentication_module
|
||||||
|
[oauthenticator]: https://github.com/jupyterhub/oauthenticator
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
# Configuration Basics
|
# Configuration Basics
|
||||||
|
|
||||||
The section contains basic information about configuring settings for a JupyterHub
|
The section contains basic information about configuring settings for a JupyterHub
|
||||||
deployment. The [Technical Reference](../reference/index.html)
|
deployment. The [Technical Reference](../reference/index)
|
||||||
documentation provides additional details.
|
documentation provides additional details.
|
||||||
|
|
||||||
This section will help you learn how to:
|
This section will help you learn how to:
|
||||||
@@ -44,7 +44,7 @@ jupyterhub -f /etc/jupyterhub/jupyterhub_config.py
|
|||||||
```
|
```
|
||||||
|
|
||||||
The IPython documentation provides additional information on the
|
The IPython documentation provides additional information on the
|
||||||
[config system](http://ipython.readthedocs.io/en/stable/development/config.html)
|
[config system](http://ipython.readthedocs.io/en/stable/development/config)
|
||||||
that Jupyter uses.
|
that Jupyter uses.
|
||||||
|
|
||||||
## Configure using command line options
|
## Configure using command line options
|
||||||
@@ -56,12 +56,12 @@ To display all command line options that are available for configuration:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Configuration using the command line options is done when launching JupyterHub.
|
Configuration using the command line options is done when launching JupyterHub.
|
||||||
For example, to start JupyterHub on ``10.0.1.2:443`` with https, you
|
For example, to start JupyterHub on `10.0.1.2:443` with https, you
|
||||||
would enter:
|
would enter:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
jupyterhub --ip 10.0.1.2 --port 443 --ssl-key my_ssl.key --ssl-cert my_ssl.cert
|
jupyterhub --ip 10.0.1.2 --port 443 --ssl-key my_ssl.key --ssl-cert my_ssl.cert
|
||||||
```
|
```
|
||||||
|
|
||||||
All configurable options may technically be set on the command-line,
|
All configurable options may technically be set on the command-line,
|
||||||
though some are inconvenient to type. To set a particular configuration
|
though some are inconvenient to type. To set a particular configuration
|
||||||
@@ -77,11 +77,24 @@ jupyterhub --Spawner.notebook_dir='~/assignments'
|
|||||||
## Configure for various deployment environments
|
## Configure for various deployment environments
|
||||||
|
|
||||||
The default authentication and process spawning mechanisms can be replaced, and
|
The default authentication and process spawning mechanisms can be replaced, and
|
||||||
specific [authenticators](./authenticators-users-basics.html) and
|
specific [authenticators](./authenticators-users-basics) and
|
||||||
[spawners](./spawners-basics.html) can be set in the configuration file.
|
[spawners](./spawners-basics) can be set in the configuration file.
|
||||||
This enables JupyterHub to be used with a variety of authentication methods or
|
This enables JupyterHub to be used with a variety of authentication methods or
|
||||||
process control and deployment environments. [Some examples](../reference/config-examples.html),
|
process control and deployment environments. [Some examples](../reference/config-examples),
|
||||||
meant as illustration, are:
|
meant as illustration, are:
|
||||||
|
|
||||||
- Using GitHub OAuth instead of PAM with [OAuthenticator](https://github.com/jupyterhub/oauthenticator)
|
- Using GitHub OAuth instead of PAM with [OAuthenticator](https://github.com/jupyterhub/oauthenticator)
|
||||||
- Spawning single-user servers with Docker, using the [DockerSpawner](https://github.com/jupyterhub/dockerspawner)
|
- Spawning single-user servers with Docker, using the [DockerSpawner](https://github.com/jupyterhub/dockerspawner)
|
||||||
|
|
||||||
|
## Run the proxy separately
|
||||||
|
|
||||||
|
This is _not_ strictly necessary, but useful in many cases. If you
|
||||||
|
use a custom proxy (e.g. Traefik), this also not needed.
|
||||||
|
|
||||||
|
Connections to user servers go through the proxy, and _not_ the hub
|
||||||
|
itself. If the proxy stays running when the hub restarts (for
|
||||||
|
maintenance, re-configuration, etc.), then use connections are not
|
||||||
|
interrupted. For simplicity, by default the hub starts the proxy
|
||||||
|
automatically, so if the hub restarts, the proxy restarts, and user
|
||||||
|
connections are interrupted. It is easy to run the proxy separately,
|
||||||
|
for information see [the separate proxy page](../reference/separate-proxy).
|
||||||
|
35
docs/source/getting-started/faq.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Frequently asked questions
|
||||||
|
|
||||||
|
### How do I share links to notebooks?
|
||||||
|
|
||||||
|
In short, where you see `/user/name/notebooks/foo.ipynb` use `/hub/user-redirect/notebooks/foo.ipynb` (replace `/user/name` with `/hub/user-redirect`).
|
||||||
|
|
||||||
|
Sharing links to notebooks is a common activity,
|
||||||
|
and can look different based on what you mean.
|
||||||
|
Your first instinct might be to copy the URL you see in the browser,
|
||||||
|
e.g. `hub.jupyter.org/user/yourname/notebooks/coolthing.ipynb`.
|
||||||
|
However, let's break down what this URL means:
|
||||||
|
|
||||||
|
`hub.jupyter.org/user/yourname/` is the URL prefix handled by _your server_,
|
||||||
|
which means that sharing this URL is asking the person you share the link with
|
||||||
|
to come to _your server_ and look at the exact same file.
|
||||||
|
In most circumstances, this is forbidden by permissions because the person you share with does not have access to your server.
|
||||||
|
What actually happens when someone visits this URL will depend on whether your server is running and other factors.
|
||||||
|
|
||||||
|
But what is our actual goal?
|
||||||
|
A typical situation is that you have some shared or common filesystem,
|
||||||
|
such that the same path corresponds to the same document
|
||||||
|
(either the exact same document or another copy of it).
|
||||||
|
Typically, what folks want when they do sharing like this
|
||||||
|
is for each visitor to open the same file _on their own server_,
|
||||||
|
so Breq would open `/user/breq/notebooks/foo.ipynb` and
|
||||||
|
Seivarden would open `/user/seivarden/notebooks/foo.ipynb`, etc.
|
||||||
|
|
||||||
|
JupyterHub has a special URL that does exactly this!
|
||||||
|
It's called `/hub/user-redirect/...` and after the visitor logs in,
|
||||||
|
So if you replace `/user/yourname` in your URL bar
|
||||||
|
with `/hub/user-redirect` any visitor should get the same
|
||||||
|
URL on their own server, rather than visiting yours.
|
||||||
|
|
||||||
|
In JupyterLab 2.0, this should also be the result of the "Copy Shareable Link"
|
||||||
|
action in the file browser.
|
@@ -1,5 +1,10 @@
|
|||||||
Getting Started
|
Get Started
|
||||||
===============
|
===========
|
||||||
|
|
||||||
|
This section covers how to configure and customize JupyterHub for your
|
||||||
|
needs. It contains information about authentication, networking, security, and
|
||||||
|
other topics that are relevant to individuals or organizations deploying their
|
||||||
|
own JupyterHub.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
@@ -10,3 +15,5 @@ Getting Started
|
|||||||
authenticators-users-basics
|
authenticators-users-basics
|
||||||
spawners-basics
|
spawners-basics
|
||||||
services-basics
|
services-basics
|
||||||
|
faq
|
||||||
|
institutional-faq
|
||||||
|
260
docs/source/getting-started/institutional-faq.md
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
# Institutional FAQ
|
||||||
|
|
||||||
|
This page contains common questions from users of JupyterHub,
|
||||||
|
broken down by their roles within organizations.
|
||||||
|
|
||||||
|
## For all
|
||||||
|
|
||||||
|
### Is it appropriate for adoption within a larger institutional context?
|
||||||
|
|
||||||
|
Yes! JupyterHub has been used at-scale for large pools of users, as well
|
||||||
|
as complex and high-performance computing. For example, UC Berkeley uses
|
||||||
|
JupyterHub for its Data Science Education Program courses (serving over
|
||||||
|
3,000 students). The Pangeo project uses JupyterHub to provide access
|
||||||
|
to scalable cloud computing with Dask. JupyterHub is stable customizable
|
||||||
|
to the use-cases of large organizations.
|
||||||
|
|
||||||
|
### I keep hearing about Jupyter Notebook, JupyterLab, and now JupyterHub. What’s the difference?
|
||||||
|
|
||||||
|
Here is a quick breakdown of these three tools:
|
||||||
|
|
||||||
|
- **The Jupyter Notebook** is a document specification (the `.ipynb`) file that interweaves
|
||||||
|
narrative text with code cells and their outputs. It is also a graphical interface
|
||||||
|
that allows users to edit these documents. There are also several other graphical interfaces
|
||||||
|
that allow users to edit the `.ipynb` format (nteract, Jupyter Lab, Google Colab, Kaggle, etc).
|
||||||
|
- **JupyterLab** is a flexible and extendible user interface for interactive computing. It
|
||||||
|
has several extensions that are tailored for using Jupyter Notebooks, as well as extensions
|
||||||
|
for other parts of the data science stack.
|
||||||
|
- **JupyterHub** is an application that manages interactive computing sessions for **multiple users**.
|
||||||
|
It also connects them with infrastructure those users wish to access. It can provide
|
||||||
|
remote access to Jupyter Notebooks and Jupyter Lab for many people.
|
||||||
|
|
||||||
|
## For management
|
||||||
|
|
||||||
|
### Briefly, what problem does JupyterHub solve for us?
|
||||||
|
|
||||||
|
JupyterHub provides a shared platform for data science and collaboration.
|
||||||
|
It allows users to utilize familiar data science workflows (such as the scientific python stack,
|
||||||
|
the R tidyverse, and Jupyter Notebooks) on institutional infrastructure. It also allows administrators
|
||||||
|
some control over access to resources, security, environments, and authentication.
|
||||||
|
|
||||||
|
### Is JupyterHub mature? Why should we trust it?
|
||||||
|
|
||||||
|
Yes - the core JupyterHub application recently
|
||||||
|
reached 1.0 status, and is considered stable and performant for most institutions.
|
||||||
|
JupyterHub has also been deployed (along with other tools) to work on
|
||||||
|
scalable infrastructure, large datasets, and high-performance computing.
|
||||||
|
|
||||||
|
### Who else uses JupyterHub?
|
||||||
|
|
||||||
|
JupyterHub is used at a variety of institutions in academia,
|
||||||
|
industry, and government research labs. It is most-commonly used by two kinds of groups:
|
||||||
|
|
||||||
|
- Small teams (e.g., data science teams, research labs, or collaborative projects) to provide a
|
||||||
|
shared resource for interactive computing, collaboration, and analytics.
|
||||||
|
- Large teams (e.g., a department, a large class, or a large group of remote users) to provide
|
||||||
|
access to organizational hardware, data, and analytics environments at scale.
|
||||||
|
|
||||||
|
Here are a sample of organizations that use JupyterHub:
|
||||||
|
|
||||||
|
- **Universities and colleges**: UC Berkeley, UC San Diego, Cal Poly SLO, Harvard University, University of Chicago,
|
||||||
|
University of Oslo, University of Sheffield, Université Paris Sud, University of Versailles
|
||||||
|
- **Research laboratories**: NASA, NCAR, NOAA, the Large Synoptic Survey Telescope, Brookhaven National Lab,
|
||||||
|
Minnesota Supercomputing Institute, ALCF, CERN, Lawrence Livermore National Laboratory
|
||||||
|
- **Online communities**: Pangeo, Quantopian, mybinder.org, MathHub, Open Humans
|
||||||
|
- **Computing infrastructure providers**: NERSC, San Diego Supercomputing Center, Compute Canada
|
||||||
|
- **Companies**: Capital One, SANDVIK code, Globus
|
||||||
|
|
||||||
|
See the [Gallery of JupyterHub deployments](../gallery-jhub-deployments.md) for
|
||||||
|
a more complete list of JupyterHub deployments at institutions.
|
||||||
|
|
||||||
|
### How does JupyterHub compare with hosted products, like Google Colaboratory, RStudio.cloud, or Anaconda Enterprise?
|
||||||
|
|
||||||
|
JupyterHub puts you in control of your data, infrastructure, and coding environment.
|
||||||
|
In addition, it is vendor neutral, which reduces lock-in to a particular vendor or service.
|
||||||
|
JupyterHub provides access to interactive computing environments in the cloud (similar to each of these services).
|
||||||
|
Compared with the tools above, it is more flexible, more customizable, free, and
|
||||||
|
gives administrators more control over their setup and hardware.
|
||||||
|
|
||||||
|
Because JupyterHub is an open-source, community-driven tool, it can be extended and
|
||||||
|
modified to fit an institution's needs. It plays nicely with the open source data science
|
||||||
|
stack, and can serve a variety of computing enviroments, user interfaces, and
|
||||||
|
computational hardware. It can also be deployed anywhere - on enterprise cloud infrastructure, on
|
||||||
|
High-Performance-Computing machines, on local hardware, or even on a single laptop, which
|
||||||
|
is not possible with most other tools for shared interactive computing.
|
||||||
|
|
||||||
|
## For IT
|
||||||
|
|
||||||
|
### How would I set up JupyterHub on institutional hardware?
|
||||||
|
|
||||||
|
That depends on what kind of hardware you've got. JupyterHub is flexible enough to be deployed
|
||||||
|
on a variety of hardware, including in-room hardware, on-prem clusters, cloud infrastructure,
|
||||||
|
etc.
|
||||||
|
|
||||||
|
The most common way to set up a JupyterHub is to use a JupyterHub distribution, these are pre-configured
|
||||||
|
and opinionated ways to set up a JupyterHub on particular kinds of infrastructure. The two distributions
|
||||||
|
that we currently suggest are:
|
||||||
|
|
||||||
|
- [Zero to JupyterHub for Kubernetes](https://z2jh.jupyter.org) is a scalable JupyterHub deployment and
|
||||||
|
guide that runs on Kubernetes. Better for larger or dynamic user groups (50-10,000) or more complex
|
||||||
|
compute/data needs.
|
||||||
|
- [The Littlest JupyterHub](https://tljh.jupyter.org) is a lightweight JupyterHub that runs on a single
|
||||||
|
single machine (in the cloud or under your desk). Better for smaller usergroups (4-80) or more
|
||||||
|
lightweight computational resources.
|
||||||
|
|
||||||
|
### Does JupyterHub run well in the cloud?
|
||||||
|
|
||||||
|
Yes - most deployments of JupyterHub are run via cloud infrastructure and on a variety of cloud providers.
|
||||||
|
Depending on the distribution of JupyterHub that you'd like to use, you can also connect your JupyterHub
|
||||||
|
deployment with a number of other cloud-native services so that users have access to other resources from
|
||||||
|
their interactive computing sessions.
|
||||||
|
|
||||||
|
For example, if you use the [Zero to JupyterHub for Kubernetes](https://z2jh.jupyter.org) distribution,
|
||||||
|
you'll be able to utilize container-based workflows of other technologies such as the [dask-kubernetes](https://kubernetes.dask.org/en/latest/)
|
||||||
|
project for distributed computing.
|
||||||
|
|
||||||
|
The Z2JH Helm Chart also has some functionality built in for auto-scaling your cluster up and down
|
||||||
|
as more resources are needed - allowing you to utilize the benefits of a flexible cloud-based deployment.
|
||||||
|
|
||||||
|
### Is JupyterHub secure?
|
||||||
|
|
||||||
|
The short answer: yes. JupyterHub as a standalone application has been battle-tested at an institutional
|
||||||
|
level for several years, and makes a number of "default" security decisions that are reasonable for most
|
||||||
|
users.
|
||||||
|
|
||||||
|
- For security considerations in the base JupyterHub application,
|
||||||
|
[see the JupyterHub security page](https://jupyterhub.readthedocs.io/en/stable/reference/websecurity.html)
|
||||||
|
- For security considerations when deploying JupyterHub on Kubernetes, see the
|
||||||
|
[JupyterHub on Kubernetes security page](https://zero-to-jupyterhub.readthedocs.io/en/latest/security.html).
|
||||||
|
|
||||||
|
The longer answer: it depends on your deployment. Because JupyterHub is very flexible, it can be used
|
||||||
|
in a variety of deployment setups. This often entails connecting your JupyterHub to **other** infrastructure
|
||||||
|
(such as a [Dask Gateway service](https://gateway.dask.org/)). There are many security decisions to be made
|
||||||
|
in these cases, and the security of your JupyterHub deployment will often depend on these decisions.
|
||||||
|
|
||||||
|
If you are worried about security, don't hesitate to reach out to the JupyterHub community in the
|
||||||
|
[Jupyter Community Forum](https://discourse.jupyter.org/c/jupyterhub). This community of practice has many
|
||||||
|
individuals with experience running secure JupyterHub deployments.
|
||||||
|
|
||||||
|
### Does JupyterHub provide computing or data infrastructure?
|
||||||
|
|
||||||
|
No - JupyterHub manages user sessions and can _control_ computing infrastructure, but it does not provide these
|
||||||
|
things itself. You are expected to run JupyterHub on your own infrastructure (local or in the cloud). Moreover,
|
||||||
|
JupyterHub has no internal concept of "data", but is designed to be able to communicate with data repositories
|
||||||
|
(again, either locally or remotely) for use within interactive computing sessions.
|
||||||
|
|
||||||
|
### How do I manage users?
|
||||||
|
|
||||||
|
JupyterHub offers a few options for managing your users. Upon setting up a JupyterHub, you can choose what
|
||||||
|
kind of **authentication** you'd like to use. For example, you can have users sign up with an institutional
|
||||||
|
email address, or choose a username / password when they first log-in, or offload authentication onto
|
||||||
|
another service such as an organization's OAuth.
|
||||||
|
|
||||||
|
The users of a JupyterHub are stored locally, and can be modified manually by an administrator of the JupyterHub.
|
||||||
|
Moreover, the _active_ users on a JupyterHub can be found on the administrator's page. This page
|
||||||
|
gives you the abiltiy to stop or restart kernels, inspect user filesystems, and even take over user
|
||||||
|
sessions to assist them with debugging.
|
||||||
|
|
||||||
|
### How do I manage software environments?
|
||||||
|
|
||||||
|
A key benefit of JupyterHub is the ability for an administrator to define the environment(s) that users
|
||||||
|
have access to. There are many ways to do this, depending on what kind of infrastructure you're using for
|
||||||
|
your JupyterHub.
|
||||||
|
|
||||||
|
For example, **The Littlest JupyterHub** runs on a single VM. In this case, the administrator defines
|
||||||
|
an environment by installing packages to a shared folder that exists on the path of all users. The
|
||||||
|
**JupyterHub for Kubernetes** deployment uses Docker images to define environments. You can create your
|
||||||
|
own list of Docker images that users can select from, and can also control things like the amount of
|
||||||
|
RAM available to users, or the types of machines that their sessions will use in the cloud.
|
||||||
|
|
||||||
|
### How does JupyterHub manage computational resources?
|
||||||
|
|
||||||
|
For interactive computing sessions, JupyterHub controls computational resources via a **spawner**.
|
||||||
|
Spawners define how a new user session is created, and are customized for particular kinds of
|
||||||
|
infrastructure. For example, the KubeSpawner knows how to control a Kubernetes deployment
|
||||||
|
to create new pods when users log in.
|
||||||
|
|
||||||
|
For more sophisticated computational resources (like distributed computing), JupyterHub can
|
||||||
|
connect with other infrastructure tools (like Dask or Spark). This allows users to control
|
||||||
|
scalable or high-performance resources from within their JupyterHub sessions. The logic of
|
||||||
|
how those resources are controlled is taken care of by the non-JupyterHub application.
|
||||||
|
|
||||||
|
### Can JupyterHub be used with my high-performance computing resources?
|
||||||
|
|
||||||
|
Yes - JupyterHub can provide access to many kinds of computing infrastructure.
|
||||||
|
Especially when combined with other open-source schedulers such as Dask, you can manage fairly
|
||||||
|
complex computing infrastructure from the interactive sessions of a JupyterHub. For example
|
||||||
|
[see the Dask HPC page](https://docs.dask.org/en/latest/setup/hpc.html).
|
||||||
|
|
||||||
|
### How much resources do user sessions take?
|
||||||
|
|
||||||
|
This is highly configurable by the administrator. If you wish for your users to have simple
|
||||||
|
data analytics environments for prototyping and light data exploring, you can restrict their
|
||||||
|
memory and CPU based on the resources that you have available. If you'd like your JupyterHub
|
||||||
|
to serve as a gateway to high-performance compute or data resources, you may increase the
|
||||||
|
resources available on user machines, or connect them with computing infrastructure elsewhere.
|
||||||
|
|
||||||
|
### Can I customize the look and feel of a JupyterHub?
|
||||||
|
|
||||||
|
JupyterHub provides some customization of the graphics displayed to users. The most common
|
||||||
|
modification is to add custom branding to the JupyterHub login page, loading pages, and
|
||||||
|
various elements that persist across all pages (such as headers).
|
||||||
|
|
||||||
|
## For Technical Leads
|
||||||
|
|
||||||
|
### Will JupyterHub “just work” with our team's interactive computing setup?
|
||||||
|
|
||||||
|
Depending on the complexity of your setup, you'll have different experiences with "out of the box"
|
||||||
|
distributions of JupyterHub. If all of the resources you need will fit on a single VM, then
|
||||||
|
[The Littlest JupyterHub](https://tljh.jupyter.org) should get you up-and-running within
|
||||||
|
a half day or so. For more complex setups, such as scalable Kubernetes clusters or access
|
||||||
|
to high-performance computing and data, it will require more time and expertise with
|
||||||
|
the technologies your JupyterHub will use (e.g., dev-ops knowledge with cloud computing).
|
||||||
|
|
||||||
|
In general, the base JupyterHub deployment is not the bottleneck for setup, it is connecting
|
||||||
|
your JupyterHub with the various services and tools that you wish to provide to your users.
|
||||||
|
|
||||||
|
### How well does JupyterHub scale? What are JupyterHub's limitations?
|
||||||
|
|
||||||
|
JupyterHub works well at both a small scale (e.g., a single VM or machine) as well as a
|
||||||
|
high scale (e.g., a scalable Kubernetes cluster). It can be used for teams as small a 2, and
|
||||||
|
for user bases as large as 10,000. The scalability of JupyterHub largely depends on the
|
||||||
|
infrastructure on which it is deployed. JupyterHub has been designed to be lightweight and
|
||||||
|
flexible, so you can tailor your JupyterHub deployment to your needs.
|
||||||
|
|
||||||
|
### Is JupyterHub resilient? What happens when a machine goes down?
|
||||||
|
|
||||||
|
For JupyterHubs that are deployed in a containerized environment (e.g., Kubernetes), it is
|
||||||
|
possible to configure the JupyterHub to be fairly resistant to failures in the system.
|
||||||
|
For example, if JupyterHub fails, then user sessions will not be affected (though new
|
||||||
|
users will not be able to log in). When a JupyterHub process is restarted, it should
|
||||||
|
seamlessly connect with the user database and the system will return to normal.
|
||||||
|
Again, the details of your JupyterHub deployment (e.g., whether it's deployed on a scalable cluster)
|
||||||
|
will affect the resiliency of the deployment.
|
||||||
|
|
||||||
|
### What interfaces does JupyterHub support?
|
||||||
|
|
||||||
|
Out of the box, JupyterHub supports a variety of popular data science interfaces for user sessions,
|
||||||
|
such as JupyterLab, Jupyter Notebooks, and RStudio. Any interface that can be served
|
||||||
|
via a web address can be served with a JupyterHub (with the right setup).
|
||||||
|
|
||||||
|
### Does JupyterHub make it easier for our team to collaborate?
|
||||||
|
|
||||||
|
JupyterHub provides a standardized environment and access to shared resources for your teams.
|
||||||
|
This greatly reduces the cost associated with sharing analyses and content with other team
|
||||||
|
members, and makes it easier to collaborate and build off of one another's ideas. Combined with
|
||||||
|
access to high-performance computing and data, JupyterHub provides a common resource to
|
||||||
|
amplify your team's ability to prototype their analyses, scale them to larger data, and then
|
||||||
|
share their results with one another.
|
||||||
|
|
||||||
|
JupyterHub also provides a computational framework to share computational narratives between
|
||||||
|
different levels of an organization. For example, data scientists can share Jupyter Notebooks
|
||||||
|
rendered as [voila dashboards](https://voila.readthedocs.io/en/stable/) with those who are not
|
||||||
|
familiar with programming, or create publicly-available interactive analyses to allow others to
|
||||||
|
interact with your work.
|
||||||
|
|
||||||
|
### Can I use JupyterHub with R/RStudio or other languages and environments?
|
||||||
|
|
||||||
|
Yes, Jupyter is a polyglot project, and there are over 40 community-provided kernels for a variety
|
||||||
|
of languages (the most common being Python, Julia, and R). You can also use a JupyterHub to provide
|
||||||
|
access to other interfaces, such as RStudio, that provide their own access to a language kernel.
|
@@ -11,7 +11,7 @@ This section will help you with basic proxy and network configuration to:
|
|||||||
|
|
||||||
The Proxy's main IP address setting determines where JupyterHub is available to users.
|
The Proxy's main IP address setting determines where JupyterHub is available to users.
|
||||||
By default, JupyterHub is configured to be available on all network interfaces
|
By default, JupyterHub is configured to be available on all network interfaces
|
||||||
(`''`) on port 8000. *Note*: Use of `'*'` is discouraged for IP configuration;
|
(`''`) on port 8000. _Note_: Use of `'*'` is discouraged for IP configuration;
|
||||||
instead, use of `'0.0.0.0'` is preferred.
|
instead, use of `'0.0.0.0'` is preferred.
|
||||||
|
|
||||||
Changing the Proxy's main IP address and port can be done with the following
|
Changing the Proxy's main IP address and port can be done with the following
|
||||||
@@ -41,7 +41,7 @@ port.
|
|||||||
|
|
||||||
## Set the Proxy's REST API communication URL (optional)
|
## Set the Proxy's REST API communication URL (optional)
|
||||||
|
|
||||||
By default, this REST API listens on port 8081 of `localhost` only.
|
By default, this REST API listens on port 8001 of `localhost` only.
|
||||||
The Hub service talks to the proxy via a REST API on a secondary port. The
|
The Hub service talks to the proxy via a REST API on a secondary port. The
|
||||||
API URL can be configured separately and override the default settings.
|
API URL can be configured separately and override the default settings.
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ The Hub service listens only on `localhost` (port 8081) by default.
|
|||||||
The Hub needs to be accessible from both the proxy and all Spawners.
|
The Hub needs to be accessible from both the proxy and all Spawners.
|
||||||
When spawning local servers, an IP address setting of `localhost` is fine.
|
When spawning local servers, an IP address setting of `localhost` is fine.
|
||||||
|
|
||||||
If *either* the Proxy *or* (more likely) the Spawners will be remote or
|
If _either_ the Proxy _or_ (more likely) the Spawners will be remote or
|
||||||
isolated in containers, the Hub must listen on an IP that is accessible.
|
isolated in containers, the Hub must listen on an IP that is accessible.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@@ -93,9 +93,9 @@ c.JupyterHub.hub_connect_ip = '10.0.1.4' # ip as seen on the docker network. Ca
|
|||||||
|
|
||||||
## Adjusting the hub's URL
|
## Adjusting the hub's URL
|
||||||
|
|
||||||
The hub will most commonly be running on a hostname of its own. If it
|
The hub will most commonly be running on a hostname of its own. If it
|
||||||
is not – for example, if the hub is being reverse-proxied and being
|
is not – for example, if the hub is being reverse-proxied and being
|
||||||
exposed at a URL such as `https://proxy.example.org/jupyter/` – then
|
exposed at a URL such as `https://proxy.example.org/jupyter/` – then
|
||||||
you will need to tell JupyterHub the base URL of the service. In such
|
you will need to tell JupyterHub the base URL of the service. In such
|
||||||
a case, it is both necessary and sufficient to set
|
a case, it is both necessary and sufficient to set
|
||||||
`c.JupyterHub.base_url = '/jupyter/'` in the configuration.
|
`c.JupyterHub.base_url = '/jupyter/'` in the configuration.
|
||||||
|
@@ -45,7 +45,7 @@ is important that these files be put in a secure location on your server, where
|
|||||||
they are not readable by regular users.
|
they are not readable by regular users.
|
||||||
|
|
||||||
If you are using a **chain certificate**, see also chained certificate for SSL
|
If you are using a **chain certificate**, see also chained certificate for SSL
|
||||||
in the JupyterHub `troubleshooting FAQ <troubleshooting>`_.
|
in the JupyterHub `Troubleshooting FAQ <../troubleshooting.html>`_.
|
||||||
|
|
||||||
Using letsencrypt
|
Using letsencrypt
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
@@ -80,6 +80,49 @@ To achieve this, simply omit the configuration settings
|
|||||||
``c.JupyterHub.ssl_key`` and ``c.JupyterHub.ssl_cert``
|
``c.JupyterHub.ssl_key`` and ``c.JupyterHub.ssl_cert``
|
||||||
(setting them to ``None`` does not have the same effect, and is an error).
|
(setting them to ``None`` does not have the same effect, and is an error).
|
||||||
|
|
||||||
|
.. _authentication-token:
|
||||||
|
|
||||||
|
Proxy authentication token
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
The Hub authenticates its requests to the Proxy using a secret token that
|
||||||
|
the Hub and Proxy agree upon. Note that this applies to the default
|
||||||
|
``ConfigurableHTTPProxy`` implementation. Not all proxy implementations
|
||||||
|
use an auth token.
|
||||||
|
|
||||||
|
The value of this token should be a random string (for example, generated by
|
||||||
|
``openssl rand -hex 32``). You can store it in the configuration file or an
|
||||||
|
environment variable
|
||||||
|
|
||||||
|
Generating and storing token in the configuration file
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
You can set the value in the configuration file, ``jupyterhub_config.py``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
c.ConfigurableHTTPProxy.api_token = 'abc123...' # any random string
|
||||||
|
|
||||||
|
Generating and storing as an environment variable
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
You can pass this value of the proxy authentication token to the Hub and Proxy
|
||||||
|
using the ``CONFIGPROXY_AUTH_TOKEN`` environment variable:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
export CONFIGPROXY_AUTH_TOKEN=$(openssl rand -hex 32)
|
||||||
|
|
||||||
|
This environment variable needs to be visible to the Hub and Proxy.
|
||||||
|
|
||||||
|
Default if token is not set
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you don't set the Proxy authentication token, the Hub will generate a random
|
||||||
|
key itself, which means that any time you restart the Hub you **must also
|
||||||
|
restart the Proxy**. If the proxy is a subprocess of the Hub, this should happen
|
||||||
|
automatically (this is the default configuration).
|
||||||
|
|
||||||
.. _cookie-secret:
|
.. _cookie-secret:
|
||||||
|
|
||||||
Cookie secret
|
Cookie secret
|
||||||
@@ -124,7 +167,7 @@ hex-encoded string. You can set it this way:
|
|||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
export JPY_COOKIE_SECRET=`openssl rand -hex 32`
|
export JPY_COOKIE_SECRET=$(openssl rand -hex 32)
|
||||||
|
|
||||||
For security reasons, this environment variable should only be visible to the
|
For security reasons, this environment variable should only be visible to the
|
||||||
Hub. If you set it dynamically as above, all users will be logged out each time
|
Hub. If you set it dynamically as above, all users will be logged out each time
|
||||||
@@ -146,41 +189,73 @@ itself, ``jupyterhub_config.py``, as a binary string:
|
|||||||
If the cookie secret value changes for the Hub, all single-user notebook
|
If the cookie secret value changes for the Hub, all single-user notebook
|
||||||
servers must also be restarted.
|
servers must also be restarted.
|
||||||
|
|
||||||
|
.. _cookies:
|
||||||
|
|
||||||
.. _authentication-token:
|
Cookies used by JupyterHub authentication
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
Proxy authentication token
|
The following cookies are used by the Hub for handling user authentication.
|
||||||
--------------------------
|
|
||||||
|
|
||||||
The Hub authenticates its requests to the Proxy using a secret token that
|
This section was created based on this post_ from Discourse.
|
||||||
the Hub and Proxy agree upon. The value of this string should be a random
|
|
||||||
string (for example, generated by ``openssl rand -hex 32``).
|
|
||||||
|
|
||||||
Generating and storing token in the configuration file
|
.. _post: https://discourse.jupyter.org/t/how-to-force-re-login-for-users/1998/6
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Or you can set the value in the configuration file, ``jupyterhub_config.py``:
|
jupyterhub-hub-login
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. code-block:: python
|
This is the login token used when visiting Hub-served pages that are
|
||||||
|
protected by authentication such as the main home, the spawn form, etc.
|
||||||
|
If this cookie is set, then the user is logged in.
|
||||||
|
|
||||||
c.JupyterHub.proxy_auth_token = '0bc02bede919e99a26de1e2a7a5aadfaf6228de836ec39a05a6c6942831d8fe5'
|
Resetting the Hub cookie secret effectively revokes this cookie.
|
||||||
|
|
||||||
Generating and storing as an environment variable
|
This cookie is restricted to the path ``/hub/``.
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
You can pass this value of the proxy authentication token to the Hub and Proxy
|
jupyterhub-user-<username>
|
||||||
using the ``CONFIGPROXY_AUTH_TOKEN`` environment variable:
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. code-block:: bash
|
This is the cookie used for authenticating with a single-user server.
|
||||||
|
It is set by the single-user server after OAuth with the Hub.
|
||||||
|
|
||||||
export CONFIGPROXY_AUTH_TOKEN='openssl rand -hex 32'
|
Effectively the same as ``jupyterhub-hub-login``, but for the
|
||||||
|
single-user server instead of the Hub. It contains an OAuth access token,
|
||||||
|
which is checked with the Hub to authenticate the browser.
|
||||||
|
|
||||||
This environment variable needs to be visible to the Hub and Proxy.
|
Each OAuth access token is associated with a session id (see ``jupyterhub-session-id`` section
|
||||||
|
below).
|
||||||
|
|
||||||
Default if token is not set
|
To avoid hitting the Hub on every request, the authentication response
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
is cached. And to avoid a stale cache the cache key is comprised of both
|
||||||
|
the token and session id.
|
||||||
|
|
||||||
If you don't set the Proxy authentication token, the Hub will generate a random
|
Resetting the Hub cookie secret effectively revokes this cookie.
|
||||||
key itself, which means that any time you restart the Hub you **must also
|
|
||||||
restart the Proxy**. If the proxy is a subprocess of the Hub, this should happen
|
This cookie is restricted to the path ``/user/<username>``, so that
|
||||||
automatically (this is the default configuration).
|
only the user’s server receives it.
|
||||||
|
|
||||||
|
jupyterhub-session-id
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This is a random string, meaningless in itself, and the only cookie
|
||||||
|
shared by the Hub and single-user servers.
|
||||||
|
|
||||||
|
Its sole purpose is to coordinate logout of the multiple OAuth cookies.
|
||||||
|
|
||||||
|
This cookie is set to ``/`` so all endpoints can receive it, or clear it, etc.
|
||||||
|
|
||||||
|
jupyterhub-user-<username>-oauth-state
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A short-lived cookie, used solely to store and validate OAuth state.
|
||||||
|
It is only set while OAuth between the single-user server and the Hub
|
||||||
|
is processing.
|
||||||
|
|
||||||
|
If you use your browser development tools, you should see this cookie
|
||||||
|
for a very brief moment before your are logged in,
|
||||||
|
with an expiration date shorter than ``jupyterhub-hub-login`` or
|
||||||
|
``jupyterhub-user-<username>``.
|
||||||
|
|
||||||
|
This cookie should not exist after you have successfully logged in.
|
||||||
|
|
||||||
|
This cookie is restricted to the path ``/user/<username>``, so that only
|
||||||
|
the user’s server receives it.
|
||||||
|
@@ -3,9 +3,9 @@
|
|||||||
When working with JupyterHub, a **Service** is defined as a process
|
When working with JupyterHub, a **Service** is defined as a process
|
||||||
that interacts with the Hub's REST API. A Service may perform a specific
|
that interacts with the Hub's REST API. A Service may perform a specific
|
||||||
or action or task. For example, shutting down individuals' single user
|
or action or task. For example, shutting down individuals' single user
|
||||||
notebook servers that have been is a good example of a task that could
|
notebook servers that have been idle for some time is a good example of
|
||||||
be automated by a Service. Let's look at how the [cull_idle_servers][]
|
a task that could be automated by a Service. Let's look at how the
|
||||||
script can be used as a Service.
|
[jupyterhub_idle_culler][] script can be used as a Service.
|
||||||
|
|
||||||
## Real-world example to cull idle servers
|
## Real-world example to cull idle servers
|
||||||
|
|
||||||
@@ -14,12 +14,12 @@ document will:
|
|||||||
|
|
||||||
- explain some basic information about API tokens
|
- explain some basic information about API tokens
|
||||||
- clarify that API tokens can be used to authenticate to
|
- clarify that API tokens can be used to authenticate to
|
||||||
single-user servers as of [version 0.8.0](../changelog.html)
|
single-user servers as of [version 0.8.0](../changelog)
|
||||||
- show how the [cull_idle_servers][] script can be:
|
- show how the [jupyterhub_idle_culler][] script can be:
|
||||||
- used in a Hub-managed service
|
- used in a Hub-managed service
|
||||||
- run as a standalone script
|
- run as a standalone script
|
||||||
|
|
||||||
Both examples for `cull_idle_servers` will communicate tasks to the
|
Both examples for `jupyterhub_idle_culler` will communicate tasks to the
|
||||||
Hub via the REST API.
|
Hub via the REST API.
|
||||||
|
|
||||||
## API Token basics
|
## API Token basics
|
||||||
@@ -29,14 +29,14 @@ Hub via the REST API.
|
|||||||
To run such an external service, an API token must be created and
|
To run such an external service, an API token must be created and
|
||||||
provided to the service.
|
provided to the service.
|
||||||
|
|
||||||
As of [version 0.6.0](../changelog.html), the preferred way of doing
|
As of [version 0.6.0](../changelog), the preferred way of doing
|
||||||
this is to first generate an API token:
|
this is to first generate an API token:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openssl rand -hex 32
|
openssl rand -hex 32
|
||||||
```
|
```
|
||||||
|
|
||||||
In [version 0.8.0](../changelog.html), a TOKEN request page for
|
In [version 0.8.0](../changelog), a TOKEN request page for
|
||||||
generating an API token is available from the JupyterHub user interface:
|
generating an API token is available from the JupyterHub user interface:
|
||||||
|
|
||||||

|

|
||||||
@@ -78,17 +78,23 @@ single-user servers, and only cookies can be used for authentication.
|
|||||||
0.8 supports using JupyterHub API tokens to authenticate to single-user
|
0.8 supports using JupyterHub API tokens to authenticate to single-user
|
||||||
servers.
|
servers.
|
||||||
|
|
||||||
## Configure `cull-idle` to run as a Hub-Managed Service
|
## Configure the idle culler to run as a Hub-Managed Service
|
||||||
|
|
||||||
|
Install the idle culler:
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install jupyterhub-idle-culler
|
||||||
|
```
|
||||||
|
|
||||||
In `jupyterhub_config.py`, add the following dictionary for the
|
In `jupyterhub_config.py`, add the following dictionary for the
|
||||||
`cull-idle` Service to the `c.JupyterHub.services` list:
|
`idle-culler` Service to the `c.JupyterHub.services` list:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
c.JupyterHub.services = [
|
c.JupyterHub.services = [
|
||||||
{
|
{
|
||||||
'name': 'cull-idle',
|
'name': 'idle-culler',
|
||||||
'admin': True,
|
'admin': True,
|
||||||
'command': 'python3 cull_idle_servers.py --timeout=3600'.split(),
|
'command': [sys.executable, '-m', 'jupyterhub_idle_culler', '--timeout=3600'],
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
@@ -101,21 +107,21 @@ where:
|
|||||||
|
|
||||||
## Run `cull-idle` manually as a standalone script
|
## Run `cull-idle` manually as a standalone script
|
||||||
|
|
||||||
Now you can run your script, i.e. `cull_idle_servers`, by providing it
|
Now you can run your script by providing it
|
||||||
the API token and it will authenticate through the REST API to
|
the API token and it will authenticate through the REST API to
|
||||||
interact with it.
|
interact with it.
|
||||||
|
|
||||||
This will run `cull-idle` manually. `cull-idle` can be run as a standalone
|
This will run the idle culler service manually. It can be run as a standalone
|
||||||
script anywhere with access to the Hub, and will periodically check for idle
|
script anywhere with access to the Hub, and will periodically check for idle
|
||||||
servers and shut them down via the Hub's REST API. In order to shutdown the
|
servers and shut them down via the Hub's REST API. In order to shutdown the
|
||||||
servers, the token given to cull-idle must have admin privileges.
|
servers, the token given to cull-idle must have admin privileges.
|
||||||
|
|
||||||
Generate an API token and store it in the `JUPYTERHUB_API_TOKEN` environment
|
Generate an API token and store it in the `JUPYTERHUB_API_TOKEN` environment
|
||||||
variable. Run `cull_idle_servers.py` manually.
|
variable. Run `jupyterhub_idle_culler` manually.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export JUPYTERHUB_API_TOKEN='token'
|
export JUPYTERHUB_API_TOKEN='token'
|
||||||
python3 cull_idle_servers.py [--timeout=900] [--url=http://127.0.0.1:8081/hub/api]
|
python -m jupyterhub_idle_culler [--timeout=900] [--url=http://127.0.0.1:8081/hub/api]
|
||||||
```
|
```
|
||||||
|
|
||||||
[cull_idle_servers]: https://github.com/jupyterhub/jupyterhub/blob/master/examples/cull-idle/cull_idle_servers.py
|
[jupyterhub_idle_culler]: https://github.com/jupyterhub/jupyterhub-idle-culler
|
||||||
|
BIN
docs/source/images/jhub-fluxogram.jpeg
Normal file
After Width: | Height: | Size: 158 KiB |
BIN
docs/source/images/login-button.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
docs/source/images/login-form.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
docs/source/images/named-servers-admin.png
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
docs/source/images/named-servers-home.png
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
docs/source/images/not-running.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
docs/source/images/spawn-pending.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
docs/source/images/token-page.png
Normal file
After Width: | Height: | Size: 103 KiB |
15
docs/source/index-about.rst
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
=====
|
||||||
|
About
|
||||||
|
=====
|
||||||
|
|
||||||
|
JupyterHub is an open source project and community. It is a part of the
|
||||||
|
`Jupyter Project <https://jupyter.org>`_. JupyterHub is an open and inclusive
|
||||||
|
community, and invites contributions from anyone. This section covers information
|
||||||
|
about our community, as well as ways that you can connect and get involved.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
contributor-list
|
||||||
|
changelog
|
||||||
|
gallery-jhub-deployments
|
13
docs/source/index-admin.rst
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
=====================
|
||||||
|
Administrator's Guide
|
||||||
|
=====================
|
||||||
|
|
||||||
|
This guide covers best-practices, tips, common questions and operations, as
|
||||||
|
well as other information relevant to running your own JupyterHub over time.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
troubleshooting
|
||||||
|
admin/upgrading
|
||||||
|
changelog
|
@@ -1,21 +1,38 @@
|
|||||||
|
==========
|
||||||
JupyterHub
|
JupyterHub
|
||||||
==========
|
==========
|
||||||
|
|
||||||
`JupyterHub`_, a multi-user **Hub**, spawns, manages, and proxies multiple
|
`JupyterHub`_ is the best way to serve `Jupyter notebook`_ for multiple users.
|
||||||
|
It can be used in a class of students, a corporate data science group or scientific
|
||||||
|
research group. It is a multi-user **Hub** that spawns, manages, and proxies multiple
|
||||||
instances of the single-user `Jupyter notebook`_ server.
|
instances of the single-user `Jupyter notebook`_ server.
|
||||||
JupyterHub can be used to serve notebooks to a class of students, a corporate
|
|
||||||
data science group, or a scientific research group.
|
|
||||||
|
|
||||||
.. image:: images/jhub-parts.png
|
To make life easier, JupyterHub has distributions. Be sure to
|
||||||
|
take a look at them before continuing with the configuration of the broad
|
||||||
|
original system of `JupyterHub`_. Today, you can find two main cases:
|
||||||
|
|
||||||
|
1. If you need a simple case for a small amount of users (0-100) and single server
|
||||||
|
take a look at
|
||||||
|
`The Littlest JupyterHub <https://github.com/jupyterhub/the-littlest-jupyterhub>`__ distribution.
|
||||||
|
2. If you need to allow for even more users, a dynamic amount of servers can be used on a cloud,
|
||||||
|
take a look at the `Zero to JupyterHub with Kubernetes <https://github.com/jupyterhub/zero-to-jupyterhub-k8s>`__ .
|
||||||
|
|
||||||
|
|
||||||
|
Four subsystems make up JupyterHub:
|
||||||
|
|
||||||
|
* a **Hub** (tornado process) that is the heart of JupyterHub
|
||||||
|
* a **configurable http proxy** (node-http-proxy) that receives the requests from the client's browser
|
||||||
|
* multiple **single-user Jupyter notebook servers** (Python/IPython/tornado) that are monitored by Spawners
|
||||||
|
* an **authentication class** that manages how users can access the system
|
||||||
|
|
||||||
|
|
||||||
|
Besides these central pieces, you can add optional configurations through a `config.py` file and manage users kernels on an admin panel. A simplification of the whole system can be seen in the figure below:
|
||||||
|
|
||||||
|
.. image:: images/jhub-fluxogram.jpeg
|
||||||
:alt: JupyterHub subsystems
|
:alt: JupyterHub subsystems
|
||||||
:width: 40%
|
:width: 80%
|
||||||
:align: right
|
:align: center
|
||||||
|
|
||||||
Three subsystems make up JupyterHub:
|
|
||||||
|
|
||||||
* a multi-user **Hub** (tornado process)
|
|
||||||
* a **configurable http proxy** (node-http-proxy)
|
|
||||||
* multiple **single-user Jupyter notebook servers** (Python/IPython/tornado)
|
|
||||||
|
|
||||||
JupyterHub performs the following functions:
|
JupyterHub performs the following functions:
|
||||||
|
|
||||||
@@ -28,98 +45,106 @@ JupyterHub performs the following functions:
|
|||||||
For convenient administration of the Hub, its users, and services,
|
For convenient administration of the Hub, its users, and services,
|
||||||
JupyterHub also provides a `REST API`_.
|
JupyterHub also provides a `REST API`_.
|
||||||
|
|
||||||
|
The JupyterHub team and Project Jupyter value our community, and JupyterHub
|
||||||
|
follows the Jupyter `Community Guides <https://jupyter.readthedocs.io/en/latest/community/content-community.html>`_.
|
||||||
|
|
||||||
Contents
|
Contents
|
||||||
--------
|
========
|
||||||
|
|
||||||
**Installation Guide**
|
.. _index/distributions:
|
||||||
|
|
||||||
* :doc:`installation-guide`
|
Distributions
|
||||||
* :doc:`quickstart`
|
-------------
|
||||||
* :doc:`quickstart-docker`
|
|
||||||
* :doc:`installation-basics`
|
|
||||||
|
|
||||||
**Getting Started**
|
A JupyterHub **distribution** is tailored towards a particular set of
|
||||||
|
use cases. These are generally easier to set up than setting up
|
||||||
|
JupyterHub from scratch, assuming they fit your use case.
|
||||||
|
|
||||||
* :doc:`getting-started/index`
|
The two popular ones are:
|
||||||
* :doc:`getting-started/config-basics`
|
|
||||||
* :doc:`getting-started/networking-basics`
|
|
||||||
* :doc:`getting-started/security-basics`
|
|
||||||
* :doc:`getting-started/authenticators-users-basics`
|
|
||||||
* :doc:`getting-started/spawners-basics`
|
|
||||||
* :doc:`getting-started/services-basics`
|
|
||||||
|
|
||||||
**Technical Reference**
|
* `Zero to JupyterHub on Kubernetes <http://z2jh.jupyter.org>`_, for
|
||||||
|
running JupyterHub on top of `Kubernetes <https://k8s.io>`_. This
|
||||||
|
can scale to large number of machines & users.
|
||||||
|
* `The Littlest JupyterHub <http://tljh.jupyter.org>`_, for an easy
|
||||||
|
to set up & run JupyterHub supporting 1-100 users on a single machine.
|
||||||
|
|
||||||
* :doc:`reference/index`
|
Installation Guide
|
||||||
* :doc:`reference/technical-overview`
|
------------------
|
||||||
* :doc:`reference/websecurity`
|
|
||||||
* :doc:`reference/authenticators`
|
|
||||||
* :doc:`reference/spawners`
|
|
||||||
* :doc:`reference/services`
|
|
||||||
* :doc:`reference/rest`
|
|
||||||
* :doc:`reference/upgrading`
|
|
||||||
* :doc:`reference/templates`
|
|
||||||
* :doc:`reference/config-user-env`
|
|
||||||
* :doc:`reference/config-examples`
|
|
||||||
* :doc:`reference/config-ghoauth`
|
|
||||||
* :doc:`reference/config-proxy`
|
|
||||||
* :doc:`reference/config-sudo`
|
|
||||||
|
|
||||||
**API Reference**
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
* :doc:`api/index`
|
installation-guide
|
||||||
|
|
||||||
**Tutorials**
|
Getting Started
|
||||||
|
---------------
|
||||||
|
|
||||||
* :doc:`tutorials/index`
|
.. toctree::
|
||||||
* :doc:`tutorials/upgrade-dot-eight`
|
:maxdepth: 2
|
||||||
* `Zero to JupyterHub with Kubernetes <https://zero-to-jupyterhub.readthedocs.io/en/latest/>`_
|
|
||||||
|
|
||||||
**Troubleshooting**
|
getting-started/index
|
||||||
|
|
||||||
* :doc:`troubleshooting`
|
Technical Reference
|
||||||
|
-------------------
|
||||||
|
|
||||||
**About JupyterHub**
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
* :doc:`contributor-list`
|
reference/index
|
||||||
* :doc:`gallery-jhub-deployments`
|
|
||||||
|
|
||||||
**Changelog**
|
Administrators guide
|
||||||
|
--------------------
|
||||||
|
|
||||||
* :doc:`changelog`
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
index-admin
|
||||||
|
|
||||||
|
API Reference
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
api/index
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
------------
|
||||||
|
|
||||||
|
We want you to contribute to JupyterHub in ways that are most exciting
|
||||||
|
& useful to you. We value documentation, testing, bug reporting & code equally,
|
||||||
|
and are glad to have your contributions in whatever form you wish :)
|
||||||
|
|
||||||
|
Our `Code of Conduct <https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md>`_
|
||||||
|
(`reporting guidelines <https://github.com/jupyter/governance/blob/master/conduct/reporting_online.md>`_)
|
||||||
|
helps keep our community welcoming to as many people as possible.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
contributing/index
|
||||||
|
|
||||||
|
About JupyterHub
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
index-about
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
------------------
|
==================
|
||||||
|
|
||||||
* :ref:`genindex`
|
* :ref:`genindex`
|
||||||
* :ref:`modindex`
|
* :ref:`modindex`
|
||||||
|
|
||||||
|
|
||||||
Questions? Suggestions?
|
Questions? Suggestions?
|
||||||
-----------------------
|
=======================
|
||||||
|
|
||||||
- `Jupyter mailing list <https://groups.google.com/forum/#!forum/jupyter>`_
|
- `Jupyter mailing list <https://groups.google.com/forum/#!forum/jupyter>`_
|
||||||
- `Jupyter website <https://jupyter.org>`_
|
- `Jupyter website <https://jupyter.org>`_
|
||||||
|
|
||||||
.. _contents:
|
|
||||||
|
|
||||||
Full Table of Contents
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
installation-guide
|
|
||||||
getting-started/index
|
|
||||||
reference/index
|
|
||||||
api/index
|
|
||||||
tutorials/index
|
|
||||||
troubleshooting
|
|
||||||
contributor-list
|
|
||||||
gallery-jhub-deployments
|
|
||||||
changelog
|
|
||||||
|
|
||||||
|
|
||||||
.. _JupyterHub: https://github.com/jupyterhub/jupyterhub
|
.. _JupyterHub: https://github.com/jupyterhub/jupyterhub
|
||||||
.. _Jupyter notebook: https://jupyter-notebook.readthedocs.io/en/latest/
|
.. _Jupyter notebook: https://jupyter-notebook.readthedocs.io/en/latest/
|
||||||
.. _REST API: http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyterhub/jupyterhub/master/docs/rest-api.yml#!/default
|
.. _REST API: http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyterhub/jupyterhub/master/docs/rest-api.yml#!/default
|
||||||
|
@@ -6,7 +6,7 @@ JupyterHub is supported on Linux/Unix based systems. To use JupyterHub, you need
|
|||||||
a Unix server (typically Linux) running somewhere that is accessible to your
|
a Unix server (typically Linux) running somewhere that is accessible to your
|
||||||
team on the network. The JupyterHub server can be on an internal network at your
|
team on the network. The JupyterHub server can be on an internal network at your
|
||||||
organization, or it can run on the public internet (in which case, take care
|
organization, or it can run on the public internet (in which case, take care
|
||||||
with the Hub's [security](./security-basics.html)).
|
with the Hub's [security](./getting-started/security-basics)).
|
||||||
|
|
||||||
JupyterHub officially **does not** support Windows. You may be able to use
|
JupyterHub officially **does not** support Windows. You may be able to use
|
||||||
JupyterHub on Windows if you use a Spawner and Authenticator that work on
|
JupyterHub on Windows if you use a Spawner and Authenticator that work on
|
||||||
@@ -28,7 +28,7 @@ Prior to beginning installation, it's helpful to consider some of the following:
|
|||||||
- Spawner of singleuser notebook servers (Docker, Batch, etc.)
|
- Spawner of singleuser notebook servers (Docker, Batch, etc.)
|
||||||
- Services (nbgrader, etc.)
|
- Services (nbgrader, etc.)
|
||||||
- JupyterHub database (default SQLite; traditional RDBMS such as PostgreSQL,)
|
- JupyterHub database (default SQLite; traditional RDBMS such as PostgreSQL,)
|
||||||
MySQL, or other databases supported by [SQLAlchemy](http://www.sqlalchemy.org))
|
MySQL, or other databases supported by [SQLAlchemy](http://www.sqlalchemy.org))
|
||||||
|
|
||||||
## Folders and File Locations
|
## Folders and File Locations
|
||||||
|
|
||||||
|
6
docs/source/installation-guide-hard.rst
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
:orphan:
|
||||||
|
|
||||||
|
JupyterHub the hard way
|
||||||
|
=======================
|
||||||
|
|
||||||
|
This guide has moved to https://github.com/jupyterhub/jupyterhub-the-hard-way/blob/master/docs/installation-guide-hard.md
|
@@ -1,5 +1,9 @@
|
|||||||
Installation Guide
|
Installation
|
||||||
==================
|
============
|
||||||
|
|
||||||
|
These sections cover how to get up-and-running with JupyterHub. They cover
|
||||||
|
some basics of the tools needed to deploy JupyterHub as well as how to get it
|
||||||
|
running on your own infrastructure.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 3
|
:maxdepth: 3
|
||||||
|
52
docs/source/ipython_security.asc
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
Version: GnuPG v2.0.22 (GNU/Linux)
|
||||||
|
|
||||||
|
mQINBFMx2LoBEAC9xU8JiKI1VlCJ4PT9zqhU5nChQZ06/bj1BBftiMJG07fdGVO0
|
||||||
|
ibOn4TrCoRYaeRlet0UpHzxT4zDa5h3/usJaJNTSRwtWePw2o7Lik8J+F3LionRf
|
||||||
|
8Jz81WpJ+81Klg4UWKErXjBHsu/50aoQm6ZNYG4S2nwOmMVEC4nc44IAA0bb+6kW
|
||||||
|
saFKKzEDsASGyuvyutdyUHiCfvvh5GOC2h9mXYvl4FaMW7K+d2UgCYERcXDNy7C1
|
||||||
|
Bw+uepQ9ELKdG4ZpvonO6BNr1BWLln3wk93AQfD5qhfsYRJIyj0hJlaRLtBU3i6c
|
||||||
|
xs+gQNF4mPmybpPSGuOyUr4FYC7NfoG7IUMLj+DYa6d8LcMJO+9px4IbdhQvzGtC
|
||||||
|
qz5av1TX7/+gnS4L8C9i1g8xgI+MtvogngPmPY4repOlK6y3l/WtxUPkGkyYkn3s
|
||||||
|
RzYyE/GJgTwuxFXzMQs91s+/iELFQq/QwmEJf+g/QYfSAuM+lVGajEDNBYVAQkxf
|
||||||
|
gau4s8Gm0GzTZmINilk+7TxpXtKbFc/Yr4A/fMIHmaQ7KmJB84zKwONsQdVv7Jjj
|
||||||
|
0dpwu8EIQdHxX3k7/Q+KKubEivgoSkVwuoQTG15X9xrOsDZNwfOVQh+JKazPvJtd
|
||||||
|
SNfep96r9t/8gnXv9JI95CGCQ8lNhXBUSBM3BDPTbudc4b6lFUyMXN0mKQARAQAB
|
||||||
|
tCxJUHl0aG9uIFNlY3VyaXR5IFRlYW0gPHNlY3VyaXR5QGlweXRob24ub3JnPokC
|
||||||
|
OAQTAQIAIgUCUzHYugIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQEwJc
|
||||||
|
LcmZYkjuXg//R/t6nMNQmf9W1h52IVfUbRAVmvZ5d063hQHKV2dssxtnA2dRm/x5
|
||||||
|
JZu8Wz7ZrEZpyqwRJO14sxN1/lC3v+zs9XzYXr2lBTZuKCPIBypYVGIynCuWJBQJ
|
||||||
|
rWnfG4+u1RHahnjqlTWTY1C/le6v7SjAvCb6GbdA6k4ZL2EJjQlRaHDmzw3rV/+l
|
||||||
|
LLx6/tYzIsotuflm/bFumyOMmpQQpJjnCkWIVjnRICZvuAn97jLgtTI0+0Rzf4Zb
|
||||||
|
k2BwmHwDRqWCTTcRI9QvTl8AzjW+dNImN22TpGOBPfYj8BCZ9twrpKUbf+jNqJ1K
|
||||||
|
THQzFtpdJ6SzqiFVm74xW4TKqCLkbCQ/HtVjTGMGGz/y7KTtaLpGutQ6XE8SSy6P
|
||||||
|
EffSb5u+kKlQOWaH7Mc3B0yAojz6T3j5RSI8ts6pFi6pZhDg9hBfPK2dT0v/7Mkv
|
||||||
|
E1Z7q2IdjZnhhtGWjDAMtDDn2NbY2wuGoa5jAWAR0WvIbEZ3kOxuLE5/ZOG1FyYm
|
||||||
|
noJRliBz7038nT92EoD5g1pdzuxgXtGCpYyyjRZwaLmmi4CvA+oThKmnqWNY5lyY
|
||||||
|
ricdNHDiyEXK0YafJL1oZgM86MSb0jKJMp5U11nUkUGzkroFfpGDmzBwAzEPgeiF
|
||||||
|
40+qgsKB9lqwb3G7PxvfSi3XwxfXgpm1cTyEaPSzsVzve3d1xeqb7Yq5Ag0EUzHY
|
||||||
|
ugEQALQ5FtLdNoxTxMsgvrRr1ejLiUeRNUfXtN1TYttOfvAhfBVnszjtkpIW8DCB
|
||||||
|
JF/bA7ETiH8OYYn/Fm6MPI5H64IHEncpzxjf57jgpXd9CA9U2OMk/P1nve5zYchP
|
||||||
|
QmP2fJxeAWr0aRH0Mse5JS5nCkh8Xv4nAjsBYeLTJEVOb1gPQFXOiFcVp3gaKAzX
|
||||||
|
GWOZ/mtG/uaNsabH/3TkcQQEgJefd11DWgMB7575GU+eME7c6hn3FPITA5TC5HUX
|
||||||
|
azvjv/PsWGTTVAJluJ3fUDvhpbGwYOh1uV0rB68lPpqVIro18IIJhNDnccM/xqko
|
||||||
|
4fpJdokdg4L1wih+B04OEXnwgjWG8OIphR/oL/+M37VV2U7Om/GE6LGefaYccC9c
|
||||||
|
tIaacRQJmZpG/8RsimFIY2wJ07z8xYBITmhMmOt0bLBv0mU0ym5KH9Dnru1m9QDO
|
||||||
|
AHwcKrDgL85f9MCn+YYw0d1lYxjOXjf+moaeW3izXCJ5brM+MqVtixY6aos3YO29
|
||||||
|
J7SzQ4aEDv3h/oKdDfZny21jcVPQxGDui8sqaZCi8usCcyqWsKvFHcr6vkwaufcm
|
||||||
|
3Knr2HKVotOUF5CDZybopIz1sJvY/5Dx9yfRmtivJtglrxoDKsLi1rQTlEQcFhCS
|
||||||
|
ACjf7txLtv03vWHxmp4YKQFkkOlbyhIcvfPVLTvqGerdT2FHABEBAAGJAh8EGAEC
|
||||||
|
AAkFAlMx2LoCGwwACgkQEwJcLcmZYkgK0BAAny0YUugpZldiHzYNf8I6p2OpiDWv
|
||||||
|
ZHaguTTPg2LJSKaTd+5UHZwRFIWjcSiFu+qTGLNtZAdcr0D5f991CPvyDSLYgOwb
|
||||||
|
Jm2p3GM2KxfECWzFbB/n/PjbZ5iky3+5sPlOdBR4TkfG4fcu5GwUgCkVe5u3USAk
|
||||||
|
C6W5lpeaspDz39HAPRSIOFEX70+xV+6FZ17B7nixFGN+giTpGYOEdGFxtUNmHmf+
|
||||||
|
waJoPECyImDwJvmlMTeP9jfahlB6Pzaxt6TBZYHetI/JR9FU69EmA+XfCSGt5S+0
|
||||||
|
Eoc330gpsSzo2VlxwRCVNrcuKmG7PsFFANok05ssFq1/Djv5rJ++3lYb88b8HSP2
|
||||||
|
3pQJPrM7cQNU8iPku9yLXkY5qsoZOH+3yAia554Dgc8WBhp6fWh58R0dIONQxbbo
|
||||||
|
apNdwvlI8hKFB7TiUL6PNShE1yL+XD201iNkGAJXbLMIC1ImGLirUfU267A3Cop5
|
||||||
|
hoGs179HGBcyj/sKA3uUIFdNtP+NndaP3v4iYhCitdVCvBJMm6K3tW88qkyRGzOk
|
||||||
|
4PW422oyWKwbAPeMk5PubvEFuFAIoBAFn1zecrcOg85RzRnEeXaiemmmH8GOe1Xu
|
||||||
|
Kh+7h8XXyG6RPFy8tCcLOTk+miTqX+4VWy+kVqoS2cQ5IV8WsJ3S7aeIy0H89Z8n
|
||||||
|
5vmLc+Ibz+eT+rM=
|
||||||
|
=XVDe
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
@@ -25,7 +25,7 @@ Starting JupyterHub with docker
|
|||||||
|
|
||||||
The JupyterHub docker image can be started with the following command::
|
The JupyterHub docker image can be started with the following command::
|
||||||
|
|
||||||
docker run -d --name jupyterhub jupyterhub/jupyterhub jupyterhub
|
docker run -d -p 8000:8000 --name jupyterhub jupyterhub/jupyterhub jupyterhub
|
||||||
|
|
||||||
This command will create a container named ``jupyterhub`` that you can
|
This command will create a container named ``jupyterhub`` that you can
|
||||||
**stop and resume** with ``docker stop/start``.
|
**stop and resume** with ``docker stop/start``.
|
||||||
|
@@ -12,20 +12,24 @@ Before installing JupyterHub, you will need:
|
|||||||
- [nodejs/npm](https://www.npmjs.com/). [Install nodejs/npm](https://docs.npmjs.com/getting-started/installing-node),
|
- [nodejs/npm](https://www.npmjs.com/). [Install nodejs/npm](https://docs.npmjs.com/getting-started/installing-node),
|
||||||
using your operating system's package manager.
|
using your operating system's package manager.
|
||||||
|
|
||||||
* If you are using **`conda`**, the nodejs and npm dependencies will be installed for
|
- If you are using **`conda`**, the nodejs and npm dependencies will be installed for
|
||||||
you by conda.
|
you by conda.
|
||||||
|
|
||||||
* If you are using **`pip`**, install a recent version of
|
- If you are using **`pip`**, install a recent version of
|
||||||
[nodejs/npm](https://docs.npmjs.com/getting-started/installing-node).
|
[nodejs/npm](https://docs.npmjs.com/getting-started/installing-node).
|
||||||
For example, install it on Linux (Debian/Ubuntu) using:
|
For example, install it on Linux (Debian/Ubuntu) using:
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo apt-get install npm nodejs-legacy
|
sudo apt-get install npm nodejs-legacy
|
||||||
```
|
```
|
||||||
|
|
||||||
The `nodejs-legacy` package installs the `node` executable and is currently
|
The `nodejs-legacy` package installs the `node` executable and is currently
|
||||||
required for npm to work on Debian/Ubuntu.
|
required for npm to work on Debian/Ubuntu.
|
||||||
|
|
||||||
|
- A [pluggable authentication module (PAM)](https://en.wikipedia.org/wiki/Pluggable_authentication_module)
|
||||||
|
to use the [default Authenticator](./getting-started/authenticators-users-basics.md).
|
||||||
|
PAM is often available by default on most distributions, if this is not the case it can be installed by
|
||||||
|
using the operating system's package manager.
|
||||||
- TLS certificate and key for HTTPS communication
|
- TLS certificate and key for HTTPS communication
|
||||||
- Domain name
|
- Domain name
|
||||||
|
|
||||||
@@ -74,12 +78,12 @@ Visit `https://localhost:8000` in your browser, and sign in with your unix
|
|||||||
credentials.
|
credentials.
|
||||||
|
|
||||||
To **allow multiple users to sign in** to the Hub server, you must start
|
To **allow multiple users to sign in** to the Hub server, you must start
|
||||||
`jupyterhub` as a *privileged user*, such as root:
|
`jupyterhub` as a _privileged user_, such as root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo jupyterhub
|
sudo jupyterhub
|
||||||
```
|
```
|
||||||
|
|
||||||
The [wiki](https://github.com/jupyterhub/jupyterhub/wiki/Using-sudo-to-run-JupyterHub-without-root-privileges)
|
The [wiki](https://github.com/jupyterhub/jupyterhub/wiki/Using-sudo-to-run-JupyterHub-without-root-privileges)
|
||||||
describes how to run the server as a *less privileged user*. This requires
|
describes how to run the server as a _less privileged user_. This requires
|
||||||
additional configuration of the system.
|
additional configuration of the system.
|
||||||
|
@@ -5,8 +5,8 @@ Hub and single user notebook servers.
|
|||||||
|
|
||||||
## The default PAM Authenticator
|
## The default PAM Authenticator
|
||||||
|
|
||||||
JupyterHub ships only with the default [PAM][]-based Authenticator,
|
JupyterHub ships with the default [PAM][]-based Authenticator, for
|
||||||
for logging in with local user accounts via a username and password.
|
logging in with local user accounts via a username and password.
|
||||||
|
|
||||||
## The OAuthenticator
|
## The OAuthenticator
|
||||||
|
|
||||||
@@ -34,12 +34,17 @@ popular services:
|
|||||||
A generic implementation, which you can use for OAuth authentication
|
A generic implementation, which you can use for OAuth authentication
|
||||||
with any provider, is also available.
|
with any provider, is also available.
|
||||||
|
|
||||||
|
## The Dummy Authenticator
|
||||||
|
|
||||||
|
When testing, it may be helpful to use the
|
||||||
|
:class:`~jupyterhub.auth.DummyAuthenticator`. This allows for any username and
|
||||||
|
password unless if a global password has been set. Once set, any username will
|
||||||
|
still be accepted but the correct password will need to be provided.
|
||||||
|
|
||||||
## Additional Authenticators
|
## Additional Authenticators
|
||||||
|
|
||||||
- ldapauthenticator for LDAP
|
A partial list of other authenticators is available on the
|
||||||
- tmpauthenticator for temporary accounts
|
[JupyterHub wiki](https://github.com/jupyterhub/jupyterhub/wiki/Authenticators).
|
||||||
- For Shibboleth, [jhub_shibboleth_auth](https://github.com/gesiscss/jhub_shibboleth_auth)
|
|
||||||
and [jhub_remote_user_authenticator](https://github.com/cwaldbieser/jhub_remote_user_authenticator)
|
|
||||||
|
|
||||||
## Technical Overview of Authentication
|
## Technical Overview of Authentication
|
||||||
|
|
||||||
@@ -70,7 +75,6 @@ Writing an Authenticator that looks up passwords in a dictionary
|
|||||||
requires only overriding this one method:
|
requires only overriding this one method:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from tornado import gen
|
|
||||||
from IPython.utils.traitlets import Dict
|
from IPython.utils.traitlets import Dict
|
||||||
from jupyterhub.auth import Authenticator
|
from jupyterhub.auth import Authenticator
|
||||||
|
|
||||||
@@ -80,13 +84,11 @@ class DictionaryAuthenticator(Authenticator):
|
|||||||
help="""dict of username:password for authentication"""
|
help="""dict of username:password for authentication"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@gen.coroutine
|
async def authenticate(self, handler, data):
|
||||||
def authenticate(self, handler, data):
|
|
||||||
if self.passwords.get(data['username']) == data['password']:
|
if self.passwords.get(data['username']) == data['password']:
|
||||||
return data['username']
|
return data['username']
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Normalize usernames
|
#### Normalize usernames
|
||||||
|
|
||||||
Since the Authenticator and Spawner both use the same username,
|
Since the Authenticator and Spawner both use the same username,
|
||||||
@@ -103,6 +105,15 @@ c.Authenticator.username_map = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When using `PAMAuthenticator`, you can set
|
||||||
|
`c.PAMAuthenticator.pam_normalize_username = True`, which will
|
||||||
|
normalize usernames using PAM (basically round-tripping them: username
|
||||||
|
to uid to username), which is useful in case you use some external
|
||||||
|
service that allows multiple usernames mapping to the same user (such
|
||||||
|
as ActiveDirectory, yes, this really happens). When
|
||||||
|
`pam_normalize_username` is on, usernames are _not_ normalized to
|
||||||
|
lowercase.
|
||||||
|
|
||||||
#### Validate usernames
|
#### Validate usernames
|
||||||
|
|
||||||
In most cases, there is a very limited set of acceptable usernames.
|
In most cases, there is a very limited set of acceptable usernames.
|
||||||
@@ -119,7 +130,6 @@ To only allow usernames that start with 'w':
|
|||||||
c.Authenticator.username_pattern = r'w.*'
|
c.Authenticator.username_pattern = r'w.*'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### How to write a custom authenticator
|
### How to write a custom authenticator
|
||||||
|
|
||||||
You can use custom Authenticator subclasses to enable authentication
|
You can use custom Authenticator subclasses to enable authentication
|
||||||
@@ -132,12 +142,45 @@ and [post_spawn_stop(user, spawner)][], are hooks that can be used to do
|
|||||||
auth-related startup (e.g. opening PAM sessions) and cleanup
|
auth-related startup (e.g. opening PAM sessions) and cleanup
|
||||||
(e.g. closing PAM sessions).
|
(e.g. closing PAM sessions).
|
||||||
|
|
||||||
|
|
||||||
See a list of custom Authenticators [on the wiki](https://github.com/jupyterhub/jupyterhub/wiki/Authenticators).
|
See a list of custom Authenticators [on the wiki](https://github.com/jupyterhub/jupyterhub/wiki/Authenticators).
|
||||||
|
|
||||||
If you are interested in writing a custom authenticator, you can read
|
If you are interested in writing a custom authenticator, you can read
|
||||||
[this tutorial](http://jupyterhub-tutorial.readthedocs.io/en/latest/authenticators.html).
|
[this tutorial](http://jupyterhub-tutorial.readthedocs.io/en/latest/authenticators.html).
|
||||||
|
|
||||||
|
### Registering custom Authenticators via entry points
|
||||||
|
|
||||||
|
As of JupyterHub 1.0, custom authenticators can register themselves via
|
||||||
|
the `jupyterhub.authenticators` entry point metadata.
|
||||||
|
To do this, in your `setup.py` add:
|
||||||
|
|
||||||
|
```python
|
||||||
|
setup(
|
||||||
|
...
|
||||||
|
entry_points={
|
||||||
|
'jupyterhub.authenticators': [
|
||||||
|
'myservice = mypackage:MyAuthenticator',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have added this metadata to your package,
|
||||||
|
users can select your authenticator with the configuration:
|
||||||
|
|
||||||
|
```python
|
||||||
|
c.JupyterHub.authenticator_class = 'myservice'
|
||||||
|
```
|
||||||
|
|
||||||
|
instead of the full
|
||||||
|
|
||||||
|
```python
|
||||||
|
c.JupyterHub.authenticator_class = 'mypackage:MyAuthenticator'
|
||||||
|
```
|
||||||
|
|
||||||
|
previously required.
|
||||||
|
Additionally, configurable attributes for your authenticator will
|
||||||
|
appear in jupyterhub help output and auto-generated configuration files
|
||||||
|
via `jupyterhub --generate-config`.
|
||||||
|
|
||||||
### Authentication state
|
### Authentication state
|
||||||
|
|
||||||
@@ -172,12 +215,10 @@ To store auth_state, two conditions must be met:
|
|||||||
export JUPYTERHUB_CRYPT_KEY=$(openssl rand -hex 32)
|
export JUPYTERHUB_CRYPT_KEY=$(openssl rand -hex 32)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
JupyterHub uses [Fernet](https://cryptography.io/en/latest/fernet/) to encrypt auth_state.
|
JupyterHub uses [Fernet](https://cryptography.io/en/latest/fernet/) to encrypt auth_state.
|
||||||
To facilitate key-rotation, `JUPYTERHUB_CRYPT_KEY` may be a semicolon-separated list of encryption keys.
|
To facilitate key-rotation, `JUPYTERHUB_CRYPT_KEY` may be a semicolon-separated list of encryption keys.
|
||||||
If there are multiple keys present, the **first** key is always used to persist any new auth_state.
|
If there are multiple keys present, the **first** key is always used to persist any new auth_state.
|
||||||
|
|
||||||
|
|
||||||
#### Using auth_state
|
#### Using auth_state
|
||||||
|
|
||||||
Typically, if `auth_state` is persisted it is desirable to affect the Spawner environment in some way.
|
Typically, if `auth_state` is persisted it is desirable to affect the Spawner environment in some way.
|
||||||
@@ -187,10 +228,9 @@ to Spawner environment:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
class MyAuthenticator(Authenticator):
|
class MyAuthenticator(Authenticator):
|
||||||
@gen.coroutine
|
async def authenticate(self, handler, data=None):
|
||||||
def authenticate(self, handler, data=None):
|
username = await identify_user(handler, data)
|
||||||
username = yield identify_user(handler, data)
|
upstream_token = await token_for_user(username)
|
||||||
upstream_token = yield token_for_user(username)
|
|
||||||
return {
|
return {
|
||||||
'name': username,
|
'name': username,
|
||||||
'auth_state': {
|
'auth_state': {
|
||||||
@@ -198,10 +238,9 @@ class MyAuthenticator(Authenticator):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@gen.coroutine
|
async def pre_spawn_start(self, user, spawner):
|
||||||
def pre_spawn_start(self, user, spawner):
|
|
||||||
"""Pass upstream_token to spawner via environment variable"""
|
"""Pass upstream_token to spawner via environment variable"""
|
||||||
auth_state = yield user.get_auth_state()
|
auth_state = await user.get_auth_state()
|
||||||
if not auth_state:
|
if not auth_state:
|
||||||
# auth_state not enabled
|
# auth_state not enabled
|
||||||
return
|
return
|
||||||
@@ -220,11 +259,10 @@ PAM session.
|
|||||||
|
|
||||||
Beginning with version 0.8, JupyterHub is an OAuth provider.
|
Beginning with version 0.8, JupyterHub is an OAuth provider.
|
||||||
|
|
||||||
|
[authenticator]: https://github.com/jupyterhub/jupyterhub/blob/master/jupyterhub/auth.py
|
||||||
[Authenticator]: https://github.com/jupyterhub/jupyterhub/blob/master/jupyterhub/auth.py
|
[pam]: https://en.wikipedia.org/wiki/Pluggable_authentication_module
|
||||||
[PAM]: https://en.wikipedia.org/wiki/Pluggable_authentication_module
|
[oauth]: https://en.wikipedia.org/wiki/OAuth
|
||||||
[OAuth]: https://en.wikipedia.org/wiki/OAuth
|
[github oauth]: https://developer.github.com/v3/oauth/
|
||||||
[GitHub OAuth]: https://developer.github.com/v3/oauth/
|
[oauthenticator]: https://github.com/jupyterhub/oauthenticator
|
||||||
[OAuthenticator]: https://github.com/jupyterhub/oauthenticator
|
|
||||||
[pre_spawn_start(user, spawner)]: https://jupyterhub.readthedocs.io/en/latest/api/auth.html#jupyterhub.auth.Authenticator.pre_spawn_start
|
[pre_spawn_start(user, spawner)]: https://jupyterhub.readthedocs.io/en/latest/api/auth.html#jupyterhub.auth.Authenticator.pre_spawn_start
|
||||||
[post_spawn_stop(user, spawner)]: https://jupyterhub.readthedocs.io/en/latest/api/auth.html#jupyterhub.auth.Authenticator.post_spawn_stop
|
[post_spawn_stop(user, spawner)]: https://jupyterhub.readthedocs.io/en/latest/api/auth.html#jupyterhub.auth.Authenticator.post_spawn_stop
|
||||||
|
@@ -3,18 +3,17 @@
|
|||||||
In this example, we show a configuration file for a fairly standard JupyterHub
|
In this example, we show a configuration file for a fairly standard JupyterHub
|
||||||
deployment with the following assumptions:
|
deployment with the following assumptions:
|
||||||
|
|
||||||
* Running JupyterHub on a single cloud server
|
- Running JupyterHub on a single cloud server
|
||||||
* Using SSL on the standard HTTPS port 443
|
- Using SSL on the standard HTTPS port 443
|
||||||
* Using GitHub OAuth (using oauthenticator) for login
|
- Using GitHub OAuth (using oauthenticator) for login
|
||||||
* Using the default spawner (to configure other spawners, uncomment and edit
|
- Using the default spawner (to configure other spawners, uncomment and edit
|
||||||
`spawner_class` as well as follow the instructions for your desired spawner)
|
`spawner_class` as well as follow the instructions for your desired spawner)
|
||||||
* Users exist locally on the server
|
- Users exist locally on the server
|
||||||
* Users' notebooks to be served from `~/assignments` to allow users to browse
|
- Users' notebooks to be served from `~/assignments` to allow users to browse
|
||||||
for notebooks within other users' home directories
|
for notebooks within other users' home directories
|
||||||
* You want the landing page for each user to be a `Welcome.ipynb` notebook in
|
- You want the landing page for each user to be a `Welcome.ipynb` notebook in
|
||||||
their assignments directory.
|
their assignments directory.
|
||||||
* All runtime files are put into `/srv/jupyterhub` and log files in `/var/log`.
|
- All runtime files are put into `/srv/jupyterhub` and log files in `/var/log`.
|
||||||
|
|
||||||
|
|
||||||
The `jupyterhub_config.py` file would have these settings:
|
The `jupyterhub_config.py` file would have these settings:
|
||||||
|
|
||||||
@@ -52,7 +51,7 @@ c.GitHubOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL']
|
|||||||
c.LocalAuthenticator.create_system_users = True
|
c.LocalAuthenticator.create_system_users = True
|
||||||
|
|
||||||
# specify users and admin
|
# specify users and admin
|
||||||
c.Authenticator.whitelist = {'rgbkrk', 'minrk', 'jhamrick'}
|
c.Authenticator.allowed_users = {'rgbkrk', 'minrk', 'jhamrick'}
|
||||||
c.Authenticator.admin_users = {'jhamrick', 'rgbkrk'}
|
c.Authenticator.admin_users = {'jhamrick', 'rgbkrk'}
|
||||||
|
|
||||||
# uses the default spawner
|
# uses the default spawner
|
||||||
|
@@ -6,21 +6,23 @@ SSL port `443`. This could be useful if the JupyterHub server machine is also
|
|||||||
hosting other domains or content on `443`. The goal in this example is to
|
hosting other domains or content on `443`. The goal in this example is to
|
||||||
satisfy the following:
|
satisfy the following:
|
||||||
|
|
||||||
* JupyterHub is running on a server, accessed *only* via `HUB.DOMAIN.TLD:443`
|
- JupyterHub is running on a server, accessed _only_ via `HUB.DOMAIN.TLD:443`
|
||||||
* On the same machine, `NO_HUB.DOMAIN.TLD` strictly serves different content,
|
- On the same machine, `NO_HUB.DOMAIN.TLD` strictly serves different content,
|
||||||
also on port `443`
|
also on port `443`
|
||||||
* `nginx` or `apache` is used as the public access point (which means that
|
- `nginx` or `apache` is used as the public access point (which means that
|
||||||
only nginx/apache will bind to `443`)
|
only nginx/apache will bind to `443`)
|
||||||
* After testing, the server in question should be able to score at least an A on the
|
- After testing, the server in question should be able to score at least an A on the
|
||||||
Qualys SSL Labs [SSL Server Test](https://www.ssllabs.com/ssltest/)
|
Qualys SSL Labs [SSL Server Test](https://www.ssllabs.com/ssltest/)
|
||||||
|
|
||||||
Let's start out with needed JupyterHub configuration in `jupyterhub_config.py`:
|
Let's start out with needed JupyterHub configuration in `jupyterhub_config.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Force the proxy to only listen to connections to 127.0.0.1
|
# Force the proxy to only listen to connections to 127.0.0.1 (on port 8000)
|
||||||
c.JupyterHub.ip = '127.0.0.1'
|
c.JupyterHub.bind_url = 'http://127.0.0.1:8000'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
(For Jupyterhub < 0.9 use `c.JupyterHub.ip = '127.0.0.1'`.)
|
||||||
|
|
||||||
For high-quality SSL configuration, we also generate Diffie-Helman parameters.
|
For high-quality SSL configuration, we also generate Diffie-Helman parameters.
|
||||||
This can take a few minutes:
|
This can take a few minutes:
|
||||||
|
|
||||||
@@ -81,8 +83,12 @@ server {
|
|||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
# websocket headers
|
# websocket headers
|
||||||
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_set_header X-Scheme $scheme;
|
||||||
|
|
||||||
|
proxy_buffering off;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Managing requests to verify letsencrypt host
|
# Managing requests to verify letsencrypt host
|
||||||
@@ -137,6 +143,21 @@ Now restart `nginx`, restart the JupyterHub, and enjoy accessing
|
|||||||
`https://HUB.DOMAIN.TLD` while serving other content securely on
|
`https://HUB.DOMAIN.TLD` while serving other content securely on
|
||||||
`https://NO_HUB.DOMAIN.TLD`.
|
`https://NO_HUB.DOMAIN.TLD`.
|
||||||
|
|
||||||
|
### SELinux permissions for nginx
|
||||||
|
|
||||||
|
On distributions with SELinux enabled (e.g. Fedora), one may encounter permission errors
|
||||||
|
when the nginx service is started.
|
||||||
|
|
||||||
|
We need to allow nginx to perform network relay and connect to the jupyterhub port. The
|
||||||
|
following commands do that:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
semanage port -a -t http_port_t -p tcp 8000
|
||||||
|
setsebool -P httpd_can_network_relay 1
|
||||||
|
setsebool -P httpd_can_network_connect 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace 8000 with the port the jupyterhub server is running from.
|
||||||
|
|
||||||
## Apache
|
## Apache
|
||||||
|
|
||||||
@@ -190,3 +211,25 @@ Listen 443
|
|||||||
</Location>
|
</Location>
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In case of the need to run the jupyterhub under /jhub/ or other location please use the below configurations:
|
||||||
|
|
||||||
|
- JupyterHub running locally at http://127.0.0.1:8000/jhub/ or other location
|
||||||
|
|
||||||
|
httpd.conf amendments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RewriteRule /jhub/(.*) ws://127.0.0.1:8000/jhub/$1 [NE.P,L]
|
||||||
|
RewriteRule /jhub/(.*) http://127.0.0.1:8000/jhub/$1 [NE,P,L]
|
||||||
|
|
||||||
|
ProxyPass /jhub/ http://127.0.0.1:8000/jhub/
|
||||||
|
ProxyPassReverse /jhub/ http://127.0.0.1:8000/jhub/
|
||||||
|
```
|
||||||
|
|
||||||
|
jupyterhub_config.py amendments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--The public facing URL of the whole JupyterHub application.
|
||||||
|
--This is the address on which the proxy will bind. Sets protocol, ip, base_url
|
||||||
|
c.JupyterHub.bind_url = 'http://127.0.0.1:8000/jhub/'
|
||||||
|
```
|
||||||
|
30
docs/source/reference/config-reference.rst
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
==============================
|
||||||
|
Configuration Reference
|
||||||
|
==============================
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
Make sure the version of JupyterHub for this documentation matches your
|
||||||
|
installation version, as the output of this command may change between versions.
|
||||||
|
|
||||||
|
JupyterHub configuration
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
As explained in the `Configuration Basics <../getting-started/config-basics.html#generate-a-default-config-file>`_
|
||||||
|
section, the ``jupyterhub_config.py`` can be automatically generated via
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
jupyterhub --generate-config
|
||||||
|
|
||||||
|
|
||||||
|
The following contains the output of that command for reference.
|
||||||
|
|
||||||
|
.. jupyterhub-generate-config::
|
||||||
|
|
||||||
|
JupyterHub help command output
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
This section contains the output of the command ``jupyterhub --help-all``.
|
||||||
|
|
||||||
|
.. jupyterhub-help-all::
|
@@ -9,7 +9,7 @@ Only do this if you are very sure you must.
|
|||||||
There are many Authenticators and Spawners available for JupyterHub. Some, such
|
There are many Authenticators and Spawners available for JupyterHub. Some, such
|
||||||
as DockerSpawner or OAuthenticator, do not need any elevated permissions. This
|
as DockerSpawner or OAuthenticator, do not need any elevated permissions. This
|
||||||
document describes how to get the full default behavior of JupyterHub while
|
document describes how to get the full default behavior of JupyterHub while
|
||||||
running notebook servers as real system users on a shared system without
|
running notebook servers as real system users on a shared system without
|
||||||
running the Hub itself as root.
|
running the Hub itself as root.
|
||||||
|
|
||||||
Since JupyterHub needs to spawn processes as other users, the simplest way
|
Since JupyterHub needs to spawn processes as other users, the simplest way
|
||||||
@@ -37,7 +37,7 @@ Next, you will need [sudospawner](https://github.com/jupyter/sudospawner)
|
|||||||
to enable monitoring the single-user servers with sudo:
|
to enable monitoring the single-user servers with sudo:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo pip install sudospawner
|
sudo python3 -m pip install sudospawner
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we have to configure sudo to allow the Hub user (`rhea`) to launch
|
Now we have to configure sudo to allow the Hub user (`rhea`) to launch
|
||||||
@@ -50,14 +50,13 @@ To do this we add to `/etc/sudoers` (use `visudo` for safe editing of sudoers):
|
|||||||
|
|
||||||
- specify the list of users `JUPYTER_USERS` for whom `rhea` can spawn servers
|
- specify the list of users `JUPYTER_USERS` for whom `rhea` can spawn servers
|
||||||
- set the command `JUPYTER_CMD` that `rhea` can execute on behalf of users
|
- set the command `JUPYTER_CMD` that `rhea` can execute on behalf of users
|
||||||
- give `rhea` permission to run `JUPYTER_CMD` on behalf of `JUPYTER_USERS`
|
- give `rhea` permission to run `JUPYTER_CMD` on behalf of `JUPYTER_USERS`
|
||||||
without entering a password
|
without entering a password
|
||||||
|
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# comma-separated whitelist of users that can spawn single-user servers
|
# comma-separated list of users that can spawn single-user servers
|
||||||
# this should include all of your Hub users
|
# this should include all of your Hub users
|
||||||
Runas_Alias JUPYTER_USERS = rhea, zoe, wash
|
Runas_Alias JUPYTER_USERS = rhea, zoe, wash
|
||||||
|
|
||||||
@@ -70,7 +69,7 @@ Cmnd_Alias JUPYTER_CMD = /usr/local/bin/sudospawner
|
|||||||
rhea ALL=(JUPYTER_USERS) NOPASSWD:JUPYTER_CMD
|
rhea ALL=(JUPYTER_USERS) NOPASSWD:JUPYTER_CMD
|
||||||
```
|
```
|
||||||
|
|
||||||
It might be useful to modifiy `secure_path` to add commands in path.
|
It might be useful to modify `secure_path` to add commands in path.
|
||||||
|
|
||||||
As an alternative to adding every user to the `/etc/sudoers` file, you can
|
As an alternative to adding every user to the `/etc/sudoers` file, you can
|
||||||
use a group in the last line above, instead of `JUPYTER_USERS`:
|
use a group in the last line above, instead of `JUPYTER_USERS`:
|
||||||
@@ -91,16 +90,16 @@ $ adduser -G jupyterhub newuser
|
|||||||
Test that the new user doesn't need to enter a password to run the sudospawner
|
Test that the new user doesn't need to enter a password to run the sudospawner
|
||||||
command.
|
command.
|
||||||
|
|
||||||
This should prompt for your password to switch to rhea, but *not* prompt for
|
This should prompt for your password to switch to rhea, but _not_ prompt for
|
||||||
any password for the second switch. It should show some help output about
|
any password for the second switch. It should show some help output about
|
||||||
logging options:
|
logging options:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ sudo -u rhea sudo -n -u $USER /usr/local/bin/sudospawner --help
|
$ sudo -u rhea sudo -n -u $USER /usr/local/bin/sudospawner --help
|
||||||
Usage: /usr/local/bin/sudospawner [OPTIONS]
|
Usage: /usr/local/bin/sudospawner [OPTIONS]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
--help show this help information
|
--help show this help information
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
@@ -120,6 +119,11 @@ the shadow password database.
|
|||||||
|
|
||||||
### Shadow group (Linux)
|
### Shadow group (Linux)
|
||||||
|
|
||||||
|
**Note:** On Fedora based distributions there is no clear way to configure
|
||||||
|
the PAM database to allow sufficient access for authenticating with the target user's password
|
||||||
|
from JupyterHub. As a workaround we recommend use an
|
||||||
|
[alternative authentication method](https://github.com/jupyterhub/jupyterhub/wiki/Authenticators).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ ls -l /etc/shadow
|
$ ls -l /etc/shadow
|
||||||
-rw-r----- 1 root shadow 2197 Jul 21 13:41 shadow
|
-rw-r----- 1 root shadow 2197 Jul 21 13:41 shadow
|
||||||
@@ -146,12 +150,13 @@ We want our new user to be able to read the shadow passwords, so add it to the s
|
|||||||
$ sudo usermod -a -G shadow rhea
|
$ sudo usermod -a -G shadow rhea
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want jupyterhub to serve pages on a restricted port (such as port 80 for http),
|
If you want jupyterhub to serve pages on a restricted port (such as port 80 for http),
|
||||||
then you will need to give `node` permission to do so:
|
then you will need to give `node` permission to do so:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo setcap 'cap_net_bind_service=+ep' /usr/bin/node
|
sudo setcap 'cap_net_bind_service=+ep' /usr/bin/node
|
||||||
```
|
```
|
||||||
|
|
||||||
However, you may want to further understand the consequences of this.
|
However, you may want to further understand the consequences of this.
|
||||||
|
|
||||||
You may also be interested in limiting the amount of CPU any process can use
|
You may also be interested in limiting the amount of CPU any process can use
|
||||||
@@ -160,7 +165,6 @@ distributions' packaging system. This can be used to keep any user's process
|
|||||||
from using too much CPU cycles. You can configure it accoring to [these
|
from using too much CPU cycles. You can configure it accoring to [these
|
||||||
instructions](http://ubuntuforums.org/showthread.php?t=992706).
|
instructions](http://ubuntuforums.org/showthread.php?t=992706).
|
||||||
|
|
||||||
|
|
||||||
### Shadow group (FreeBSD)
|
### Shadow group (FreeBSD)
|
||||||
|
|
||||||
**NOTE:** This has not been tested and may not work as expected.
|
**NOTE:** This has not been tested and may not work as expected.
|
||||||
@@ -181,7 +185,7 @@ $ sudo chgrp shadow /etc/master.passwd
|
|||||||
$ sudo chmod g+r /etc/master.passwd
|
$ sudo chmod g+r /etc/master.passwd
|
||||||
```
|
```
|
||||||
|
|
||||||
We want our new user to be able to read the shadow passwords, so add it to the
|
We want our new user to be able to read the shadow passwords, so add it to the
|
||||||
shadow group:
|
shadow group:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -204,8 +208,8 @@ The simplest way to deal with this is to make a directory owned by your Hub user
|
|||||||
and use that as the CWD when launching the server.
|
and use that as the CWD when launching the server.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ sudo mkdir /etc/jupyterhub
|
$ sudo mkdir /etc/jupyterhub
|
||||||
$ sudo chown rhea /etc/jupyterhub
|
$ sudo chown rhea /etc/jupyterhub
|
||||||
```
|
```
|
||||||
|
|
||||||
## Start jupyterhub
|
## Start jupyterhub
|
||||||
@@ -213,20 +217,20 @@ and use that as the CWD when launching the server.
|
|||||||
Finally, start the server as our newly configured user, `rhea`:
|
Finally, start the server as our newly configured user, `rhea`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cd /etc/jupyterhub
|
$ cd /etc/jupyterhub
|
||||||
$ sudo -u rhea jupyterhub --JupyterHub.spawner_class=sudospawner.SudoSpawner
|
$ sudo -u rhea jupyterhub --JupyterHub.spawner_class=sudospawner.SudoSpawner
|
||||||
```
|
```
|
||||||
|
|
||||||
And try logging in.
|
And try logging in.
|
||||||
|
|
||||||
### Troubleshooting: SELinux
|
## Troubleshooting: SELinux
|
||||||
|
|
||||||
If you still get a generic `Permission denied` `PermissionError`, it's possible SELinux is blocking you.
|
If you still get a generic `Permission denied` `PermissionError`, it's possible SELinux is blocking you.
|
||||||
Here's how you can make a module to allow this.
|
Here's how you can make a module to allow this.
|
||||||
First, put this in a file sudo_exec_selinux.te:
|
First, put this in a file named `sudo_exec_selinux.te`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
module sudo_exec 1.1;
|
module sudo_exec_selinux 1.1;
|
||||||
|
|
||||||
require {
|
require {
|
||||||
type unconfined_t;
|
type unconfined_t;
|
||||||
@@ -246,9 +250,9 @@ $ semodule_package -o sudo_exec_selinux.pp -m sudo_exec_selinux.mod
|
|||||||
$ semodule -i sudo_exec_selinux.pp
|
$ semodule -i sudo_exec_selinux.pp
|
||||||
```
|
```
|
||||||
|
|
||||||
### Troubleshooting: PAM session errors
|
## Troubleshooting: PAM session errors
|
||||||
|
|
||||||
If the PAM authentication doesn't work and you see errors for
|
If the PAM authentication doesn't work and you see errors for
|
||||||
`login:session-auth`, or similar, considering updating to `master`
|
`login:session-auth`, or similar, considering updating to a more recent version
|
||||||
and/or incorporating this commit https://github.com/jupyter/jupyterhub/commit/40368b8f555f04ffdd662ffe99d32392a088b1d2
|
of jupyterhub and disabling the opening of PAM sessions with
|
||||||
and configuration option, `c.PAMAuthenticator.open_sessions = False`.
|
`c.PAMAuthenticator.open_sessions=False`.
|
||||||
|
@@ -22,20 +22,18 @@ This section will focus on user environments, including:
|
|||||||
- Installing kernelspecs
|
- Installing kernelspecs
|
||||||
- Using containers vs. multi-user hosts
|
- Using containers vs. multi-user hosts
|
||||||
|
|
||||||
|
|
||||||
## Installing packages
|
## Installing packages
|
||||||
|
|
||||||
To make packages available to users, you generally will install packages
|
To make packages available to users, you generally will install packages
|
||||||
system-wide or in a shared environment.
|
system-wide or in a shared environment.
|
||||||
|
|
||||||
This installation location should always be in the same environment that
|
This installation location should always be in the same environment that
|
||||||
`jupyterhub-singleuser` itself is installed in, and must be *readable and
|
`jupyterhub-singleuser` itself is installed in, and must be _readable and
|
||||||
executable* by your users. If you want users to be able to install additional
|
executable_ by your users. If you want users to be able to install additional
|
||||||
packages, it must also be *writable* by your users.
|
packages, it must also be _writable_ by your users.
|
||||||
|
|
||||||
If you are using a standard system Python install, you would use:
|
If you are using a standard system Python install, you would use:
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo python3 -m pip install numpy
|
sudo python3 -m pip install numpy
|
||||||
```
|
```
|
||||||
@@ -47,7 +45,6 @@ You may also use conda to install packages. If you do, you should make sure
|
|||||||
that the conda environment has appropriate permissions for users to be able to
|
that the conda environment has appropriate permissions for users to be able to
|
||||||
run Python code in the env.
|
run Python code in the env.
|
||||||
|
|
||||||
|
|
||||||
## Configuring Jupyter and IPython
|
## Configuring Jupyter and IPython
|
||||||
|
|
||||||
[Jupyter](https://jupyter-notebook.readthedocs.io/en/stable/config_overview.html)
|
[Jupyter](https://jupyter-notebook.readthedocs.io/en/stable/config_overview.html)
|
||||||
@@ -64,6 +61,7 @@ users. It's generally more efficient to configure user environments "system-wide
|
|||||||
and it's a good idea to avoid creating files in users' home directories.
|
and it's a good idea to avoid creating files in users' home directories.
|
||||||
|
|
||||||
The typical locations for these config files are:
|
The typical locations for these config files are:
|
||||||
|
|
||||||
- **system-wide** in `/etc/{jupyter|ipython}`
|
- **system-wide** in `/etc/{jupyter|ipython}`
|
||||||
- **env-wide** (environment wide) in `{sys.prefix}/etc/{jupyter|ipython}`.
|
- **env-wide** (environment wide) in `{sys.prefix}/etc/{jupyter|ipython}`.
|
||||||
|
|
||||||
@@ -91,7 +89,6 @@ c.MappingKernelManager.cull_idle_timeout = 20 * 60
|
|||||||
c.MappingKernelManager.cull_interval = 2 * 60
|
c.MappingKernelManager.cull_interval = 2 * 60
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Installing kernelspecs
|
## Installing kernelspecs
|
||||||
|
|
||||||
You may have multiple Jupyter kernels installed and want to make sure that
|
You may have multiple Jupyter kernels installed and want to make sure that
|
||||||
@@ -119,13 +116,12 @@ sure are available, I can install their specs system-wide (in /usr/local) with:
|
|||||||
/path/to/python2 -m IPython kernel install --prefix=/usr/local
|
/path/to/python2 -m IPython kernel install --prefix=/usr/local
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Multi-user hosts vs. Containers
|
||||||
## Multi-user hosts vs. Containers
|
|
||||||
|
|
||||||
There are two broad categories of user environments that depend on what
|
There are two broad categories of user environments that depend on what
|
||||||
Spawner you choose:
|
Spawner you choose:
|
||||||
|
|
||||||
- Multi-user hosts (shared sytem)
|
- Multi-user hosts (shared system)
|
||||||
- Container-based
|
- Container-based
|
||||||
|
|
||||||
How you configure user environments for each category can differ a bit
|
How you configure user environments for each category can differ a bit
|
||||||
@@ -141,7 +137,51 @@ When JupyterHub uses **container-based** Spawners (e.g. KubeSpawner or
|
|||||||
DockerSpawner), the 'system-wide' environment is really the container image
|
DockerSpawner), the 'system-wide' environment is really the container image
|
||||||
which you are using for users.
|
which you are using for users.
|
||||||
|
|
||||||
In both cases, you want to *avoid putting configuration in user home
|
In both cases, you want to _avoid putting configuration in user home
|
||||||
directories* because users can change those configuration settings. Also,
|
directories_ because users can change those configuration settings. Also,
|
||||||
home directories typically persist once they are created, so they are
|
home directories typically persist once they are created, so they are
|
||||||
difficult for admins to update later.
|
difficult for admins to update later.
|
||||||
|
|
||||||
|
## Named servers
|
||||||
|
|
||||||
|
By default, in a JupyterHub deployment each user has exactly one server.
|
||||||
|
|
||||||
|
JupyterHub can, however, have multiple servers per user.
|
||||||
|
This is most useful in deployments where users can configure the environment
|
||||||
|
in which their server will start (e.g. resource requests on an HPC cluster),
|
||||||
|
so that a given user can have multiple configurations running at the same time,
|
||||||
|
without having to stop and restart their one server.
|
||||||
|
|
||||||
|
To allow named servers:
|
||||||
|
|
||||||
|
```python
|
||||||
|
c.JupyterHub.allow_named_servers = True
|
||||||
|
```
|
||||||
|
|
||||||
|
Named servers were implemented in the REST API in JupyterHub 0.8,
|
||||||
|
and JupyterHub 1.0 introduces UI for managing named servers via the user home page:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
as well as the admin page:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Named servers can be accessed, created, started, stopped, and deleted
|
||||||
|
from these pages. Activity tracking is now per-server as well.
|
||||||
|
|
||||||
|
The number of named servers per user can be limited by setting
|
||||||
|
|
||||||
|
```python
|
||||||
|
c.JupyterHub.named_server_limit_per_user = 5
|
||||||
|
```
|
||||||
|
|
||||||
|
## Switching to Jupyter Server
|
||||||
|
|
||||||
|
[Jupyter Server](https://jupyter-server.readthedocs.io/en/latest/) is a new Tornado Server backend for Jupyter web applications (e.g. JupyterLab 3.0 uses this package as its default backend).
|
||||||
|
|
||||||
|
By default, the single-user notebook server uses the (old) `NotebookApp` from the [notebook](https://github.com/jupyter/notebook) package. You can switch to using Jupyter Server's `ServerApp` backend (this will likely become the default in future releases) by setting the `JUPYTERHUB_SINGLEUSER_APP` environment variable to:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export JUPYTERHUB_SINGLEUSER_APP='jupyter_server.serverapp.ServerApp'
|
||||||
|
```
|
||||||
|
@@ -46,8 +46,8 @@ additional configuration required for MySQL that is not needed for PostgreSQL.
|
|||||||
|
|
||||||
- You should use the `pymysql` sqlalchemy provider (the other one, MySQLdb,
|
- You should use the `pymysql` sqlalchemy provider (the other one, MySQLdb,
|
||||||
isn't available for py3).
|
isn't available for py3).
|
||||||
- You also need to set `pool_recycle` to some value (typically 60 - 300)
|
- You also need to set `pool_recycle` to some value (typically 60 - 300)
|
||||||
which depends on your MySQL setup. This is necessary since MySQL kills
|
which depends on your MySQL setup. This is necessary since MySQL kills
|
||||||
connections serverside if they've been idle for a while, and the connection
|
connections serverside if they've been idle for a while, and the connection
|
||||||
from the hub will be idle for longer than most connections. This behavior
|
from the hub will be idle for longer than most connections. This behavior
|
||||||
will lead to frustrating 'the connection has gone away' errors from
|
will lead to frustrating 'the connection has gone away' errors from
|
||||||
|
@@ -1,21 +1,28 @@
|
|||||||
Technical Reference
|
Technical Reference
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
This section covers more of the details of the JupyterHub architecture, as well as
|
||||||
|
what happens under-the-hood when you deploy and configure your JupyterHub.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
technical-overview
|
technical-overview
|
||||||
|
urls
|
||||||
websecurity
|
websecurity
|
||||||
authenticators
|
authenticators
|
||||||
spawners
|
spawners
|
||||||
services
|
services
|
||||||
proxy
|
proxy
|
||||||
|
separate-proxy
|
||||||
rest
|
rest
|
||||||
|
monitoring
|
||||||
database
|
database
|
||||||
upgrading
|
|
||||||
templates
|
templates
|
||||||
|
../events/index
|
||||||
config-user-env
|
config-user-env
|
||||||
config-examples
|
config-examples
|
||||||
config-ghoauth
|
config-ghoauth
|
||||||
config-proxy
|
config-proxy
|
||||||
config-sudo
|
config-sudo
|
||||||
|
config-reference
|
||||||
|
20
docs/source/reference/monitoring.rst
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Monitoring
|
||||||
|
==========
|
||||||
|
|
||||||
|
This section covers details on monitoring the state of your JupyterHub installation.
|
||||||
|
|
||||||
|
JupyterHub expose the ``/metrics`` endpoint that returns text describing its current
|
||||||
|
operational state formatted in a way `Prometheus <https://prometheus.io/docs/introduction/overview/>`_ understands.
|
||||||
|
|
||||||
|
Prometheus is a separate open source tool that can be configured to repeatedly poll
|
||||||
|
JupyterHub's ``/metrics`` endpoint to parse and save its current state.
|
||||||
|
|
||||||
|
By doing so, Prometheus can describe JupyterHub's evolving state over time.
|
||||||
|
This evolving state can then be accessed through Prometheus that expose its underlying
|
||||||
|
storage to those allowed to access it, and be presented with dashboards by a
|
||||||
|
tool like `Grafana <https://grafana.com/docs/grafana/latest/getting-started/what-is-grafana/>`_.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
metrics
|
@@ -19,7 +19,7 @@ In general, for a proxy to be usable by JupyterHub, it must:
|
|||||||
|
|
||||||
1. support websockets without prior knowledge of the URL where websockets may
|
1. support websockets without prior knowledge of the URL where websockets may
|
||||||
occur
|
occur
|
||||||
2. support trie-based routing (i.e. allow different routes on `/foo` and
|
2. support trie-based routing (i.e. allow different routes on `/foo` and
|
||||||
`/foo/bar` and route based on specificity)
|
`/foo/bar` and route based on specificity)
|
||||||
3. adding or removing a route should not cause existing connections to drop
|
3. adding or removing a route should not cause existing connections to drop
|
||||||
|
|
||||||
@@ -45,23 +45,32 @@ If your proxy should be launched when the Hub starts, you must define how
|
|||||||
to start and stop your proxy:
|
to start and stop your proxy:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from tornado import gen
|
|
||||||
class MyProxy(Proxy):
|
class MyProxy(Proxy):
|
||||||
...
|
...
|
||||||
@gen.coroutine
|
async def start(self):
|
||||||
def start(self):
|
|
||||||
"""Start the proxy"""
|
"""Start the proxy"""
|
||||||
|
|
||||||
@gen.coroutine
|
async def stop(self):
|
||||||
def stop(self):
|
|
||||||
"""Stop the proxy"""
|
"""Stop the proxy"""
|
||||||
```
|
```
|
||||||
|
|
||||||
These methods **may** be coroutines.
|
These methods **may** be coroutines.
|
||||||
|
|
||||||
`c.Proxy.should_start` is a configurable flag that determines whether the
|
`c.Proxy.should_start` is a configurable flag that determines whether the
|
||||||
Hub should call these methods when the Hub itself starts and stops.
|
Hub should call these methods when the Hub itself starts and stops.
|
||||||
|
|
||||||
|
## Encryption
|
||||||
|
|
||||||
|
When using `internal_ssl` to encrypt traffic behind the proxy, at minimum,
|
||||||
|
your `Proxy` will need client ssl certificates which the `Hub` must be made
|
||||||
|
aware of. These can be generated with the command `jupyterhub --generate-certs`
|
||||||
|
which will write them to the `internal_certs_location` in folders named
|
||||||
|
`proxy_api` and `proxy_client`. Alternatively, these can be provided to the
|
||||||
|
hub via the `jupyterhub_config.py` file by providing a `dict` of named paths
|
||||||
|
to the `external_authorities` option. The hub will include all certificates
|
||||||
|
provided in that `dict` in the trust bundle utilized by all internal
|
||||||
|
components.
|
||||||
|
|
||||||
### Purely external proxies
|
### Purely external proxies
|
||||||
|
|
||||||
Probably most custom proxies will be externally managed,
|
Probably most custom proxies will be externally managed,
|
||||||
@@ -93,15 +102,14 @@ route to be proxied, such as `/user/name/`. A routespec will:
|
|||||||
### Adding a route
|
### Adding a route
|
||||||
|
|
||||||
When adding a route, JupyterHub may pass a JSON-serializable dict as a `data`
|
When adding a route, JupyterHub may pass a JSON-serializable dict as a `data`
|
||||||
argument that should be attacked to the proxy route. When that route is
|
argument that should be attached to the proxy route. When that route is
|
||||||
retrieved, the `data` argument should be returned as well. If your proxy
|
retrieved, the `data` argument should be returned as well. If your proxy
|
||||||
implementation doesn't support storing data attached to routes, then your
|
implementation doesn't support storing data attached to routes, then your
|
||||||
Python wrapper may have to handle storing the `data` piece itself, e.g in a
|
Python wrapper may have to handle storing the `data` piece itself, e.g in a
|
||||||
simple file or database.
|
simple file or database.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@gen.coroutine
|
async def add_route(self, routespec, target, data):
|
||||||
def add_route(self, routespec, target, data):
|
|
||||||
"""Proxy `routespec` to `target`.
|
"""Proxy `routespec` to `target`.
|
||||||
|
|
||||||
Store `data` associated with the routespec
|
Store `data` associated with the routespec
|
||||||
@@ -112,7 +120,7 @@ def add_route(self, routespec, target, data):
|
|||||||
Adding a route for a user looks like this:
|
Adding a route for a user looks like this:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
proxy.add_route('/user/pgeorgiou/', 'http://127.0.0.1:1227',
|
await proxy.add_route('/user/pgeorgiou/', 'http://127.0.0.1:1227',
|
||||||
{'user': 'pgeorgiou'})
|
{'user': 'pgeorgiou'})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -122,21 +130,19 @@ proxy.add_route('/user/pgeorgiou/', 'http://127.0.0.1:1227',
|
|||||||
`delete_route` should still succeed, but a warning may be issued.
|
`delete_route` should still succeed, but a warning may be issued.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@gen.coroutine
|
async def delete_route(self, routespec):
|
||||||
def delete_route(self, routespec):
|
|
||||||
"""Delete the route"""
|
"""Delete the route"""
|
||||||
```
|
```
|
||||||
|
|
||||||
### Retrieving routes
|
### Retrieving routes
|
||||||
|
|
||||||
For retrieval, you only *need* to implement a single method that retrieves all
|
For retrieval, you only _need_ to implement a single method that retrieves all
|
||||||
routes. The return value for this function should be a dictionary, keyed by
|
routes. The return value for this function should be a dictionary, keyed by
|
||||||
`routespect`, of dicts whose keys are the same three arguments passed to
|
`routespect`, of dicts whose keys are the same three arguments passed to
|
||||||
`add_route` (`routespec`, `target`, `data`)
|
`add_route` (`routespec`, `target`, `data`)
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@gen.coroutine
|
async def get_all_routes(self):
|
||||||
def get_all_routes(self):
|
|
||||||
"""Return all routes, keyed by routespec"""
|
"""Return all routes, keyed by routespec"""
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -179,3 +185,38 @@ tracked, and services such as cull-idle will not work.
|
|||||||
Now that `notebook-5.0` tracks activity internally, we can retrieve activity
|
Now that `notebook-5.0` tracks activity internally, we can retrieve activity
|
||||||
information from the single-user servers instead, removing the need to track
|
information from the single-user servers instead, removing the need to track
|
||||||
activity in the proxy. But this is not yet implemented in JupyterHub 0.8.0.
|
activity in the proxy. But this is not yet implemented in JupyterHub 0.8.0.
|
||||||
|
|
||||||
|
### Registering custom Proxies via entry points
|
||||||
|
|
||||||
|
As of JupyterHub 1.0, custom proxy implementations can register themselves via
|
||||||
|
the `jupyterhub.proxies` entry point metadata.
|
||||||
|
To do this, in your `setup.py` add:
|
||||||
|
|
||||||
|
```python
|
||||||
|
setup(
|
||||||
|
...
|
||||||
|
entry_points={
|
||||||
|
'jupyterhub.proxies': [
|
||||||
|
'mything = mypackage:MyProxy',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have added this metadata to your package,
|
||||||
|
users can select your proxy with the configuration:
|
||||||
|
|
||||||
|
```python
|
||||||
|
c.JupyterHub.proxy_class = 'mything'
|
||||||
|
```
|
||||||
|
|
||||||
|
instead of the full
|
||||||
|
|
||||||
|
```python
|
||||||
|
c.JupyterHub.proxy_class = 'mypackage:MyProxy'
|
||||||
|
```
|
||||||
|
|
||||||
|
previously required.
|
||||||
|
Additionally, configurable attributes for your proxy will
|
||||||
|
appear in jupyterhub help output and auto-generated configuration files
|
||||||
|
via `jupyterhub --generate-config`.
|
||||||
|
14
docs/source/reference/rest-api.rst
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
:orphan:
|
||||||
|
|
||||||
|
===================
|
||||||
|
JupyterHub REST API
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. this doc exists as a resolvable link target
|
||||||
|
.. which _static files are not
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:http-equiv=refresh: 0;url=../_static/rest-api/index.html
|
||||||
|
|
||||||
|
The rest API docs are `here <../_static/rest-api/index.html>`_
|
||||||
|
if you are not redirected automatically.
|
@@ -27,7 +27,7 @@ Hub.
|
|||||||
To send requests using JupyterHub API, you must pass an API token with
|
To send requests using JupyterHub API, you must pass an API token with
|
||||||
the request.
|
the request.
|
||||||
|
|
||||||
As of [version 0.6.0](../changelog.html), the preferred way of
|
As of [version 0.6.0](../changelog.md), the preferred way of
|
||||||
generating an API token is:
|
generating an API token is:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -48,7 +48,7 @@ jupyterhub token <username>
|
|||||||
This command generates a random string to use as a token and registers
|
This command generates a random string to use as a token and registers
|
||||||
it for the given user with the Hub's database.
|
it for the given user with the Hub's database.
|
||||||
|
|
||||||
In [version 0.8.0](../changelog.html), a TOKEN request page for
|
In [version 0.8.0](../changelog.md), a TOKEN request page for
|
||||||
generating an API token is available from the JupyterHub user interface:
|
generating an API token is available from the JupyterHub user interface:
|
||||||
|
|
||||||

|

|
||||||
@@ -57,6 +57,9 @@ generating an API token is available from the JupyterHub user interface:
|
|||||||
|
|
||||||
## Add API tokens to the config file
|
## Add API tokens to the config file
|
||||||
|
|
||||||
|
**This is deprecated. We are in no rush to remove this feature,
|
||||||
|
but please consider if service tokens are right for you.**
|
||||||
|
|
||||||
You may also add a dictionary of API tokens and usernames to the hub's
|
You may also add a dictionary of API tokens and usernames to the hub's
|
||||||
configuration file, `jupyterhub_config.py` (note that
|
configuration file, `jupyterhub_config.py` (note that
|
||||||
the **key** is the 'secret-token' while the **value** is the 'username'):
|
the **key** is the 'secret-token' while the **value** is the 'username'):
|
||||||
@@ -67,6 +70,41 @@ c.JupyterHub.api_tokens = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Updating to admin services
|
||||||
|
|
||||||
|
The `api_tokens` configuration has been softly deprecated since the introduction of services.
|
||||||
|
We have no plans to remove it,
|
||||||
|
but users are encouraged to use service configuration instead.
|
||||||
|
|
||||||
|
If you have been using `api_tokens` to create an admin user
|
||||||
|
and a token for that user to perform some automations,
|
||||||
|
the services mechanism may be a better fit.
|
||||||
|
If you have the following configuration:
|
||||||
|
|
||||||
|
```python
|
||||||
|
c.JupyterHub.admin_users = {"service-admin",}
|
||||||
|
c.JupyterHub.api_tokens = {
|
||||||
|
"secret-token": "service-admin",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be updated to create an admin service, with the following configuration:
|
||||||
|
|
||||||
|
```python
|
||||||
|
c.JupyterHub.services = [
|
||||||
|
{
|
||||||
|
"name": "service-token",
|
||||||
|
"admin": True,
|
||||||
|
"api_token": "secret-token",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
The token will have the same admin permissions,
|
||||||
|
but there will no longer be a user account created to house it.
|
||||||
|
The main noticeable difference is that there will be no notebook server associated with the account
|
||||||
|
and the service will not show up in the various user list pages and APIs.
|
||||||
|
|
||||||
## Make an API request
|
## Make an API request
|
||||||
|
|
||||||
To authenticate your requests, pass the API token in the request's
|
To authenticate your requests, pass the API token in the request's
|
||||||
@@ -131,15 +169,15 @@ curl -X POST -H "Authorization: token <token>" "http://127.0.0.1:8081/hub/api/us
|
|||||||
```
|
```
|
||||||
|
|
||||||
With the named-server functionality, it's now possible to launch more than one
|
With the named-server functionality, it's now possible to launch more than one
|
||||||
specifically named servers against a given user. This could be used, for instance,
|
specifically named servers against a given user. This could be used, for instance,
|
||||||
to launch each server based on a different image.
|
to launch each server based on a different image.
|
||||||
|
|
||||||
First you must enable named-servers by including the following setting in the `jupyterhub_config.py` file.
|
First you must enable named-servers by including the following setting in the `jupyterhub_config.py` file.
|
||||||
|
|
||||||
`c.JupyterHub.allow_named_servers = True`
|
`c.JupyterHub.allow_named_servers = True`
|
||||||
|
|
||||||
If using the [zero-to-jupyterhub-k8s](https://github.com/jupyterhub/zero-to-jupyterhub-k8s) set-up to run JupyterHub,
|
If using the [zero-to-jupyterhub-k8s](https://github.com/jupyterhub/zero-to-jupyterhub-k8s) set-up to run JupyterHub,
|
||||||
then instead of editing the `jupyterhub_config.py` file directly, you could pass
|
then instead of editing the `jupyterhub_config.py` file directly, you could pass
|
||||||
the following as part of the `config.yaml` file, as per the [tutorial](https://zero-to-jupyterhub.readthedocs.io/en/latest/):
|
the following as part of the `config.yaml` file, as per the [tutorial](https://zero-to-jupyterhub.readthedocs.io/en/latest/):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -149,6 +187,7 @@ hub:
|
|||||||
```
|
```
|
||||||
|
|
||||||
With that setting in place, a new named-server is activated like this:
|
With that setting in place, a new named-server is activated like this:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST -H "Authorization: token <token>" "http://127.0.0.1:8081/hub/api/users/<user>/servers/<serverA>"
|
curl -X POST -H "Authorization: token <token>" "http://127.0.0.1:8081/hub/api/users/<user>/servers/<serverA>"
|
||||||
curl -X POST -H "Authorization: token <token>" "http://127.0.0.1:8081/hub/api/users/<user>/servers/<serverB>"
|
curl -X POST -H "Authorization: token <token>" "http://127.0.0.1:8081/hub/api/users/<user>/servers/<serverB>"
|
||||||
@@ -158,17 +197,11 @@ The same servers can be stopped by substituting `DELETE` for `POST` above.
|
|||||||
|
|
||||||
### Some caveats for using named-servers
|
### Some caveats for using named-servers
|
||||||
|
|
||||||
The named-server capabilities are not fully implemented for JupyterHub as yet.
|
|
||||||
While it's possible to start/stop a server via the API, the UI on the
|
|
||||||
JupyterHub control-panel has not been implemented, and so it may not be obvious
|
|
||||||
to those viewing the panel that a named-server may be running for a given user.
|
|
||||||
|
|
||||||
For named-servers via the API to work, the spawner used to spawn these servers
|
For named-servers via the API to work, the spawner used to spawn these servers
|
||||||
will need to be able to handle the case of multiple servers per user and ensure
|
will need to be able to handle the case of multiple servers per user and ensure
|
||||||
uniqueness of names, particularly if servers are spawned via docker containers
|
uniqueness of names, particularly if servers are spawned via docker containers
|
||||||
or kubernetes pods.
|
or kubernetes pods.
|
||||||
|
|
||||||
|
|
||||||
## Learn more about the API
|
## Learn more about the API
|
||||||
|
|
||||||
You can see the full [JupyterHub REST API][] for details. This REST API Spec can
|
You can see the full [JupyterHub REST API][] for details. This REST API Spec can
|
||||||
@@ -177,6 +210,6 @@ Both resources contain the same information and differ only in its display.
|
|||||||
Note: The Swagger specification is being renamed the [OpenAPI Initiative][].
|
Note: The Swagger specification is being renamed the [OpenAPI Initiative][].
|
||||||
|
|
||||||
[interactive style on swagger's petstore]: http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyterhub/jupyterhub/master/docs/rest-api.yml#!/default
|
[interactive style on swagger's petstore]: http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyterhub/jupyterhub/master/docs/rest-api.yml#!/default
|
||||||
[OpenAPI Initiative]: https://www.openapis.org/
|
[openapi initiative]: https://www.openapis.org/
|
||||||
[JupyterHub REST API]: ../_static/rest-api/index.html
|
[jupyterhub rest api]: ./rest-api
|
||||||
[Jupyter Notebook REST API]: http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml
|
[jupyter notebook rest api]: http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml
|
||||||
|
72
docs/source/reference/separate-proxy.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Running proxy separately from the hub
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
The thing which users directly connect to is the proxy, by default
|
||||||
|
`configurable-http-proxy`. The proxy either redirects users to the
|
||||||
|
hub (for login and managing servers), or to their own single-user
|
||||||
|
servers. Thus, as long as the proxy stays running, access to existing
|
||||||
|
servers continues, even if the hub itself restarts or goes down.
|
||||||
|
|
||||||
|
When you first configure the hub, you may not even realize this
|
||||||
|
because the proxy is automatically managed by the hub. This is great
|
||||||
|
for getting started and even most use, but everytime you restart the
|
||||||
|
hub, all user connections also get restarted. But it's also simple to
|
||||||
|
run the proxy as a service separate from the hub, so that you are free
|
||||||
|
to reconfigure the hub while only interrupting users who are currently
|
||||||
|
actively starting the hub.
|
||||||
|
|
||||||
|
The default JupyterHub proxy is
|
||||||
|
[configurable-http-proxy](https://github.com/jupyterhub/configurable-http-proxy),
|
||||||
|
and that page has some docs. If you are using a different proxy, such
|
||||||
|
as Traefik, these instructions are probably not relevant to you.
|
||||||
|
|
||||||
|
## Configuration options
|
||||||
|
|
||||||
|
`c.JupyterHub.cleanup_servers = False` should be set, which tells the
|
||||||
|
hub to not stop servers when the hub restarts (this is useful even if
|
||||||
|
you don't run the proxy separately).
|
||||||
|
|
||||||
|
`c.ConfigurableHTTPProxy.should_start = False` should be set, which
|
||||||
|
tells the hub that the proxy should not be started (because you start
|
||||||
|
it yourself).
|
||||||
|
|
||||||
|
`c.ConfigurableHTTPProxy.auth_token = "CONFIGPROXY_AUTH_TOKEN"` should be set to a
|
||||||
|
token for authenticating communication with the proxy.
|
||||||
|
|
||||||
|
`c.ConfigurableHTTPProxy.api_url = 'http://localhost:8001'` should be
|
||||||
|
set to the URL which the hub uses to connect _to the proxy's API_.
|
||||||
|
|
||||||
|
## Proxy configuration
|
||||||
|
|
||||||
|
You need to configure a service to start the proxy. An example
|
||||||
|
command line for this is `configurable-http-proxy --ip=127.0.0.1 --port=8000 --api-ip=127.0.0.1 --api-port=8001 --default-target=http://localhost:8081 --error-target=http://localhost:8081/hub/error`. (Details for how to
|
||||||
|
do this is out of scope for this tutorial - for example it might be a
|
||||||
|
systemd service on within another docker cotainer). The proxy has no
|
||||||
|
configuration files, all configuration is via the command line and
|
||||||
|
environment variables.
|
||||||
|
|
||||||
|
`--api-ip` and `--api-port` (which tells the proxy where to listen) should match the hub's `ConfigurableHTTPProxy.api_url`.
|
||||||
|
|
||||||
|
`--ip`, `-port`, and other options configure the _user_ connections to the proxy.
|
||||||
|
|
||||||
|
`--default-target` and `--error-target` should point to the hub, and used when users navigate to the proxy originally.
|
||||||
|
|
||||||
|
You must define the environment variable `CONFIGPROXY_AUTH_TOKEN` to
|
||||||
|
match the token given to `c.ConfigurableHTTPProxy.auth_token`.
|
||||||
|
|
||||||
|
You should check the [configurable-http-proxy
|
||||||
|
options](https://github.com/jupyterhub/configurable-http-proxy) to see
|
||||||
|
what other options are needed, for example SSL options. Note that
|
||||||
|
these are configured in the hub if the hub is starting the proxy - you
|
||||||
|
need to move the options to here.
|
||||||
|
|
||||||
|
## Docker image
|
||||||
|
|
||||||
|
You can use [jupyterhub configurable-http-proxy docker
|
||||||
|
image](https://hub.docker.com/r/jupyterhub/configurable-http-proxy/)
|
||||||
|
to run the proxy.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [jupyterhub configurable-http-proxy](https://github.com/jupyterhub/configurable-http-proxy)
|
@@ -45,17 +45,14 @@ A Service may have the following properties:
|
|||||||
- `url: str (default - None)` - The URL where the service is/should be. If a
|
- `url: str (default - None)` - The URL where the service is/should be. If a
|
||||||
url is specified for where the Service runs its own web server,
|
url is specified for where the Service runs its own web server,
|
||||||
the service will be added to the proxy at `/services/:name`
|
the service will be added to the proxy at `/services/:name`
|
||||||
- `api_token: str (default - None)` - For Externally-Managed Services you need to specify
|
- `api_token: str (default - None)` - For Externally-Managed Services you need to specify
|
||||||
an API token to perform API requests to the Hub
|
an API token to perform API requests to the Hub
|
||||||
|
|
||||||
If a service is also to be managed by the Hub, it has a few extra options:
|
If a service is also to be managed by the Hub, it has a few extra options:
|
||||||
|
|
||||||
- `command: (str/Popen list`) - Command for JupyterHub to spawn the service.
|
- `command: (str/Popen list)` - Command for JupyterHub to spawn the service. - Only use this if the service should be a subprocess. - If command is not specified, the Service is assumed to be managed
|
||||||
- Only use this if the service should be a subprocess.
|
externally. - If a command is specified for launching the Service, the Service will
|
||||||
- If command is not specified, the Service is assumed to be managed
|
be started and managed by the Hub.
|
||||||
externally.
|
|
||||||
- If a command is specified for launching the Service, the Service will
|
|
||||||
be started and managed by the Hub.
|
|
||||||
- `environment: dict` - additional environment variables for the Service.
|
- `environment: dict` - additional environment variables for the Service.
|
||||||
- `user: str` - the name of a system user to manage the Service. If
|
- `user: str` - the name of a system user to manage the Service. If
|
||||||
unspecified, run as the same user as the Hub.
|
unspecified, run as the same user as the Hub.
|
||||||
@@ -91,9 +88,9 @@ This example would be configured as follows in `jupyterhub_config.py`:
|
|||||||
```python
|
```python
|
||||||
c.JupyterHub.services = [
|
c.JupyterHub.services = [
|
||||||
{
|
{
|
||||||
'name': 'cull-idle',
|
'name': 'idle-culler',
|
||||||
'admin': True,
|
'admin': True,
|
||||||
'command': ['python', '/path/to/cull-idle.py', '--timeout']
|
'command': [sys.executable, '-m', 'jupyterhub_idle_culler', '--timeout=3600']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
@@ -103,9 +100,9 @@ parameters, which describe the environment needed to start the Service process:
|
|||||||
|
|
||||||
- `environment: dict` - additional environment variables for the Service.
|
- `environment: dict` - additional environment variables for the Service.
|
||||||
- `user: str` - name of the user to run the server if different from the Hub.
|
- `user: str` - name of the user to run the server if different from the Hub.
|
||||||
Requires Hub to be root.
|
Requires Hub to be root.
|
||||||
- `cwd: path` directory in which to run the Service, if different from the
|
- `cwd: path` directory in which to run the Service, if different from the
|
||||||
Hub directory.
|
Hub directory.
|
||||||
|
|
||||||
The Hub will pass the following environment variables to launch the Service:
|
The Hub will pass the following environment variables to launch the Service:
|
||||||
|
|
||||||
@@ -123,15 +120,14 @@ For the previous 'cull idle' Service example, these environment variables
|
|||||||
would be passed to the Service when the Hub starts the 'cull idle' Service:
|
would be passed to the Service when the Hub starts the 'cull idle' Service:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
JUPYTERHUB_SERVICE_NAME: 'cull-idle'
|
JUPYTERHUB_SERVICE_NAME: 'idle-culler'
|
||||||
JUPYTERHUB_API_TOKEN: API token assigned to the service
|
JUPYTERHUB_API_TOKEN: API token assigned to the service
|
||||||
JUPYTERHUB_API_URL: http://127.0.0.1:8080/hub/api
|
JUPYTERHUB_API_URL: http://127.0.0.1:8080/hub/api
|
||||||
JUPYTERHUB_BASE_URL: https://mydomain[:port]
|
JUPYTERHUB_BASE_URL: https://mydomain[:port]
|
||||||
JUPYTERHUB_SERVICE_PREFIX: /services/cull-idle/
|
JUPYTERHUB_SERVICE_PREFIX: /services/idle-culler/
|
||||||
```
|
```
|
||||||
|
|
||||||
See the JupyterHub GitHub repo for additional information about the
|
See the GitHub repo for additional information about the [jupyterhub_idle_culler][].
|
||||||
[`cull-idle` example](https://github.com/jupyterhub/jupyterhub/tree/master/examples/cull-idle).
|
|
||||||
|
|
||||||
## Externally-Managed Services
|
## Externally-Managed Services
|
||||||
|
|
||||||
@@ -151,6 +147,8 @@ c.JupyterHub.services = [
|
|||||||
{
|
{
|
||||||
'name': 'my-web-service',
|
'name': 'my-web-service',
|
||||||
'url': 'https://10.0.1.1:1984',
|
'url': 'https://10.0.1.1:1984',
|
||||||
|
# any secret >8 characters, you'll use api_token to
|
||||||
|
# authenticate api requests to the hub from your service
|
||||||
'api_token': 'super-secret',
|
'api_token': 'super-secret',
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -198,16 +196,16 @@ can be used by services. You may go beyond this reference implementation and
|
|||||||
create custom hub-authenticating clients and services. We describe the process
|
create custom hub-authenticating clients and services. We describe the process
|
||||||
below.
|
below.
|
||||||
|
|
||||||
The reference, or base, implementation is the [`HubAuth`][HubAuth] class,
|
The reference, or base, implementation is the [`HubAuth`][hubauth] class,
|
||||||
which implements the requests to the Hub.
|
which implements the requests to the Hub.
|
||||||
|
|
||||||
To use HubAuth, you must set the `.api_token`, either programmatically when constructing the class,
|
To use HubAuth, you must set the `.api_token`, either programmatically when constructing the class,
|
||||||
or via the `JUPYTERHUB_API_TOKEN` environment variable.
|
or via the `JUPYTERHUB_API_TOKEN` environment variable.
|
||||||
|
|
||||||
Most of the logic for authentication implementation is found in the
|
Most of the logic for authentication implementation is found in the
|
||||||
[`HubAuth.user_for_cookie`][HubAuth.user_for_cookie]
|
[`HubAuth.user_for_cookie`][hubauth.user_for_cookie]
|
||||||
and in the
|
and in the
|
||||||
[`HubAuth.user_for_token`][HubAuth.user_for_token]
|
[`HubAuth.user_for_token`][hubauth.user_for_token]
|
||||||
methods, which makes a request of the Hub, and returns:
|
methods, which makes a request of the Hub, and returns:
|
||||||
|
|
||||||
- None, if no user could be identified, or
|
- None, if no user could be identified, or
|
||||||
@@ -249,7 +247,7 @@ prefix = os.environ.get('JUPYTERHUB_SERVICE_PREFIX', '/')
|
|||||||
|
|
||||||
auth = HubAuth(
|
auth = HubAuth(
|
||||||
api_token=os.environ['JUPYTERHUB_API_TOKEN'],
|
api_token=os.environ['JUPYTERHUB_API_TOKEN'],
|
||||||
cookie_cache_max_age=60,
|
cache_max_age=60,
|
||||||
)
|
)
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@@ -284,11 +282,10 @@ def whoami(user):
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Authenticating tornado services with JupyterHub
|
### Authenticating tornado services with JupyterHub
|
||||||
|
|
||||||
Since most Jupyter services are written with tornado,
|
Since most Jupyter services are written with tornado,
|
||||||
we include a mixin class, [`HubAuthenticated`][HubAuthenticated],
|
we include a mixin class, [`HubAuthenticated`][hubauthenticated],
|
||||||
for quickly authenticating your own tornado services with JupyterHub.
|
for quickly authenticating your own tornado services with JupyterHub.
|
||||||
|
|
||||||
Tornado's `@web.authenticated` method calls a Handler's `.get_current_user`
|
Tornado's `@web.authenticated` method calls a Handler's `.get_current_user`
|
||||||
@@ -309,66 +306,65 @@ class MyHandler(HubAuthenticated, web.RequestHandler):
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
The HubAuth will automatically load the desired configuration from the Service
|
The HubAuth will automatically load the desired configuration from the Service
|
||||||
environment variables.
|
environment variables.
|
||||||
|
|
||||||
If you want to limit user access, you can whitelist users through either the
|
If you want to limit user access, you can specify allowed users through either the
|
||||||
`.hub_users` attribute or `.hub_groups`. These are sets that check against the
|
`.hub_users` attribute or `.hub_groups`. These are sets that check against the
|
||||||
username and user group list, respectively. If a user matches neither the user
|
username and user group list, respectively. If a user matches neither the user
|
||||||
list nor the group list, they will not be allowed access. If both are left
|
list nor the group list, they will not be allowed access. If both are left
|
||||||
undefined, then any user will be allowed.
|
undefined, then any user will be allowed.
|
||||||
|
|
||||||
|
|
||||||
### Implementing your own Authentication with JupyterHub
|
### Implementing your own Authentication with JupyterHub
|
||||||
|
|
||||||
If you don't want to use the reference implementation
|
If you don't want to use the reference implementation
|
||||||
(e.g. you find the implementation a poor fit for your Flask app),
|
(e.g. you find the implementation a poor fit for your Flask app),
|
||||||
you can implement authentication via the Hub yourself.
|
you can implement authentication via the Hub yourself.
|
||||||
We recommend looking at the [`HubAuth`][HubAuth] class implementation for reference,
|
We recommend looking at the [`HubAuth`][hubauth] class implementation for reference,
|
||||||
and taking note of the following process:
|
and taking note of the following process:
|
||||||
|
|
||||||
1. retrieve the cookie `jupyterhub-services` from the request.
|
1. retrieve the cookie `jupyterhub-services` from the request.
|
||||||
2. Make an API request `GET /hub/api/authorizations/cookie/jupyterhub-services/cookie-value`,
|
2. Make an API request `GET /hub/api/authorizations/cookie/jupyterhub-services/cookie-value`,
|
||||||
where cookie-value is the url-encoded value of the `jupyterhub-services` cookie.
|
where cookie-value is the url-encoded value of the `jupyterhub-services` cookie.
|
||||||
This request must be authenticated with a Hub API token in the `Authorization` header.
|
This request must be authenticated with a Hub API token in the `Authorization` header,
|
||||||
For example, with [requests][]:
|
for example using the `api_token` from your [external service's configuration](#externally-managed-services).
|
||||||
|
|
||||||
```python
|
For example, with [requests][]:
|
||||||
r = requests.get(
|
|
||||||
'/'.join((["http://127.0.0.1:8081/hub/api",
|
```python
|
||||||
"authorizations/cookie/jupyterhub-services",
|
r = requests.get(
|
||||||
quote(encrypted_cookie, safe=''),
|
'/'.join(["http://127.0.0.1:8081/hub/api",
|
||||||
]),
|
"authorizations/cookie/jupyterhub-services",
|
||||||
headers = {
|
quote(encrypted_cookie, safe=''),
|
||||||
'Authorization' : 'token %s' % api_token,
|
]),
|
||||||
},
|
headers = {
|
||||||
)
|
'Authorization' : 'token %s' % api_token,
|
||||||
r.raise_for_status()
|
},
|
||||||
user = r.json()
|
)
|
||||||
```
|
r.raise_for_status()
|
||||||
|
user = r.json()
|
||||||
|
```
|
||||||
|
|
||||||
3. On success, the reply will be a JSON model describing the user:
|
3. On success, the reply will be a JSON model describing the user:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"name": "inara",
|
"name": "inara",
|
||||||
"groups": ["serenity", "guild"],
|
"groups": ["serenity", "guild"]
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
An example of using an Externally-Managed Service and authentication is
|
An example of using an Externally-Managed Service and authentication is
|
||||||
in [nbviewer README][nbviewer example] section on securing the notebook viewer,
|
in [nbviewer README][nbviewer example] section on securing the notebook viewer,
|
||||||
and an example of its configuration is found [here](https://github.com/jupyter/nbviewer/blob/master/nbviewer/providers/base.py#L94).
|
and an example of its configuration is found [here](https://github.com/jupyter/nbviewer/blob/ed942b10a52b6259099e2dd687930871dc8aac22/nbviewer/providers/base.py#L95).
|
||||||
nbviewer can also be run as a Hub-Managed Service as described [nbviewer README][nbviewer example]
|
nbviewer can also be run as a Hub-Managed Service as described [nbviewer README][nbviewer example]
|
||||||
section on securing the notebook viewer.
|
section on securing the notebook viewer.
|
||||||
|
|
||||||
|
|
||||||
[requests]: http://docs.python-requests.org/en/master/
|
[requests]: http://docs.python-requests.org/en/master/
|
||||||
[services_auth]: ../api/services.auth.html
|
[services_auth]: ../api/services.auth.html
|
||||||
[HubAuth]: ../api/services.auth.html#jupyterhub.services.auth.HubAuth
|
[hubauth]: ../api/services.auth.html#jupyterhub.services.auth.HubAuth
|
||||||
[HubAuth.user_for_cookie]: ../api/services.auth.html#jupyterhub.services.auth.HubAuth.user_for_cookie
|
[hubauth.user_for_cookie]: ../api/services.auth.html#jupyterhub.services.auth.HubAuth.user_for_cookie
|
||||||
[HubAuth.user_for_token]: ../api/services.auth.html#jupyterhub.services.auth.HubAuth.user_for_token
|
[hubauth.user_for_token]: ../api/services.auth.html#jupyterhub.services.auth.HubAuth.user_for_token
|
||||||
[HubAuthenticated]: ../api/services.auth.html#jupyterhub.services.auth.HubAuthenticated
|
[hubauthenticated]: ../api/services.auth.html#jupyterhub.services.auth.HubAuthenticated
|
||||||
[nbviewer example]: https://github.com/jupyter/nbviewer#securing-the-notebook-viewer
|
[nbviewer example]: https://github.com/jupyter/nbviewer#securing-the-notebook-viewer
|
||||||
|
[jupyterhub_idle_culler]: https://github.com/jupyterhub/jupyterhub-idle-culler
|
||||||
|
@@ -8,25 +8,26 @@ and a custom Spawner needs to be able to take three actions:
|
|||||||
- poll whether the process is still running
|
- poll whether the process is still running
|
||||||
- stop the process
|
- stop the process
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Custom Spawners for JupyterHub can be found on the [JupyterHub wiki](https://github.com/jupyterhub/jupyterhub/wiki/Spawners).
|
Custom Spawners for JupyterHub can be found on the [JupyterHub wiki](https://github.com/jupyterhub/jupyterhub/wiki/Spawners).
|
||||||
Some examples include:
|
Some examples include:
|
||||||
|
|
||||||
- [DockerSpawner](https://github.com/jupyterhub/dockerspawner) for spawning user servers in Docker containers
|
- [DockerSpawner](https://github.com/jupyterhub/dockerspawner) for spawning user servers in Docker containers
|
||||||
* `dockerspawner.DockerSpawner` for spawning identical Docker containers for
|
- `dockerspawner.DockerSpawner` for spawning identical Docker containers for
|
||||||
each users
|
each users
|
||||||
* `dockerspawner.SystemUserSpawner` for spawning Docker containers with an
|
- `dockerspawner.SystemUserSpawner` for spawning Docker containers with an
|
||||||
environment and home directory for each users
|
environment and home directory for each users
|
||||||
* both `DockerSpawner` and `SystemUserSpawner` also work with Docker Swarm for
|
- both `DockerSpawner` and `SystemUserSpawner` also work with Docker Swarm for
|
||||||
launching containers on remote machines
|
launching containers on remote machines
|
||||||
- [SudoSpawner](https://github.com/jupyterhub/sudospawner) enables JupyterHub to
|
- [SudoSpawner](https://github.com/jupyterhub/sudospawner) enables JupyterHub to
|
||||||
run without being root, by spawning an intermediate process via `sudo`
|
run without being root, by spawning an intermediate process via `sudo`
|
||||||
- [BatchSpawner](https://github.com/jupyterhub/batchspawner) for spawning remote
|
- [BatchSpawner](https://github.com/jupyterhub/batchspawner) for spawning remote
|
||||||
servers using batch systems
|
servers using batch systems
|
||||||
- [RemoteSpawner](https://github.com/zonca/remotespawner) to spawn notebooks
|
- [YarnSpawner](https://github.com/jupyterhub/yarnspawner) for spawning notebook
|
||||||
and a remote server and tunnel the port via SSH
|
servers in YARN containers on a Hadoop cluster
|
||||||
|
- [SSHSpawner](https://github.com/NERSC/sshspawner) to spawn notebooks
|
||||||
|
on a remote server using SSH
|
||||||
|
|
||||||
## Spawner control methods
|
## Spawner control methods
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ an object encapsulating the user's name, authentication, and server info.
|
|||||||
|
|
||||||
The return value of `Spawner.start` should be the (ip, port) of the running server.
|
The return value of `Spawner.start` should be the (ip, port) of the running server.
|
||||||
|
|
||||||
**NOTE:** When writing coroutines, *never* `yield` in between a database change and a commit.
|
**NOTE:** When writing coroutines, _never_ `yield` in between a database change and a commit.
|
||||||
|
|
||||||
Most `Spawner.start` functions will look similar to this example:
|
Most `Spawner.start` functions will look similar to this example:
|
||||||
|
|
||||||
@@ -71,13 +72,12 @@ It should return `None` if it is still running,
|
|||||||
and an integer exit status, otherwise.
|
and an integer exit status, otherwise.
|
||||||
|
|
||||||
For the local process case, `Spawner.poll` uses `os.kill(PID, 0)`
|
For the local process case, `Spawner.poll` uses `os.kill(PID, 0)`
|
||||||
to check if the local process is still running.
|
to check if the local process is still running. On Windows, it uses `psutil.pid_exists`.
|
||||||
|
|
||||||
### Spawner.stop
|
### Spawner.stop
|
||||||
|
|
||||||
`Spawner.stop` should stop the process. It must be a tornado coroutine, which should return when the process has finished exiting.
|
`Spawner.stop` should stop the process. It must be a tornado coroutine, which should return when the process has finished exiting.
|
||||||
|
|
||||||
|
|
||||||
## Spawner state
|
## Spawner state
|
||||||
|
|
||||||
JupyterHub should be able to stop and restart without tearing down
|
JupyterHub should be able to stop and restart without tearing down
|
||||||
@@ -109,7 +109,6 @@ def clear_state(self):
|
|||||||
self.pid = 0
|
self.pid = 0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Spawner options form
|
## Spawner options form
|
||||||
|
|
||||||
(new in 0.4)
|
(new in 0.4)
|
||||||
@@ -167,13 +166,47 @@ which would return:
|
|||||||
|
|
||||||
When `Spawner.start` is called, this dictionary is accessible as `self.user_options`.
|
When `Spawner.start` is called, this dictionary is accessible as `self.user_options`.
|
||||||
|
|
||||||
|
[spawner]: https://github.com/jupyterhub/jupyterhub/blob/master/jupyterhub/spawner.py
|
||||||
[Spawner]: https://github.com/jupyterhub/jupyterhub/blob/master/jupyterhub/spawner.py
|
|
||||||
|
|
||||||
## Writing a custom spawner
|
## Writing a custom spawner
|
||||||
|
|
||||||
If you are interested in building a custom spawner, you can read [this tutorial](http://jupyterhub-tutorial.readthedocs.io/en/latest/spawners.html).
|
If you are interested in building a custom spawner, you can read [this tutorial](http://jupyterhub-tutorial.readthedocs.io/en/latest/spawners.html).
|
||||||
|
|
||||||
|
### Registering custom Spawners via entry points
|
||||||
|
|
||||||
|
As of JupyterHub 1.0, custom Spawners can register themselves via
|
||||||
|
the `jupyterhub.spawners` entry point metadata.
|
||||||
|
To do this, in your `setup.py` add:
|
||||||
|
|
||||||
|
```python
|
||||||
|
setup(
|
||||||
|
...
|
||||||
|
entry_points={
|
||||||
|
'jupyterhub.spawners': [
|
||||||
|
'myservice = mypackage:MySpawner',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have added this metadata to your package,
|
||||||
|
users can select your spawner with the configuration:
|
||||||
|
|
||||||
|
```python
|
||||||
|
c.JupyterHub.spawner_class = 'myservice'
|
||||||
|
```
|
||||||
|
|
||||||
|
instead of the full
|
||||||
|
|
||||||
|
```python
|
||||||
|
c.JupyterHub.spawner_class = 'mypackage:MySpawner'
|
||||||
|
```
|
||||||
|
|
||||||
|
previously required.
|
||||||
|
Additionally, configurable attributes for your spawner will
|
||||||
|
appear in jupyterhub help output and auto-generated configuration files
|
||||||
|
via `jupyterhub --generate-config`.
|
||||||
|
|
||||||
## Spawners, resource limits, and guarantees (Optional)
|
## Spawners, resource limits, and guarantees (Optional)
|
||||||
|
|
||||||
Some spawners of the single-user notebook servers allow setting limits or
|
Some spawners of the single-user notebook servers allow setting limits or
|
||||||
@@ -185,10 +218,9 @@ support for them**. For example, LocalProcessSpawner, the default
|
|||||||
spawner, does not support limits and guarantees. One of the spawners
|
spawner, does not support limits and guarantees. One of the spawners
|
||||||
that supports limits and guarantees is the `systemdspawner`.
|
that supports limits and guarantees is the `systemdspawner`.
|
||||||
|
|
||||||
|
|
||||||
### Memory Limits & Guarantees
|
### Memory Limits & Guarantees
|
||||||
|
|
||||||
`c.Spawner.mem_limit`: A **limit** specifies the *maximum amount of memory*
|
`c.Spawner.mem_limit`: A **limit** specifies the _maximum amount of memory_
|
||||||
that may be allocated, though there is no promise that the maximum amount will
|
that may be allocated, though there is no promise that the maximum amount will
|
||||||
be available. In supported spawners, you can set `c.Spawner.mem_limit` to
|
be available. In supported spawners, you can set `c.Spawner.mem_limit` to
|
||||||
limit the total amount of memory that a single-user notebook server can
|
limit the total amount of memory that a single-user notebook server can
|
||||||
@@ -196,8 +228,8 @@ allocate. Attempting to use more memory than this limit will cause errors. The
|
|||||||
single-user notebook server can discover its own memory limit by looking at
|
single-user notebook server can discover its own memory limit by looking at
|
||||||
the environment variable `MEM_LIMIT`, which is specified in absolute bytes.
|
the environment variable `MEM_LIMIT`, which is specified in absolute bytes.
|
||||||
|
|
||||||
`c.Spawner.mem_guarantee`: Sometimes, a **guarantee** of a *minumum amount of
|
`c.Spawner.mem_guarantee`: Sometimes, a **guarantee** of a _minimum amount of
|
||||||
memory* is desirable. In this case, you can set `c.Spawner.mem_guarantee` to
|
memory_ is desirable. In this case, you can set `c.Spawner.mem_guarantee` to
|
||||||
to provide a guarantee that at minimum this much memory will always be
|
to provide a guarantee that at minimum this much memory will always be
|
||||||
available for the single-user notebook server to use. The environment variable
|
available for the single-user notebook server to use. The environment variable
|
||||||
`MEM_GUARANTEE` will also be set in the single-user notebook server.
|
`MEM_GUARANTEE` will also be set in the single-user notebook server.
|
||||||
@@ -223,3 +255,30 @@ in the single-user notebook server when a guarantee is being provided.
|
|||||||
**The spawner's underlying system or cluster is responsible for enforcing these
|
**The spawner's underlying system or cluster is responsible for enforcing these
|
||||||
limits and providing these guarantees.** If these values are set to `None`, no
|
limits and providing these guarantees.** If these values are set to `None`, no
|
||||||
limits or guarantees are provided, and no environment values are set.
|
limits or guarantees are provided, and no environment values are set.
|
||||||
|
|
||||||
|
### Encryption
|
||||||
|
|
||||||
|
Communication between the `Proxy`, `Hub`, and `Notebook` can be secured by
|
||||||
|
turning on `internal_ssl` in `jupyterhub_config.py`. For a custom spawner to
|
||||||
|
utilize these certs, there are two methods of interest on the base `Spawner`
|
||||||
|
class: `.create_certs` and `.move_certs`.
|
||||||
|
|
||||||
|
The first method, `.create_certs` will sign a key-cert pair using an internally
|
||||||
|
trusted authority for notebooks. During this process, `.create_certs` can
|
||||||
|
apply `ip` and `dns` name information to the cert via an `alt_names` `kwarg`.
|
||||||
|
This is used for certificate authentication (verification). Without proper
|
||||||
|
verification, the `Notebook` will be unable to communicate with the `Hub` and
|
||||||
|
vice versa when `internal_ssl` is enabled. For example, given a deployment
|
||||||
|
using the `DockerSpawner` which will start containers with `ips` from the
|
||||||
|
`docker` subnet pool, the `DockerSpawner` would need to instead choose a
|
||||||
|
container `ip` prior to starting and pass that to `.create_certs` (TODO: edit).
|
||||||
|
|
||||||
|
In general though, this method will not need to be changed and the default
|
||||||
|
`ip`/`dns` (localhost) info will suffice.
|
||||||
|
|
||||||
|
When `.create_certs` is run, it will `.create_certs` in a default, central
|
||||||
|
location specified by `c.JupyterHub.internal_certs_location`. For `Spawners`
|
||||||
|
that need access to these certs elsewhere (i.e. on another host altogether),
|
||||||
|
the `.move_certs` method can be overridden to move the certs appropriately.
|
||||||
|
Again, using `DockerSpawner` as an example, this would entail moving certs
|
||||||
|
to a directory that will get mounted into the container this spawner starts.
|
||||||
|
@@ -28,7 +28,7 @@ by the `jupyterhub` command line program:
|
|||||||
- **Single-User Notebook Server** (Python/Tornado): a dedicated,
|
- **Single-User Notebook Server** (Python/Tornado): a dedicated,
|
||||||
single-user, Jupyter Notebook server is started for each user on the system
|
single-user, Jupyter Notebook server is started for each user on the system
|
||||||
when the user logs in. The object that starts the single-user notebook
|
when the user logs in. The object that starts the single-user notebook
|
||||||
servers is called a **Spawner**.
|
servers is called a **Spawner**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -49,14 +49,14 @@ The proxy is the only process that listens on a public interface. The Hub sits
|
|||||||
behind the proxy at `/hub`. Single-user servers sit behind the proxy at
|
behind the proxy at `/hub`. Single-user servers sit behind the proxy at
|
||||||
`/user/[username]`.
|
`/user/[username]`.
|
||||||
|
|
||||||
Different **[authenticators](./authenticators.html)** control access
|
Different **[authenticators](./authenticators.md)** control access
|
||||||
to JupyterHub. The default one (PAM) uses the user accounts on the server where
|
to JupyterHub. The default one (PAM) uses the user accounts on the server where
|
||||||
JupyterHub is running. If you use this, you will need to create a user account
|
JupyterHub is running. If you use this, you will need to create a user account
|
||||||
on the system for each user on your team. Using other authenticators, you can
|
on the system for each user on your team. Using other authenticators, you can
|
||||||
allow users to sign in with e.g. a GitHub account, or with any single-sign-on
|
allow users to sign in with e.g. a GitHub account, or with any single-sign-on
|
||||||
system your organization has.
|
system your organization has.
|
||||||
|
|
||||||
Next, **[spawners](./spawners.html)** control how JupyterHub starts
|
Next, **[spawners](./spawners.md)** control how JupyterHub starts
|
||||||
the individual notebook server for each user. The default spawner will
|
the individual notebook server for each user. The default spawner will
|
||||||
start a notebook server on the same machine running under their system username.
|
start a notebook server on the same machine running under their system username.
|
||||||
The other main option is to start each server in a separate container, often
|
The other main option is to start each server in a separate container, often
|
||||||
@@ -66,10 +66,10 @@ using Docker.
|
|||||||
|
|
||||||
When a user accesses JupyterHub, the following events take place:
|
When a user accesses JupyterHub, the following events take place:
|
||||||
|
|
||||||
- Login data is handed to the [Authenticator](./authenticators.html) instance for
|
- Login data is handed to the [Authenticator](./authenticators.md) instance for
|
||||||
validation
|
validation
|
||||||
- The Authenticator returns the username if the login information is valid
|
- The Authenticator returns the username if the login information is valid
|
||||||
- A single-user notebook server instance is [spawned](./spawners.html) for the
|
- A single-user notebook server instance is [spawned](./spawners.md) for the
|
||||||
logged-in user
|
logged-in user
|
||||||
- When the single-user notebook server starts, the proxy is notified to forward
|
- When the single-user notebook server starts, the proxy is notified to forward
|
||||||
requests to `/user/[username]/*` to the single-user notebook server.
|
requests to `/user/[username]/*` to the single-user notebook server.
|
||||||
@@ -111,7 +111,7 @@ working directory:
|
|||||||
This file needs to persist so that a **Hub** server restart will avoid
|
This file needs to persist so that a **Hub** server restart will avoid
|
||||||
invalidating cookies. Conversely, deleting this file and restarting the server
|
invalidating cookies. Conversely, deleting this file and restarting the server
|
||||||
effectively invalidates all login cookies. The cookie secret file is discussed
|
effectively invalidates all login cookies. The cookie secret file is discussed
|
||||||
in the [Cookie Secret section of the Security Settings document](../getting-started/security-basics.html).
|
in the [Cookie Secret section of the Security Settings document](../getting-started/security-basics.md).
|
||||||
|
|
||||||
The location of these files can be specified via configuration settings. It is
|
The location of these files can be specified via configuration settings. It is
|
||||||
recommended that these files be stored in standard UNIX filesystem locations,
|
recommended that these files be stored in standard UNIX filesystem locations,
|
||||||
@@ -122,9 +122,9 @@ all security and runtime files.
|
|||||||
|
|
||||||
There are two basic extension points for JupyterHub:
|
There are two basic extension points for JupyterHub:
|
||||||
|
|
||||||
- How users are authenticated by [Authenticators](./authenticators.html)
|
- How users are authenticated by [Authenticators](./authenticators.md)
|
||||||
- How user's single-user notebook server processes are started by
|
- How user's single-user notebook server processes are started by
|
||||||
[Spawners](./spawners.html)
|
[Spawners](./spawners.md)
|
||||||
|
|
||||||
Each is governed by a customizable class, and JupyterHub ships with basic
|
Each is governed by a customizable class, and JupyterHub ships with basic
|
||||||
defaults for each.
|
defaults for each.
|
||||||
|