Compare commits
776 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
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 | ||
![]() |
935baa8bc6 | ||
![]() |
9b77732319 | ||
![]() |
85aac0fa2d | ||
![]() |
abd6f35638 | ||
![]() |
ba4700b3f3 | ||
![]() |
05b11bd47a | ||
![]() |
71cb628563 | ||
![]() |
0d664355f0 | ||
![]() |
dd6261d031 | ||
![]() |
f3f5b69e49 | ||
![]() |
9ea4ca3646 | ||
![]() |
8ee9869ca0 | ||
![]() |
6cedd73d2a | ||
![]() |
59145ca0f7 | ||
![]() |
9607edcc23 | ||
![]() |
e082b923e0 | ||
![]() |
dd4df873b4 | ||
![]() |
ab02f9c568 | ||
![]() |
3adbfe315e | ||
![]() |
6000a84ffc | ||
![]() |
a2f003ed31 | ||
![]() |
7b6dd9f5cf | ||
![]() |
0fa5c20f89 | ||
![]() |
204399ee2c | ||
![]() |
5e68dce02f | ||
![]() |
952bbea039 | ||
![]() |
630e85bfec | ||
![]() |
26f7bb51bd | ||
![]() |
d429433bb2 | ||
![]() |
5de870be41 | ||
![]() |
1fc75086aa | ||
![]() |
fa3437c09a | ||
![]() |
01b27645fb | ||
![]() |
373c3f82dd | ||
![]() |
a1c2a50810 | ||
![]() |
5c39325104 | ||
![]() |
0304dd0040 | ||
![]() |
a549edfd75 | ||
![]() |
25e6b31a5f | ||
![]() |
3c21e7d45b | ||
![]() |
7c6972df7e | ||
![]() |
753bd0701f | ||
![]() |
c5faf2c5ea | ||
![]() |
c50cd1ba7f | ||
![]() |
a69e906c6e | ||
![]() |
f7f4759bde | ||
![]() |
906abcc2f3 | ||
![]() |
5269370e4a | ||
![]() |
897f5f62d5 | ||
![]() |
727356870a | ||
![]() |
39aed3a5a0 | ||
![]() |
ed26578717 | ||
![]() |
22863f765f | ||
![]() |
b500bd002b | ||
![]() |
aca40b24c3 | ||
![]() |
b5fe5a80c6 | ||
![]() |
ad073dd5dd | ||
![]() |
7b815558c6 | ||
![]() |
55f58b3ba7 | ||
![]() |
e1f93a4721 | ||
![]() |
2e95f3c039 | ||
![]() |
b0ba51f209 | ||
![]() |
89e6c2110e | ||
![]() |
7dfdc23b4e | ||
![]() |
4c7df53a8a | ||
![]() |
678afd3783 | ||
![]() |
0185a08f32 | ||
![]() |
f3787dd2c8 | ||
![]() |
30f19cfc8c | ||
![]() |
a84fa38c6b | ||
![]() |
867ce4c213 | ||
![]() |
005118e09d | ||
![]() |
04ce67ee71 | ||
![]() |
31807929cb | ||
![]() |
cb4105b53e | ||
![]() |
151887dd56 | ||
![]() |
5f97487184 | ||
![]() |
4d2d677777 | ||
![]() |
6a3b3807c9 | ||
![]() |
02a52a0289 | ||
![]() |
7bd1e387df | ||
![]() |
edc0d7901f | ||
![]() |
8e561f1c12 | ||
![]() |
24d87c882f | ||
![]() |
1e333e2f29 | ||
![]() |
a507fa1c8a | ||
![]() |
90cc03b3ec | ||
![]() |
6f15113e2a | ||
![]() |
f3f08c9caa | ||
![]() |
c495c4731a | ||
![]() |
e08a50ef66 | ||
![]() |
fbcd792062 | ||
![]() |
bb81ce0160 | ||
![]() |
315087d67c | ||
![]() |
31e6a15a85 | ||
![]() |
aed99d8d19 | ||
![]() |
ec83708892 | ||
![]() |
bedac5f148 | ||
![]() |
376aa13981 | ||
![]() |
4bc8b48763 | ||
![]() |
21496890f6 | ||
![]() |
70dcd50e44 | ||
![]() |
24094567e5 | ||
![]() |
6bd0febbe1 | ||
![]() |
57075aba52 | ||
![]() |
f0260aae52 | ||
![]() |
edd8e21f71 | ||
![]() |
681d3ce2d8 | ||
![]() |
97e792ccde | ||
![]() |
a5a0543b2a | ||
![]() |
5a810ccba3 | ||
![]() |
0a6b2cdadc | ||
![]() |
08903e7af8 | ||
![]() |
78439329c0 | ||
![]() |
4dfd6bc4b9 | ||
![]() |
574cc39b5f | ||
![]() |
6fb43a8241 | ||
![]() |
84c82fe382 | ||
![]() |
5e45e76f5b | ||
![]() |
92fd819cd6 | ||
![]() |
cb5ef0c302 | ||
![]() |
34fab033fe | ||
![]() |
37f4c4429e | ||
![]() |
293410ec94 | ||
![]() |
ed6ee27dcd | ||
![]() |
ca16ddb7ad | ||
![]() |
2102c1fd1c | ||
![]() |
aa9676ec5e | ||
![]() |
5e93c7de4c | ||
![]() |
d22626906b | ||
![]() |
5f91ed044e | ||
![]() |
5c3c7493c1 | ||
![]() |
1b7965092e | ||
![]() |
ef60be5a99 | ||
![]() |
f78d652cd6 | ||
![]() |
3650575797 | ||
![]() |
0f000f6d41 | ||
![]() |
643729ac0c | ||
![]() |
91a67bf580 | ||
![]() |
c75eddb730 | ||
![]() |
0f5888ad6c | ||
![]() |
8c48f3b856 | ||
![]() |
6e7e18bc3c | ||
![]() |
3dfd7e5a84 | ||
![]() |
19ecbf3734 | ||
![]() |
eac3e8ba90 | ||
![]() |
a7a6829b69 | ||
![]() |
61299113c8 | ||
![]() |
21a57dfa0b | ||
![]() |
a7226a8231 | ||
![]() |
6e3dd21f60 | ||
![]() |
cf049730d4 | ||
![]() |
cb9ce4d3af | ||
![]() |
925ee1dfb2 | ||
![]() |
5d9122b26c | ||
![]() |
6821ad0c59 | ||
![]() |
ff7851ee2e | ||
![]() |
6940ed85b1 | ||
![]() |
3d497a7f43 | ||
![]() |
cc6968e225 | ||
![]() |
a6c517c344 | ||
![]() |
a3e08b7f52 | ||
![]() |
14c8d7dc46 | ||
![]() |
ac2590c679 | ||
![]() |
ead13c6a11 | ||
![]() |
5002ab2990 | ||
![]() |
ab3e7293a4 | ||
![]() |
062af5e5cb | ||
![]() |
92088570ea | ||
![]() |
604ccf515d | ||
![]() |
ec9b244990 | ||
![]() |
09acdc23b5 | ||
![]() |
e7808b50af | ||
![]() |
9c27095744 | ||
![]() |
690b07982e | ||
![]() |
784e5aa4ee | ||
![]() |
29187cab3a | ||
![]() |
43a72807c6 | ||
![]() |
1d1f6f1870 | ||
![]() |
505a6eb4e3 | ||
![]() |
cc49df8147 | ||
![]() |
98d60402b5 | ||
![]() |
319e8a1062 | ||
![]() |
0c5d564830 | ||
![]() |
c0404cf9d9 | ||
![]() |
f364661363 | ||
![]() |
f92d77b06d | ||
![]() |
2cf00e6aae | ||
![]() |
dfdb0cff2b | ||
![]() |
d0dad84ffa | ||
![]() |
1745937f1a | ||
![]() |
e7eb674a89 | ||
![]() |
b232633100 | ||
![]() |
6abd19c149 | ||
![]() |
0aa0ff8db7 | ||
![]() |
a907429fd4 | ||
![]() |
598b550a67 | ||
![]() |
92bb442494 | ||
![]() |
2d41f6223e | ||
![]() |
791dd5fb9f | ||
![]() |
9a0ccf4c98 | ||
![]() |
ad2abc5771 | ||
![]() |
2d99b3943f | ||
![]() |
a358132f95 | ||
![]() |
09cd37feee | ||
![]() |
0f3610e81d | ||
![]() |
3f97c438e2 | ||
![]() |
42351201d2 | ||
![]() |
907bbb8e9d | ||
![]() |
63f3d8b621 | ||
![]() |
47d6e841fd | ||
![]() |
e3bb09fabe | ||
![]() |
d4e0c01189 | ||
![]() |
50370d42b0 | ||
![]() |
aa190a80b7 | ||
![]() |
e48bae77aa | ||
![]() |
96cf0f99ed | ||
![]() |
f380968049 | ||
![]() |
02468f4625 | ||
![]() |
24611f94cf | ||
![]() |
dc75a9a4b7 | ||
![]() |
33f459a23a | ||
![]() |
bdcc251002 | ||
![]() |
86052ba7b4 | ||
![]() |
62ebcf55c9 | ||
![]() |
80ac2475a0 | ||
![]() |
5179d922f5 | ||
![]() |
26f085a8ed | ||
![]() |
b7d302cc72 | ||
![]() |
f2941e3631 | ||
![]() |
26a6401af4 | ||
![]() |
5c8ce338a1 | ||
![]() |
5addc7bbaf | ||
![]() |
da095170bf | ||
![]() |
1aab0a69bd | ||
![]() |
fc8e04b62f | ||
![]() |
c6c53b4e10 | ||
![]() |
9b0219a2d8 | ||
![]() |
6e212fa476 | ||
![]() |
58f9237b12 | ||
![]() |
74fd925219 | ||
![]() |
2696bb97d2 | ||
![]() |
9cefb27704 | ||
![]() |
5e75357b06 | ||
![]() |
79bebb4bc9 | ||
![]() |
0ed88f212b | ||
![]() |
a8c1cab5fe | ||
![]() |
e1a6b1a70f | ||
![]() |
c95ed16786 | ||
![]() |
ec784803b4 | ||
![]() |
302d7a22d3 | ||
![]() |
7c6591aefe | ||
![]() |
58c91e3fd4 | ||
![]() |
db4cf7ae62 | ||
![]() |
bc86ee1c31 | ||
![]() |
a73e6f0bf8 |
@@ -1,4 +1,5 @@
|
||||
[run]
|
||||
parallel = True
|
||||
branch = False
|
||||
omit =
|
||||
jupyterhub/tests/*
|
||||
|
5
.flake8
@@ -10,13 +10,12 @@
|
||||
# E402: module level import not at top of file
|
||||
# I100: Import statements are in the wrong order
|
||||
# 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 =
|
||||
.cache,
|
||||
.github,
|
||||
docs,
|
||||
examples,
|
||||
jupyterhub/alembic*,
|
||||
onbuild,
|
||||
scripts,
|
||||
|
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
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.
|
7
.github/ISSUE_TEMPLATE/installation-and-configuration-issues.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
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.
|
0
.github/PULL_REQUEST_TEMPLATE/.keep
vendored
Normal file
29
.github/issue_template.md
vendored
@@ -1,29 +0,0 @@
|
||||
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.
|
||||
|
||||
- Where applicable, please fill out the details below to help us troubleshoot
|
||||
the issue that you are facing. Please be as thorough as you are able to
|
||||
provide details on the issue.
|
||||
|
||||
**How to reproduce the issue**
|
||||
|
||||
**What you expected to happen**
|
||||
|
||||
**What actually happens**
|
||||
|
||||
**Share what version of JupyterHub you are using**
|
||||
|
||||
Running `jupyter troubleshoot` from the command line, if possible, and posting
|
||||
its output would also be helpful.
|
||||
|
||||
```
|
||||
|
||||
Insert jupyter troubleshoot output here
|
||||
|
||||
|
||||
```
|
2
.gitignore
vendored
@@ -21,6 +21,8 @@ share/jupyterhub/static/css/style.min.css.map
|
||||
*.egg-info
|
||||
MANIFEST
|
||||
.coverage
|
||||
.coverage.*
|
||||
htmlcov
|
||||
.idea/
|
||||
.pytest_cache
|
||||
pip-wheel-metadata
|
||||
|
20
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
repos:
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v1.3.5
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
language_version: python3.6
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 18.9b0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.1.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: check-json
|
||||
- id: check-yaml
|
||||
- id: check-case-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: requirements-txt-fixer
|
||||
- id: flake8
|
46
.travis.yml
@@ -17,6 +17,7 @@ services:
|
||||
|
||||
# installing dependencies
|
||||
before_install:
|
||||
- set -e
|
||||
- nvm install 6; nvm use 6
|
||||
- npm install
|
||||
- npm install -g configurable-http-proxy
|
||||
@@ -26,34 +27,59 @@ before_install:
|
||||
unset MYSQL_UNIX_PORT
|
||||
DB=mysql bash ci/docker-db.sh
|
||||
DB=mysql bash ci/init-db.sh
|
||||
pip install 'mysql-connector<2.2'
|
||||
pip install 'mysql-connector-python'
|
||||
elif [[ $JUPYTERHUB_TEST_DB_URL == postgresql* ]]; then
|
||||
DB=postgres bash ci/init-db.sh
|
||||
pip install psycopg2
|
||||
pip install psycopg2-binary
|
||||
fi
|
||||
install:
|
||||
- pip install --upgrade pip
|
||||
- pip install --pre -r dev-requirements.txt .
|
||||
- pip install --upgrade --pre -r dev-requirements.txt .
|
||||
- pip freeze
|
||||
|
||||
# running tests
|
||||
script:
|
||||
- |
|
||||
# run tests
|
||||
set -e
|
||||
pytest -v --maxfail=2 --cov=jupyterhub jupyterhub/tests
|
||||
if [[ -z "$TEST" ]]; then
|
||||
pytest -v --maxfail=2 --cov=jupyterhub jupyterhub/tests
|
||||
fi
|
||||
- |
|
||||
# run autoformat
|
||||
if [[ "$TEST" == "lint" ]]; then
|
||||
pre-commit run --all-files
|
||||
fi
|
||||
- |
|
||||
# build docs
|
||||
pushd docs
|
||||
pip install -r requirements.txt
|
||||
make html
|
||||
popd
|
||||
if [[ "$TEST" == "docs" ]]; then
|
||||
pushd docs
|
||||
pip install --upgrade -r requirements.txt
|
||||
pip install --upgrade alabaster_jupyterhub
|
||||
make html
|
||||
popd
|
||||
fi
|
||||
after_success:
|
||||
- codecov
|
||||
after_failure:
|
||||
- |
|
||||
# point to auto-lint-fix
|
||||
if [[ "$TEST" == "lint" ]]; then
|
||||
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"
|
||||
fi
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: 3.6
|
||||
env: TEST=lint
|
||||
- python: 3.6
|
||||
env: TEST=docs
|
||||
- python: 3.6
|
||||
env: JUPYTERHUB_TEST_SUBDOMAIN_HOST=http://localhost.jovyan.org:8000
|
||||
- python: 3.6
|
||||
@@ -62,5 +88,7 @@ matrix:
|
||||
- python: 3.6
|
||||
env:
|
||||
- JUPYTERHUB_TEST_DB_URL=postgresql://postgres@127.0.0.1/jupyterhub
|
||||
- python: 3.7
|
||||
dist: xenial
|
||||
allow_failures:
|
||||
- python: nightly
|
||||
|
1
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1 @@
|
||||
Please refer to [Project Jupyter's Code of Conduct](https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md).
|
154
CONTRIBUTING.md
@@ -1,98 +1,102 @@
|
||||
# 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/contributor/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.
|
||||
|
||||
## Setting up a development environment
|
||||
|
||||
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).
|
||||
|
||||
|
||||
## Set up your development system
|
||||
1. clone the repo
|
||||
```bash
|
||||
git clone https://github.com/jupyterhub/jupyterhub
|
||||
```
|
||||
2. do a development install with pip
|
||||
|
||||
For a development install, clone the [repository](https://github.com/jupyterhub/jupyterhub)
|
||||
and then install from source:
|
||||
```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
|
||||
git clone https://github.com/jupyterhub/jupyterhub
|
||||
cd jupyterhub
|
||||
npm install -g configurable-http-proxy
|
||||
pip3 install -r dev-requirements.txt -e .
|
||||
pre-commit run
|
||||
```
|
||||
|
||||
### 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/ambv/black#editor-integration)
|
||||
into your text editor to format code automatically.
|
||||
|
||||
If the `pip3 install` command fails and complains about `lessc` being
|
||||
unavailable, you may need to explicitly install some additional JavaScript
|
||||
dependencies:
|
||||
If you have already committed files before setting up the pre-commit
|
||||
hook with `pre-commit install`, you can fix everything up using
|
||||
`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
|
||||
updates, with:
|
||||
You can run the tests with:
|
||||
|
||||
```bash
|
||||
python3 setup.py js # fetch updated client-side js
|
||||
python3 setup.py css # recompile CSS from LESS sources
|
||||
pytest -v
|
||||
```
|
||||
|
||||
## Running the test suite
|
||||
|
||||
We use [pytest](http://doc.pytest.org/en/latest/) for running tests.
|
||||
|
||||
1. Set up a development install as described above.
|
||||
|
||||
2. Set environment variable for `ASYNC_TEST_TIMEOUT` to 15 seconds:
|
||||
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)
|
||||
for how pytest can be called.
|
||||
For instance, to test only spawner-related things in the REST API:
|
||||
|
||||
```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
|
||||
pytest -v jupyterhub/tests
|
||||
```
|
||||
and other collections of tests for different components.
|
||||
When writing a new test, there should usually be a test of
|
||||
similar functionality already written and related tests should
|
||||
be added nearby.
|
||||
When in doubt, feel free to ask.
|
||||
|
||||
To run an individual test file (i.e. `test_api.py`):
|
||||
|
||||
```bash
|
||||
pytest -v jupyterhub/tests/test_api.py
|
||||
```
|
||||
|
||||
### Troubleshooting tests
|
||||
|
||||
If you see test failures because of timeouts, you may wish to increase the
|
||||
`ASYNC_TEST_TIMEOUT` used by the
|
||||
[pytest-tornado-plugin](https://github.com/eugeniy/pytest-tornado/blob/c79f68de2222eb7cf84edcfe28650ebf309a4d0c/README.rst#markers)
|
||||
from the default of 5 seconds:
|
||||
|
||||
```bash
|
||||
export ASYNC_TEST_TIMEOUT=15
|
||||
```
|
||||
|
||||
If you see many test errors and failures, double check that you have installed
|
||||
`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
|
||||
```
|
||||
TODO: describe some details about fixtures, etc.
|
||||
|
@@ -35,8 +35,8 @@ RUN apt-get -y update && \
|
||||
ENV LANG C.UTF-8
|
||||
|
||||
# install Python + NodeJS with conda
|
||||
RUN wget -q https://repo.continuum.io/miniconda/Miniconda3-4.4.10-Linux-x86_64.sh -O /tmp/miniconda.sh && \
|
||||
echo 'bec6203dbb2f53011e974e9bf4d46e93 */tmp/miniconda.sh' | md5sum -c - && \
|
||||
RUN wget -q https://repo.continuum.io/miniconda/Miniconda3-4.5.11-Linux-x86_64.sh -O /tmp/miniconda.sh && \
|
||||
echo 'e1045ee415162f944b6aebfe560b8fee */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 \
|
||||
|
0
PULL_REQUEST_TEMPLATE.md
Normal file
61
README.md
@@ -11,12 +11,13 @@
|
||||
|
||||
|
||||
[](https://pypi.python.org/pypi/jupyterhub)
|
||||
[](http://jupyterhub.readthedocs.org/en/latest/?badge=latest)
|
||||
[](http://jupyterhub.readthedocs.io/en/0.7.2/?badge=0.7.2)
|
||||
[](https://jupyterhub.readthedocs.org/en/latest/?badge=latest)
|
||||
[](https://travis-ci.org/jupyterhub/jupyterhub)
|
||||
[](https://circleci.com/gh/jupyterhub/jupyterhub)
|
||||
[](https://codecov.io/github/jupyterhub/jupyterhub?branch=master)
|
||||
[](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
|
||||
**multi-user Hub** which spawns, manages, and proxies multiple instances of the
|
||||
@@ -50,37 +51,62 @@ for administration of the Hub and its users.
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
### Check prerequisites
|
||||
|
||||
A Linux/Unix based system with the following:
|
||||
- A Linux/Unix based system
|
||||
- [Python](https://www.python.org/downloads/) 3.5 or greater
|
||||
- [nodejs/npm](https://www.npmjs.com/)
|
||||
|
||||
- [Python](https://www.python.org/downloads/) 3.4 or greater
|
||||
- [nodejs/npm](https://www.npmjs.com/) Install a recent version of
|
||||
[nodejs/npm](https://docs.npmjs.com/getting-started/installing-node)
|
||||
For example, install it on Linux (Debian/Ubuntu) using:
|
||||
* If you are using **`conda`**, the nodejs and npm dependencies will be installed for
|
||||
you by conda.
|
||||
|
||||
sudo apt-get install npm nodejs-legacy
|
||||
* If you are using **`pip`**, install a recent version of
|
||||
[nodejs/npm](https://docs.npmjs.com/getting-started/installing-node).
|
||||
For example, install it on Linux (Debian/Ubuntu) using:
|
||||
|
||||
The `nodejs-legacy` package installs the `node` executable and is currently
|
||||
required for npm to work on Debian/Ubuntu.
|
||||
```
|
||||
sudo apt-get install npm nodejs-legacy
|
||||
```
|
||||
|
||||
The `nodejs-legacy` package installs the `node` executable and is currently
|
||||
required for npm to work on Debian/Ubuntu.
|
||||
|
||||
- TLS certificate and key for HTTPS communication
|
||||
- Domain name
|
||||
|
||||
### Install packages
|
||||
|
||||
#### Using `conda`
|
||||
|
||||
To install JupyterHub along with its dependencies including nodejs/npm:
|
||||
|
||||
```bash
|
||||
conda install -c conda-forge jupyterhub
|
||||
```
|
||||
|
||||
If you plan to run notebook servers locally, install the Jupyter notebook
|
||||
or JupyterLab:
|
||||
|
||||
```bash
|
||||
conda install notebook
|
||||
conda install jupyterlab
|
||||
```
|
||||
|
||||
#### Using `pip`
|
||||
|
||||
JupyterHub can be installed with `pip`, and the proxy with `npm`:
|
||||
|
||||
```bash
|
||||
npm install -g configurable-http-proxy
|
||||
pip3 install jupyterhub
|
||||
python3 -m pip install jupyterhub
|
||||
```
|
||||
|
||||
If you plan to run notebook servers locally, you will need to install the
|
||||
[Jupyter notebook](https://jupyter.readthedocs.io/en/latest/install.html)
|
||||
package:
|
||||
|
||||
pip3 install --upgrade notebook
|
||||
python3 -m pip install --upgrade notebook
|
||||
|
||||
### Run the Hub server
|
||||
|
||||
@@ -99,7 +125,7 @@ more configuration of the system.
|
||||
|
||||
## Configuration
|
||||
|
||||
The [Getting Started](http://jupyterhub.readthedocs.io/en/latest/getting-started/index.html) section of the
|
||||
The [Getting Started](https://jupyterhub.readthedocs.io/en/latest/getting-started/index.html) section of the
|
||||
documentation explains the common steps in setting up JupyterHub.
|
||||
|
||||
The [**JupyterHub tutorial**](https://github.com/jupyterhub/jupyterhub-tutorial)
|
||||
@@ -179,6 +205,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,
|
||||
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
|
||||
|
||||
JupyterHub is supported on Linux/Unix based systems.
|
||||
@@ -208,11 +237,13 @@ our JupyterHub [Gitter](https://gitter.im/jupyterhub/jupyterhub) channel.
|
||||
|
||||
- [Reporting Issues](https://github.com/jupyterhub/jupyterhub/issues)
|
||||
- [JupyterHub tutorial](https://github.com/jupyterhub/jupyterhub-tutorial)
|
||||
- [Documentation for JupyterHub](http://jupyterhub.readthedocs.io/en/latest/) | [PDF (latest)](https://media.readthedocs.org/pdf/jupyterhub/latest/jupyterhub.pdf) | [PDF (stable)](https://media.readthedocs.org/pdf/jupyterhub/stable/jupyterhub.pdf)
|
||||
- [Documentation for JupyterHub](https://jupyterhub.readthedocs.io/en/latest/) | [PDF (latest)](https://media.readthedocs.org/pdf/jupyterhub/latest/jupyterhub.pdf) | [PDF (stable)](https://media.readthedocs.org/pdf/jupyterhub/stable/jupyterhub.pdf)
|
||||
- [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)
|
||||
- [Project Jupyter website](https://jupyter.org)
|
||||
|
||||
JupyterHub follows the Jupyter [Community Guides](https://jupyter.readthedocs.io/en/latest/community/content-community.html).
|
||||
|
||||
---
|
||||
|
||||
**[Technical Overview](#technical-overview)** |
|
||||
|
@@ -1,19 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
"""
|
||||
bower-lite
|
||||
|
||||
Since Bower's on its way out,
|
||||
stage frontend dependencies from node_modules into components
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from os.path import join
|
||||
import shutil
|
||||
from os.path import join
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
@@ -8,7 +8,7 @@ export MYSQL_HOST=127.0.0.1
|
||||
export MYSQL_TCP_PORT=${MYSQL_TCP_PORT:-13306}
|
||||
export PGHOST=127.0.0.1
|
||||
NAME="hub-test-$DB"
|
||||
DOCKER_RUN="docker run --rm -d --name $NAME"
|
||||
DOCKER_RUN="docker run -d --name $NAME"
|
||||
|
||||
docker rm -f "$NAME" 2>/dev/null || true
|
||||
|
||||
@@ -47,4 +47,4 @@ Set these environment variables:
|
||||
export MYSQL_HOST=127.0.0.1
|
||||
export MYSQL_TCP_PORT=$MYSQL_TCP_PORT
|
||||
export PGHOST=127.0.0.1
|
||||
"
|
||||
"
|
||||
|
@@ -21,7 +21,7 @@ esac
|
||||
|
||||
set -x
|
||||
|
||||
for SUFFIX in '' _upgrade_072 _upgrade_081; do
|
||||
for SUFFIX in '' _upgrade_072 _upgrade_081 _upgrade_094; do
|
||||
$SQL "DROP DATABASE jupyterhub${SUFFIX};" 2>/dev/null || true
|
||||
$SQL "CREATE DATABASE jupyterhub${SUFFIX} ${EXTRA_CREATE};"
|
||||
done
|
||||
|
@@ -1,10 +1,17 @@
|
||||
-r requirements.txt
|
||||
mock
|
||||
# temporary pin of attrs for jsonschema 0.3.0a1
|
||||
# seems to be a pip bug
|
||||
attrs>=17.4.0
|
||||
beautifulsoup4
|
||||
codecov
|
||||
coverage
|
||||
cryptography
|
||||
pytest-cov
|
||||
pytest-tornado
|
||||
pytest>=3.3
|
||||
html5lib # needed for beautifulsoup
|
||||
mock
|
||||
notebook
|
||||
pre-commit
|
||||
pytest-asyncio
|
||||
pytest-cov
|
||||
pytest>=3.3
|
||||
requests-mock
|
||||
virtualenv
|
||||
|
@@ -7,5 +7,3 @@ ENV LANG=en_US.UTF-8
|
||||
|
||||
USER nobody
|
||||
CMD ["jupyterhub"]
|
||||
|
||||
|
||||
|
@@ -18,4 +18,3 @@ Dockerfile.alpine contains base image for jupyterhub. It does not work independ
|
||||
* Use dummy authenticator for ease of testing. Update following in jupyterhub_config file
|
||||
- c.JupyterHub.authenticator_class = 'dummyauthenticator.DummyAuthenticator'
|
||||
- c.DummyAuthenticator.password = "your strong password"
|
||||
|
||||
|
@@ -1,3 +1,5 @@
|
||||
# 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
|
||||
@@ -13,5 +15,11 @@ dependencies:
|
||||
- traitlets>=4.1
|
||||
- sphinx>=1.7
|
||||
- pip:
|
||||
- python-oauth2
|
||||
- recommonmark==0.4.0
|
||||
- entrypoints
|
||||
- oauthlib>=2.0
|
||||
- recommonmark==0.5.0
|
||||
- async_generator
|
||||
- prometheus_client
|
||||
- attrs>=17.4.0
|
||||
- sphinx-copybutton
|
||||
- alabaster_jupyterhub
|
||||
|
@@ -1,3 +1,7 @@
|
||||
# ReadTheDocs uses the `environment.yaml` so make sure to update that as well
|
||||
# if you change this file
|
||||
-r ../requirements.txt
|
||||
alabaster_jupyterhub
|
||||
recommonmark==0.5.0
|
||||
sphinx-copybutton
|
||||
sphinx>=1.7
|
||||
recommonmark==0.4.0
|
||||
|
@@ -89,7 +89,7 @@ paths:
|
||||
post:
|
||||
summary: Create multiple users
|
||||
parameters:
|
||||
- name: data
|
||||
- name: body
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
@@ -147,7 +147,7 @@ paths:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- name: data
|
||||
- name: body
|
||||
in: body
|
||||
required: true
|
||||
description: Updated user info. At least one key to be updated (name or admin) is required.
|
||||
@@ -176,6 +176,60 @@ paths:
|
||||
responses:
|
||||
'204':
|
||||
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
|
||||
- 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.
|
||||
required: false
|
||||
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 ('').
|
||||
required: false
|
||||
type: object
|
||||
properties:
|
||||
'<server name>':
|
||||
description: |
|
||||
Activity for a single server.
|
||||
type: object
|
||||
properties:
|
||||
last_activity:
|
||||
required: true
|
||||
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'
|
||||
|
||||
/users/{name}/server:
|
||||
post:
|
||||
summary: Start a user's single-user notebook server
|
||||
@@ -185,6 +239,15 @@ paths:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- 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
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
description: The user's notebook server has started
|
||||
@@ -217,6 +280,15 @@ paths:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- 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
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
description: The user's notebook named-server has started
|
||||
@@ -235,6 +307,13 @@ paths:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- name: remove
|
||||
description: |
|
||||
Whether to fully remove the server, rather than just stop it.
|
||||
Removing a server deletes things like the state of the stopped server.
|
||||
in: body
|
||||
required: false
|
||||
type: boolean
|
||||
responses:
|
||||
'204':
|
||||
description: The user's notebook named-server has stopped
|
||||
@@ -252,6 +331,17 @@ paths:
|
||||
$ref: '#/definitions/Token'
|
||||
post:
|
||||
summary: Create a new token for the user
|
||||
parameters:
|
||||
- name: expires_in
|
||||
type: number
|
||||
required: false
|
||||
in: body
|
||||
description: lifetime (in seconds) after which the requested token will expire.
|
||||
- name: note
|
||||
type: string
|
||||
required: false
|
||||
in: body
|
||||
description: A note attached to the token for future bookkeeping
|
||||
responses:
|
||||
'201':
|
||||
description: The newly created token
|
||||
@@ -334,7 +424,7 @@ paths:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- name: data
|
||||
- name: body
|
||||
in: body
|
||||
required: true
|
||||
description: The users to add to the group
|
||||
@@ -359,7 +449,7 @@ paths:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- name: data
|
||||
- name: body
|
||||
in: body
|
||||
required: true
|
||||
description: The users to remove from the group
|
||||
@@ -417,7 +507,7 @@ paths:
|
||||
summary: Notify the Hub about a new proxy
|
||||
description: Notifies the Hub of a new proxy to use.
|
||||
parameters:
|
||||
- name: data
|
||||
- name: body
|
||||
in: body
|
||||
required: true
|
||||
description: Any values that have changed for the new proxy. All keys are optional.
|
||||
@@ -689,6 +779,11 @@ definitions:
|
||||
description: The command used to start the service (if managed)
|
||||
items:
|
||||
type: string
|
||||
info:
|
||||
type: object
|
||||
description: |
|
||||
Additional information a deployment can attach to a service.
|
||||
JupyterHub does not use this field.
|
||||
Token:
|
||||
type: object
|
||||
properties:
|
||||
@@ -711,6 +806,10 @@ definitions:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Timestamp when this token was created
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Timestamp when this token expires. Null if there is no expiry.
|
||||
last_activity:
|
||||
type: string
|
||||
format: date-time
|
||||
|
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
|
||||
|
||||
|
@@ -26,3 +26,7 @@ Module: :mod:`jupyterhub.auth`
|
||||
|
||||
.. autoconfigurable:: PAMAuthenticator
|
||||
|
||||
:class:`DummyAuthenticator`
|
||||
---------------------------
|
||||
|
||||
.. autoconfigurable:: DummyAuthenticator
|
||||
|
@@ -20,4 +20,3 @@ Module: :mod:`jupyterhub.proxy`
|
||||
|
||||
.. autoconfigurable:: ConfigurableHTTPProxy
|
||||
:members: debug, auth_token, check_running_interval, api_url, command
|
||||
|
||||
|
@@ -14,4 +14,3 @@ Module: :mod:`jupyterhub.services.service`
|
||||
|
||||
.. autoconfigurable:: Service
|
||||
: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
|
||||
|
||||
|
@@ -13,10 +13,9 @@ Module: :mod:`jupyterhub.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`
|
||||
----------------------------
|
||||
|
||||
.. autoconfigurable:: LocalProcessSpawner
|
||||
|
||||
|
@@ -34,4 +34,3 @@ Module: :mod:`jupyterhub.user`
|
||||
.. attribute:: spawner
|
||||
|
||||
The user's :class:`~.Spawner` instance.
|
||||
|
||||
|
@@ -7,9 +7,206 @@ command line for details.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## 1.0
|
||||
|
||||
### [1.0.0] 2019-04-XX
|
||||
|
||||
JupyterHub 1.0 is a major milestone for JupyterHub.
|
||||
Huge thanks to the many people who have contributed to this release,
|
||||
whether it was through discussion, testing, documentation, or development.
|
||||
|
||||
#### Major new features
|
||||
|
||||
- Support TLS encryption and authentication of all internal communication.
|
||||
Spawners must implement `.move_certs` method to make certificates available
|
||||
to the notebook server if it is not local to the Hub.
|
||||
- There is now full UI support for managing named servers.
|
||||
With named servers, each jupyterhub user may have access to more than one named server. For example, a professor may access a server named `research` and another named `teaching`.
|
||||
|
||||

|
||||
- Authenticators can now expire and refresh authentication data by implementing
|
||||
`Authenticator.refresh_user(user)`.
|
||||
This allows things like OAuth data and access tokens to be refreshed.
|
||||
When used together with `Authenticator.refresh_pre_spawn = True`,
|
||||
auth refresh can be forced prior to Spawn,
|
||||
allowing the Authenticator to *require* that authentication data is fresh
|
||||
immediately before the user's server is launched.
|
||||
|
||||
```eval_rst
|
||||
.. seealso::
|
||||
|
||||
- :meth:`.Authenticator.refresh_user`
|
||||
- :meth:`.Spawner.create_certs`
|
||||
- :meth:`.Spawner.move_certs`
|
||||
```
|
||||
|
||||
#### New features
|
||||
|
||||
- allow custom spawners, authenticators, and proxies to register themselves via 'entry points', enabling more convenient configuration such as:
|
||||
|
||||
```python
|
||||
c.JupyterHub.authenticator_class = 'github'
|
||||
c.JupyterHub.spawner_class = 'docker'
|
||||
c.JupyterHub.proxy_class = 'traefik_etcd'
|
||||
```
|
||||
- Spawners are passed the tornado Handler object that requested their spawn (as `self.handler`),
|
||||
so they can do things like make decisions based on query arguments in the request.
|
||||
- SimpleSpawner and DummyAuthenticator, which are useful for testing, have been merged into JupyterHub itself:
|
||||
|
||||
```python
|
||||
# For testing purposes only. Should not be used in production.
|
||||
c.JupyterHub.authenticator_class = 'dummy'
|
||||
c.JupyterHub.spawner_class = 'simple'
|
||||
```
|
||||
|
||||
These classes are **not** appropriate for production use. Only testing.
|
||||
- Add health check endpoint at `/hub/health`
|
||||
- Several prometheus metrics have been added (thanks to [Outreachy](https://www.outreachy.org/) applicants!)
|
||||
- A new API for registering user activity.
|
||||
To prepare for the addition of [alternate proxy implementations](https://github.com/jupyterhub/traefik-proxy),
|
||||
responsibility for tracking activity is taken away from the proxy
|
||||
and moved to the notebook server (which already has activity tracking features).
|
||||
Activity is now tracked by pushing it to the Hub from user servers instead of polling the
|
||||
proxy API.
|
||||
- Dynamic `options_form` callables may now return an empty string
|
||||
which will result in no options form being rendered.
|
||||
- `Spawner.user_options` is persisted to the database to be re-used,
|
||||
so that a server spawned once via the form can be re-spawned via the API
|
||||
with the same options.
|
||||
- Added `c.PAMAuthenticator.pam_normalize_username` option for round-tripping
|
||||
usernames through PAM to retrieve the normalized form.
|
||||
- Added `c.JupyterHub.named_server_limit_per_user` configuration to limit
|
||||
the number of named servers each user can have.
|
||||
The default is 0, for no limit.
|
||||
- API requests to HubAuthenticated services (e.g. single-user servers)
|
||||
may pass a token in the `Authorization` header,
|
||||
matching authentication with the Hub API itself.
|
||||
- Added `Authenticator.is_admin(handler, authentication)` method
|
||||
and `Authenticator.admin_groups` configuration for automatically
|
||||
determining that a member of a group should be considered an admin.
|
||||
- New `c.Authenticator.post_auth_hook` configuration
|
||||
that can be any callable of the form `async def hook(authenticator, handler, authentication=None):`.
|
||||
This hook may transform the return value of `Authenticator.authenticate()`
|
||||
and return a new authentication dictionary,
|
||||
e.g. specifying admin privileges, group membership,
|
||||
or custom white/blacklisting logic.
|
||||
This hook is called *after* existing normalization and whitelist checking.
|
||||
- `Spawner.options_from_form` may now be async
|
||||
- Added `JupyterHub.shutdown_on_logout` option to trigger shutdown of a user's
|
||||
servers when they log out.
|
||||
- When `Spawner.start` raises an Exception,
|
||||
a message can be passed on to the user if the exception has a `.jupyterhub_message` attribute.
|
||||
|
||||
|
||||
#### Changes
|
||||
|
||||
- Authentication methods such as `check_whitelist` should now take an additional
|
||||
`authentication` argument
|
||||
that will be a dictionary (default: None) of authentication data,
|
||||
as returned by `Authenticator.authenticate()`:
|
||||
|
||||
```python
|
||||
def check_whitelist(self, username, authentication=None):
|
||||
...
|
||||
```
|
||||
|
||||
`authentication` should have a default value of None
|
||||
for backward-compatibility with jupyterhub < 1.0.
|
||||
- Prometheus metrics page is now authenticated.
|
||||
Any authenticated user may see the prometheus metrics.
|
||||
To disable prometheus authentication,
|
||||
set `JupyterHub.authenticate_prometheus = False`.
|
||||
- Visits to `/user/:name` no longer trigger an implicit launch of the user's server.
|
||||
Instead, a page is shown indicating that the server is not running
|
||||
with a link to request the spawn.
|
||||
- API requests to `/user/:name` for a not-running server will have status 503 instead of 404.
|
||||
- OAuth includes a confirmation page when attempting to visit another user's server,
|
||||
so that users can choose to cancel authentication with the single-user server.
|
||||
Confirmation is still skipped when accessing your own server.
|
||||
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Various fixes to improve Windows compatibility
|
||||
(default Authenticator and Spawner still do not support Windows, but other Spawners may)
|
||||
- Fixed compatibility with Oracle db
|
||||
- Fewer redirects following a visit to the default `/` url
|
||||
- Error when progress is requested before progress is ready
|
||||
- Error when API requests are made to a not-running server without authentication
|
||||
- Avoid logging database password on connect if password is specified in `JupyterHub.db_url`.
|
||||
|
||||
#### Development changes
|
||||
|
||||
There have been several changes to the development process that shouldn't
|
||||
generally affect users of JupyterHub, but may affect contributors.
|
||||
In general, see `CONTRIBUTING.md` for contribution info or ask if you have questions.
|
||||
|
||||
- JupyterHub has adopted `black` as a code autoformatter and `pre-commit`
|
||||
as a tool for automatically running code formatting on commit.
|
||||
This is meant to make it *easier* to contribute to JupyterHub,
|
||||
so let us know if it's having the opposite effect.
|
||||
- JupyterHub has switched its test suite to using `pytest-asyncio` from `pytest-tornado`.
|
||||
- OAuth is now implemented internally using `oauthlib` instead of `python-oauth2`. This should have no effect on behavior.
|
||||
|
||||
|
||||
## 0.9
|
||||
|
||||
### 0.9.0
|
||||
### [0.9.6] 2019-04-01
|
||||
|
||||
JupyterHub 0.9.6 is a security release.
|
||||
|
||||
- Fixes an Open Redirect vulnerability (CVE-2019-10255).
|
||||
|
||||
JupyterHub 0.9.5 included a partial fix for this issue.
|
||||
|
||||
### [0.9.4] 2018-09-24
|
||||
|
||||
JupyterHub 0.9.4 is a small bugfix release.
|
||||
|
||||
- Fixes an issue that required all running user servers to be restarted
|
||||
when performing an upgrade from 0.8 to 0.9.
|
||||
- Fixes content-type for API endpoints back to `application/json`.
|
||||
It was `text/html` in 0.9.0-0.9.3.
|
||||
|
||||
### [0.9.3] 2018-09-12
|
||||
|
||||
JupyterHub 0.9.3 contains small bugfixes and improvements
|
||||
|
||||
- Fix token page and model handling of `expires_at`.
|
||||
This field was missing from the REST API model for tokens
|
||||
and could cause the token page to not render
|
||||
- Add keep-alive to progress event stream to avoid proxies dropping
|
||||
the connection due to inactivity
|
||||
- Documentation and example improvements
|
||||
- Disable quit button when using notebook 5.6
|
||||
- Prototype new feature (may change prior to 1.0):
|
||||
pass requesting Handler to Spawners during start,
|
||||
accessible as `self.handler`
|
||||
|
||||
### [0.9.2] 2018-08-10
|
||||
|
||||
JupyterHub 0.9.2 contains small bugfixes and improvements.
|
||||
|
||||
- Documentation and example improvements
|
||||
- Add `Spawner.consecutive_failure_limit` config for aborting the Hub if too many spawns fail in a row.
|
||||
- Fix for handling SIGTERM when run with asyncio (tornado 5)
|
||||
- Windows compatibility fixes
|
||||
|
||||
|
||||
### [0.9.1] 2018-07-04
|
||||
|
||||
JupyterHub 0.9.1 contains a number of small bugfixes on top of 0.9.
|
||||
|
||||
- Use a PID file for the proxy to decrease the likelihood that a leftover proxy process will prevent JupyterHub from restarting
|
||||
- `c.LocalProcessSpawner.shell_cmd` is now configurable
|
||||
- API requests to stopped servers (requests to the hub for `/user/:name/api/...`) fail with 404 rather than triggering a restart of the server
|
||||
- Compatibility fix for notebook 5.6.0 which will introduce further
|
||||
security checks for local connections
|
||||
- Managed services always use localhost to talk to the Hub if the Hub listening on all interfaces
|
||||
- When using a URL prefix, the Hub route will be `JupyterHub.base_url` instead of unconditionally `/`
|
||||
- additional fixes and improvements
|
||||
|
||||
### [0.9.0] 2018-06-15
|
||||
|
||||
JupyterHub 0.9 is a major upgrade of JupyterHub.
|
||||
There are several changes to the database schema,
|
||||
@@ -51,7 +248,7 @@ and tornado < 5.0.
|
||||
Sets ip, port, base_url all at once.
|
||||
- Add `JupyterHub.hub_bind_url` for setting the full host+port of the Hub.
|
||||
`hub_bind_url` supports unix domain sockets, e.g.
|
||||
`unix+http://%2Fsrv%2Fjupytrehub.sock`
|
||||
`unix+http://%2Fsrv%2Fjupyterhub.sock`
|
||||
- Deprecate `JupyterHub.hub_connect_port` config in favor of `JupyterHub.hub_connect_url`. `hub_connect_ip` is not deprecated
|
||||
and can still be used in the common case where only the ip address of the hub differs from the bind ip.
|
||||
|
||||
@@ -93,6 +290,12 @@ and tornado < 5.0.
|
||||
- Add session-id cookie, enabling immediate revocation of login tokens.
|
||||
- Authenticators may specify that users are admins by specifying the `admin` key when return the user model as a dict.
|
||||
- Added "Start All" button to admin page for launching all user servers at once.
|
||||
- Services have an `info` field which is a dictionary.
|
||||
This is accessible via the REST API.
|
||||
- `JupyterHub.extra_handlers` allows defining additional tornado RequestHandlers attached to the Hub.
|
||||
- API tokens may now expire.
|
||||
Expiry is available in the REST model as `expires_at`,
|
||||
and settable when creating API tokens by specifying `expires_in`.
|
||||
|
||||
|
||||
#### Fixed
|
||||
@@ -113,6 +316,11 @@ and tornado < 5.0.
|
||||
- Various fixes in race conditions and performance improvements with the default proxy.
|
||||
- Fixes for CORS headers
|
||||
- Stop setting `.form-control` on spawner form inputs unconditionally.
|
||||
- Better recovery from database errors and database connection issues
|
||||
without having to restart the Hub.
|
||||
- Fix handling of `~` character in usernames.
|
||||
- Fix jupyterhub startup when `getpass.getuser()` would fail,
|
||||
e.g. due to missing entry in passwd file in containers.
|
||||
|
||||
|
||||
## 0.8
|
||||
@@ -368,7 +576,14 @@ Fix removal of `/login` page in 0.4.0, breaking some OAuth providers.
|
||||
First preview release
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/jupyterhub/jupyterhub/compare/0.8.1...HEAD
|
||||
[Unreleased]: https://github.com/jupyterhub/jupyterhub/compare/1.0.0...HEAD
|
||||
[1.0.0]: https://github.com/jupyterhub/jupyterhub/compare/0.9.5...HEAD
|
||||
[0.9.6]: https://github.com/jupyterhub/jupyterhub/compare/0.9.4...0.9.6
|
||||
[0.9.4]: https://github.com/jupyterhub/jupyterhub/compare/0.9.3...0.9.4
|
||||
[0.9.3]: https://github.com/jupyterhub/jupyterhub/compare/0.9.2...0.9.3
|
||||
[0.9.2]: https://github.com/jupyterhub/jupyterhub/compare/0.9.1...0.9.2
|
||||
[0.9.1]: https://github.com/jupyterhub/jupyterhub/compare/0.9.0...0.9.1
|
||||
[0.9.0]: https://github.com/jupyterhub/jupyterhub/compare/0.8.1...0.9.0
|
||||
[0.8.1]: https://github.com/jupyterhub/jupyterhub/compare/0.8.0...0.8.1
|
||||
[0.8.0]: https://github.com/jupyterhub/jupyterhub/compare/0.7.2...0.8.0
|
||||
[0.7.2]: https://github.com/jupyterhub/jupyterhub/compare/0.7.1...0.7.2
|
||||
|
@@ -1,11 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import sys
|
||||
import os
|
||||
import shlex
|
||||
|
||||
# For conversion from markdown to html
|
||||
import recommonmark.parser
|
||||
import sys
|
||||
|
||||
# Set paths
|
||||
sys.path.insert(0, os.path.abspath('.'))
|
||||
@@ -21,6 +18,7 @@ extensions = [
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.napoleon',
|
||||
'autodoc_traits',
|
||||
'sphinx_copybutton',
|
||||
]
|
||||
|
||||
templates_path = ['_templates']
|
||||
@@ -35,12 +33,14 @@ author = u'Project Jupyter team'
|
||||
|
||||
# Autopopulate version
|
||||
from os.path import dirname
|
||||
|
||||
docs = dirname(dirname(__file__))
|
||||
root = dirname(docs)
|
||||
sys.path.insert(0, root)
|
||||
sys.path.insert(0, os.path.join(docs, 'sphinxext'))
|
||||
|
||||
import jupyterhub
|
||||
|
||||
# The short X.Y version.
|
||||
version = '%i.%i' % jupyterhub.version_info[:2]
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
@@ -56,17 +56,28 @@ default_role = 'literal'
|
||||
|
||||
# -- Source -------------------------------------------------------------
|
||||
|
||||
source_parsers = {
|
||||
'.md': 'recommonmark.parser.CommonMarkParser',
|
||||
}
|
||||
import recommonmark
|
||||
from recommonmark.transform import AutoStructify
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_config_value('recommonmark_config', {'enable_eval_rst': True}, True)
|
||||
app.add_stylesheet('custom.css')
|
||||
app.add_transform(AutoStructify)
|
||||
|
||||
|
||||
source_parsers = {'.md': 'recommonmark.parser.CommonMarkParser'}
|
||||
|
||||
source_suffix = ['.rst', '.md']
|
||||
#source_encoding = 'utf-8-sig'
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages.
|
||||
html_theme = 'alabaster'
|
||||
import alabaster_jupyterhub
|
||||
|
||||
html_theme = 'alabaster_jupyterhub'
|
||||
html_theme_path = [alabaster_jupyterhub.get_html_theme_path()]
|
||||
|
||||
html_logo = '_static/images/logo/logo.png'
|
||||
html_favicon = '_static/images/logo/favicon.ico'
|
||||
@@ -96,7 +107,7 @@ html_sidebars = {
|
||||
'navigation.html',
|
||||
'relations.html',
|
||||
'sourcelink.html',
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
htmlhelp_basename = 'JupyterHubdoc'
|
||||
@@ -104,38 +115,40 @@ htmlhelp_basename = 'JupyterHubdoc'
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
#'papersize': 'letterpaper',
|
||||
#'pointsize': '10pt',
|
||||
#'preamble': '',
|
||||
#'figure_align': 'htbp',
|
||||
# 'papersize': 'letterpaper',
|
||||
# 'pointsize': '10pt',
|
||||
# 'preamble': '',
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'JupyterHub.tex', u'JupyterHub Documentation',
|
||||
u'Project Jupyter team', 'manual'),
|
||||
(
|
||||
master_doc,
|
||||
'JupyterHub.tex',
|
||||
u'JupyterHub Documentation',
|
||||
u'Project Jupyter team',
|
||||
'manual',
|
||||
)
|
||||
]
|
||||
|
||||
#latex_logo = None
|
||||
#latex_use_parts = False
|
||||
#latex_show_pagerefs = False
|
||||
#latex_show_urls = False
|
||||
#latex_appendices = []
|
||||
#latex_domain_indices = True
|
||||
# latex_logo = None
|
||||
# latex_use_parts = False
|
||||
# latex_show_pagerefs = False
|
||||
# latex_show_urls = False
|
||||
# latex_appendices = []
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- manual page output -------------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'jupyterhub', u'JupyterHub Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
man_pages = [(master_doc, 'jupyterhub', u'JupyterHub Documentation', [author], 1)]
|
||||
|
||||
#man_show_urls = False
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Texinfo output -----------------------------------------------------
|
||||
@@ -144,15 +157,21 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'JupyterHub', u'JupyterHub Documentation',
|
||||
author, 'JupyterHub', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(
|
||||
master_doc,
|
||||
'JupyterHub',
|
||||
u'JupyterHub Documentation',
|
||||
author,
|
||||
'JupyterHub',
|
||||
'One line description of project.',
|
||||
'Miscellaneous',
|
||||
)
|
||||
]
|
||||
|
||||
#texinfo_appendices = []
|
||||
#texinfo_domain_indices = True
|
||||
#texinfo_show_urls = 'footnote'
|
||||
#texinfo_no_detailmenu = False
|
||||
# texinfo_appendices = []
|
||||
# texinfo_domain_indices = True
|
||||
# texinfo_show_urls = 'footnote'
|
||||
# texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
# -- Epub output --------------------------------------------------------
|
||||
@@ -179,6 +198,7 @@ else:
|
||||
# readthedocs.org uses their theme by default, so no need to specify it
|
||||
# build rest-api, since RTD doesn't run make
|
||||
from subprocess import check_call as sh
|
||||
|
||||
sh(['make', 'rest-api'], cwd=docs)
|
||||
|
||||
# -- Spell checking -------------------------------------------------------
|
||||
@@ -190,4 +210,4 @@ except ImportError:
|
||||
else:
|
||||
extensions.append("sphinxcontrib.spelling")
|
||||
|
||||
spelling_word_list_filename='spelling_wordlist.txt'
|
||||
spelling_word_list_filename = 'spelling_wordlist.txt'
|
||||
|
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/>`_.
|
98
docs/source/contributing/roadmap.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 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
|
||||
- UI for managing named servers
|
||||
|
||||
|
||||
### 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>`.
|
177
docs/source/contributing/setup.rst
Normal file
@@ -0,0 +1,177 @@
|
||||
.. _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.
|
||||
|
||||
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. 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 .
|
||||
|
||||
6. You are now ready to start JupyterHub!
|
||||
|
||||
.. code:: bash
|
||||
|
||||
jupyterhub
|
||||
|
||||
7. You can access JupyterHub from your browser at
|
||||
``http://localhost:8000`` now.
|
||||
|
||||
Happy developing!
|
||||
|
||||
Using DummyAuthenticator & SimpleSpawner
|
||||
========================================
|
||||
|
||||
To simplify testing of JupyterHub, it’s helpful to use
|
||||
:class:`~jupyterhub.auth.DummyAuthenticator` instead of the default JupyterHub
|
||||
authenticator and `SimpleSpawner <https://github.com/jupyterhub/simplespawner>`_
|
||||
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
|
||||
|
||||
pip install jupyterhub-simplespawner
|
||||
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 SimpleSpawner 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 &
|
||||
SimpleSpawner. If you are working on just authenticator related parts,
|
||||
use only SimpleSpawner. 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
|
78
docs/source/contributing/tests.rst
Normal file
@@ -0,0 +1,78 @@
|
||||
.. _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 --async-test-timeout 15 -v jupyterhub/tests
|
||||
|
||||
This should display progress as it runs all the tests, printing
|
||||
information about any test failures as they occur.
|
||||
|
||||
The ``--async-test-timeout`` parameter is used by `pytest-tornado
|
||||
<https://github.com/eugeniy/pytest-tornado#markers>`_ to set the
|
||||
asynchronous test timeout to 15 seconds rather than the default 5,
|
||||
since some of our tests take longer than 5s to execute.
|
||||
|
||||
#. You can also run tests in just a specific file:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --async-test-timeout 15 -v jupyterhub/tests/<test-file-name>
|
||||
|
||||
#. To run a specific test only, you can do:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --async-test-timeout 15 -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` sucessfully, and
|
||||
can launch ``jupyterhub`` from the terminal.
|
||||
|
||||
Tests are timing out
|
||||
--------------------
|
||||
|
||||
The ``--async-test-timeout`` parameter to ``pytest`` is used by
|
||||
`pytest-tornado <https://github.com/eugeniy/pytest-tornado#markers>`_ to set
|
||||
the asynchronous test timeout to a higher value than the default of 5s,
|
||||
since some of our tests take longer than 5s to execute. If the tests
|
||||
are still timing out, try increasing that value even more. You can
|
||||
also set an environment variable ``ASYNC_TEST_TIMEOUT`` instead of
|
||||
passing ``--async-test-timeout`` to each invocation of pytest.
|
@@ -3,38 +3,65 @@
|
||||
Project Jupyter thanks the following people for their help and
|
||||
contribution on JupyterHub:
|
||||
|
||||
- adelcast
|
||||
- Analect
|
||||
- anderbubble
|
||||
- anikitml
|
||||
- ankitksharma
|
||||
- apetresc
|
||||
- athornton
|
||||
- barrachri
|
||||
- BerserkerTroll
|
||||
- betatim
|
||||
- Carreau
|
||||
- cfournie
|
||||
- charnpreetsingh
|
||||
- chicovenancio
|
||||
- cikao
|
||||
- ckald
|
||||
- cmoscardi
|
||||
- consideRatio
|
||||
- cqzlxl
|
||||
- CRegenschein
|
||||
- cwaldbieser
|
||||
- danielballen
|
||||
- danoventa
|
||||
- daradib
|
||||
- darky2004
|
||||
- datapolitan
|
||||
- dblockow-d2dcrc
|
||||
- DeepHorizons
|
||||
- DerekHeldtWerle
|
||||
- dhirschfeld
|
||||
- dietmarw
|
||||
- dingc3
|
||||
- dmartzol
|
||||
- DominicFollettSmith
|
||||
- dsblank
|
||||
- dtaniwaki
|
||||
- echarles
|
||||
- ellisonbg
|
||||
- emmanuel
|
||||
- evanlinde
|
||||
- Fokko
|
||||
- fperez
|
||||
- franga2000
|
||||
- GladysNalvarte
|
||||
- glenak1911
|
||||
- gweis
|
||||
- iamed18
|
||||
- jamescurtin
|
||||
- JamiesHQ
|
||||
- JasonJWilliamsNY
|
||||
- jbweston
|
||||
- jdavidheiser
|
||||
- jencabral
|
||||
- jhamrick
|
||||
- jkinkead
|
||||
- johnkpark
|
||||
- josephtate
|
||||
- jzf2101
|
||||
- karfai
|
||||
- kinuax
|
||||
- KrishnaPG
|
||||
- kroq-gar78
|
||||
@@ -44,27 +71,44 @@ contribution on JupyterHub:
|
||||
- minrk
|
||||
- mistercrunch
|
||||
- Mistobaan
|
||||
- mpacer
|
||||
- mwmarkland
|
||||
- ndly
|
||||
- nthiery
|
||||
- nxg
|
||||
- ObiWahn
|
||||
- ozancaglayan
|
||||
- paccorsi
|
||||
- parente
|
||||
- PeterDaveHello
|
||||
- peterruppel
|
||||
- phill84
|
||||
- pjamason
|
||||
- prasadkatti
|
||||
- rafael-ladislau
|
||||
- rcthomas
|
||||
- rgbkrk
|
||||
- rkdarst
|
||||
- robnagler
|
||||
- rschroll
|
||||
- ryanlovett
|
||||
- sangramga
|
||||
- Scrypy
|
||||
- schon
|
||||
- shreddd
|
||||
- Siecje
|
||||
- smiller5678
|
||||
- spoorthyv
|
||||
- ssanderson
|
||||
- summerswallow
|
||||
- syutbai
|
||||
- takluyver
|
||||
- temogen
|
||||
- ThomasMChen
|
||||
- Thoralf Gutierrez
|
||||
- timfreund
|
||||
- TimShawver
|
||||
- tklever
|
||||
- Todd-Z-Li
|
||||
- toobaz
|
||||
- tsaeger
|
||||
|
@@ -85,8 +85,13 @@ easy to do with RStudio too.
|
||||
|
||||
- https://datascience.business.illinois.edu
|
||||
|
||||
### 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
|
||||
|
||||
- https://supercloud.mit.edu/
|
||||
|
||||
### Michigan State University
|
||||
|
||||
@@ -100,6 +105,11 @@ easy to do with RStudio too.
|
||||
|
||||
- https://dsa.missouri.edu/faq/
|
||||
|
||||
### 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.
|
||||
|
||||
### University of Rochester CIRC
|
||||
|
||||
- [JupyterHub Userguide](https://info.circ.rochester.edu/Web_Applications/JupyterHub.html) - Slurm, beehive
|
||||
@@ -141,7 +151,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/)
|
||||
|
||||
|
||||
### Microsoft Azure
|
||||
|
||||
- https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-linux-dsvm-intro
|
||||
@@ -151,9 +160,7 @@ easy to do with RStudio too.
|
||||
- https://getcarina.com/blog/learning-how-to-whale/
|
||||
- http://carolynvanslyck.com/talk/carina/jupyterhub/#/
|
||||
|
||||
### jcloud.io
|
||||
- Open to public JupyterHub server
|
||||
- https://jcloud.io
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
- https://medium.com/@ybarraud/setting-up-jupyterhub-with-sudospawner-and-anaconda-844628c0dbee#.rm3yt87e1
|
||||
|
@@ -31,6 +31,15 @@ c.Authenticator.admin_users = {'mal', 'zoe'}
|
||||
Users in the admin list are automatically added to the user `whitelist`,
|
||||
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`)
|
||||
|
||||
Since the default `JupyterHub.admin_access` setting is False, the admins
|
||||
@@ -95,5 +104,16 @@ popular services:
|
||||
A generic implementation, which you can use for OAuth authentication
|
||||
with any provider, is also available.
|
||||
|
||||
## Use DummyAuthenticator for testing
|
||||
|
||||
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
|
||||
[OAuthenticator]: https://github.com/jupyterhub/oauthenticator
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Configuration Basics
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
[config system](http://ipython.readthedocs.io/en/stable/development/config.html)
|
||||
[config system](http://ipython.readthedocs.io/en/stable/development/config)
|
||||
that Jupyter uses.
|
||||
|
||||
## Configure using command line options
|
||||
@@ -61,7 +61,7 @@ would enter:
|
||||
|
||||
```bash
|
||||
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,
|
||||
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
|
||||
|
||||
The default authentication and process spawning mechanisms can be replaced, and
|
||||
specific [authenticators](./authenticators-users-basics.html) and
|
||||
[spawners](./spawners-basics.html) can be set in the configuration file.
|
||||
specific [authenticators](./authenticators-users-basics) and
|
||||
[spawners](./spawners-basics) can be set in the configuration file.
|
||||
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:
|
||||
|
||||
- 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)
|
||||
|
||||
## 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,6 +35,10 @@ Configuring only the main IP and port of JupyterHub should be sufficient for
|
||||
most deployments of JupyterHub. However, more customized scenarios may need
|
||||
additional networking details to be configured.
|
||||
|
||||
Note that `c.JupyterHub.ip` and `c.JupyterHub.port` are single values,
|
||||
not tuples or lists – JupyterHub listens to only a single IP address and
|
||||
port.
|
||||
|
||||
## Set the Proxy's REST API communication URL (optional)
|
||||
|
||||
By default, this REST API listens on port 8081 of `localhost` only.
|
||||
@@ -86,3 +90,12 @@ configuration for, e.g. docker, is:
|
||||
c.JupyterHub.hub_ip = '0.0.0.0' # listen on all interfaces
|
||||
c.JupyterHub.hub_connect_ip = '10.0.1.4' # ip as seen on the docker network. Can also be a hostname.
|
||||
```
|
||||
|
||||
## Adjusting the hub's URL
|
||||
|
||||
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
|
||||
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
|
||||
a case, it is both necessary and sufficient to set
|
||||
`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.
|
||||
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@@ -72,8 +72,13 @@ would be the needed configuration:
|
||||
If SSL termination happens outside of the Hub
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In certain cases, e.g. behind `SSL termination in NGINX <https://www.nginx.com/resources/admin-guide/nginx-ssl-termination/>`_,
|
||||
allowing no SSL running on the hub may be the desired configuration option.
|
||||
In certain cases, for example if the hub is running behind a reverse proxy, and
|
||||
`SSL termination is being provided by NGINX <https://www.nginx.com/resources/admin-guide/nginx-ssl-termination/>`_,
|
||||
it is reasonable to run the hub without SSL.
|
||||
|
||||
To achieve this, simply omit the configuration settings
|
||||
``c.JupyterHub.ssl_key`` and ``c.JupyterHub.ssl_cert``
|
||||
(setting them to ``None`` does not have the same effect, and is an error).
|
||||
|
||||
.. _cookie-secret:
|
||||
|
||||
@@ -119,7 +124,7 @@ hex-encoded string. You can set it this way:
|
||||
|
||||
.. 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
|
||||
Hub. If you set it dynamically as above, all users will be logged out each time
|
||||
@@ -168,7 +173,7 @@ using the ``CONFIGPROXY_AUTH_TOKEN`` environment variable:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
export CONFIGPROXY_AUTH_TOKEN='openssl rand -hex 32'
|
||||
export CONFIGPROXY_AUTH_TOKEN=$(openssl rand -hex 32)
|
||||
|
||||
This environment variable needs to be visible to the Hub and Proxy.
|
||||
|
||||
|
@@ -14,7 +14,7 @@ document will:
|
||||
|
||||
- explain some basic information about API tokens
|
||||
- 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:
|
||||
- used in a Hub-managed service
|
||||
- run as a standalone script
|
||||
@@ -29,14 +29,14 @@ Hub via the REST API.
|
||||
To run such an external service, an API token must be created and
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||

|
||||
@@ -88,7 +88,7 @@ c.JupyterHub.services = [
|
||||
{
|
||||
'name': 'cull-idle',
|
||||
'admin': True,
|
||||
'command': 'python3 cull_idle_servers.py --timeout=3600'.split(),
|
||||
'command': [sys.executable, 'cull_idle_servers.py', '--timeout=3600'],
|
||||
}
|
||||
]
|
||||
```
|
||||
|
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 |
@@ -1,3 +1,4 @@
|
||||
==========
|
||||
JupyterHub
|
||||
==========
|
||||
|
||||
@@ -28,70 +29,141 @@ JupyterHub performs the following functions:
|
||||
For convenient administration of the Hub, its users, and services,
|
||||
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
|
||||
--------
|
||||
========
|
||||
|
||||
**Installation Guide**
|
||||
.. _index/distributions:
|
||||
|
||||
* :doc:`installation-guide`
|
||||
* :doc:`quickstart`
|
||||
* :doc:`quickstart-docker`
|
||||
* :doc:`installation-basics`
|
||||
Distributions
|
||||
-------------
|
||||
|
||||
**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`
|
||||
* :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`
|
||||
The two popular ones are:
|
||||
|
||||
**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`
|
||||
* :doc:`reference/technical-overview`
|
||||
* :doc:`reference/websecurity`
|
||||
* :doc:`reference/authenticators`
|
||||
* :doc:`reference/spawners`
|
||||
* :doc:`reference/services`
|
||||
* :doc:`reference/rest`
|
||||
* :doc:`reference/upgrading`
|
||||
* :doc:`reference/config-examples`
|
||||
Installation Guide
|
||||
------------------
|
||||
|
||||
**API Reference**
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
* :doc:`api/index`
|
||||
installation-guide
|
||||
quickstart
|
||||
quickstart-docker
|
||||
installation-basics
|
||||
|
||||
**Tutorials**
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
* :doc:`tutorials/index`
|
||||
* :doc:`tutorials/upgrade-dot-eight`
|
||||
* `Zero to JupyterHub with Kubernetes <https://zero-to-jupyterhub.readthedocs.io/en/latest/>`_
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
**Troubleshooting**
|
||||
getting-started/index
|
||||
getting-started/config-basics
|
||||
getting-started/networking-basics
|
||||
getting-started/security-basics
|
||||
getting-started/authenticators-users-basics
|
||||
getting-started/spawners-basics
|
||||
getting-started/services-basics
|
||||
|
||||
* :doc:`troubleshooting`
|
||||
Technical Reference
|
||||
-------------------
|
||||
|
||||
**About JupyterHub**
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
* :doc:`contributor-list`
|
||||
* :doc:`gallery-jhub-deployments`
|
||||
reference/index
|
||||
reference/technical-overview
|
||||
reference/websecurity
|
||||
reference/authenticators
|
||||
reference/spawners
|
||||
reference/services
|
||||
reference/rest
|
||||
reference/templates
|
||||
reference/config-user-env
|
||||
reference/config-examples
|
||||
reference/config-ghoauth
|
||||
reference/config-proxy
|
||||
reference/config-sudo
|
||||
|
||||
**Changelog**
|
||||
Contributing
|
||||
------------
|
||||
|
||||
* :doc:`changelog`
|
||||
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: 1
|
||||
|
||||
contributing/community
|
||||
contributing/setup
|
||||
contributing/docs
|
||||
contributing/tests
|
||||
contributing/roadmap
|
||||
contributing/security
|
||||
|
||||
Upgrading JupyterHub
|
||||
--------------------
|
||||
|
||||
We try to make upgrades between minor versions as painless as possible.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
admin/upgrading
|
||||
changelog
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api/index
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
troubleshooting
|
||||
|
||||
About JupyterHub
|
||||
----------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
contributor-list
|
||||
changelog
|
||||
gallery-jhub-deployments
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
|
||||
|
||||
Questions? Suggestions?
|
||||
-----------------------
|
||||
=======================
|
||||
|
||||
- `Jupyter mailing list <https://groups.google.com/forum/#!forum/jupyter>`_
|
||||
- `Jupyter website <https://jupyter.org>`_
|
||||
@@ -99,7 +171,7 @@ Questions? Suggestions?
|
||||
.. _contents:
|
||||
|
||||
Full Table of Contents
|
||||
----------------------
|
||||
======================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
@@ -108,7 +180,6 @@ Full Table of Contents
|
||||
getting-started/index
|
||||
reference/index
|
||||
api/index
|
||||
tutorials/index
|
||||
troubleshooting
|
||||
contributor-list
|
||||
gallery-jhub-deployments
|
||||
|
@@ -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
|
||||
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
|
||||
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 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.)
|
||||
- Services (nbgrader, etc.)
|
||||
- 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
|
||||
|
||||
|
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::
|
||||
|
||||
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
|
||||
**stop and resume** with ``docker stop/start``.
|
||||
|
@@ -5,20 +5,27 @@
|
||||
Before installing JupyterHub, you will need:
|
||||
|
||||
- a Linux/Unix based system
|
||||
- [Python](https://www.python.org/downloads/) 3.4 or greater. An understanding
|
||||
- [Python](https://www.python.org/downloads/) 3.5 or greater. An understanding
|
||||
of using [`pip`](https://pip.pypa.io/en/stable/) or
|
||||
[`conda`](https://conda.io/docs/get-started.html) for
|
||||
installing Python packages is helpful.
|
||||
- [nodejs/npm](https://www.npmjs.com/). [Install nodejs/npm](https://docs.npmjs.com/getting-started/installing-node),
|
||||
using your operating system's package manager. For example, install on Linux
|
||||
Debian/Ubuntu using:
|
||||
using your operating system's package manager.
|
||||
|
||||
```bash
|
||||
sudo apt-get install npm nodejs-legacy
|
||||
```
|
||||
* If you are using **`conda`**, the nodejs and npm dependencies will be installed for
|
||||
you by conda.
|
||||
|
||||
* If you are using **`pip`**, install a recent version of
|
||||
[nodejs/npm](https://docs.npmjs.com/getting-started/installing-node).
|
||||
For example, install it on Linux (Debian/Ubuntu) using:
|
||||
|
||||
```
|
||||
sudo apt-get install npm nodejs-legacy
|
||||
```
|
||||
|
||||
The `nodejs-legacy` package installs the `node` executable and is currently
|
||||
required for npm to work on Debian/Ubuntu.
|
||||
|
||||
The `nodejs-legacy` package installs the `node` executable and is currently
|
||||
required for `npm` to work on Debian/Ubuntu.
|
||||
- TLS certificate and key for HTTPS communication
|
||||
- Domain name
|
||||
|
||||
|
@@ -5,8 +5,8 @@ Hub and single user notebook servers.
|
||||
|
||||
## The default PAM Authenticator
|
||||
|
||||
JupyterHub ships only with the default [PAM][]-based Authenticator,
|
||||
for logging in with local user accounts via a username and password.
|
||||
JupyterHub ships with the default [PAM][]-based Authenticator, for
|
||||
logging in with local user accounts via a username and password.
|
||||
|
||||
## The OAuthenticator
|
||||
|
||||
@@ -34,10 +34,17 @@ popular services:
|
||||
A generic implementation, which you can use for OAuth authentication
|
||||
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
|
||||
|
||||
- ldapauthenticator for LDAP
|
||||
- tmpauthenticator for temporary accounts
|
||||
A partial list of other authenticators is available on the
|
||||
[JupyterHub wiki](https://github.com/jupyterhub/jupyterhub/wiki/Authenticators).
|
||||
|
||||
## Technical Overview of Authentication
|
||||
|
||||
@@ -68,7 +75,6 @@ Writing an Authenticator that looks up passwords in a dictionary
|
||||
requires only overriding this one method:
|
||||
|
||||
```python
|
||||
from tornado import gen
|
||||
from IPython.utils.traitlets import Dict
|
||||
from jupyterhub.auth import Authenticator
|
||||
|
||||
@@ -78,8 +84,7 @@ class DictionaryAuthenticator(Authenticator):
|
||||
help="""dict of username:password for authentication"""
|
||||
)
|
||||
|
||||
@gen.coroutine
|
||||
def authenticate(self, handler, data):
|
||||
async def authenticate(self, handler, data):
|
||||
if self.passwords.get(data['username']) == data['password']:
|
||||
return data['username']
|
||||
```
|
||||
@@ -101,6 +106,16 @@ 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
|
||||
|
||||
In most cases, there is a very limited set of acceptable usernames.
|
||||
@@ -136,6 +151,41 @@ See a list of custom Authenticators [on the wiki](https://github.com/jupyterhub/
|
||||
If you are interested in writing a custom authenticator, you can read
|
||||
[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
|
||||
|
||||
@@ -206,7 +256,13 @@ class MyAuthenticator(Authenticator):
|
||||
spawner.environment['UPSTREAM_TOKEN'] = auth_state['upstream_token']
|
||||
```
|
||||
|
||||
## pre_spawn_start and post_spawn_stop hooks
|
||||
|
||||
Authenticators uses two hooks, [pre_spawn_start(user, spawner)][] and
|
||||
[post_spawn_stop(user, spawner)][] to add pass additional state information
|
||||
between the authenticator and a spawner. These hooks are typically used auth-related
|
||||
startup, i.e. opening a PAM session, and auth-related cleanup, i.e. closing a
|
||||
PAM session.
|
||||
|
||||
## JupyterHub as an OAuth provider
|
||||
|
||||
@@ -218,5 +274,5 @@ Beginning with version 0.8, JupyterHub is an OAuth provider.
|
||||
[OAuth]: https://en.wikipedia.org/wiki/OAuth
|
||||
[GitHub OAuth]: https://developer.github.com/v3/oauth/
|
||||
[OAuthenticator]: https://github.com/jupyterhub/oauthenticator
|
||||
[pre_spawn_start(user, spawner)]: http://jupyterhub.readthedocs.io/en/latest/api/auth.html#jupyterhub.auth.Authenticator.pre_spawn_start
|
||||
[post_spawn_stop(user, spawner)]: http://jupyterhub.readthedocs.io/en/latest/api/auth.html#jupyterhub.auth.Authenticator.post_spawn_stop
|
||||
[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
|
||||
|
@@ -1,281 +1,8 @@
|
||||
# Configuration examples
|
||||
|
||||
This section provides examples, including configuration files and tips, for the
|
||||
following configurations:
|
||||
The following sections provide examples, including configuration files and tips, for the
|
||||
following:
|
||||
|
||||
- Using GitHub OAuth
|
||||
- Using nginx reverse proxy
|
||||
|
||||
## Using GitHub OAuth
|
||||
|
||||
In this example, we show a configuration file for a fairly standard JupyterHub
|
||||
deployment with the following assumptions:
|
||||
|
||||
* Running JupyterHub on a single cloud server
|
||||
* Using SSL on the standard HTTPS port 443
|
||||
* Using GitHub OAuth (using oauthenticator) for login
|
||||
* Using the default spawner (to configure other spawners, uncomment and edit
|
||||
`spawner_class` as well as follow the instructions for your desired spawner)
|
||||
* Users exist locally on the server
|
||||
* Users' notebooks to be served from `~/assignments` to allow users to browse
|
||||
for notebooks within other users' home directories
|
||||
* You want the landing page for each user to be a `Welcome.ipynb` notebook in
|
||||
their assignments directory.
|
||||
* All runtime files are put into `/srv/jupyterhub` and log files in `/var/log`.
|
||||
|
||||
|
||||
The `jupyterhub_config.py` file would have these settings:
|
||||
|
||||
```python
|
||||
# jupyterhub_config.py file
|
||||
c = get_config()
|
||||
|
||||
import os
|
||||
pjoin = os.path.join
|
||||
|
||||
runtime_dir = os.path.join('/srv/jupyterhub')
|
||||
ssl_dir = pjoin(runtime_dir, 'ssl')
|
||||
if not os.path.exists(ssl_dir):
|
||||
os.makedirs(ssl_dir)
|
||||
|
||||
# Allows multiple single-server per user
|
||||
c.JupyterHub.allow_named_servers = True
|
||||
|
||||
# https on :443
|
||||
c.JupyterHub.port = 443
|
||||
c.JupyterHub.ssl_key = pjoin(ssl_dir, 'ssl.key')
|
||||
c.JupyterHub.ssl_cert = pjoin(ssl_dir, 'ssl.cert')
|
||||
|
||||
# put the JupyterHub cookie secret and state db
|
||||
# in /var/run/jupyterhub
|
||||
c.JupyterHub.cookie_secret_file = pjoin(runtime_dir, 'cookie_secret')
|
||||
c.JupyterHub.db_url = pjoin(runtime_dir, 'jupyterhub.sqlite')
|
||||
# or `--db=/path/to/jupyterhub.sqlite` on the command-line
|
||||
|
||||
# use GitHub OAuthenticator for local users
|
||||
c.JupyterHub.authenticator_class = 'oauthenticator.LocalGitHubOAuthenticator'
|
||||
c.GitHubOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL']
|
||||
|
||||
# create system users that don't exist yet
|
||||
c.LocalAuthenticator.create_system_users = True
|
||||
|
||||
# specify users and admin
|
||||
c.Authenticator.whitelist = {'rgbkrk', 'minrk', 'jhamrick'}
|
||||
c.Authenticator.admin_users = {'jhamrick', 'rgbkrk'}
|
||||
|
||||
# uses the default spawner
|
||||
# To use a different spawner, uncomment `spawner_class` and set to desired
|
||||
# spawner (e.g. SudoSpawner). Follow instructions for desired spawner
|
||||
# configuration.
|
||||
# c.JupyterHub.spawner_class = 'sudospawner.SudoSpawner'
|
||||
|
||||
# start single-user notebook servers in ~/assignments,
|
||||
# with ~/assignments/Welcome.ipynb as the default landing page
|
||||
# this config could also be put in
|
||||
# /etc/jupyter/jupyter_notebook_config.py
|
||||
c.Spawner.notebook_dir = '~/assignments'
|
||||
c.Spawner.args = ['--NotebookApp.default_url=/notebooks/Welcome.ipynb']
|
||||
```
|
||||
|
||||
Using the GitHub Authenticator requires a few additional
|
||||
environment variable to be set prior to launching JupyterHub:
|
||||
|
||||
```bash
|
||||
export GITHUB_CLIENT_ID=github_id
|
||||
export GITHUB_CLIENT_SECRET=github_secret
|
||||
export OAUTH_CALLBACK_URL=https://example.com/hub/oauth_callback
|
||||
export CONFIGPROXY_AUTH_TOKEN=super-secret
|
||||
# append log output to log file /var/log/jupyterhub.log
|
||||
jupyterhub -f /etc/jupyterhub/jupyterhub_config.py &>> /var/log/jupyterhub.log
|
||||
```
|
||||
|
||||
## Using a reverse proxy
|
||||
|
||||
In the following example, we show configuration files for a JupyterHub server
|
||||
running locally on port `8000` but accessible from the outside on the standard
|
||||
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
|
||||
satisfy the following:
|
||||
|
||||
* 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,
|
||||
also on port `443`
|
||||
* `nginx` or `apache` is used as the public access point (which means that
|
||||
only nginx/apache will bind to `443`)
|
||||
* 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/)
|
||||
|
||||
Let's start out with needed JupyterHub configuration in `jupyterhub_config.py`:
|
||||
|
||||
```python
|
||||
# Force the proxy to only listen to connections to 127.0.0.1
|
||||
c.JupyterHub.ip = '127.0.0.1'
|
||||
```
|
||||
|
||||
For high-quality SSL configuration, we also generate Diffie-Helman parameters.
|
||||
This can take a few minutes:
|
||||
|
||||
```bash
|
||||
openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
|
||||
```
|
||||
|
||||
### nginx
|
||||
|
||||
The **`nginx` server config file** is fairly standard fare except for the two
|
||||
`location` blocks within the `HUB.DOMAIN.TLD` config file:
|
||||
|
||||
```bash
|
||||
# top-level http config for websocket headers
|
||||
# If Upgrade is defined, Connection = upgrade
|
||||
# If Upgrade is empty, Connection = close
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
# HTTP server to redirect all 80 traffic to SSL/HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name HUB.DOMAIN.TLD;
|
||||
|
||||
# Tell all requests to port 80 to be 302 redirected to HTTPS
|
||||
return 302 https://$host$request_uri;
|
||||
}
|
||||
|
||||
# HTTPS server to handle JupyterHub
|
||||
server {
|
||||
listen 443;
|
||||
ssl on;
|
||||
|
||||
server_name HUB.DOMAIN.TLD;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/HUB.DOMAIN.TLD/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/HUB.DOMAIN.TLD/privkey.pem;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_dhparam /etc/ssl/certs/dhparam.pem;
|
||||
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:50m;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
add_header Strict-Transport-Security max-age=15768000;
|
||||
|
||||
# Managing literal requests to the JupyterHub front end
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# websocket headers
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
}
|
||||
|
||||
# Managing requests to verify letsencrypt host
|
||||
location ~ /.well-known {
|
||||
allow all;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If `nginx` is not running on port 443, substitute `$http_host` for `$host` on
|
||||
the lines setting the `Host` header.
|
||||
|
||||
`nginx` will now be the front facing element of JupyterHub on `443` which means
|
||||
it is also free to bind other servers, like `NO_HUB.DOMAIN.TLD` to the same port
|
||||
on the same machine and network interface. In fact, one can simply use the same
|
||||
server blocks as above for `NO_HUB` and simply add line for the root directory
|
||||
of the site as well as the applicable location call:
|
||||
|
||||
```bash
|
||||
server {
|
||||
listen 80;
|
||||
server_name NO_HUB.DOMAIN.TLD;
|
||||
|
||||
# Tell all requests to port 80 to be 302 redirected to HTTPS
|
||||
return 302 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
ssl on;
|
||||
|
||||
# INSERT OTHER SSL PARAMETERS HERE AS ABOVE
|
||||
# SSL cert may differ
|
||||
|
||||
# Set the appropriate root directory
|
||||
root /var/www/html
|
||||
|
||||
# Set URI handling
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# Managing requests to verify letsencrypt host
|
||||
location ~ /.well-known {
|
||||
allow all;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Now restart `nginx`, restart the JupyterHub, and enjoy accessing
|
||||
`https://HUB.DOMAIN.TLD` while serving other content securely on
|
||||
`https://NO_HUB.DOMAIN.TLD`.
|
||||
|
||||
|
||||
### Apache
|
||||
|
||||
As with nginx above, you can use [Apache](https://httpd.apache.org) as the reverse proxy.
|
||||
First, we will need to enable the apache modules that we are going to need:
|
||||
|
||||
```bash
|
||||
a2enmod ssl rewrite proxy proxy_http proxy_wstunnel
|
||||
```
|
||||
|
||||
Our Apache configuration is equivalent to the nginx configuration above:
|
||||
|
||||
- Redirect HTTP to HTTPS
|
||||
- Good SSL Configuration
|
||||
- Support for websockets on any proxied URL
|
||||
- JupyterHub is running locally at http://127.0.0.1:8000
|
||||
|
||||
```bash
|
||||
# redirect HTTP to HTTPS
|
||||
Listen 80
|
||||
<VirtualHost HUB.DOMAIN.TLD:80>
|
||||
ServerName HUB.DOMAIN.TLD
|
||||
Redirect / https://HUB.DOMAIN.TLD/
|
||||
</VirtualHost>
|
||||
|
||||
Listen 443
|
||||
<VirtualHost HUB.DOMAIN.TLD:443>
|
||||
|
||||
ServerName HUB.DOMAIN.TLD
|
||||
|
||||
# configure SSL
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/HUB.DOMAIN.TLD/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/HUB.DOMAIN.TLD/privkey.pem
|
||||
SSLProtocol All -SSLv2 -SSLv3
|
||||
SSLOpenSSLConfCmd DHParameters /etc/ssl/certs/dhparam.pem
|
||||
SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
|
||||
|
||||
# Use RewriteEngine to handle websocket connection upgrades
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:Connection} Upgrade [NC]
|
||||
RewriteCond %{HTTP:Upgrade} websocket [NC]
|
||||
RewriteRule /(.*) ws://127.0.0.1:8000/$1 [P,L]
|
||||
|
||||
<Location "/">
|
||||
# preserve Host header to avoid cross-origin problems
|
||||
ProxyPreserveHost on
|
||||
# proxy to JupyterHub
|
||||
ProxyPass http://127.0.0.1:8000/
|
||||
ProxyPassReverse http://127.0.0.1:8000/
|
||||
</Location>
|
||||
</VirtualHost>
|
||||
```
|
||||
- Configuring GitHub OAuth
|
||||
- Using reverse proxy (nginx and Apache)
|
||||
- Run JupyterHub without root privileges using `sudo`
|
||||
|
82
docs/source/reference/config-ghoauth.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Configure GitHub OAuth
|
||||
|
||||
In this example, we show a configuration file for a fairly standard JupyterHub
|
||||
deployment with the following assumptions:
|
||||
|
||||
* Running JupyterHub on a single cloud server
|
||||
* Using SSL on the standard HTTPS port 443
|
||||
* Using GitHub OAuth (using oauthenticator) for login
|
||||
* Using the default spawner (to configure other spawners, uncomment and edit
|
||||
`spawner_class` as well as follow the instructions for your desired spawner)
|
||||
* Users exist locally on the server
|
||||
* Users' notebooks to be served from `~/assignments` to allow users to browse
|
||||
for notebooks within other users' home directories
|
||||
* You want the landing page for each user to be a `Welcome.ipynb` notebook in
|
||||
their assignments directory.
|
||||
* All runtime files are put into `/srv/jupyterhub` and log files in `/var/log`.
|
||||
|
||||
|
||||
The `jupyterhub_config.py` file would have these settings:
|
||||
|
||||
```python
|
||||
# jupyterhub_config.py file
|
||||
c = get_config()
|
||||
|
||||
import os
|
||||
pjoin = os.path.join
|
||||
|
||||
runtime_dir = os.path.join('/srv/jupyterhub')
|
||||
ssl_dir = pjoin(runtime_dir, 'ssl')
|
||||
if not os.path.exists(ssl_dir):
|
||||
os.makedirs(ssl_dir)
|
||||
|
||||
# Allows multiple single-server per user
|
||||
c.JupyterHub.allow_named_servers = True
|
||||
|
||||
# https on :443
|
||||
c.JupyterHub.port = 443
|
||||
c.JupyterHub.ssl_key = pjoin(ssl_dir, 'ssl.key')
|
||||
c.JupyterHub.ssl_cert = pjoin(ssl_dir, 'ssl.cert')
|
||||
|
||||
# put the JupyterHub cookie secret and state db
|
||||
# in /var/run/jupyterhub
|
||||
c.JupyterHub.cookie_secret_file = pjoin(runtime_dir, 'cookie_secret')
|
||||
c.JupyterHub.db_url = pjoin(runtime_dir, 'jupyterhub.sqlite')
|
||||
# or `--db=/path/to/jupyterhub.sqlite` on the command-line
|
||||
|
||||
# use GitHub OAuthenticator for local users
|
||||
c.JupyterHub.authenticator_class = 'oauthenticator.LocalGitHubOAuthenticator'
|
||||
c.GitHubOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL']
|
||||
|
||||
# create system users that don't exist yet
|
||||
c.LocalAuthenticator.create_system_users = True
|
||||
|
||||
# specify users and admin
|
||||
c.Authenticator.whitelist = {'rgbkrk', 'minrk', 'jhamrick'}
|
||||
c.Authenticator.admin_users = {'jhamrick', 'rgbkrk'}
|
||||
|
||||
# uses the default spawner
|
||||
# To use a different spawner, uncomment `spawner_class` and set to desired
|
||||
# spawner (e.g. SudoSpawner). Follow instructions for desired spawner
|
||||
# configuration.
|
||||
# c.JupyterHub.spawner_class = 'sudospawner.SudoSpawner'
|
||||
|
||||
# start single-user notebook servers in ~/assignments,
|
||||
# with ~/assignments/Welcome.ipynb as the default landing page
|
||||
# this config could also be put in
|
||||
# /etc/jupyter/jupyter_notebook_config.py
|
||||
c.Spawner.notebook_dir = '~/assignments'
|
||||
c.Spawner.args = ['--NotebookApp.default_url=/notebooks/Welcome.ipynb']
|
||||
```
|
||||
|
||||
Using the GitHub Authenticator requires a few additional
|
||||
environment variable to be set prior to launching JupyterHub:
|
||||
|
||||
```bash
|
||||
export GITHUB_CLIENT_ID=github_id
|
||||
export GITHUB_CLIENT_SECRET=github_secret
|
||||
export OAUTH_CALLBACK_URL=https://example.com/hub/oauth_callback
|
||||
export CONFIGPROXY_AUTH_TOKEN=super-secret
|
||||
# append log output to log file /var/log/jupyterhub.log
|
||||
jupyterhub -f /etc/jupyterhub/jupyterhub_config.py &>> /var/log/jupyterhub.log
|
||||
```
|
212
docs/source/reference/config-proxy.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# Using a reverse proxy
|
||||
|
||||
In the following example, we show configuration files for a JupyterHub server
|
||||
running locally on port `8000` but accessible from the outside on the standard
|
||||
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
|
||||
satisfy the following:
|
||||
|
||||
* 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,
|
||||
also on port `443`
|
||||
* `nginx` or `apache` is used as the public access point (which means that
|
||||
only nginx/apache will bind to `443`)
|
||||
* 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/)
|
||||
|
||||
Let's start out with needed JupyterHub configuration in `jupyterhub_config.py`:
|
||||
|
||||
```python
|
||||
# Force the proxy to only listen to connections to 127.0.0.1
|
||||
c.JupyterHub.ip = '127.0.0.1'
|
||||
```
|
||||
|
||||
For high-quality SSL configuration, we also generate Diffie-Helman parameters.
|
||||
This can take a few minutes:
|
||||
|
||||
```bash
|
||||
openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
|
||||
```
|
||||
|
||||
## nginx
|
||||
|
||||
This **`nginx` config file** is fairly standard fare except for the two
|
||||
`location` blocks within the main section for HUB.DOMAIN.tld.
|
||||
To create a new site for jupyterhub in your nginx config, make a new file
|
||||
in `sites.enabled`, e.g. `/etc/nginx/sites.enabled/jupyterhub.conf`:
|
||||
|
||||
```bash
|
||||
# top-level http config for websocket headers
|
||||
# If Upgrade is defined, Connection = upgrade
|
||||
# If Upgrade is empty, Connection = close
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
# HTTP server to redirect all 80 traffic to SSL/HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name HUB.DOMAIN.TLD;
|
||||
|
||||
# Tell all requests to port 80 to be 302 redirected to HTTPS
|
||||
return 302 https://$host$request_uri;
|
||||
}
|
||||
|
||||
# HTTPS server to handle JupyterHub
|
||||
server {
|
||||
listen 443;
|
||||
ssl on;
|
||||
|
||||
server_name HUB.DOMAIN.TLD;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/HUB.DOMAIN.TLD/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/HUB.DOMAIN.TLD/privkey.pem;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_dhparam /etc/ssl/certs/dhparam.pem;
|
||||
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:50m;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
add_header Strict-Transport-Security max-age=15768000;
|
||||
|
||||
# Managing literal requests to the JupyterHub front end
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# websocket headers
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
}
|
||||
|
||||
# Managing requests to verify letsencrypt host
|
||||
location ~ /.well-known {
|
||||
allow all;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If `nginx` is not running on port 443, substitute `$http_host` for `$host` on
|
||||
the lines setting the `Host` header.
|
||||
|
||||
`nginx` will now be the front facing element of JupyterHub on `443` which means
|
||||
it is also free to bind other servers, like `NO_HUB.DOMAIN.TLD` to the same port
|
||||
on the same machine and network interface. In fact, one can simply use the same
|
||||
server blocks as above for `NO_HUB` and simply add line for the root directory
|
||||
of the site as well as the applicable location call:
|
||||
|
||||
```bash
|
||||
server {
|
||||
listen 80;
|
||||
server_name NO_HUB.DOMAIN.TLD;
|
||||
|
||||
# Tell all requests to port 80 to be 302 redirected to HTTPS
|
||||
return 302 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
ssl on;
|
||||
|
||||
# INSERT OTHER SSL PARAMETERS HERE AS ABOVE
|
||||
# SSL cert may differ
|
||||
|
||||
# Set the appropriate root directory
|
||||
root /var/www/html
|
||||
|
||||
# Set URI handling
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# Managing requests to verify letsencrypt host
|
||||
location ~ /.well-known {
|
||||
allow all;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Now restart `nginx`, restart the JupyterHub, and enjoy accessing
|
||||
`https://HUB.DOMAIN.TLD` while serving other content securely on
|
||||
`https://NO_HUB.DOMAIN.TLD`.
|
||||
|
||||
|
||||
## Apache
|
||||
|
||||
As with nginx above, you can use [Apache](https://httpd.apache.org) as the reverse proxy.
|
||||
First, we will need to enable the apache modules that we are going to need:
|
||||
|
||||
```bash
|
||||
a2enmod ssl rewrite proxy proxy_http proxy_wstunnel
|
||||
```
|
||||
|
||||
Our Apache configuration is equivalent to the nginx configuration above:
|
||||
|
||||
- Redirect HTTP to HTTPS
|
||||
- Good SSL Configuration
|
||||
- Support for websockets on any proxied URL
|
||||
- JupyterHub is running locally at http://127.0.0.1:8000
|
||||
|
||||
```bash
|
||||
# redirect HTTP to HTTPS
|
||||
Listen 80
|
||||
<VirtualHost HUB.DOMAIN.TLD:80>
|
||||
ServerName HUB.DOMAIN.TLD
|
||||
Redirect / https://HUB.DOMAIN.TLD/
|
||||
</VirtualHost>
|
||||
|
||||
Listen 443
|
||||
<VirtualHost HUB.DOMAIN.TLD:443>
|
||||
|
||||
ServerName HUB.DOMAIN.TLD
|
||||
|
||||
# configure SSL
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/HUB.DOMAIN.TLD/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/HUB.DOMAIN.TLD/privkey.pem
|
||||
SSLProtocol All -SSLv2 -SSLv3
|
||||
SSLOpenSSLConfCmd DHParameters /etc/ssl/certs/dhparam.pem
|
||||
SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
|
||||
|
||||
# Use RewriteEngine to handle websocket connection upgrades
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:Connection} Upgrade [NC]
|
||||
RewriteCond %{HTTP:Upgrade} websocket [NC]
|
||||
RewriteRule /(.*) ws://127.0.0.1:8000/$1 [P,L]
|
||||
|
||||
<Location "/">
|
||||
# preserve Host header to avoid cross-origin problems
|
||||
ProxyPreserveHost on
|
||||
# proxy to JupyterHub
|
||||
ProxyPass http://127.0.0.1:8000/
|
||||
ProxyPassReverse http://127.0.0.1:8000/
|
||||
</Location>
|
||||
</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 [P,L]
|
||||
RewriteRule /jhub/(.*) http://127.0.0.1:8000/jhub/$1 [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/'
|
||||
```
|
254
docs/source/reference/config-sudo.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# Run JupyterHub without root privileges using `sudo`
|
||||
|
||||
**Note:** Setting up `sudo` permissions involves many pieces of system
|
||||
configuration. It is quite easy to get wrong and very difficult to debug.
|
||||
Only do this if you are very sure you must.
|
||||
|
||||
## Overview
|
||||
|
||||
There are many Authenticators and Spawners available for JupyterHub. Some, such
|
||||
as DockerSpawner or OAuthenticator, do not need any elevated permissions. This
|
||||
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 the Hub itself as root.
|
||||
|
||||
Since JupyterHub needs to spawn processes as other users, the simplest way
|
||||
is to run it as root, spawning user servers with [setuid](http://linux.die.net/man/2/setuid).
|
||||
But this isn't especially safe, because you have a process running on the
|
||||
public web as root.
|
||||
|
||||
A **more prudent way** to run the server while preserving functionality is to
|
||||
create a dedicated user with `sudo` access restricted to launching and
|
||||
monitoring single-user servers.
|
||||
|
||||
## Create a user
|
||||
|
||||
To do this, first create a user that will run the Hub:
|
||||
|
||||
```bash
|
||||
sudo useradd rhea
|
||||
```
|
||||
|
||||
This user shouldn't have a login shell or password (possible with -r).
|
||||
|
||||
## Set up sudospawner
|
||||
|
||||
Next, you will need [sudospawner](https://github.com/jupyter/sudospawner)
|
||||
to enable monitoring the single-user servers with sudo:
|
||||
|
||||
```bash
|
||||
sudo python3 -m pip install sudospawner
|
||||
```
|
||||
|
||||
Now we have to configure sudo to allow the Hub user (`rhea`) to launch
|
||||
the sudospawner script on behalf of our hub users (here `zoe` and `wash`).
|
||||
We want to confine these permissions to only what we really need.
|
||||
|
||||
## Edit `/etc/sudoers`
|
||||
|
||||
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
|
||||
- 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`
|
||||
without entering a password
|
||||
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
# comma-separated whitelist of users that can spawn single-user servers
|
||||
# this should include all of your Hub users
|
||||
Runas_Alias JUPYTER_USERS = rhea, zoe, wash
|
||||
|
||||
# the command(s) the Hub can run on behalf of the above users without needing a password
|
||||
# the exact path may differ, depending on how sudospawner was installed
|
||||
Cmnd_Alias JUPYTER_CMD = /usr/local/bin/sudospawner
|
||||
|
||||
# actually give the Hub user permission to run the above command on behalf
|
||||
# of the above users without prompting for a password
|
||||
rhea ALL=(JUPYTER_USERS) NOPASSWD:JUPYTER_CMD
|
||||
```
|
||||
|
||||
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
|
||||
use a group in the last line above, instead of `JUPYTER_USERS`:
|
||||
|
||||
```bash
|
||||
rhea ALL=(%jupyterhub) NOPASSWD:JUPYTER_CMD
|
||||
```
|
||||
|
||||
If the `jupyterhub` group exists, there will be no need to edit `/etc/sudoers`
|
||||
again. A new user will gain access to the application when added to the group:
|
||||
|
||||
```bash
|
||||
$ adduser -G jupyterhub newuser
|
||||
```
|
||||
|
||||
## Test `sudo` setup
|
||||
|
||||
Test that the new user doesn't need to enter a password to run the sudospawner
|
||||
command.
|
||||
|
||||
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
|
||||
logging options:
|
||||
|
||||
```bash
|
||||
$ sudo -u rhea sudo -n -u $USER /usr/local/bin/sudospawner --help
|
||||
Usage: /usr/local/bin/sudospawner [OPTIONS]
|
||||
|
||||
Options:
|
||||
|
||||
--help show this help information
|
||||
...
|
||||
```
|
||||
|
||||
And this should fail:
|
||||
|
||||
```bash
|
||||
$ sudo -u rhea sudo -n -u $USER echo 'fail'
|
||||
sudo: a password is required
|
||||
```
|
||||
|
||||
## Enable PAM for non-root
|
||||
|
||||
By default, [PAM authentication](http://en.wikipedia.org/wiki/Pluggable_authentication_module)
|
||||
is used by JupyterHub. To use PAM, the process may need to be able to read
|
||||
the shadow password database.
|
||||
|
||||
### Shadow group (Linux)
|
||||
|
||||
```bash
|
||||
$ ls -l /etc/shadow
|
||||
-rw-r----- 1 root shadow 2197 Jul 21 13:41 shadow
|
||||
```
|
||||
|
||||
If there's already a shadow group, you are set. If its permissions are more like:
|
||||
|
||||
```bash
|
||||
$ ls -l /etc/shadow
|
||||
-rw------- 1 root wheel 2197 Jul 21 13:41 shadow
|
||||
```
|
||||
|
||||
Then you may want to add a shadow group, and make the shadow file group-readable:
|
||||
|
||||
```bash
|
||||
$ sudo groupadd shadow
|
||||
$ sudo chgrp shadow /etc/shadow
|
||||
$ sudo chmod g+r /etc/shadow
|
||||
```
|
||||
|
||||
We want our new user to be able to read the shadow passwords, so add it to the shadow group:
|
||||
|
||||
```bash
|
||||
$ sudo usermod -a -G shadow rhea
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
sudo setcap 'cap_net_bind_service=+ep' /usr/bin/node
|
||||
```
|
||||
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
|
||||
on your server. `cpulimit` is a useful tool that is available for many Linux
|
||||
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
|
||||
instructions](http://ubuntuforums.org/showthread.php?t=992706).
|
||||
|
||||
|
||||
### Shadow group (FreeBSD)
|
||||
|
||||
**NOTE:** This has not been tested and may not work as expected.
|
||||
|
||||
```bash
|
||||
$ ls -l /etc/spwd.db /etc/master.passwd
|
||||
-rw------- 1 root wheel 2516 Aug 22 13:35 /etc/master.passwd
|
||||
-rw------- 1 root wheel 40960 Aug 22 13:35 /etc/spwd.db
|
||||
```
|
||||
|
||||
Add a shadow group if there isn't one, and make the shadow file group-readable:
|
||||
|
||||
```bash
|
||||
$ sudo pw group add shadow
|
||||
$ sudo chgrp shadow /etc/spwd.db
|
||||
$ sudo chmod g+r /etc/spwd.db
|
||||
$ sudo chgrp shadow /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
|
||||
shadow group:
|
||||
|
||||
```bash
|
||||
$ sudo pw user mod rhea -G shadow
|
||||
```
|
||||
|
||||
## Test that PAM works
|
||||
|
||||
We can verify that PAM is working, with:
|
||||
|
||||
```bash
|
||||
$ sudo -u rhea python3 -c "import pamela, getpass; print(pamela.authenticate('$USER', getpass.getpass()))"
|
||||
Password: [enter your unix password]
|
||||
```
|
||||
|
||||
## Make a directory for JupyterHub
|
||||
|
||||
JupyterHub stores its state in a database, so it needs write access to a directory.
|
||||
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.
|
||||
|
||||
```bash
|
||||
$ sudo mkdir /etc/jupyterhub
|
||||
$ sudo chown rhea /etc/jupyterhub
|
||||
```
|
||||
|
||||
## Start jupyterhub
|
||||
|
||||
Finally, start the server as our newly configured user, `rhea`:
|
||||
|
||||
```bash
|
||||
$ cd /etc/jupyterhub
|
||||
$ sudo -u rhea jupyterhub --JupyterHub.spawner_class=sudospawner.SudoSpawner
|
||||
```
|
||||
|
||||
And try logging in.
|
||||
|
||||
## Troubleshooting: SELinux
|
||||
|
||||
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.
|
||||
First, put this in a file named `sudo_exec_selinux.te`:
|
||||
|
||||
```bash
|
||||
module sudo_exec_selinux 1.1;
|
||||
|
||||
require {
|
||||
type unconfined_t;
|
||||
type sudo_exec_t;
|
||||
class file { read entrypoint };
|
||||
}
|
||||
|
||||
#============= unconfined_t ==============
|
||||
allow unconfined_t sudo_exec_t:file entrypoint;
|
||||
```
|
||||
|
||||
Then run all of these commands as root:
|
||||
|
||||
```bash
|
||||
$ checkmodule -M -m -o sudo_exec_selinux.mod sudo_exec_selinux.te
|
||||
$ semodule_package -o sudo_exec_selinux.pp -m sudo_exec_selinux.mod
|
||||
$ semodule -i sudo_exec_selinux.pp
|
||||
```
|
||||
|
||||
## Troubleshooting: PAM session errors
|
||||
|
||||
If the PAM authentication doesn't work and you see errors for
|
||||
`login:session-auth`, or similar, considering updating to a more recent version
|
||||
of jupyterhub and disabling the opening of PAM sessions with
|
||||
`c.PAMAuthenticator.open_sessions=False`.
|
181
docs/source/reference/config-user-env.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Configuring user environments
|
||||
|
||||
Deploying JupyterHub means you are providing Jupyter notebook environments for
|
||||
multiple users. Often, this includes a desire to configure the user
|
||||
environment in some way.
|
||||
|
||||
Since the `jupyterhub-singleuser` server extends the standard Jupyter notebook
|
||||
server, most configuration and documentation that applies to Jupyter Notebook
|
||||
applies to the single-user environments. Configuration of user environments
|
||||
typically does not occur through JupyterHub itself, but rather through system-
|
||||
wide configuration of Jupyter, which is inherited by `jupyterhub-singleuser`.
|
||||
|
||||
**Tip:** When searching for configuration tips for JupyterHub user
|
||||
environments, try removing JupyterHub from your search because there are a lot
|
||||
more people out there configuring Jupyter than JupyterHub and the
|
||||
configuration is the same.
|
||||
|
||||
This section will focus on user environments, including:
|
||||
|
||||
- Installing packages
|
||||
- Configuring Jupyter and IPython
|
||||
- Installing kernelspecs
|
||||
- Using containers vs. multi-user hosts
|
||||
|
||||
|
||||
## Installing packages
|
||||
|
||||
To make packages available to users, you generally will install packages
|
||||
system-wide or in a shared environment.
|
||||
|
||||
This installation location should always be in the same environment that
|
||||
`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
|
||||
packages, it must also be *writable* by your users.
|
||||
|
||||
If you are using a standard system Python install, you would use:
|
||||
|
||||
|
||||
```bash
|
||||
sudo python3 -m pip install numpy
|
||||
```
|
||||
|
||||
to install the numpy package in the default system Python 3 environment
|
||||
(typically `/usr/local`).
|
||||
|
||||
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
|
||||
run Python code in the env.
|
||||
|
||||
|
||||
## Configuring Jupyter and IPython
|
||||
|
||||
[Jupyter](https://jupyter-notebook.readthedocs.io/en/stable/config_overview.html)
|
||||
and [IPython](https://ipython.readthedocs.io/en/stable/development/config.html)
|
||||
have their own configuration systems.
|
||||
|
||||
As a JupyterHub administrator, you will typically want to install and configure
|
||||
environments for all JupyterHub users. For example, you wish for each student in
|
||||
a class to have the same user environment configuration.
|
||||
|
||||
Jupyter and IPython support **"system-wide"** locations for configuration, which
|
||||
is the logical place to put global configuration that you want to affect all
|
||||
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.
|
||||
|
||||
The typical locations for these config files are:
|
||||
- **system-wide** in `/etc/{jupyter|ipython}`
|
||||
- **env-wide** (environment wide) in `{sys.prefix}/etc/{jupyter|ipython}`.
|
||||
|
||||
### Example: Enable an extension system-wide
|
||||
|
||||
For example, to enable the `cython` IPython extension for all of your users,
|
||||
create the file `/etc/ipython/ipython_config.py`:
|
||||
|
||||
```python
|
||||
c.InteractiveShellApp.extensions.append("cython")
|
||||
```
|
||||
|
||||
### Example: Enable a Jupyter notebook configuration setting for all users
|
||||
|
||||
To enable Jupyter notebook's internal idle-shutdown behavior (requires
|
||||
notebook ≥ 5.4), set the following in the `/etc/jupyter/jupyter_notebook_config.py`
|
||||
file:
|
||||
|
||||
```python
|
||||
# shutdown the server after no activity for an hour
|
||||
c.NotebookApp.shutdown_no_activity_timeout = 60 * 60
|
||||
# shutdown kernels after no activity for 20 minutes
|
||||
c.MappingKernelManager.cull_idle_timeout = 20 * 60
|
||||
# check for idle kernels every two minutes
|
||||
c.MappingKernelManager.cull_interval = 2 * 60
|
||||
```
|
||||
|
||||
|
||||
## Installing kernelspecs
|
||||
|
||||
You may have multiple Jupyter kernels installed and want to make sure that
|
||||
they are available to all of your users. This means installing kernelspecs
|
||||
either system-wide (e.g. in /usr/local/) or in the `sys.prefix` of JupyterHub
|
||||
itself.
|
||||
|
||||
Jupyter kernelspec installation is system wide by default, but some kernels
|
||||
may default to installing kernelspecs in your home directory. These will need
|
||||
to be moved system-wide to ensure that they are accessible.
|
||||
|
||||
You can see where your kernelspecs are with:
|
||||
|
||||
```bash
|
||||
jupyter kernelspec list
|
||||
```
|
||||
|
||||
### Example: Installing kernels system-wide
|
||||
|
||||
Assuming I have a Python 2 and Python 3 environment that I want to make
|
||||
sure are available, I can install their specs system-wide (in /usr/local) with:
|
||||
|
||||
```bash
|
||||
/path/to/python3 -m IPython kernel install --prefix=/usr/local
|
||||
/path/to/python2 -m IPython kernel install --prefix=/usr/local
|
||||
```
|
||||
|
||||
|
||||
## Multi-user hosts vs. Containers
|
||||
|
||||
There are two broad categories of user environments that depend on what
|
||||
Spawner you choose:
|
||||
|
||||
- Multi-user hosts (shared system)
|
||||
- Container-based
|
||||
|
||||
How you configure user environments for each category can differ a bit
|
||||
depending on what Spawner you are using.
|
||||
|
||||
The first category is a **shared system (multi-user host)** where
|
||||
each user has a JupyterHub account and a home directory as well as being
|
||||
a real system user. In this example, shared configuration and installation
|
||||
must be in a 'system-wide' location, such as `/etc/` or `/usr/local`
|
||||
or a custom prefix such as `/opt/conda`.
|
||||
|
||||
When JupyterHub uses **container-based** Spawners (e.g. KubeSpawner or
|
||||
DockerSpawner), the 'system-wide' environment is really the container image
|
||||
which you are using for users.
|
||||
|
||||
In both cases, you want to *avoid putting configuration in user home
|
||||
directories* because users can change those configuration settings. Also,
|
||||
home directories typically persist once they are created, so they are
|
||||
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
|
||||
```
|
62
docs/source/reference/database.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# The Hub's Database
|
||||
|
||||
JupyterHub uses a database to store information about users, services, and other
|
||||
data needed for operating the Hub.
|
||||
|
||||
## Default SQLite database
|
||||
|
||||
The default database for JupyterHub is a [SQLite](https://sqlite.org) database.
|
||||
We have chosen SQLite as JupyterHub's default for its lightweight simplicity
|
||||
in certain uses such as testing, small deployments and workshops.
|
||||
|
||||
For production systems, SQLite has some disadvantages when used with JupyterHub:
|
||||
|
||||
- `upgrade-db` may not work, and you may need to start with a fresh database
|
||||
- `downgrade-db` **will not** work if you want to rollback to an earlier
|
||||
version, so backup the `jupyterhub.sqlite` file before upgrading
|
||||
|
||||
The sqlite documentation provides a helpful page about [when to use SQLite and
|
||||
where traditional RDBMS may be a better choice](https://sqlite.org/whentouse.html).
|
||||
|
||||
## Using an RDBMS (PostgreSQL, MySQL)
|
||||
|
||||
When running a long term deployment or a production system, we recommend using
|
||||
a traditional RDBMS database, such as [PostgreSQL](https://www.postgresql.org)
|
||||
or [MySQL](https://www.mysql.com), that supports the SQL `ALTER TABLE`
|
||||
statement.
|
||||
|
||||
## Notes and Tips
|
||||
|
||||
### SQLite
|
||||
|
||||
The SQLite database should not be used on NFS. SQLite uses reader/writer locks
|
||||
to control access to the database. This locking mechanism might not work
|
||||
correctly if the database file is kept on an NFS filesystem. This is because
|
||||
`fcntl()` file locking is broken on many NFS implementations. Therefore, you
|
||||
should avoid putting SQLite database files on NFS since it will not handle well
|
||||
multiple processes which might try to access the file at the same time.
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
We recommend using PostgreSQL for production if you are unsure whether to use
|
||||
MySQL or PostgreSQL or if you do not have a strong preference. There is
|
||||
additional configuration required for MySQL that is not needed for PostgreSQL.
|
||||
|
||||
### MySQL / MariaDB
|
||||
|
||||
- You should use the `pymysql` sqlalchemy provider (the other one, MySQLdb,
|
||||
isn't available for py3).
|
||||
- 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
|
||||
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
|
||||
will lead to frustrating 'the connection has gone away' errors from
|
||||
sqlalchemy if `pool_recycle` is not set.
|
||||
- If you use `utf8mb4` collation with MySQL earlier than 5.7.7 or MariaDB
|
||||
earlier than 10.2.1 you may get an `1709, Index column size too large` error.
|
||||
To fix this you need to set `innodb_large_prefix` to enabled and
|
||||
`innodb_file_format` to `Barracuda` to allow for the index sizes jupyterhub
|
||||
uses. `row_format` will be set to `DYNAMIC` as long as those options are set
|
||||
correctly. Later versions of MariaDB and MySQL should set these values by
|
||||
default, as well as have a default `DYNAMIC` `row_format` and pose no trouble
|
||||
to users.
|
@@ -5,12 +5,18 @@ Technical Reference
|
||||
:maxdepth: 2
|
||||
|
||||
technical-overview
|
||||
urls
|
||||
websecurity
|
||||
authenticators
|
||||
spawners
|
||||
services
|
||||
proxy
|
||||
separate-proxy
|
||||
rest
|
||||
upgrading
|
||||
database
|
||||
templates
|
||||
config-user-env
|
||||
config-examples
|
||||
config-ghoauth
|
||||
config-proxy
|
||||
config-sudo
|
||||
|
@@ -1,22 +1,26 @@
|
||||
# Writing a custom Proxy implementation
|
||||
|
||||
JupyterHub 0.8 introduced the ability to write a custom implementation of the proxy.
|
||||
This enables deployments with different needs than the default proxy,
|
||||
configurable-http-proxy (CHP).
|
||||
CHP is a single-process nodejs proxy that they Hub manages by default as a subprocess
|
||||
(it can be run externally, as well, and typically is in production deployments).
|
||||
JupyterHub 0.8 introduced the ability to write a custom implementation of the
|
||||
proxy. This enables deployments with different needs than the default proxy,
|
||||
configurable-http-proxy (CHP). CHP is a single-process nodejs proxy that the
|
||||
Hub manages by default as a subprocess (it can be run externally, as well, and
|
||||
typically is in production deployments).
|
||||
|
||||
The upside to CHP, and why we use it by default, is that it's easy to install and run (if you have nodejs, you are set!).
|
||||
The downsides are that it's a single process and does not support any persistence of the routing table.
|
||||
So if the proxy process dies, your whole JupyterHub instance is inaccessible until the Hub notices, restarts the proxy, and restores the routing table.
|
||||
For deployments that want to avoid such a single point of failure,
|
||||
or leverage existing proxy infrastructure in their chosen deployment (such as Kubernetes ingress objects),
|
||||
the Proxy API provides a way to do that.
|
||||
The upside to CHP, and why we use it by default, is that it's easy to install
|
||||
and run (if you have nodejs, you are set!). The downsides are that it's a
|
||||
single process and does not support any persistence of the routing table. So
|
||||
if the proxy process dies, your whole JupyterHub instance is inaccessible
|
||||
until the Hub notices, restarts the proxy, and restores the routing table. For
|
||||
deployments that want to avoid such a single point of failure, or leverage
|
||||
existing proxy infrastructure in their chosen deployment (such as Kubernetes
|
||||
ingress objects), the Proxy API provides a way to do that.
|
||||
|
||||
In general, for a proxy to be usable by JupyterHub, it must:
|
||||
|
||||
1. support websockets without prior knowledge of the URL where websockets may occur
|
||||
2. support trie-based routing (i.e. allow different routes on `/foo` and `/foo/bar` and route based on specificity)
|
||||
1. support websockets without prior knowledge of the URL where websockets may
|
||||
occur
|
||||
2. support trie-based routing (i.e. allow different routes on `/foo` and
|
||||
`/foo/bar` and route based on specificity)
|
||||
3. adding or removing a route should not cause existing connections to drop
|
||||
|
||||
Optionally, if the JupyterHub deployment is to use host-based routing,
|
||||
@@ -35,28 +39,37 @@ class MyProxy(Proxy):
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## Starting and stopping the proxy
|
||||
|
||||
If your proxy should be launched when the Hub starts, you must define how to start and stop your proxy:
|
||||
If your proxy should be launched when the Hub starts, you must define how
|
||||
to start and stop your proxy:
|
||||
|
||||
```python
|
||||
from tornado import gen
|
||||
class MyProxy(Proxy):
|
||||
...
|
||||
@gen.coroutine
|
||||
def start(self):
|
||||
async def start(self):
|
||||
"""Start the proxy"""
|
||||
|
||||
@gen.coroutine
|
||||
def stop(self):
|
||||
async def stop(self):
|
||||
"""Stop the proxy"""
|
||||
```
|
||||
|
||||
These methods **may** be coroutines.
|
||||
|
||||
`c.Proxy.should_start` is a configurable flag that determines whether the Hub should call these methods when the Hub itself starts and stops.
|
||||
`c.Proxy.should_start` is a configurable flag that determines whether the
|
||||
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
|
||||
|
||||
@@ -70,35 +83,33 @@ class MyProxy(Proxy):
|
||||
should_start = False
|
||||
```
|
||||
|
||||
## Routes
|
||||
|
||||
## Adding and removing routes
|
||||
|
||||
At its most basic, a Proxy implementation defines a mechanism to add, remove, and retrieve routes.
|
||||
A proxy that implements these three methods is complete.
|
||||
At its most basic, a Proxy implementation defines a mechanism to add, remove,
|
||||
and retrieve routes. A proxy that implements these three methods is complete.
|
||||
Each of these methods **may** be a coroutine.
|
||||
|
||||
**Definition:** routespec
|
||||
|
||||
A routespec, which will appear in these methods, is a string describing a route to be proxied,
|
||||
such as `/user/name/`. A routespec will:
|
||||
A routespec, which will appear in these methods, is a string describing a
|
||||
route to be proxied, such as `/user/name/`. A routespec will:
|
||||
|
||||
1. always end with `/`
|
||||
2. always start with `/` if it is a path-based route `/proxy/path/`
|
||||
3. precede the leading `/` with a host for host-based routing, e.g. `host.tld/proxy/path/`
|
||||
|
||||
3. precede the leading `/` with a host for host-based routing, e.g.
|
||||
`host.tld/proxy/path/`
|
||||
|
||||
### Adding a route
|
||||
|
||||
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 retrieved, the `data` argument should be returned as well.
|
||||
If your proxy 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 simple file or database.
|
||||
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
|
||||
retrieved, the `data` argument should be returned as well. If your proxy
|
||||
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
|
||||
simple file or database.
|
||||
|
||||
```python
|
||||
@gen.coroutine
|
||||
def add_route(self, routespec, target, data):
|
||||
async def add_route(self, routespec, target, data):
|
||||
"""Proxy `routespec` to `target`.
|
||||
|
||||
Store `data` associated with the routespec
|
||||
@@ -109,35 +120,30 @@ def add_route(self, routespec, target, data):
|
||||
Adding a route for a user looks like this:
|
||||
|
||||
```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'})
|
||||
```
|
||||
|
||||
|
||||
### Removing routes
|
||||
|
||||
`delete_route()` is given a routespec to delete.
|
||||
If there is no such route, `delete_route` should still succeed,
|
||||
but a warning may be issued.
|
||||
`delete_route()` is given a routespec to delete. If there is no such route,
|
||||
`delete_route` should still succeed, but a warning may be issued.
|
||||
|
||||
```python
|
||||
@gen.coroutine
|
||||
def delete_route(self, routespec):
|
||||
async def delete_route(self, routespec):
|
||||
"""Delete the route"""
|
||||
```
|
||||
|
||||
|
||||
### Retrieving routes
|
||||
|
||||
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 `routespect`,
|
||||
of dicts whose keys are the same three arguments passed to `add_route`
|
||||
(`routespec`, `target`, `data`)
|
||||
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
|
||||
`routespect`, of dicts whose keys are the same three arguments passed to
|
||||
`add_route` (`routespec`, `target`, `data`)
|
||||
|
||||
```python
|
||||
@gen.coroutine
|
||||
def get_all_routes(self):
|
||||
"""Return all routes, keyed by routespec""""
|
||||
async def get_all_routes(self):
|
||||
"""Return all routes, keyed by routespec"""
|
||||
```
|
||||
|
||||
```python
|
||||
@@ -150,15 +156,15 @@ def get_all_routes(self):
|
||||
}
|
||||
```
|
||||
|
||||
## Note on activity tracking
|
||||
|
||||
|
||||
#### Note on activity tracking
|
||||
|
||||
JupyterHub can track activity of users, for use in services such as culling idle servers.
|
||||
As of JupyterHub 0.8, this activity tracking is the responsibility of the proxy.
|
||||
If your proxy implementation can track activity to endpoints,
|
||||
it may add a `last_activity` key to the `data` of routes retrieved in `.get_all_routes()`.
|
||||
If present, the value of `last_activity` should be an [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) UTC date string:
|
||||
JupyterHub can track activity of users, for use in services such as culling
|
||||
idle servers. As of JupyterHub 0.8, this activity tracking is the
|
||||
responsibility of the proxy. If your proxy implementation can track activity
|
||||
to endpoints, it may add a `last_activity` key to the `data` of routes
|
||||
retrieved in `.get_all_routes()`. If present, the value of `last_activity`
|
||||
should be an [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) UTC date
|
||||
string:
|
||||
|
||||
```python
|
||||
{
|
||||
@@ -173,11 +179,44 @@ If present, the value of `last_activity` should be an [ISO8601](https://en.wikip
|
||||
}
|
||||
```
|
||||
|
||||
If the proxy does not track activity, then only activity to the Hub itself is
|
||||
tracked, and services such as cull-idle will not work.
|
||||
|
||||
If the proxy does not track activity, then only activity to the Hub itself is tracked,
|
||||
and services such as cull-idle will not work.
|
||||
Now that `notebook-5.0` tracks activity internally, we can retrieve activity
|
||||
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.
|
||||
|
||||
Now that `notebook-5.0` tracks activity internally,
|
||||
we can retrieve activity 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.
|
||||
### 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 authenticator 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
|
||||
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:
|
||||
|
||||
```bash
|
||||
@@ -48,7 +48,7 @@ jupyterhub token <username>
|
||||
This command generates a random string to use as a token and registers
|
||||
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:
|
||||
|
||||

|
||||
@@ -138,8 +138,8 @@ First you must enable named-servers by including the following setting in the `j
|
||||
|
||||
`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,
|
||||
then instead of editing the `jupyterhub_config.py` file directly, you could pass
|
||||
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
|
||||
the following as part of the `config.yaml` file, as per the [tutorial](https://zero-to-jupyterhub.readthedocs.io/en/latest/):
|
||||
|
||||
```bash
|
||||
@@ -158,11 +158,6 @@ The same servers can be stopped by substituting `DELETE` for `POST` above.
|
||||
|
||||
### 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
|
||||
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
|
||||
@@ -178,5 +173,5 @@ 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
|
||||
[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
|
||||
|
80
docs/source/reference/separate-proxy.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# 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)
|
@@ -15,7 +15,7 @@ This section provides the following information about Services:
|
||||
## Definition of a Service
|
||||
|
||||
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 or
|
||||
with the Hub's REST API. A Service may perform a specific
|
||||
action or task. For example, the following tasks can each be a unique Service:
|
||||
|
||||
- shutting down individuals' single user notebook servers that have been idle
|
||||
@@ -93,7 +93,7 @@ c.JupyterHub.services = [
|
||||
{
|
||||
'name': 'cull-idle',
|
||||
'admin': True,
|
||||
'command': ['python', '/path/to/cull-idle.py', '--timeout']
|
||||
'command': [sys.executable, '/path/to/cull-idle.py', '--timeout']
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -204,10 +204,10 @@ which implements the requests to the Hub.
|
||||
To use HubAuth, you must set the `.api_token`, either programmatically when constructing the class,
|
||||
or via the `JUPYTERHUB_API_TOKEN` environment variable.
|
||||
|
||||
Most of the logic for authentication implementation is found in the
|
||||
[`HubAuth.user_for_cookie`](services.auth.html#jupyterhub.services.auth.HubAuth.user_for_cookie)
|
||||
and in the
|
||||
[`HubAuth.user_for_token`](services.auth.html#jupyterhub.services.auth.HubAuth.user_for_token)
|
||||
Most of the logic for authentication implementation is found in the
|
||||
[`HubAuth.user_for_cookie`][HubAuth.user_for_cookie]
|
||||
and in the
|
||||
[`HubAuth.user_for_token`][HubAuth.user_for_token]
|
||||
methods, which makes a request of the Hub, and returns:
|
||||
|
||||
- None, if no user could be identified, or
|
||||
@@ -359,14 +359,16 @@ and taking note of the following process:
|
||||
```
|
||||
|
||||
An example of using an Externally-Managed Service and authentication is
|
||||
in [nbviewer README]_ 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).
|
||||
nbviewer can also be run as a Hub-Managed Service as described [nbviewer README]_
|
||||
nbviewer can also be run as a Hub-Managed Service as described [nbviewer README][nbviewer example]
|
||||
section on securing the notebook viewer.
|
||||
|
||||
|
||||
[requests]: http://docs.python-requests.org/en/master/
|
||||
[services_auth]: ../api/services.auth.html
|
||||
[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_token]: ../api/services.auth.html#jupyterhub.services.auth.HubAuth.user_for_token
|
||||
[HubAuthenticated]: ../api/services.auth.html#jupyterhub.services.auth.HubAuthenticated
|
||||
[nbviewer example]: https://github.com/jupyter/nbviewer#securing-the-notebook-viewer
|
||||
|
@@ -10,6 +10,7 @@ and a custom Spawner needs to be able to take three actions:
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
Custom Spawners for JupyterHub can be found on the [JupyterHub wiki](https://github.com/jupyterhub/jupyterhub/wiki/Spawners).
|
||||
Some examples include:
|
||||
|
||||
@@ -46,7 +47,16 @@ Most `Spawner.start` functions will look similar to this example:
|
||||
def start(self):
|
||||
self.ip = '127.0.0.1'
|
||||
self.port = random_port()
|
||||
yield self._actually_start_server_somehow()
|
||||
# get environment variables,
|
||||
# several of which are required for configuring the single-user server
|
||||
env = self.get_env()
|
||||
cmd = []
|
||||
# get jupyterhub command to run,
|
||||
# typically ['jupyterhub-singleuser']
|
||||
cmd.extend(self.cmd)
|
||||
cmd.extend(self.get_args())
|
||||
|
||||
yield self._actually_start_server_somehow(cmd, env)
|
||||
return (self.ip, self.port)
|
||||
```
|
||||
|
||||
@@ -165,6 +175,42 @@ When `Spawner.start` is called, this dictionary is accessible as `self.user_opti
|
||||
|
||||
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 authenticator 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)
|
||||
|
||||
Some spawners of the single-user notebook servers allow setting limits or
|
||||
@@ -187,7 +233,7 @@ 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
|
||||
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
|
||||
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
|
||||
@@ -214,3 +260,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
|
||||
limits and providing these guarantees.** If these values are set to `None`, no
|
||||
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, Jupyter Notebook server is started for each user on the system
|
||||
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
|
||||
`/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
|
||||
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
|
||||
allow users to sign in with e.g. a GitHub account, or with any single-sign-on
|
||||
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
|
||||
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
|
||||
@@ -66,10 +66,10 @@ using Docker.
|
||||
|
||||
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
|
||||
- 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
|
||||
- When the single-user notebook server starts, the proxy is notified to forward
|
||||
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
|
||||
invalidating cookies. Conversely, deleting this file and restarting the server
|
||||
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
|
||||
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:
|
||||
|
||||
- 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
|
||||
[Spawners](./spawners.html)
|
||||
[Spawners](./spawners.md)
|
||||
|
||||
Each is governed by a customizable class, and JupyterHub ships with basic
|
||||
defaults for each.
|
||||
|
@@ -1,28 +1,55 @@
|
||||
# Templates
|
||||
# Working with templates and UI
|
||||
|
||||
The pages of the JupyterHub application are generated from [Jinja](http://jinja.pocoo.org/) templates. These allow the header, for example, to be defined once and incorporated into all pages. By providing your own templates, you can have complete control over JupyterHub's appearance.
|
||||
The pages of the JupyterHub application are generated from
|
||||
[Jinja](http://jinja.pocoo.org/) templates. These allow the header, for
|
||||
example, to be defined once and incorporated into all pages. By providing
|
||||
your own templates, you can have complete control over JupyterHub's
|
||||
appearance.
|
||||
|
||||
## Custom Templates
|
||||
|
||||
JupyterHub will look for custom templates in all of the paths in the `JupyterHub.template_paths` configuration option, falling back on the [default templates](https://github.com/jupyterhub/jupyterhub/tree/master/share/jupyterhub/templates) if no custom template with that name is found. (This fallback behavior is new in version 0.9; previous versions searched only those paths explicitly included in `template_paths`.) This means you can override as many or as few templates as you desire.
|
||||
JupyterHub will look for custom templates in all of the paths in the
|
||||
`JupyterHub.template_paths` configuration option, falling back on the
|
||||
[default templates](https://github.com/jupyterhub/jupyterhub/tree/master/share/jupyterhub/templates)
|
||||
if no custom template with that name is found. This fallback
|
||||
behavior is new in version 0.9; previous versions searched only those paths
|
||||
explicitly included in `template_paths`. You may override as many
|
||||
or as few templates as you desire.
|
||||
|
||||
## Extending Templates
|
||||
|
||||
Jinja provides a mechanism to [extend templates](http://jinja.pocoo.org/docs/2.10/templates/#template-inheritance). A base template can define a `block`, and child templates can replace or supplement the material in the block. The [JupyterHub templates](https://github.com/jupyterhub/jupyterhub/tree/master/share/jupyterhub/templates) make extensive use of this feature, which allows you to customize parts of the interface easily.
|
||||
Jinja provides a mechanism to [extend templates](http://jinja.pocoo.org/docs/2.10/templates/#template-inheritance).
|
||||
A base template can define a `block`, and child templates can replace or
|
||||
supplement the material in the block. The
|
||||
[JupyterHub templates](https://github.com/jupyterhub/jupyterhub/tree/master/share/jupyterhub/templates)
|
||||
make extensive use of blocks, which allows you to customize parts of the
|
||||
interface easily.
|
||||
|
||||
In general, a child template can extend a base template, `base.html`, by beginning with
|
||||
In general, a child template can extend a base template, `page.html`, by beginning with:
|
||||
|
||||
```html
|
||||
{% extends "page.html" %}
|
||||
```
|
||||
{% extends "base.html" %}
|
||||
|
||||
This works, unless you are trying to extend the default template for the same
|
||||
file name. Starting in version 0.9, you may refer to the base file with a
|
||||
`templates/` prefix. Thus, if you are writing a custom `page.html`, start the
|
||||
file with this block:
|
||||
|
||||
```html
|
||||
{% extends "templates/page.html" %}
|
||||
```
|
||||
This works, unless you are trying to extend the default template for the same file name. Starting in version 0.9, you may refer to the base file with a `templates/` prefix. Thus, if you are writing a custom `base.html`, start it with
|
||||
```
|
||||
{% extends "templates/base.html" %}
|
||||
```
|
||||
By defining `block`s with same name as in the base template, child templates can replace those sections with custom content. The content from the base template can be included with the `{{ super() }}` directive.
|
||||
|
||||
By defining `block`s with same name as in the base template, child templates
|
||||
can replace those sections with custom content. The content from the base
|
||||
template can be included with the `{{ super() }}` directive.
|
||||
|
||||
### Example
|
||||
|
||||
To add an additional message to the spawn-pending page, below the existing text about the server starting up, place this content in a file named `spawn_pending.html` in a directory included in the `JupyterHub.template_paths` configuration option.
|
||||
To add an additional message to the spawn-pending page, below the existing
|
||||
text about the server starting up, place this content in a file named
|
||||
`spawn_pending.html` in a directory included in the
|
||||
`JupyterHub.template_paths` configuration option.
|
||||
|
||||
```html
|
||||
{% extends "templates/spawn_pending.html" %}
|
||||
@@ -32,3 +59,35 @@ To add an additional message to the spawn-pending page, below the existing text
|
||||
<p>Patience is a virtue.</p>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
## Page Announcements
|
||||
|
||||
To add announcements to be displayed on a page, you have two options:
|
||||
|
||||
- Extend the page templates as described above
|
||||
- Use configuration variables
|
||||
|
||||
### Announcement Configuration Variables
|
||||
|
||||
If you set the configuration variable `JupyterHub.template_vars =
|
||||
{'announcement': 'some_text}`, the given `some_text` will be placed on
|
||||
the top of all pages. The more specific variables
|
||||
`announcement_login`, `announcement_spawn`, `announcement_home`, and
|
||||
`announcement_logout` are more specific and only show on their
|
||||
respective pages (overriding the global `announcement` variable).
|
||||
Note that changing these variables require a restart, unlike direct
|
||||
template extension.
|
||||
|
||||
You can get the same effect by extending templates, which allows you
|
||||
to update the messages without restarting. Set
|
||||
`c.JupyterHub.template_paths` as mentioned above, and then create a
|
||||
template (for example, `login.html`) with:
|
||||
|
||||
```html
|
||||
{% extends "templates/login.html" %}
|
||||
{% set announcement = 'some message' %}
|
||||
```
|
||||
|
||||
Extending `page.html` puts the message on all pages, but note that
|
||||
extending `page.html` take precedence over an extension of a specific
|
||||
page (unlike the variable-based approach above).
|
||||
|
@@ -1,106 +0,0 @@
|
||||
# Upgrading JupyterHub and its database
|
||||
|
||||
From time to time, you may wish to upgrade JupyterHub to take advantage
|
||||
of new releases. Much of this process is automated using scripts,
|
||||
such as those generated by alembic for database upgrades. Before upgrading a
|
||||
JupyterHub deployment, it's critical to backup your data and configurations
|
||||
before shutting down the JupyterHub process and server.
|
||||
|
||||
## Databases: SQLite (default) or RDBMS (PostgreSQL, MySQL)
|
||||
|
||||
The default database for JupyterHub is a [SQLite](https://sqlite.org) database.
|
||||
We have chosen SQLite as JupyterHub's default for its lightweight simplicity
|
||||
in certain uses such as testing, small deployments and workshops.
|
||||
|
||||
When running a long term deployment or a production system, we recommend using
|
||||
a traditional RDBMS database, such as [PostgreSQL](https://www.postgresql.org)
|
||||
or [MySQL](https://www.mysql.com), that supports the SQL `ALTER TABLE`
|
||||
statement.
|
||||
|
||||
For production systems, SQLite has some disadvantages when used with JupyterHub:
|
||||
|
||||
- `upgrade-db` may not work, and you may need to start with a fresh database
|
||||
- `downgrade-db` **will not** work if you want to rollback to an earlier
|
||||
version, so backup the `jupyterhub.sqlite` file before upgrading
|
||||
|
||||
The sqlite documentation provides a helpful page about [when to use sqlite and
|
||||
where traditional RDBMS may be a better choice](https://sqlite.org/whentouse.html).
|
||||
|
||||
## The upgrade process
|
||||
|
||||
Five fundamental process steps are needed when upgrading JupyterHub and its
|
||||
database:
|
||||
|
||||
1. Backup JupyterHub database
|
||||
2. Backup JupyterHub configuration file
|
||||
3. Shutdown the Hub
|
||||
4. Upgrade JupyterHub
|
||||
5. Upgrade the database using run `jupyterhub upgrade-db`
|
||||
|
||||
Let's take a closer look at each step in the upgrade process as well as some
|
||||
additional information about JupyterHub databases.
|
||||
|
||||
### Backup JupyterHub database
|
||||
|
||||
To prevent unintended loss of data or configuration information, you should
|
||||
back up the JupyterHub database (the default SQLite database or a RDBMS
|
||||
database using PostgreSQL, MySQL, or others supported by SQLAlchemy):
|
||||
|
||||
- If using the default SQLite database, back up the `jupyterhub.sqlite`
|
||||
database.
|
||||
- If using an RDBMS database such as PostgreSQL, MySQL, or other supported by
|
||||
SQLAlchemy, back up the JupyterHub 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 GitHub 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
|
||||
- user servers are stopped during upgrade
|
||||
- don't mind causing users to login again after upgrade
|
||||
|
||||
### Backup JupyterHub configuration file
|
||||
|
||||
Additionally, backing up your configuration file, `jupyterhub_config.py`, to
|
||||
a secure location.
|
||||
|
||||
### Shutdown JupyterHub
|
||||
|
||||
Prior to shutting down JupyterHub, you should notify the Hub users of the
|
||||
scheduled downtime. This gives users the opportunity to finish any outstanding
|
||||
work in process.
|
||||
|
||||
Next, shutdown the JupyterHub service.
|
||||
|
||||
### Upgrade JupyterHub
|
||||
|
||||
Follow directions that correspond to your package manager, `pip` or `conda`,
|
||||
for the new JupyterHub release. These directions will guide you to the
|
||||
specific command. In general, `pip install -U jupyterhub` or
|
||||
`conda upgrade jupyterhub`
|
||||
|
||||
### Upgrade JupyterHub databases
|
||||
|
||||
To run the upgrade process for JupyterHub databases, enter:
|
||||
|
||||
```
|
||||
jupyterhub upgrade-db
|
||||
```
|
||||
|
||||
## Upgrade checklist
|
||||
|
||||
1. Backup JupyterHub database:
|
||||
- `jupyterhub.sqlite` when using the default sqlite database
|
||||
- Your JupyterHub database when using an RDBMS
|
||||
2. Backup JupyterHub configuration file: `jupyterhub_config.py`
|
||||
3. Shutdown the Hub
|
||||
4. Upgrade JupyterHub
|
||||
- `pip install -U jupyterhub` when using `pip`
|
||||
- `conda upgrade jupyterhub` when using `conda`
|
||||
5. Upgrade the database using run `jupyterhub upgrade-db`
|
255
docs/source/reference/urls.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# JupyterHub URL scheme
|
||||
|
||||
This document describes how JupyterHub routes requests.
|
||||
|
||||
This does not include the [REST API](./rest.md) urls.
|
||||
|
||||
In general, all URLs can be prefixed with `c.JupyterHub.base_url` to
|
||||
run the whole JupyterHub application on a prefix.
|
||||
|
||||
All authenticated handlers redirect to `/hub/login` to login users
|
||||
prior to being redirected back to the originating page.
|
||||
The returned request should preserve all query parameters.
|
||||
|
||||
|
||||
|
||||
## `/`
|
||||
|
||||
The top-level request is always a simple redirect to `/hub/`,
|
||||
to be handled by the default JupyterHub handler.
|
||||
|
||||
In general, all requests to `/anything` that do not start with `/hub/`
|
||||
but are routed to the Hub, will be redirected to `/hub/anything` before being handled by the Hub.
|
||||
|
||||
## `/hub/`
|
||||
|
||||
This is an authenticated URL.
|
||||
|
||||
This handler redirects users to the default URL of the application,
|
||||
which defaults to the user's default server.
|
||||
That is, it redirects to `/hub/spawn` if the user's server is not running,
|
||||
or the server itself (`/user/:name`) if the server is running.
|
||||
|
||||
This default url behavior can be customized in two ways:
|
||||
|
||||
To redirect users to the JupyterHub home page (`/hub/home`)
|
||||
instead of spawning their server,
|
||||
set `redirect_to_server` to False:
|
||||
|
||||
```python
|
||||
c.JupyterHub.redirect_to_server = False
|
||||
```
|
||||
|
||||
This might be useful if you have a Hub where you expect
|
||||
users to be managing multiple server configurations
|
||||
and automatic spawning is not desirable.
|
||||
|
||||
Second, you can customise the landing page to any page you like,
|
||||
such as a custom service you have deployed e.g. with course information:
|
||||
|
||||
```python
|
||||
c.JupyterHub.default_url = '/services/my-landing-service'
|
||||
```
|
||||
|
||||
## `/hub/home`
|
||||
|
||||

|
||||
|
||||
By default, the Hub home page has just one or two buttons
|
||||
for starting and stopping the user's server.
|
||||
|
||||
If named servers are enabled, there will be some additional
|
||||
tools for management of named servers.
|
||||
|
||||
*Version added: 1.0* named server UI is new in 1.0.
|
||||
|
||||
## `/hub/login`
|
||||
|
||||
This is the JupyterHub login page.
|
||||
If you have a form-based username+password login,
|
||||
such as the default PAMAuthenticator,
|
||||
this page will render the login form.
|
||||
|
||||

|
||||
|
||||
If login is handled by an external service,
|
||||
e.g. with OAuth, this page will have a button,
|
||||
declaring "Login with ..." which users can click
|
||||
to login with the chosen service.
|
||||
|
||||

|
||||
|
||||
If you want to skip the user-interaction to initiate logging in
|
||||
via the button, you can set
|
||||
|
||||
```python
|
||||
c.Authenticator.auto_login = True
|
||||
```
|
||||
|
||||
This can be useful when the user is "already logged in" via some mechanism,
|
||||
but a handshake via redirects is necessary to complete the authentication with JupyterHub.
|
||||
|
||||
## `/hub/logout`
|
||||
|
||||
Visiting `/hub/logout` clears cookies from the current browser.
|
||||
Note that **logging out does not stop a user's server(s)** by default.
|
||||
|
||||
If you would like to shutdown user servers on logout,
|
||||
you can enable this behavior with:
|
||||
|
||||
```python
|
||||
c.JupyterHub.shutdown_on_logout = True
|
||||
```
|
||||
|
||||
Be careful with this setting because logging out one browser
|
||||
does not mean the user is no longer actively using their server from another machine.
|
||||
|
||||
## `/user/:username[/:servername]`
|
||||
|
||||
If a user's server is running, this URL is handled by the user's given server,
|
||||
not the Hub.
|
||||
The username is the first part and, if using named servers,
|
||||
the server name is the second part.
|
||||
|
||||
If the user's server is *not* running, this will be redirected to `/hub/user/:username/...`
|
||||
|
||||
## `/hub/user/:username[/:servername]`
|
||||
|
||||
This URL indicates a request for a user server that is not running
|
||||
(because `/user/...` would have been handled by the notebook server
|
||||
if the specified server were running).
|
||||
|
||||
Handling this URL is the most complicated condition in JupyterHub,
|
||||
because there can be many states:
|
||||
|
||||
1. server is not active
|
||||
a. user matches
|
||||
b. user doesn't match
|
||||
2. server is ready
|
||||
3. server is pending, but not ready
|
||||
|
||||
If the server is pending spawn,
|
||||
the browser will be redirected to `/hub/spawn-pending/:username/:servername`
|
||||
to see a progress page while waiting for the server to be ready.
|
||||
|
||||
If the server is not active at all,
|
||||
a page will be served with a link to `/hub/spawn/:username/:servername`.
|
||||
Following that link will launch the requested server.
|
||||
The HTTP status will be 503 in this case because a request has been made for a server that is not running.
|
||||
|
||||
If the server is ready, it is assumed that the proxy has not yet registered the route.
|
||||
Some checks are performed and a delay is added before redirecting back to `/user/:username/:servername/...`.
|
||||
If something is really wrong, this can result in a redirect loop.
|
||||
|
||||
Visiting this page will never result in triggering the spawn of servers
|
||||
without additional user action (i.e. clicking the link on the page)
|
||||
|
||||

|
||||
|
||||
*Version changed: 1.0*
|
||||
|
||||
Prior to 1.0, this URL itself was responsible for spawning servers,
|
||||
and served the progress page if it was pending,
|
||||
redirected to running servers, and
|
||||
This was useful because it made sure that requested servers were restarted after they stopped,
|
||||
but could also be harmful because unused servers would continuously be restarted if e.g.
|
||||
an idle JupyterLab frontend were open pointed at it,
|
||||
which constantly makes polling requests.
|
||||
|
||||
### Special handling of API requests
|
||||
|
||||
Requests to `/user/:username[/:servername]/api/...` are assumed to be
|
||||
from applications connected to stopped servers.
|
||||
These are failed with 503 and an informative JSON error message
|
||||
indicating how to spawn the server.
|
||||
This is meant to help applications such as JupyterLab
|
||||
that are connected to a server that has stopped.
|
||||
|
||||
*Version changed: 1.0*
|
||||
|
||||
JupyterHub 0.9 failed these API requests with status 404,
|
||||
but 1.0 uses 503.
|
||||
|
||||
## `/user-redirect/...`
|
||||
|
||||
This URL is for sharing a URL that will redirect a user
|
||||
to a path on their own default server.
|
||||
This is useful when users have the same file at the same URL on their servers,
|
||||
and you want a single link to give to any user that will open that file on their server.
|
||||
|
||||
e.g. a link to `/user-redirect/notebooks/Index.ipynb`
|
||||
will send user `hortense` to `/user/hortense/notebooks/Index.ipynb`
|
||||
|
||||
**DO NOT** share links to your own server with other users.
|
||||
This will not work in general,
|
||||
unless you grant those users access to your server.
|
||||
|
||||
**Contributions welcome:** The JupyterLab "shareable link" should share this link
|
||||
when run with JupyterHub, but it does not.
|
||||
See [jupyterlab-hub](https://github.com/jupyterhub/jupyterlab-hub)
|
||||
where this should probably be done and
|
||||
[this issue in JupyterLab](https://github.com/jupyterlab/jupyterlab/issues/5388)
|
||||
that is intended to make it possible.
|
||||
|
||||
## Spawning
|
||||
|
||||
### `/hub/spawn[/:username[/:servername]]`
|
||||
|
||||
Requesting `/hub/spawn` will spawn the default server for the current user.
|
||||
If `username` and optionally `servername` are specified,
|
||||
then the specified server for the specified user will be spawned.
|
||||
Once spawn has been requested,
|
||||
the browser is redirected to `/hub/spawn-pending/...`.
|
||||
|
||||
If `Spawner.options_form` is used,
|
||||
this will render a form,
|
||||
and a POST request will trigger the actual spawn and redirect.
|
||||
|
||||

|
||||
|
||||
*Version added: 1.0*
|
||||
|
||||
1.0 adds the ability to specify username and servername.
|
||||
Prior to 1.0, only `/hub/spawn` was recognized for the default server.
|
||||
|
||||
*Version changed: 1.0*
|
||||
|
||||
Prior to 1.0, this page redirected back to `/hub/user/:username`,
|
||||
which was responsible for triggering spawn and rendering progress, etc.
|
||||
|
||||
### `/hub/spawn-pending[/:username[/:servername]]`
|
||||
|
||||

|
||||
|
||||
*Version added: 1.0* this URL is new in JupyterHub 1.0.
|
||||
|
||||
This page renders the progress view for the given spawn request.
|
||||
Once the server is ready,
|
||||
the browser is redirected to the running server at `/user/:username/:servername/...`.
|
||||
|
||||
If this page is requested at any time after the specified server is ready,
|
||||
the browser will be redirected to the running server.
|
||||
|
||||
Requesting this page will never trigger any side effects.
|
||||
If the server is not running (e.g. because the spawn has failed),
|
||||
the spawn failure message (if applicable) will be displayed,
|
||||
and the page will show a link back to `/hub/spawn/...`.
|
||||
|
||||
## `/hub/token`
|
||||
|
||||

|
||||
|
||||
On this page, users can manage their JupyterHub API tokens.
|
||||
They can revoke access and request new tokens for writing scripts
|
||||
against the [JupyterHub REST API](./rest.md).
|
||||
|
||||
## `/hub/admin`
|
||||
|
||||

|
||||
|
||||
Administrators can take various administrative actions from this page:
|
||||
|
||||
1. add/remove users
|
||||
2. grant admin privileges
|
||||
3. start/stop user servers
|
||||
4. shutdown JupyterHub itself
|
@@ -99,6 +99,23 @@ single-user server, and not the environment(s) in which the user's kernel(s)
|
||||
may run. Installing additional packages in the kernel environment does not
|
||||
pose additional risk to the web application's security.
|
||||
|
||||
### Encrypt internal connections with SSL/TLS
|
||||
|
||||
By default, all communication on the server, between the proxy, hub, and single
|
||||
-user notebooks is performed unencrypted. Setting the `internal_ssl` flag in
|
||||
`jupyterhub_config.py` secures the aforementioned routes. Turning this
|
||||
feature on does require that the enabled `Spawner` can use the certificates
|
||||
generated by the `Hub` (the default `LocalProcessSpawner` can, for instance).
|
||||
|
||||
It is also important to note that this encryption **does not** (yet) cover the
|
||||
`zmq tcp` sockets between the Notebook client and kernel. While users cannot
|
||||
submit arbitrary commands to another user's kernel, they can bind to these
|
||||
sockets and listen. When serving untrusted users, this eavesdropping can be
|
||||
mitigated by setting `KernelManager.transport` to `ipc`. This applies standard
|
||||
Unix permissions to the communication sockets thereby restricting
|
||||
communication to the socket owner. The `internal_ssl` option will eventually
|
||||
extend to securing the `tcp` sockets as well.
|
||||
|
||||
## Security audits
|
||||
|
||||
We recommend that you do periodic reviews of your deployment's security. It's
|
||||
|
@@ -166,7 +166,7 @@ startup
|
||||
statsd
|
||||
stdin
|
||||
stdout
|
||||
stoppped
|
||||
stopped
|
||||
subclasses
|
||||
subcommand
|
||||
subdomain
|
||||
@@ -210,4 +210,4 @@ Wildcards
|
||||
willingc
|
||||
wordlist
|
||||
Workflow
|
||||
workflow
|
||||
workflow
|
||||
|
@@ -9,6 +9,7 @@ problem and how to resolve it.
|
||||
- sudospawner fails to run
|
||||
- What is the default behavior when none of the lists (admin, whitelist,
|
||||
group whitelist) are set?
|
||||
- JupyterHub Docker container not accessible at localhost
|
||||
|
||||
[*Errors*](#errors)
|
||||
- 500 error after spawning my single-user server
|
||||
@@ -63,6 +64,17 @@ this to a particular set of users, and the admin_users lets you specify who
|
||||
among them may use the admin interface (not necessary, unless you need to do
|
||||
things like inspect other users' servers, or modify the userlist at runtime).
|
||||
|
||||
### JupyterHub Docker container not accessible at localhost
|
||||
|
||||
Even though the command to start your Docker container exposes port 8000
|
||||
(`docker run -p 8000:8000 -d --name jupyterhub jupyterhub/jupyterhub jupyterhub`),
|
||||
it is possible that the IP address itself is not accessible/visible. As a result
|
||||
when you try http://localhost:8000 in your browser, you are unable to connect
|
||||
even though the container is running properly. One workaround is to explicitly
|
||||
tell Jupyterhub to start at `0.0.0.0` which is visible to everyone. Try this
|
||||
command:
|
||||
`docker run -p 8000:8000 -d --name jupyterhub jupyterhub/jupyterhub jupyterhub --ip 0.0.0.0 --port 8000`
|
||||
|
||||
|
||||
## Errors
|
||||
|
||||
@@ -192,7 +204,7 @@ from there instead of the internet.
|
||||
For instance, you can install JupyterHub with pip and configurable-http-proxy
|
||||
with npmbox:
|
||||
|
||||
pip wheel jupyterhub
|
||||
python3 -m pip wheel jupyterhub
|
||||
npmbox configurable-http-proxy
|
||||
|
||||
### I want access to the whole filesystem, but still default users to their home directory
|
||||
@@ -224,7 +236,7 @@ then you can change the default URL to `/lab`.
|
||||
|
||||
For instance:
|
||||
|
||||
pip install jupyterlab
|
||||
python3 -m pip install jupyterlab
|
||||
jupyter serverextension enable --py jupyterlab --sys-prefix
|
||||
|
||||
The important thing is that jupyterlab is installed and enabled in the
|
||||
|
@@ -1,14 +0,0 @@
|
||||
Tutorials
|
||||
=========
|
||||
|
||||
This section provides links to documentation that helps a user do a specific
|
||||
task.
|
||||
|
||||
* :doc:`upgrade-dot-eight`
|
||||
* `Zero to JupyterHub with Kubernetes <https://zero-to-jupyterhub.readthedocs.io/en/latest/>`_
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:hidden:
|
||||
|
||||
upgrade-dot-eight
|
@@ -1,93 +0,0 @@
|
||||
.. upgrade-dot-eight:
|
||||
|
||||
Upgrading to JupyterHub version 0.8
|
||||
===================================
|
||||
|
||||
This document will assist you in upgrading an existing JupyterHub deployment
|
||||
from version 0.7 to version 0.8.
|
||||
|
||||
Upgrade checklist
|
||||
-----------------
|
||||
|
||||
0. Review the release notes. Review any deprecated features and pay attention
|
||||
to any backwards incompatible changes
|
||||
1. Backup JupyterHub database:
|
||||
- ``jupyterhub.sqlite`` when using the default sqlite database
|
||||
- Your JupyterHub database when using an RDBMS
|
||||
2. Backup the existing JupyterHub configuration file: ``jupyterhub_config.py``
|
||||
3. Shutdown the Hub
|
||||
4. Upgrade JupyterHub
|
||||
- ``pip install -U jupyterhub`` when using ``pip``
|
||||
- ``conda upgrade jupyterhub`` when using ``conda``
|
||||
5. Upgrade the database using run ```jupyterhub upgrade-db``
|
||||
6. Update the JupyterHub configuration file ``jupyterhub_config.py``
|
||||
|
||||
Backup JupyterHub database
|
||||
--------------------------
|
||||
|
||||
To prevent unintended loss of data or configuration information, you should
|
||||
back up the JupyterHub database (the default SQLite database or a RDBMS
|
||||
database using PostgreSQL, MySQL, or others supported by SQLAlchemy):
|
||||
|
||||
- If using the default SQLite database, back up the ``jupyterhub.sqlite``
|
||||
database.
|
||||
- If using an RDBMS database such as PostgreSQL, MySQL, or other supported by
|
||||
SQLAlchemy, back up the JupyterHub database.
|
||||
|
||||
.. note::
|
||||
|
||||
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 GitHub 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
|
||||
- user servers are stopped during upgrade
|
||||
- don't mind causing users to login again after upgrade
|
||||
|
||||
Backup JupyterHub configuration file
|
||||
------------------------------------
|
||||
|
||||
Backup up your configuration file, ``jupyterhub_config.py``, to a secure
|
||||
location.
|
||||
|
||||
Shutdown JupyterHub
|
||||
-------------------
|
||||
|
||||
- Prior to shutting down JupyterHub, you should notify the Hub users of the
|
||||
scheduled downtime.
|
||||
- Shutdown the JupyterHub service.
|
||||
|
||||
Upgrade JupyterHub
|
||||
------------------
|
||||
|
||||
Follow directions that correspond to your package manager, ``pip`` or ``conda``,
|
||||
for the new JupyterHub release:
|
||||
|
||||
- ``pip install -U jupyterhub`` for ``pip``
|
||||
- ``conda upgrade jupyterhub`` for ``conda``
|
||||
|
||||
Upgrade the proxy, authenticator, or spawner if needed.
|
||||
|
||||
Upgrade JupyterHub database
|
||||
---------------------------
|
||||
|
||||
To run the upgrade process for JupyterHub databases, enter::
|
||||
|
||||
jupyterhub upgrade-db
|
||||
|
||||
Update the JupyterHub configuration file
|
||||
----------------------------------------
|
||||
|
||||
Create a new JupyterHub configuration file or edit a copy of the existing
|
||||
file ``jupyterhub_config.py``.
|
||||
|
||||
Start JupyterHub
|
||||
----------------
|
||||
|
||||
Start JupyterHub with the same command that you used before the upgrade.
|
@@ -1,20 +1,25 @@
|
||||
"""autodoc extension for configurable traits"""
|
||||
|
||||
from traitlets import TraitType, Undefined
|
||||
from sphinx.domains.python import PyClassmember
|
||||
from sphinx.ext.autodoc import ClassDocumenter, AttributeDocumenter
|
||||
from sphinx.ext.autodoc import AttributeDocumenter
|
||||
from sphinx.ext.autodoc import ClassDocumenter
|
||||
from traitlets import TraitType
|
||||
from traitlets import Undefined
|
||||
|
||||
|
||||
class ConfigurableDocumenter(ClassDocumenter):
|
||||
"""Specialized Documenter subclass for traits with config=True"""
|
||||
|
||||
objtype = 'configurable'
|
||||
directivetype = 'class'
|
||||
|
||||
def get_object_members(self, want_all):
|
||||
"""Add traits with .tag(config=True) to members list"""
|
||||
check, members = super().get_object_members(want_all)
|
||||
get_traits = self.object.class_own_traits if self.options.inherited_members \
|
||||
else self.object.class_traits
|
||||
get_traits = (
|
||||
self.object.class_own_traits
|
||||
if self.options.inherited_members
|
||||
else self.object.class_traits
|
||||
)
|
||||
trait_members = []
|
||||
for name, trait in sorted(get_traits(config=True).items()):
|
||||
# put help in __doc__ where autodoc will look for it
|
||||
@@ -42,10 +47,7 @@ class TraitDocumenter(AttributeDocumenter):
|
||||
default_s = ''
|
||||
else:
|
||||
default_s = repr(default)
|
||||
sig = ' = {}({})'.format(
|
||||
self.object.__class__.__name__,
|
||||
default_s,
|
||||
)
|
||||
sig = ' = {}({})'.format(self.object.__class__.__name__, default_s)
|
||||
return super().add_directive_header(sig)
|
||||
|
||||
|
||||
|
@@ -59,7 +59,31 @@ def create_dir_hook(spawner):
|
||||
c.Spawner.pre_spawn_hook = create_dir_hook
|
||||
```
|
||||
|
||||
### Example #2 - Run a shell script
|
||||
### Example #2 - Run `mkhomedir_helper`
|
||||
|
||||
Many Linux distributions provide a script that is responsible for user homedir bootstrapping: `/sbin/mkhomedir_helper`. To make use of it, you can use
|
||||
|
||||
```python
|
||||
def create_dir_hook(spawner):
|
||||
username = spawner.user.name
|
||||
if not os.path.exists(os.path.join('/volumes/jupyterhub', username)):
|
||||
subprocess.call(["sudo", "/sbin/mkhomedir_helper", spawner.user.name])
|
||||
|
||||
# attach the hook function to the spawner
|
||||
c.Spawner.pre_spawn_hook = create_dir_hook
|
||||
```
|
||||
|
||||
and make sure to add
|
||||
|
||||
```
|
||||
jupyterhub ALL = (root) NOPASSWD: /sbin/mkhomedir_helper
|
||||
```
|
||||
|
||||
in a new file in `/etc/sudoers.d`, or simply in `/etc/sudoers`.
|
||||
|
||||
All new home directories will be created from `/etc/skel`, so make sure to place any custom homedir-contents in there.
|
||||
|
||||
### Example #3 - Run a shell script
|
||||
|
||||
You can specify a plain ole' shell script (or any other executable) to be run
|
||||
by the bootstrap process.
|
||||
@@ -94,7 +118,7 @@ Here's an example on what you could do in your shell script. See also
|
||||
|
||||
# - The first parameter for the Bootstrap Script is the USER.
|
||||
USER=$1
|
||||
if ["$USER" == ""]; then
|
||||
if [ "$USER" == "" ]; then
|
||||
exit 1
|
||||
fi
|
||||
# ----------------------------------------------------------------------------
|
||||
@@ -130,4 +154,4 @@ else
|
||||
fi
|
||||
|
||||
exit 0
|
||||
```
|
||||
```
|
||||
|
@@ -6,7 +6,7 @@
|
||||
|
||||
# - The first parameter for the Bootstrap Script is the USER.
|
||||
USER=$1
|
||||
if ["$USER" == ""]; then
|
||||
if [ "$USER" == "" ]; then
|
||||
exit 1
|
||||
fi
|
||||
# ----------------------------------------------------------------------------
|
||||
|
@@ -1,34 +1,44 @@
|
||||
# Example for a Spawner.pre_spawn_hook
|
||||
# create a directory for the user before the spawner starts
|
||||
|
||||
"""
|
||||
Example for a Spawner.pre_spawn_hook
|
||||
create a directory for the user before the spawner starts
|
||||
"""
|
||||
# pylint: disable=import-error
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from jupyter_client.localinterfaces import public_ips
|
||||
|
||||
|
||||
def create_dir_hook(spawner):
|
||||
username = spawner.user.name # get the username
|
||||
""" Create directory """
|
||||
username = spawner.user.name # get the username
|
||||
volume_path = os.path.join('/volumes/jupyterhub', username)
|
||||
if not os.path.exists(volume_path):
|
||||
os.mkdir(volume_path, 0o755)
|
||||
# now do whatever you think your user needs
|
||||
# ...
|
||||
|
||||
|
||||
def clean_dir_hook(spawner):
|
||||
username = spawner.user.name # get the username
|
||||
""" Delete directory """
|
||||
username = spawner.user.name # get the username
|
||||
temp_path = os.path.join('/volumes/jupyterhub', username, 'temp')
|
||||
if os.path.exists(temp_path) and os.path.isdir(temp_path):
|
||||
shutil.rmtree(temp_path)
|
||||
|
||||
|
||||
# attach the hook functions to the spawner
|
||||
# pylint: disable=undefined-variable
|
||||
c.Spawner.pre_spawn_hook = create_dir_hook
|
||||
c.Spawner.post_stop_hook = clean_dir_hook
|
||||
|
||||
# Use the DockerSpawner to serve your users' notebooks
|
||||
c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
|
||||
from jupyter_client.localinterfaces import public_ips
|
||||
c.JupyterHub.hub_ip = public_ips()[0]
|
||||
c.DockerSpawner.hub_ip_connect = public_ips()[0]
|
||||
c.DockerSpawner.container_ip = "0.0.0.0"
|
||||
|
||||
# You can now mount the volume to the docker container as we've
|
||||
# made sure the directory exists
|
||||
c.DockerSpawner.volumes = { '/volumes/jupyterhub/{username}/': '/home/jovyan/work' }
|
||||
|
||||
# pylint: disable=bad-whitespace
|
||||
c.DockerSpawner.volumes = {'/volumes/jupyterhub/{username}/': '/home/jovyan/work'}
|
||||
|
@@ -15,7 +15,7 @@ c.JupyterHub.services = [
|
||||
{
|
||||
'name': 'cull-idle',
|
||||
'admin': True,
|
||||
'command': 'python3 cull_idle_servers.py --timeout=3600'.split(),
|
||||
'command': [sys.executable, 'cull_idle_servers.py', '--timeout=3600'],
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -36,6 +36,6 @@ Generate an API token and store it in the `JUPYTERHUB_API_TOKEN` environment
|
||||
variable. Run `cull_idle_servers.py` manually.
|
||||
|
||||
```bash
|
||||
export JUPYTERHUB_API_TOKEN=`jupyterhub token`
|
||||
export JUPYTERHUB_API_TOKEN=$(jupyterhub token)
|
||||
python3 cull_idle_servers.py [--timeout=900] [--url=http://127.0.0.1:8081/hub/api]
|
||||
```
|
||||
|
@@ -16,13 +16,13 @@ You can run this as a service managed by JupyterHub with this in your config::
|
||||
{
|
||||
'name': 'cull-idle',
|
||||
'admin': True,
|
||||
'command': 'python3 cull_idle_servers.py --timeout=3600'.split(),
|
||||
'command': [sys.executable, 'cull_idle_servers.py', '--timeout=3600'],
|
||||
}
|
||||
]
|
||||
|
||||
Or run it manually by generating an API token and storing it in `JUPYTERHUB_API_TOKEN`:
|
||||
|
||||
export JUPYTERHUB_API_TOKEN=`jupyterhub token`
|
||||
export JUPYTERHUB_API_TOKEN=$(jupyterhub token)
|
||||
python3 cull_idle_servers.py [--timeout=900] [--url=http://127.0.0.1:8081/hub/api]
|
||||
|
||||
This script uses the same ``--timeout`` and ``--max-age`` values for
|
||||
@@ -31,11 +31,11 @@ users and servers, you should add this script to the services list
|
||||
twice, just with different ``name``s, different values, and one with
|
||||
the ``--cull-users`` option.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from functools import partial
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from datetime import timezone
|
||||
from functools import partial
|
||||
|
||||
try:
|
||||
from urllib.parse import quote
|
||||
@@ -85,23 +85,21 @@ def format_td(td):
|
||||
|
||||
|
||||
@coroutine
|
||||
def cull_idle(url, api_token, inactive_limit, cull_users=False, max_age=0, concurrency=10):
|
||||
def cull_idle(
|
||||
url, api_token, inactive_limit, cull_users=False, max_age=0, concurrency=10
|
||||
):
|
||||
"""Shutdown idle single-user servers
|
||||
|
||||
If cull_users, inactive *users* will be deleted as well.
|
||||
"""
|
||||
auth_header = {
|
||||
'Authorization': 'token %s' % api_token,
|
||||
}
|
||||
req = HTTPRequest(
|
||||
url=url + '/users',
|
||||
headers=auth_header,
|
||||
)
|
||||
auth_header = {'Authorization': 'token %s' % api_token}
|
||||
req = HTTPRequest(url=url + '/users', headers=auth_header)
|
||||
now = datetime.now(timezone.utc)
|
||||
client = AsyncHTTPClient()
|
||||
|
||||
if concurrency:
|
||||
semaphore = Semaphore(concurrency)
|
||||
|
||||
@coroutine
|
||||
def fetch(req):
|
||||
"""client.fetch wrapped in a semaphore to limit concurrency"""
|
||||
@@ -110,6 +108,7 @@ def cull_idle(url, api_token, inactive_limit, cull_users=False, max_age=0, concu
|
||||
return (yield client.fetch(req))
|
||||
finally:
|
||||
yield semaphore.release()
|
||||
|
||||
else:
|
||||
fetch = client.fetch
|
||||
|
||||
@@ -129,8 +128,8 @@ def cull_idle(url, api_token, inactive_limit, cull_users=False, max_age=0, concu
|
||||
log_name = '%s/%s' % (user['name'], server_name)
|
||||
if server.get('pending'):
|
||||
app_log.warning(
|
||||
"Not culling server %s with pending %s",
|
||||
log_name, server['pending'])
|
||||
"Not culling server %s with pending %s", log_name, server['pending']
|
||||
)
|
||||
return False
|
||||
|
||||
# jupyterhub < 0.9 defined 'server.url' once the server was ready
|
||||
@@ -142,8 +141,8 @@ def cull_idle(url, api_token, inactive_limit, cull_users=False, max_age=0, concu
|
||||
|
||||
if not server.get('ready', bool(server['url'])):
|
||||
app_log.warning(
|
||||
"Not culling not-ready not-pending server %s: %s",
|
||||
log_name, server)
|
||||
"Not culling not-ready not-pending server %s: %s", log_name, server
|
||||
)
|
||||
return False
|
||||
|
||||
if server.get('started'):
|
||||
@@ -163,12 +162,13 @@ def cull_idle(url, api_token, inactive_limit, cull_users=False, max_age=0, concu
|
||||
# for running servers
|
||||
inactive = age
|
||||
|
||||
should_cull = (inactive is not None and
|
||||
inactive.total_seconds() >= inactive_limit)
|
||||
should_cull = (
|
||||
inactive is not None and inactive.total_seconds() >= inactive_limit
|
||||
)
|
||||
if should_cull:
|
||||
app_log.info(
|
||||
"Culling server %s (inactive for %s)",
|
||||
log_name, format_td(inactive))
|
||||
"Culling server %s (inactive for %s)", log_name, format_td(inactive)
|
||||
)
|
||||
|
||||
if max_age and not should_cull:
|
||||
# only check started if max_age is specified
|
||||
@@ -177,26 +177,34 @@ def cull_idle(url, api_token, inactive_limit, cull_users=False, max_age=0, concu
|
||||
if age is not None and age.total_seconds() >= max_age:
|
||||
app_log.info(
|
||||
"Culling server %s (age: %s, inactive for %s)",
|
||||
log_name, format_td(age), format_td(inactive))
|
||||
log_name,
|
||||
format_td(age),
|
||||
format_td(inactive),
|
||||
)
|
||||
should_cull = True
|
||||
|
||||
if not should_cull:
|
||||
app_log.debug(
|
||||
"Not culling server %s (age: %s, inactive for %s)",
|
||||
log_name, format_td(age), format_td(inactive))
|
||||
log_name,
|
||||
format_td(age),
|
||||
format_td(inactive),
|
||||
)
|
||||
return False
|
||||
|
||||
req = HTTPRequest(
|
||||
url=url + '/users/%s/server' % quote(user['name']),
|
||||
method='DELETE',
|
||||
headers=auth_header,
|
||||
)
|
||||
if server_name:
|
||||
# culling a named server
|
||||
delete_url = url + "/users/%s/servers/%s" % (
|
||||
quote(user['name']),
|
||||
quote(server['name']),
|
||||
)
|
||||
else:
|
||||
delete_url = url + '/users/%s/server' % quote(user['name'])
|
||||
|
||||
req = HTTPRequest(url=delete_url, method='DELETE', headers=auth_header)
|
||||
resp = yield fetch(req)
|
||||
if resp.code == 202:
|
||||
app_log.warning(
|
||||
"Server %s is slow to stop",
|
||||
log_name,
|
||||
)
|
||||
app_log.warning("Server %s is slow to stop", log_name)
|
||||
# return False to prevent culling user with pending shutdowns
|
||||
return False
|
||||
return True
|
||||
@@ -239,7 +247,9 @@ def cull_idle(url, api_token, inactive_limit, cull_users=False, max_age=0, concu
|
||||
if still_alive:
|
||||
app_log.debug(
|
||||
"Not culling user %s with %i servers still alive",
|
||||
user['name'], still_alive)
|
||||
user['name'],
|
||||
still_alive,
|
||||
)
|
||||
return False
|
||||
|
||||
should_cull = False
|
||||
@@ -259,12 +269,11 @@ def cull_idle(url, api_token, inactive_limit, cull_users=False, max_age=0, concu
|
||||
# which introduces the 'created' field which is never None
|
||||
inactive = age
|
||||
|
||||
should_cull = (inactive is not None and
|
||||
inactive.total_seconds() >= inactive_limit)
|
||||
should_cull = (
|
||||
inactive is not None and inactive.total_seconds() >= inactive_limit
|
||||
)
|
||||
if should_cull:
|
||||
app_log.info(
|
||||
"Culling user %s (inactive for %s)",
|
||||
user['name'], inactive)
|
||||
app_log.info("Culling user %s (inactive for %s)", user['name'], inactive)
|
||||
|
||||
if max_age and not should_cull:
|
||||
# only check created if max_age is specified
|
||||
@@ -273,19 +282,23 @@ def cull_idle(url, api_token, inactive_limit, cull_users=False, max_age=0, concu
|
||||
if age is not None and age.total_seconds() >= max_age:
|
||||
app_log.info(
|
||||
"Culling user %s (age: %s, inactive for %s)",
|
||||
user['name'], format_td(age), format_td(inactive))
|
||||
user['name'],
|
||||
format_td(age),
|
||||
format_td(inactive),
|
||||
)
|
||||
should_cull = True
|
||||
|
||||
if not should_cull:
|
||||
app_log.debug(
|
||||
"Not culling user %s (created: %s, last active: %s)",
|
||||
user['name'], format_td(age), format_td(inactive))
|
||||
user['name'],
|
||||
format_td(age),
|
||||
format_td(inactive),
|
||||
)
|
||||
return False
|
||||
|
||||
req = HTTPRequest(
|
||||
url=url + '/users/%s' % user['name'],
|
||||
method='DELETE',
|
||||
headers=auth_header,
|
||||
url=url + '/users/%s' % user['name'], method='DELETE', headers=auth_header
|
||||
)
|
||||
yield fetch(req)
|
||||
return True
|
||||
@@ -310,21 +323,31 @@ if __name__ == '__main__':
|
||||
help="The JupyterHub API URL",
|
||||
)
|
||||
define('timeout', default=600, help="The idle timeout (in seconds)")
|
||||
define('cull_every', default=0,
|
||||
help="The interval (in seconds) for checking for idle servers to cull")
|
||||
define('max_age', default=0,
|
||||
help="The maximum age (in seconds) of servers that should be culled even if they are active")
|
||||
define('cull_users', default=False,
|
||||
help="""Cull users in addition to servers.
|
||||
define(
|
||||
'cull_every',
|
||||
default=0,
|
||||
help="The interval (in seconds) for checking for idle servers to cull",
|
||||
)
|
||||
define(
|
||||
'max_age',
|
||||
default=0,
|
||||
help="The maximum age (in seconds) of servers that should be culled even if they are active",
|
||||
)
|
||||
define(
|
||||
'cull_users',
|
||||
default=False,
|
||||
help="""Cull users in addition to servers.
|
||||
This is for use in temporary-user cases such as tmpnb.""",
|
||||
)
|
||||
define('concurrency', default=10,
|
||||
help="""Limit the number of concurrent requests made to the Hub.
|
||||
)
|
||||
define(
|
||||
'concurrency',
|
||||
default=10,
|
||||
help="""Limit the number of concurrent requests made to the Hub.
|
||||
|
||||
Deleting a lot of users at the same time can slow down the Hub,
|
||||
so limit the number of API requests we have outstanding at any given time.
|
||||
"""
|
||||
)
|
||||
""",
|
||||
)
|
||||
|
||||
parse_command_line()
|
||||
if not options.cull_every:
|
||||
@@ -337,7 +360,8 @@ if __name__ == '__main__':
|
||||
app_log.warning(
|
||||
"Could not load pycurl: %s\n"
|
||||
"pycurl is recommended if you have a large number of users.",
|
||||
e)
|
||||
e,
|
||||
)
|
||||
|
||||
loop = IOLoop.current()
|
||||
cull = partial(
|
||||
|
@@ -1,8 +1,11 @@
|
||||
import sys
|
||||
|
||||
# run cull-idle as a service
|
||||
|
||||
c.JupyterHub.services = [
|
||||
{
|
||||
'name': 'cull-idle',
|
||||
'admin': True,
|
||||
'command': 'python3 cull_idle_servers.py --timeout=3600'.split(),
|
||||
'command': [sys.executable, 'cull_idle_servers.py', '--timeout=3600'],
|
||||
}
|
||||
]
|
||||
|
@@ -5,7 +5,7 @@ for external services that may not be otherwise integrated with JupyterHub.
|
||||
The main feature this enables is using JupyterHub like a 'regular' OAuth 2
|
||||
provider for services running anywhere.
|
||||
|
||||
There are two examples here. `whoami-oauth` uses `jupyterhub.services.HubOAuthenticated`
|
||||
There are two examples here. `whoami-oauth` (in the service-whoami directory) uses `jupyterhub.services.HubOAuthenticated`
|
||||
to authenticate requests with the Hub for a service run on its own host.
|
||||
This is an implementation of OAuth 2.0 provided by the jupyterhub package,
|
||||
which configures all of the necessary URLs from environment variables.
|
||||
@@ -18,7 +18,7 @@ implementations in other web servers or languages.
|
||||
|
||||
1. generate an API token:
|
||||
|
||||
export JUPYTERHUB_API_TOKEN=`openssl rand -hex 32`
|
||||
export JUPYTERHUB_API_TOKEN=$(openssl rand -hex 32)
|
||||
|
||||
2. launch a version of the the whoami service.
|
||||
For `whoami-oauth`:
|
||||
|
@@ -4,7 +4,9 @@ import os
|
||||
# this could come from anywhere
|
||||
api_token = os.getenv("JUPYTERHUB_API_TOKEN")
|
||||
if not api_token:
|
||||
raise ValueError("Make sure to `export JUPYTERHUB_API_TOKEN=$(openssl rand -hex 32)`")
|
||||
raise ValueError(
|
||||
"Make sure to `export JUPYTERHUB_API_TOKEN=$(openssl rand -hex 32)`"
|
||||
)
|
||||
|
||||
# tell JupyterHub to register the service as an external oauth client
|
||||
|
||||
@@ -14,5 +16,5 @@ c.JupyterHub.services = [
|
||||
'oauth_client_id': "whoami-oauth-client-test",
|
||||
'api_token': api_token,
|
||||
'oauth_redirect_uri': 'http://127.0.0.1:5555/oauth_callback',
|
||||
},
|
||||
}
|
||||
]
|
||||
|
@@ -18,4 +18,4 @@ export JUPYTERHUB_OAUTH_CALLBACK_URL="$JUPYTERHUB_SERVICE_URL/oauth_callback"
|
||||
export JUPYTERHUB_HOST='http://127.0.0.1:8000'
|
||||
|
||||
# launch the service
|
||||
exec python3 whoami-oauth.py
|
||||
exec python3 ../service-whoami/whoami-oauth.py
|
||||
|
@@ -3,18 +3,19 @@
|
||||
Implements OAuth handshake manually
|
||||
so all URLs and requests necessary for OAuth with JupyterHub should be in one place
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from urllib.parse import urlencode, urlparse
|
||||
from urllib.parse import urlencode
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from tornado.auth import OAuth2Mixin
|
||||
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
|
||||
from tornado.httputil import url_concat
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado import log
|
||||
from tornado import web
|
||||
from tornado.auth import OAuth2Mixin
|
||||
from tornado.httpclient import AsyncHTTPClient
|
||||
from tornado.httpclient import HTTPRequest
|
||||
from tornado.httputil import url_concat
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
|
||||
class JupyterHubLoginHandler(web.RequestHandler):
|
||||
@@ -32,11 +33,11 @@ class JupyterHubLoginHandler(web.RequestHandler):
|
||||
code=code,
|
||||
redirect_uri=self.settings['redirect_uri'],
|
||||
)
|
||||
req = HTTPRequest(self.settings['token_url'], method='POST',
|
||||
body=urlencode(params).encode('utf8'),
|
||||
headers={
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
req = HTTPRequest(
|
||||
self.settings['token_url'],
|
||||
method='POST',
|
||||
body=urlencode(params).encode('utf8'),
|
||||
headers={'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
)
|
||||
response = await AsyncHTTPClient().fetch(req)
|
||||
data = json.loads(response.body.decode('utf8', 'replace'))
|
||||
@@ -55,14 +56,16 @@ class JupyterHubLoginHandler(web.RequestHandler):
|
||||
# we are the login handler,
|
||||
# begin oauth process which will come back later with an
|
||||
# authorization_code
|
||||
self.redirect(url_concat(
|
||||
self.settings['authorize_url'],
|
||||
dict(
|
||||
redirect_uri=self.settings['redirect_uri'],
|
||||
client_id=self.settings['client_id'],
|
||||
response_type='code',
|
||||
self.redirect(
|
||||
url_concat(
|
||||
self.settings['authorize_url'],
|
||||
dict(
|
||||
redirect_uri=self.settings['redirect_uri'],
|
||||
client_id=self.settings['client_id'],
|
||||
response_type='code',
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
|
||||
class WhoAmIHandler(web.RequestHandler):
|
||||
@@ -85,10 +88,7 @@ class WhoAmIHandler(web.RequestHandler):
|
||||
"""Retrieve the user for a given token, via /hub/api/user"""
|
||||
|
||||
req = HTTPRequest(
|
||||
self.settings['user_url'],
|
||||
headers={
|
||||
'Authorization': f'token {token}'
|
||||
},
|
||||
self.settings['user_url'], headers={'Authorization': f'token {token}'}
|
||||
)
|
||||
response = await AsyncHTTPClient().fetch(req)
|
||||
return json.loads(response.body.decode('utf8', 'replace'))
|
||||
@@ -110,23 +110,23 @@ def main():
|
||||
token_url = hub_api + '/oauth2/token'
|
||||
user_url = hub_api + '/user'
|
||||
|
||||
app = web.Application([
|
||||
('/oauth_callback', JupyterHubLoginHandler),
|
||||
('/', WhoAmIHandler),
|
||||
],
|
||||
app = web.Application(
|
||||
[('/oauth_callback', JupyterHubLoginHandler), ('/', WhoAmIHandler)],
|
||||
login_url='/oauth_callback',
|
||||
cookie_secret=os.urandom(32),
|
||||
api_token=os.environ['JUPYTERHUB_API_TOKEN'],
|
||||
client_id=os.environ['JUPYTERHUB_CLIENT_ID'],
|
||||
redirect_uri=os.environ['JUPYTERHUB_SERVICE_URL'].rstrip('/') + '/oauth_callback',
|
||||
redirect_uri=os.environ['JUPYTERHUB_SERVICE_URL'].rstrip('/')
|
||||
+ '/oauth_callback',
|
||||
authorize_url=authorize_url,
|
||||
token_url=token_url,
|
||||
user_url=user_url,
|
||||
)
|
||||
|
||||
url = urlparse(os.environ['JUPYTERHUB_SERVICE_URL'])
|
||||
log.app_log.info("Running basic whoami service on %s",
|
||||
os.environ['JUPYTERHUB_SERVICE_URL'])
|
||||
log.app_log.info(
|
||||
"Running basic whoami service on %s", os.environ['JUPYTERHUB_SERVICE_URL']
|
||||
)
|
||||
app.listen(url.port, url.hostname)
|
||||
IOLoop.current().start()
|
||||
|
||||
|
@@ -1,46 +0,0 @@
|
||||
"""An example service authenticating with the Hub.
|
||||
|
||||
This example service serves `/services/whoami/`,
|
||||
authenticated with the Hub,
|
||||
showing the user their own info.
|
||||
"""
|
||||
from getpass import getuser
|
||||
import json
|
||||
import os
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado import log
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.web import RequestHandler, Application, authenticated
|
||||
|
||||
from jupyterhub.services.auth import HubOAuthenticated, HubOAuthCallbackHandler
|
||||
from jupyterhub.utils import url_path_join
|
||||
|
||||
class WhoAmIHandler(HubOAuthenticated, RequestHandler):
|
||||
hub_users = {getuser()} # the users allowed to access this service
|
||||
|
||||
@authenticated
|
||||
def get(self):
|
||||
user_model = self.get_current_user()
|
||||
self.set_header('content-type', 'application/json')
|
||||
self.write(json.dumps(user_model, indent=1, sort_keys=True))
|
||||
|
||||
def main():
|
||||
log.enable_pretty_logging()
|
||||
app = Application([
|
||||
(os.environ['JUPYTERHUB_SERVICE_PREFIX'], WhoAmIHandler),
|
||||
(url_path_join(os.environ['JUPYTERHUB_SERVICE_PREFIX'], 'oauth_callback'), HubOAuthCallbackHandler),
|
||||
(r'.*', WhoAmIHandler),
|
||||
], cookie_secret=os.urandom(32))
|
||||
|
||||
http_server = HTTPServer(app)
|
||||
url = urlparse(os.environ['JUPYTERHUB_SERVICE_URL'])
|
||||
log.app_log.info("Running whoami service on %s", os.environ['JUPYTERHUB_SERVICE_URL'])
|
||||
|
||||
http_server.listen(url.port, url.hostname)
|
||||
|
||||
IOLoop.current().start()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@@ -8,10 +8,10 @@ c.Authenticator.whitelist = {'ganymede', 'io', 'rhea'}
|
||||
|
||||
# These environment variables are automatically supplied by the linked postgres
|
||||
# container.
|
||||
import os;
|
||||
import os
|
||||
|
||||
pg_pass = os.getenv('POSTGRES_ENV_JPY_PSQL_PASSWORD')
|
||||
pg_host = os.getenv('POSTGRES_PORT_5432_TCP_ADDR')
|
||||
c.JupyterHub.db_url = 'postgresql://jupyterhub:{}@{}:5432/jupyterhub'.format(
|
||||
pg_pass,
|
||||
pg_host,
|
||||
pg_pass, pg_host
|
||||
)
|
||||
|
60
examples/service-announcement/README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
# Simple Announcement Service Example
|
||||
|
||||
This is a simple service that allows administrators to manage announcements
|
||||
that appear when JupyterHub renders pages.
|
||||
|
||||
To run the service as a hub-managed service simply include in your JupyterHub
|
||||
configuration file something like:
|
||||
|
||||
c.JupyterHub.services = [
|
||||
{
|
||||
'name': 'announcement',
|
||||
'url': 'http://127.0.0.1:8888',
|
||||
'command': [sys.executable, "-m", "announcement"],
|
||||
}
|
||||
]
|
||||
|
||||
This starts the announcements service up at `/services/announcement` when
|
||||
JupyterHub launches. By default the announcement text is empty.
|
||||
|
||||
The `announcement` module has a configurable port (default 8888) and an API
|
||||
prefix setting. By default the API prefix is `JUPYTERHUB_SERVICE_PREFIX` if
|
||||
that environment variable is set or `/` if it is not.
|
||||
|
||||
## Managing the Announcement
|
||||
|
||||
Admin users can set the announcement text with an API token:
|
||||
|
||||
$ curl -X POST -H "Authorization: token <token>" \
|
||||
-d "{'announcement':'JupyterHub will be upgraded on August 14!'}" \
|
||||
https://.../services/announcement
|
||||
|
||||
Anyone can read the announcement:
|
||||
|
||||
$ curl https://.../services/announcement | python -m json.tool
|
||||
{
|
||||
announcement: "JupyterHub will be upgraded on August 14!",
|
||||
timestamp: "...",
|
||||
user: "..."
|
||||
}
|
||||
|
||||
The time the announcement was posted is recorded in the `timestamp` field and
|
||||
the user who posted the announcement is recorded in the `user` field.
|
||||
|
||||
To clear the announcement text, just DELETE. Only admin users can do this.
|
||||
|
||||
$ curl -X POST -H "Authorization: token <token>" \
|
||||
https://.../services/announcement
|
||||
|
||||
## Seeing the Announcement in JupyterHub
|
||||
|
||||
To be able to render the announcement, include the provide `page.html` template
|
||||
that extends the base `page.html` template. Set `c.JupyterHub.template_paths`
|
||||
in JupyterHub's configuration to include the path to the extending template.
|
||||
The template changes the `announcement` element and does a JQuery `$.get()` call
|
||||
to retrieve the announcement text.
|
||||
|
||||
JupyterHub's configurable announcement template variables can be set for various
|
||||
pages like login, logout, spawn, and home. Including the template provided in
|
||||
this example overrides all of those.
|
77
examples/service-announcement/announcement.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
|
||||
from tornado import escape
|
||||
from tornado import gen
|
||||
from tornado import ioloop
|
||||
from tornado import web
|
||||
|
||||
from jupyterhub.services.auth import HubAuthenticated
|
||||
|
||||
|
||||
class AnnouncementRequestHandler(HubAuthenticated, web.RequestHandler):
|
||||
"""Dynamically manage page announcements"""
|
||||
|
||||
hub_users = []
|
||||
allow_admin = True
|
||||
|
||||
def initialize(self, storage):
|
||||
"""Create storage for announcement text"""
|
||||
self.storage = storage
|
||||
|
||||
@web.authenticated
|
||||
def post(self):
|
||||
"""Update announcement"""
|
||||
user = self.get_current_user()
|
||||
doc = escape.json_decode(self.request.body)
|
||||
self.storage["announcement"] = doc["announcement"]
|
||||
self.storage["timestamp"] = datetime.datetime.now().isoformat()
|
||||
self.storage["user"] = user["name"]
|
||||
self.write_to_json(self.storage)
|
||||
|
||||
def get(self):
|
||||
"""Retrieve announcement"""
|
||||
self.write_to_json(self.storage)
|
||||
|
||||
@web.authenticated
|
||||
def delete(self):
|
||||
"""Clear announcement"""
|
||||
self.storage["announcement"] = ""
|
||||
self.write_to_json(self.storage)
|
||||
|
||||
def write_to_json(self, doc):
|
||||
"""Write dictionary document as JSON"""
|
||||
self.set_header("Content-Type", "application/json; charset=UTF-8")
|
||||
self.write(escape.utf8(json.dumps(doc)))
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_arguments()
|
||||
application = create_application(**vars(args))
|
||||
application.listen(args.port)
|
||||
ioloop.IOLoop.current().start()
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--api-prefix",
|
||||
"-a",
|
||||
default=os.environ.get("JUPYTERHUB_SERVICE_PREFIX", "/"),
|
||||
help="application API prefix",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--port", "-p", default=8888, help="port for API to listen on", type=int
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def create_application(api_prefix="/", handler=AnnouncementRequestHandler, **kwargs):
|
||||
storage = dict(announcement="", timestamp="", user="")
|
||||
return web.Application([(api_prefix, handler, dict(storage=storage))])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
16
examples/service-announcement/jupyterhub_config.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import sys
|
||||
|
||||
# To run the announcement service managed by the hub, add this.
|
||||
|
||||
c.JupyterHub.services = [
|
||||
{
|
||||
'name': 'announcement',
|
||||
'url': 'http://127.0.0.1:8888',
|
||||
'command': [sys.executable, "-m", "announcement"],
|
||||
}
|
||||
]
|
||||
|
||||
# The announcements need to get on the templates somehow, see page.html
|
||||
# for an example of how to do this.
|
||||
|
||||
c.JupyterHub.template_paths = ["templates"]
|
14
examples/service-announcement/templates/page.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends "templates/page.html" %}
|
||||
{% block announcement %}
|
||||
<div class="container text-center announcement">
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
$.get("/services/announcement/", function(data) {
|
||||
$(".announcement").html(data["announcement"]);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@@ -22,4 +22,3 @@ In the external example, some extra steps are required to set up supervisor:
|
||||
3. install `shared-notebook-service` somewhere on your system, and update `/path/to/shared-notebook-service` to the absolute path of this destination
|
||||
3. copy `shared-notebook.conf` to `/etc/supervisor/conf.d/`
|
||||
4. `supervisorctl reload`
|
||||
|
||||
|
@@ -1,18 +1,9 @@
|
||||
# our user list
|
||||
c.Authenticator.whitelist = [
|
||||
'minrk',
|
||||
'ellisonbg',
|
||||
'willingc',
|
||||
]
|
||||
c.Authenticator.whitelist = ['minrk', 'ellisonbg', 'willingc']
|
||||
|
||||
# ellisonbg and willingc have access to a shared server:
|
||||
|
||||
c.JupyterHub.load_groups = {
|
||||
'shared': [
|
||||
'ellisonbg',
|
||||
'willingc',
|
||||
]
|
||||
}
|
||||
c.JupyterHub.load_groups = {'shared': ['ellisonbg', 'willingc']}
|
||||
|
||||
# start the notebook server as a service
|
||||
c.JupyterHub.services = [
|
||||
@@ -21,4 +12,4 @@ c.JupyterHub.services = [
|
||||
'url': 'http://127.0.0.1:9999',
|
||||
'api_token': 'super-secret',
|
||||
}
|
||||
]
|
||||
]
|
||||
|