Compare commits
1024 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a50409bdca | |||
| 60a722e6cc | |||
| 4d6cafa589 | |||
| 63e557430b | |||
| 04acb4423d | |||
| ea813acf4c | |||
| b1198dfdbf | |||
| 4342de3747 | |||
| ef8bc7e7e9 | |||
| e18e401f6b | |||
| ab998c47e8 | |||
| 9fb830b2e1 | |||
| 415c3f90a1 | |||
| 60c8ff7fb1 | |||
| 037816313c | |||
| 3d285d1ac6 | |||
| 135338786f | |||
| 661f1fce31 | |||
| 03664ef784 | |||
| d2111ef2b6 | |||
| e0df19c888 | |||
| 6a523606ca | |||
| b6cd40e63c | |||
| b421866bf5 | |||
| fe06075816 | |||
| 2b73eb90ec | |||
| 5555321cf5 | |||
| f087ebbee0 | |||
| d04f64d3d4 | |||
| 777a5a0929 | |||
| 6c297f890e | |||
| 3c8d0b1b37 | |||
| 74f2cd156f | |||
| a9fdffa9af | |||
| e6f8e8eb94 | |||
| 1bd89ca055 | |||
| 0e226d0314 | |||
| e8d4e2c792 | |||
| 4cfbed8273 | |||
| 0410ac9780 | |||
| 82fcf6a770 | |||
| a1332865c0 | |||
| ae0e4de93e | |||
| 02a6525558 | |||
| 5afef14760 | |||
| 890d589a36 | |||
| 89a50c4b83 | |||
| da5cd2b62c | |||
| 57321624aa | |||
| 876ae822b2 | |||
| 1ceb75868b | |||
| 98ad16f943 | |||
| 9363746c1a | |||
| 7a1b9ab94c | |||
| 46d6b5b81f | |||
| 7e8757a78c | |||
| e508b25ecd | |||
| 3fdc10c523 | |||
| 717953c162 | |||
| daa34c3b4d | |||
| bf5c78d819 | |||
| 1763144278 | |||
| 2f598529fc | |||
| 8264e69e2f | |||
| b0638df94e | |||
| bb61eee557 | |||
| 39c39b861d | |||
| e3deda4ef3 | |||
| 7e44e7de82 | |||
| 9dd0518c00 | |||
| 81313d1c40 | |||
| 2ceccc4557 | |||
| 1c36918e92 | |||
| 8d93df23c1 | |||
| 0c06b34a2c | |||
| fe980eab7f | |||
| 979b903bf2 | |||
| 4b8ee0934a | |||
| 0439725790 | |||
| 4b3ef33989 | |||
| 9e99d51853 | |||
| 00a9fa8f34 | |||
| 84a35343d1 | |||
| 397bd17c55 | |||
| c8e377a9bd | |||
| 90e3138bae | |||
| 24b32a763b | |||
| 69a12d36ef | |||
| 1485718fa6 | |||
| 750f03d9de | |||
| b5ddf1d24d | |||
| 043a35111d | |||
| 676457b589 | |||
| e61f11be81 | |||
| 101a44affd | |||
| 7995c664ed | |||
| 6023c0e5dc | |||
| d49d76c1ee | |||
| 77ef212daa | |||
| 7aa80193c0 | |||
| 5632c74556 | |||
| 7a08745af1 | |||
| d9ba0858c7 | |||
| 617e51d294 | |||
| c07d322fff | |||
| 9b8fa8a772 | |||
| c351242af7 | |||
| 55245557f5 | |||
| ee1cef3ee8 | |||
| 5d51a7178f | |||
| 9d52397bcc | |||
| 5098fbe061 | |||
| 7062aa4ac7 | |||
| d6fec4f2b9 | |||
| 86ef462c76 | |||
| c76e7a3f63 | |||
| 2516a08659 | |||
| 562fe30333 | |||
| 4e0eed4bb2 | |||
| b604caec72 | |||
| 6b409e9089 | |||
| 015d434358 | |||
| c8e448cb84 | |||
| 03924be491 | |||
| 2729cecf4a | |||
| 32e2377828 | |||
| fdb8139b03 | |||
| 4b25c8a5ad | |||
| ae930a7fe8 | |||
| 3b9144ba4d | |||
| be6ea3d4c1 | |||
| a2983e58b5 | |||
| a99e86a5df | |||
| 906ad80069 | |||
| ac65f765e5 | |||
| c5bfe82315 | |||
| 7035b3c18a | |||
| 2108c61d97 | |||
| 2bdbb47286 | |||
| 333b8970b8 | |||
| 5673cfe2be | |||
| 4429239dbc | |||
| b6ab9aa9f5 | |||
| 84bde6327f | |||
| d6f49eb54f | |||
| 3c8c5e158b | |||
| b3045b796f | |||
| c0febacc30 | |||
| f8ada91dc5 | |||
| d0e2ce9a9e | |||
| e157608992 | |||
| 8dbe0ddaf3 | |||
| b0cb18539c | |||
| 97b6d76694 | |||
| 9de6c8ee2b | |||
| cd28b1106b | |||
| b3a5dafee0 | |||
| eb4ab8defd | |||
| 639744e9cb | |||
| 6a942ab27a | |||
| 278f1d6d24 | |||
| 563eeca1a9 | |||
| 7a9c954646 | |||
| d768c36afb | |||
| 36ae3b267d | |||
| cd60f394d3 | |||
| 9aba90a6f7 | |||
| 68a8155f49 | |||
| 16695fd4ec | |||
| 9b6c6dc709 | |||
| 7923ed4f0d | |||
| 0b3d1c855c | |||
| d8273719d2 | |||
| c6d2c39ff7 | |||
| 6960afdf0b | |||
| 3a5000ab1d | |||
| 98951bab9e | |||
| 96fc3b8612 | |||
| 2b345b6c2d | |||
| 504662b466 | |||
| f56e6edbe4 | |||
| 191b84d389 | |||
| 8a4350d22e | |||
| cc6dae0f9e | |||
| 58528450e2 | |||
| ebf3559e60 | |||
| 57d20b2b32 | |||
| fd27240b26 | |||
| cad69d335c | |||
| 1f08cca355 | |||
| 7f4f525551 | |||
| b0037b6141 | |||
| 7956c8f58d | |||
| 330c9054b4 | |||
| d444d8552e | |||
| 595bf583c7 | |||
| 3386b99a29 | |||
| 5fd667cdaf | |||
| 4217db9e18 | |||
| b4717e2edb | |||
| 1d5465f21e | |||
| 2f1998fa67 | |||
| a7e998c030 | |||
| 8cc15726ec | |||
| 62e59868b4 | |||
| a64027f4af | |||
| f5a02930ec | |||
| 530ca20ee2 | |||
| f3b84ece3d | |||
| ca2d5957e4 | |||
| 7837214276 | |||
| 994202ca94 | |||
| ff7ceb1442 | |||
| 56545b7f41 | |||
| 586e78dfea | |||
| 92ede4c242 | |||
| 5ca2c2d564 | |||
| 9692aa3c08 | |||
| 10ad1028ae | |||
| 7155856b08 | |||
| 69aa771d44 | |||
| d164b5ae3a | |||
| b34d09f547 | |||
| 9e2850ffad | |||
| 480cface63 | |||
| 85aba589b8 | |||
| e890140aa9 | |||
| 53d56ef3a0 | |||
| b91674799b | |||
| 4bb864e2ac | |||
| 7db091525e | |||
| 695923ed75 | |||
| 1b43ccca6f | |||
| 96a0bad149 | |||
| 243ade15e1 | |||
| 9d3cf990d1 | |||
| 02bcff2223 | |||
| 8f388c86a6 | |||
| 8dc929f0ff | |||
| 509bd7e79b | |||
| 19c665d747 | |||
| cb09086ae8 | |||
| fa915d0b23 | |||
| a383f01406 | |||
| 1a46e80403 | |||
| e8cd230c12 | |||
| 0711dc2c5a | |||
| 486e72457d | |||
| 450e017bdb | |||
| c6d9cfc0d7 | |||
| a0b073d881 | |||
| 4dde16f987 | |||
| f7d2e262f4 | |||
| 34fedb5835 | |||
| ff491be976 | |||
| 7635482191 | |||
| b23001e43f | |||
| 06c8e8f0cb | |||
| ce2cd00fbf | |||
| 651af185c8 | |||
| 6951383ae0 | |||
| 37596e89b4 | |||
| 711fe37dad | |||
| 7fee3d0da0 | |||
| 45a61e9541 | |||
| bd0be2affc | |||
| 7812c0e5c2 | |||
| 7efb6d60bc | |||
| cd31e12bec | |||
| 87755c6097 | |||
| 73f56efe2c | |||
| 20eaa60a97 | |||
| b80f0082e9 | |||
| 1ff800a842 | |||
| 5b0abb1b17 | |||
| 178aa4794a | |||
| 76583cb2fa | |||
| aa484dc5b4 | |||
| 19a098d34b | |||
| db452d9bc0 | |||
| 90efb96635 | |||
| 0cee6de476 | |||
| 854d29330c | |||
| 34a3dd6d46 | |||
| 4787ee3301 | |||
| 7b547e7ae9 | |||
| fe5e31e528 | |||
| 841a838910 | |||
| 4f27fe4f1e | |||
| 96eab86341 | |||
| 95d7a991dc | |||
| dc309afbbd | |||
| 16d65d3665 | |||
| ccb340cf80 | |||
| 56b0f57e11 | |||
| 7c1e056152 | |||
| 08ffa99c78 | |||
| cdede5a009 | |||
| 4cadffa6ea | |||
| 04e13eac55 | |||
| 2b3ae69f63 | |||
| 8f4813f691 | |||
| 5b05baeced | |||
| 3d60e36c98 | |||
| 40c7bd114a | |||
| e0033b31f2 | |||
| 2d3bdda1c8 | |||
| fd40940ef5 | |||
| 6d58f65a1a | |||
| 44775e1791 | |||
| 4be1f4dd73 | |||
| 93bab552c9 | |||
| 023c03ddcd | |||
| a5bffad556 | |||
| 836348cbc0 | |||
| 1ac7570cfb | |||
| 0dceba8a1c | |||
| 599b070779 | |||
| c581e0ad09 | |||
| e14b59af5d | |||
| eff9de3ded | |||
| 4f128c6503 | |||
| 8dc9d4c083 | |||
| 21e3300396 | |||
| d136895598 | |||
| dac3eef57c | |||
| 2fac7dd736 | |||
| 74e2415308 | |||
| 41fae04b69 | |||
| 32a88a342c | |||
| b5bcde5093 | |||
| 68c36e8a18 | |||
| f6a9e1f4d8 | |||
| 2abd42096e | |||
| 922e214c52 | |||
| 6ce8899231 | |||
| cbfad632c2 | |||
| 7804aed5d7 | |||
| b90b1dbbbe | |||
| 020ec54264 | |||
| 0568093a2a | |||
| c9281bf863 | |||
| de451b2fe8 | |||
| ddf5c51737 | |||
| a33ccb32d2 | |||
| 0b03018a7b | |||
| 1b688410e7 | |||
| 6d031af012 | |||
| 67a5151070 | |||
| a4b299bf6e | |||
| 383d1eb406 | |||
| 3901144eae | |||
| 317c6db1d5 | |||
| 1e14f8e2b9 | |||
| 88fc7ca915 | |||
| b983e205d2 | |||
| 9cdbc6ba36 | |||
| 895f5f7398 | |||
| f41b08d573 | |||
| 3e21b6cad3 | |||
| 1a32482f66 | |||
| ee1e083f32 | |||
| ebd3a15140 | |||
| d93edc6375 | |||
| 3ed17f3a2a | |||
| 8d9cfbd3de | |||
| f142d34f83 | |||
| 357ca55dec | |||
| d7a8731027 | |||
| 9117c7d141 | |||
| 472020f90c | |||
| 2256a0dd3a | |||
| 458b5d1e32 | |||
| 1e6abed4aa | |||
| cdd4b426d5 | |||
| 75b60a2949 | |||
| 9ab34ee43a | |||
| 3c9d7706de | |||
| 8b5b954cbb | |||
| b2204925d3 | |||
| 63734155f2 | |||
| eb0ae3400a | |||
| db8db430b9 | |||
| c0b2b1c26d | |||
| 7da20e95e3 | |||
| f30f90e6be | |||
| 7f05b48bd7 | |||
| ea257b95d9 | |||
| e7c399c36a | |||
| d84666fb43 | |||
| 1eb33099af | |||
| e35dbd522f | |||
| db6474ef2a | |||
| e437671baf | |||
| f60d640c8e | |||
| 56c992e51b | |||
| 12ee7b9521 | |||
| c8de557ff7 | |||
| 90adaf29d7 | |||
| a71323f8b3 | |||
| 155995c7f3 | |||
| 319632e996 | |||
| 33d55318d8 | |||
| ec1abf8926 | |||
| 9a41f111b0 | |||
| 7ef6bd0d3f | |||
| 02f0bb3ea5 | |||
| e12b236617 | |||
| 6662a4d7d6 | |||
| 85315d8fc5 | |||
| 9f5a7e4c08 | |||
| ea0e61e6a4 | |||
| c301e9b088 | |||
| 70e861b106 | |||
| f5c6862627 | |||
| d845f1ae5b | |||
| 7c7d67c6c2 | |||
| c9fcbcc61c | |||
| 9ac06e7f85 | |||
| 6eafac2cad | |||
| 60cb0bdfb1 | |||
| 979956315c | |||
| 62ba031702 | |||
| 284cb7bee5 | |||
| 735c22bc98 | |||
| a2beed01a1 | |||
| 93fc6b06a2 | |||
| a327ce8a82 | |||
| f8374929ac | |||
| 5f93290fc7 | |||
| 4d139232bf | |||
| 804947f039 | |||
| 89fb2b57ff | |||
| 1262d11cb3 | |||
| 1ba72db4f8 | |||
| 7d2304e4a1 | |||
| ebf1dc1b08 | |||
| ce31f56eb6 | |||
| 7dd52779dc | |||
| 2eb5cab74b | |||
| db50382b18 | |||
| 32b061c768 | |||
| 740e85d28c | |||
| 568a7f814d | |||
| b99438e550 | |||
| bcdf90a8d9 | |||
| 536c16929b | |||
| d392293b50 | |||
| 16371d4528 | |||
| cdd0b48023 | |||
| 15cac726c4 | |||
| 6dc69a4d5d | |||
| c52dfcf52f | |||
| eaac13b1c1 | |||
| 3e83f3d4ee | |||
| 3845a8f02b | |||
| c932be77f8 | |||
| d89324162f | |||
| a0ef86f287 | |||
| 7255a86b32 | |||
| 81862bf934 | |||
| 81b7e5645c | |||
| 801367b68d | |||
| f2e8f325d1 | |||
| 138743b55f | |||
| 7f8db644d1 | |||
| c7e410c41b | |||
| 08f3b0b612 | |||
| a2782ef7a6 | |||
| 34fac8eb05 | |||
| 56338beae1 | |||
| 17e9f3b41d | |||
| 2c06b9325f | |||
| 2dfb91dcc9 | |||
| 9f20dfb237 | |||
| da2aecc76a | |||
| 7c72cd4399 | |||
| 5647b0430a | |||
| 7c94543da8 | |||
| 2118952120 | |||
| d45927cdf4 | |||
| c8e99e351e | |||
| fb56237122 | |||
| 89152fabde | |||
| 726463d497 | |||
| 055e41ac90 | |||
| 878878e5e4 | |||
| 7742c8a58e | |||
| 04476999f7 | |||
| 5bff7ebaa1 | |||
| 44742ea3ae | |||
| d6ea7fc3a0 | |||
| 2b49cde2c2 | |||
| 1008981306 | |||
| 146f3ad00e | |||
| 5219eff190 | |||
| abfd7b8aea | |||
| d98f64094e | |||
| a8d254738e | |||
| 1c9f2495e3 | |||
| aa4d95f352 | |||
| 558093eab1 | |||
| 865b041474 | |||
| 1888319313 | |||
| 0be7679619 | |||
| bbef6c2bc2 | |||
| be59267747 | |||
| b4477d26b7 | |||
| ce0afb3d80 | |||
| 0b5cd304ea | |||
| e54ad97fa7 | |||
| 66960ea785 | |||
| 72dd3026ca | |||
| 4c719de86c | |||
| c7a0b017b4 | |||
| 91c931b53c | |||
| 6f2b2adca9 | |||
| 3176bc1afa | |||
| b929adf2dd | |||
| f3d3b31bed | |||
| f17eaaf025 | |||
| 80d65acd0d | |||
| ba02d333d1 | |||
| 9b9d30c092 | |||
| d47de31744 | |||
| edc7efae5f | |||
| 18007be9e1 | |||
| d68ae4866c | |||
| f4b635a169 | |||
| d674d72508 | |||
| 6ee76f8ee4 | |||
| 06338e0a1f | |||
| 349c261238 | |||
| eb057fb399 | |||
| 5d739f012c | |||
| 741d56635f | |||
| 35404a2832 | |||
| 99505fc287 | |||
| a20b331095 | |||
| 06a9a82da0 | |||
| 03383eecbc | |||
| 89ae1a8b92 | |||
| 7061195059 | |||
| 9556d4b72c | |||
| dd764f1508 | |||
| 0a154339e6 | |||
| 2502b94f20 | |||
| 9b1b833fac | |||
| 848ca9817d | |||
| 9a159b50c6 | |||
| 11fb0d9850 | |||
| 3f925e5b96 | |||
| 714ae18658 | |||
| 226164c591 | |||
| 1d44d0a987 | |||
| babfb5efbb | |||
| badbb89c92 | |||
| 50e705fb25 | |||
| b9e0530ced | |||
| 9c793f1317 | |||
| cef93012bf | |||
| bd099cc844 | |||
| c1029ba3b0 | |||
| 152025baa7 | |||
| 94f0f48cba | |||
| 9b5c312aa1 | |||
| fdb488a4c3 | |||
| 69536e2263 | |||
| 3f8ea6f2ee | |||
| 3b035405b0 | |||
| 7b1a6e605b | |||
| 26ed331f8e | |||
| 29581b1f48 | |||
| 16ea13b88c | |||
| 2311107465 | |||
| 35cf9c454a | |||
| 4c2a57daf3 | |||
| ed9889af11 | |||
| 89dc2ec3f6 | |||
| 7811359b2f | |||
| 21c66915a6 | |||
| e3e99408d5 | |||
| 01f16659ac | |||
| 9e8f120fdd | |||
| 3b9b9a1629 | |||
| 9e2f43c3b1 | |||
| 588bb2df2f | |||
| 3c55ba1ea9 | |||
| 2a86216a4a | |||
| e3ea2323c5 | |||
| 6b55f3ae11 | |||
| f3496a421b | |||
| a4bba37606 | |||
| 56c4908365 | |||
| 18f6c4f2cd | |||
| d0ea1a4cf4 | |||
| aa75824cc6 | |||
| 61d5005c4b | |||
| 72d58f48e4 | |||
| 3f3b97dc16 | |||
| 8a05fdcb10 | |||
| 6fd3466db1 | |||
| f354baf685 | |||
| d009acf8e0 | |||
| fd479d04a0 | |||
| a3dc641be1 | |||
| a59f179e9d | |||
| 4128bc437b | |||
| e1b176594a | |||
| 35b11d7b22 | |||
| bd65e1f35d | |||
| a243478fff | |||
| f0fdc00e78 | |||
| a21210ab29 | |||
| 684e7df939 | |||
| 9be5f5d837 | |||
| 6c5fb67b58 | |||
| 616ec408d6 | |||
| 5969b4825c | |||
| 64c888fbdb | |||
| 8a0fe413ba | |||
| 270a1f4b95 | |||
| 8f4ed47b63 | |||
| 09997398b1 | |||
| 0b68d1c9aa | |||
| cc9904c8c7 | |||
| 16ab523cb2 | |||
| 20a75b7819 | |||
| 49e299b62d | |||
| 98a2090c72 | |||
| 38c542b05a | |||
| fc5fa621f3 | |||
| 6ec1a75cbb | |||
| bbba16cc9a | |||
| 564d3d563c | |||
| a858a4b4c1 | |||
| 2d6d8a7ea8 | |||
| 5b5ed9e043 | |||
| 801c40420c | |||
| c185b3db71 | |||
| 0f70b73e81 | |||
| d9865f9b0f | |||
| 59deb8b708 | |||
| 617fa98dee | |||
| c9cb1cabc4 | |||
| 92ab6b5aa4 | |||
| a66f250350 | |||
| 39200f4418 | |||
| 4f1c7742ef | |||
| e812cbcbe9 | |||
| 2e0670a5c1 | |||
| 92c92db595 | |||
| 1764567e1f | |||
| 7eeb8bcac1 | |||
| c718b4ccdd | |||
| 4f5ffc92a6 | |||
| 4c485f7bd0 | |||
| 7076a31821 | |||
| 68965f6da3 | |||
| b6a545d1f5 | |||
| c0afff4d13 | |||
| 604faa6669 | |||
| d94d1af7f5 | |||
| 9feb5dedd5 | |||
| 99948c4ed5 | |||
| 967bab678d | |||
| 135c296ac7 | |||
| e83ee48ed5 | |||
| 1539fe0906 | |||
| c06bddd19e | |||
| ceb78f21bb | |||
| 5af201d4ee | |||
| 794efb5ef5 | |||
| 31a9437b2c | |||
| 2b27e554fd | |||
| 4784b7b00e | |||
| e547a719f6 | |||
| 24f2d201ed | |||
| 792dfc731c | |||
| 6697b39e79 | |||
| db1eeff2c3 | |||
| fc624701bf | |||
| 591cc52944 | |||
| 67d840a1b3 | |||
| 8ffa951407 | |||
| af39c2c7ae | |||
| 5903c7d0bc | |||
| dbb79fc9e6 | |||
| ef1408fddb | |||
| 47ecb0e1cf | |||
| 55fad3d57e | |||
| 496a44d412 | |||
| 05721f73cc | |||
| 424c36ea49 | |||
| a38097e2f5 | |||
| b26cb4d339 | |||
| 3523974163 | |||
| a2bdd294a8 | |||
| f85bfdf451 | |||
| cfad186a6b | |||
| c8a9412995 | |||
| 318ea04efc | |||
| 90c1fd4c31 | |||
| fad6221750 | |||
| 9f0047478d | |||
| 591ef3271b | |||
| 9afbbde062 | |||
| 73e6e519a3 | |||
| 4268ba54bf | |||
| 47037b0066 | |||
| 05a6a36a62 | |||
| d72b1d8bd5 | |||
| 0f1a4422f5 | |||
| 7d06f9e1e3 | |||
| 1e4e76b0dd | |||
| 49d70f487e | |||
| 456cb22ac0 | |||
| ba1dfee5ca | |||
| 143a600a5c | |||
| 68b4bf0a7f | |||
| bc75d07391 | |||
| 7eaa3ef52e | |||
| af69ddc220 | |||
| b25d61fbb5 | |||
| 81a60b029d | |||
| 751fd8cc4b | |||
| 503e3d6ff2 | |||
| decbfe0505 | |||
| 379042616f | |||
| df2878bc2e | |||
| 1ff35461a2 | |||
| 7de94fff1b | |||
| 3236f70d8b | |||
| cf7cef19f9 | |||
| e159cdad5b | |||
| 2ddb533ef2 | |||
| 36a6e02269 | |||
| 6fbbf0ad61 | |||
| 1040fbddc6 | |||
| bbd63b2c57 | |||
| 905bdb1d27 | |||
| 11ce5ffa4c | |||
| b1854f82f2 | |||
| 745b7a26b7 | |||
| 764a38f23e | |||
| 7873fdc7bb | |||
| 76435460f0 | |||
| 7e3a54ff1b | |||
| 61789e3fda | |||
| 441c5fe534 | |||
| f30001d98b | |||
| fae0ba5678 | |||
| 7e592f34bd | |||
| 691f6c7c5c | |||
| f5eb5d545f | |||
| 91e4f6fcec | |||
| b759b12e90 | |||
| 103019984b | |||
| 01126aaeea | |||
| a6ab8ff02f | |||
| b89886a945 | |||
| d12b71f69c | |||
| 53c2ed3c82 | |||
| 148c8e6250 | |||
| 4a99eb105a | |||
| c5ca64af50 | |||
| 984b920fde | |||
| 54dae6827e | |||
| 58cf214bf2 | |||
| eeefdf5927 | |||
| 29c172deab | |||
| af1e83f12a | |||
| 3a3edc4617 | |||
| e13f52e371 | |||
| 5687b4bee0 | |||
| 48d0e73e9b | |||
| 3d4e3638be | |||
| f07e6b29a3 | |||
| a92f75f7d4 | |||
| 6e87111c99 | |||
| ad3594eebc | |||
| af99e31c63 | |||
| c8ee5b10be | |||
| cd471040b4 | |||
| f7beecc510 | |||
| ca8b61caba | |||
| d672b1e3f6 | |||
| 22ae39323b | |||
| 420a57aef9 | |||
| 7d76c32334 | |||
| 2fa4f4c66a | |||
| 37d146a683 | |||
| b95808be54 | |||
| dbdbdd9a2a | |||
| 16b8df7b9c | |||
| 293d4b4a47 | |||
| da7b2e62f5 | |||
| 33e87c7ffa | |||
| f417a35ad7 | |||
| c86acff698 | |||
| 0ec55b0cd4 | |||
| cf98d2a9d5 | |||
| ec75b14d9e | |||
| 4bad31f7cc | |||
| 288baa7e94 | |||
| d1161b3ff8 | |||
| 27e5886a0b | |||
| eaebf9fd73 | |||
| ea4c16604b | |||
| 66a4abeb50 | |||
| a57705264f | |||
| e7fc40cfdd | |||
| 55d306c938 | |||
| 8fe1f2fef1 | |||
| 1065b56380 | |||
| e58068688c | |||
| 9a51feed0a | |||
| 9ac8cc2cd7 | |||
| 54a388af5e | |||
| 5dda872917 | |||
| 3277cfdc6b | |||
| c759a1c3f6 | |||
| b77b2ab82d | |||
| 855de8565e | |||
| f1ad003b41 | |||
| f6507ecbe3 | |||
| 79083925d1 | |||
| de1c677e75 | |||
| 3ede9af34b | |||
| d475d9bcbf | |||
| bf095f0698 | |||
| 90d9d6da8b | |||
| 5ed4d66dfe | |||
| 60b45912ce | |||
| 29aad624d5 | |||
| 2bf8584f30 | |||
| d083ff3400 | |||
| b6e96d77aa | |||
| 6e1751d0ed | |||
| c1700069dc | |||
| 17c2aa4faf | |||
| 8f47861b6d | |||
| 8f2ee9a7cd | |||
| 93e976fdb0 | |||
| c737ea1954 | |||
| 700d815d54 | |||
| 382219a29f | |||
| a372853777 | |||
| 79f1cd16a3 | |||
| b2dbb5a100 | |||
| 01631e0477 | |||
| 816911d071 | |||
| 2cf0d6db9d | |||
| 1df47b7c05 | |||
| 622ac54213 | |||
| e2d8853704 | |||
| 4993c5010b | |||
| 8bd0d7c143 | |||
| 761ce99f8e | |||
| ba7c901d7a | |||
| 99c88ed7a0 | |||
| c27244cfbd | |||
| 099a42a2d4 | |||
| 74c89cf7d4 | |||
| 805125b17f | |||
| 7d93cfaac1 | |||
| 3cd1e7a972 | |||
| 4ed2651c5f | |||
| e83cb0fb3c | |||
| b1be65d9ce | |||
| eacc4412ba | |||
| 0baf092ba4 | |||
| ebd9249f87 | |||
| e1ee4973eb | |||
| ac09ad3393 | |||
| 2bba87d951 | |||
| d54e02eed4 | |||
| db41633663 | |||
| 0568387679 | |||
| ffbbb88917 | |||
| 756b36d227 | |||
| a2afadfe92 | |||
| 0c76cee737 | |||
| b1ec3fe271 | |||
| 19bf130ccd | |||
| 32c14e0aa1 | |||
| 0ff5050452 | |||
| ca83d4afb8 | |||
| 21c1591f58 | |||
| cb64ac1b7f | |||
| 337f808a62 | |||
| 48d97947c1 | |||
| df4dd4f93a | |||
| a5eb34d680 | |||
| eba03caa23 | |||
| 61a41a10ce | |||
| d3109022b1 | |||
| 1c828f19a3 | |||
| 2f1572b404 | |||
| 2ca12db362 | |||
| 14ef7688b8 | |||
| a1c83c79b2 | |||
| 376678881c | |||
| 0f7b11decd | |||
| 22b8540843 | |||
| afe5a1aa6c | |||
| 83b5bb394c | |||
| 539d430f60 | |||
| 6d898398df | |||
| 23a2077056 | |||
| d5bb797224 | |||
| 907bae53ba | |||
| 97122ed2be | |||
| 7b65529f63 | |||
| a87831b48c | |||
| baba7ca80d | |||
| d39a84ea53 | |||
| 3bcd255a07 | |||
| 67a87cd040 | |||
| be2aa70f7d | |||
| 2fac681b62 | |||
| dd4f7bf176 | |||
| 00a4b7ba09 | |||
| 51799f7f14 | |||
| 1b291365d5 | |||
| 9337f832d3 | |||
| ab540cb3e4 | |||
| 1adc47ab32 | |||
| 94037e5266 | |||
| 3457890b24 | |||
| b23c06d443 | |||
| f5ebb782c0 | |||
| 72f31744e3 | |||
| 2065a5f7f2 | |||
| 2ecf0c32cb | |||
| 9c0f2175f7 | |||
| 6064db9467 | |||
| 8cb8510d72 | |||
| 552ca43175 | |||
| 7c27f01ab8 | |||
| a8ec9a4329 | |||
| 797cf31969 | |||
| 37e365f679 | |||
| f53a9ab1aa | |||
| 4579de85bf | |||
| affc5ee7d9 | |||
| 40fa3818cc | |||
| 4a264ba8c5 | |||
| 8a47c36e20 | |||
| 2dc06a01b6 | |||
| f6695c9567 | |||
| fc3768101d | |||
| 5645954686 | |||
| f16d1c80f4 | |||
| a25b884dbb | |||
| 567401c337 | |||
| 1c80f3d667 | |||
| 17ebc67d36 | |||
| 4248776c16 | |||
| 3e0d6f698e | |||
| 67e2589a15 | |||
| 2398a515b5 | |||
| ad83d805ac | |||
| a6ba3535df | |||
| 3510d8f097 | |||
| d0100218c9 | |||
| 2cdeb40f33 | |||
| e033dce93e | |||
| 4c62338e97 | |||
| 606599a65b | |||
| d091ac4e0a | |||
| b676ebf9d7 | |||
| e270c27cb0 | |||
| 63561a51a4 | |||
| cde7599f87 | |||
| c9e7308f49 | |||
| 0088d9d5fc | |||
| 4fd5b369f8 | |||
| 5e0ed1dff3 | |||
| 215a16cd18 | |||
| cd5ae290bc | |||
| bd0b66aaad | |||
| 45b83232d7 | |||
| bf2885d7d3 | |||
| eeb8cc10ae | |||
| 4668e3a771 | |||
| 95a90dd050 | |||
| 908aa6f426 | |||
| 15f7ada958 | |||
| 18b58ced8d | |||
| 4f6f5bf3b7 | |||
| 50cbae420c | |||
| a1207de93f | |||
| a6824d8272 | |||
| 0eaeb67ba0 | |||
| b40a9803a8 | |||
| f1ab8fde76 | |||
| 55d11b2832 | |||
| e01da9b065 | |||
| b703dbd7f7 | |||
| c70c7462bf | |||
| 342dd26645 | |||
| 8e03295362 | |||
| 18cc3537d6 | |||
| 16deb001bf | |||
| 78035e0b2e | |||
| c23755c028 | |||
| 38ddf12542 | |||
| 525c7f2685 | |||
| 4d360e3798 | |||
| 8adf9f3643 | |||
| 6236a9c15e | |||
| cc6b260189 | |||
| 01953ded0f | |||
| 645dc21f7a | |||
| 34acb38d40 | |||
| 73918f8808 | |||
| 9f973133e8 | |||
| 5ba86d5c35 | |||
| 7b1b369e40 | |||
| 894384cf3c | |||
| 9768f8171c | |||
| 7672bc0c40 | |||
| 064c584b45 |
@@ -1,5 +1,6 @@
|
||||
# following files are skipped when exporting using git archive
|
||||
test export-ignore
|
||||
docs export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
|
||||
|
||||
@@ -656,3 +656,152 @@
|
||||
* Send email to admins if backup fails
|
||||
* Add UI to set digitalocean as DNS provider
|
||||
|
||||
[0.60.1]
|
||||
* Apply less strict hostname checking for email
|
||||
* Fix bug in Cloudron plan listing
|
||||
* Improved storage provider interface
|
||||
|
||||
[0.70.0]
|
||||
* Remove standalone installer daemon
|
||||
|
||||
[0.70.1]
|
||||
* Add additional platform healthcheck
|
||||
|
||||
[0.80.0]
|
||||
* Add optional SSO for apps
|
||||
* Improve app status page
|
||||
* Several webinterface improvements
|
||||
|
||||
[0.80.1]
|
||||
* Improved DNS handling
|
||||
* Better error messages in UI
|
||||
|
||||
[0.90.0]
|
||||
* Remove customAuth support
|
||||
* Support non AWS S3 object storage
|
||||
* Settings UI improvements
|
||||
|
||||
[0.91.0]
|
||||
* Support installing Cloudron on intranet and VirtualBox
|
||||
* Fix bug where relocating an app did not free the old location
|
||||
* Allow Email server to be enabled with wildcard DNS
|
||||
|
||||
[0.92.0]
|
||||
* Backup encryption key is now optional
|
||||
* Fix bug where DNS mail record warning was shown by mistake
|
||||
* Make cloudron-setup finish with `manual` DNS provider
|
||||
|
||||
[0.92.1]
|
||||
* Remove DO specific grub cmd line
|
||||
* Fix License text
|
||||
|
||||
[0.93.0]
|
||||
* Smoother upgrades
|
||||
|
||||
[0.94.0]
|
||||
* Cloudron domain can now be set after installation
|
||||
* Backups are now organized by directory
|
||||
* Document upgrading from Filesystem backend
|
||||
* Send certificate renewal errors, OOM errors to cloudron admins
|
||||
* Email bounce alerts are sent to the Cloudron owner
|
||||
|
||||
[0.94.1]
|
||||
* Suppress upgrade emails
|
||||
* Enable unattended upgrades
|
||||
* Standardize on using devicemapper for docker storage backend
|
||||
* Show detailed backup progress
|
||||
* Fix DNSBL issue in mail container
|
||||
* Fix issue where bounce emails were not sent to aliases
|
||||
* Remove tutorial
|
||||
* Restart mail container on certificate change
|
||||
|
||||
[0.97.0]
|
||||
* Fix missing app icon issue
|
||||
* Fix issue where box sends out crash reports incessantly
|
||||
* (API) Allow memory limit to be set to -1 (unlimited)
|
||||
* (API) Move developmentMode flag from manifest to apps route
|
||||
|
||||
[0.98.0]
|
||||
* Send stat on whether email is enabled
|
||||
* Fix bug where heartbeat was sent for self-hosted Cloudrons
|
||||
* Make Cloudron function even when disk is full
|
||||
* Fix thunderbird connection issue
|
||||
* Send more detailed logs for backup failures
|
||||
* Restart nginx if it crashed automatically
|
||||
* Support all DNS providers for managed Cloudrons
|
||||
* Add granular configuration for auto-updates
|
||||
|
||||
[0.99.0]
|
||||
* Fix bug where ports <= 1023 were not reserved
|
||||
* Cleanup graphs UI
|
||||
* Polish webadmin UI
|
||||
* Fix bug where hard disk size was detected incorrectly
|
||||
|
||||
[0.99.1]
|
||||
* Fix bug with duplicate nginx configs
|
||||
|
||||
[0.100.0]
|
||||
* Improve DNS notifications for email
|
||||
* Do not enable HSTS for subdomains
|
||||
|
||||
[0.100.1]
|
||||
* Fix crash when fetching mail records
|
||||
* Fix crash in LDAP server when username and displayName are empty
|
||||
|
||||
[0.101.0]
|
||||
* New base image 0.10.0
|
||||
* Better error handling of unpurchase errors
|
||||
* Validate that cloudron domain name is a subdomain of public suffic list
|
||||
* Add canada and london to S3 backup regions
|
||||
* Bundle Font Awesome as part of webadmin
|
||||
* Fix crash in custom certiicate validation
|
||||
* Get A+ rating in SSL Check
|
||||
* More robust detection and injection of SPF record
|
||||
* Add azure, lightsail, linode, ovh, vultr to provider list
|
||||
|
||||
[0.102.0]
|
||||
* Fix issue where SPF record check was only done 5 times (updated 'async')
|
||||
* Make auto-generated self-signed cert load quickly on Firefox
|
||||
* Ensure we download docker images and have an app data volume on app re-configure
|
||||
* Improve certificate renewal erorr message
|
||||
* Fix disk usage graph
|
||||
* Show Repair UI for errored apps
|
||||
|
||||
[0.102.1]
|
||||
* Add terms link when signing up for Cloudron.io account
|
||||
* Fix issue where Cloudrons with many apps (> 35) were unable to backup
|
||||
* Improve wording of DNS Setup
|
||||
|
||||
[0.103.0]
|
||||
* Do not send crash logs and other notifications to support@cloudron.io for self-hosted instances
|
||||
* Make auto-generated self-signed cert load quickly on Firefox (take 2)
|
||||
|
||||
[0.104.0]
|
||||
* (mail) Fix crash when sending mails to groups with just 1 user
|
||||
* (ldap) Add isadmin attribute to better map users in apps
|
||||
* (ldap) Hide users which have not yet set a username in ldap searches
|
||||
* (core) Add SSH authorized_keys management
|
||||
* (core) Add additional security related headers to the nginx reverse proxy
|
||||
* (ui) Add remote SSH support option
|
||||
* (ui) Fix eventlog display
|
||||
* (ui) Fix CNAME setup information
|
||||
|
||||
[0.105.0]
|
||||
* Always show email related checks
|
||||
* Show outbound SMTP port 25 status
|
||||
* Hide remote feature for normal users
|
||||
* Only list users via ldap searches who have access to the app
|
||||
* Fix installation issue on servers with a differente locale set
|
||||
|
||||
[0.105.1]
|
||||
* Fix crash when setupToken is not provided in activate API
|
||||
* Add inline Docker GPG key
|
||||
* Re-download icon when repairing app
|
||||
* Fix issue where pre-installed apps were not installed correctly
|
||||
* Fix issue where new cloudrons could not be activated
|
||||
|
||||
[0.106.0]
|
||||
* (mail) Fix email forwarding to external domains
|
||||
* (mail) Set maximum email size to 25MB
|
||||
* Remove SimpleAuth addon
|
||||
|
||||
|
||||
@@ -630,7 +630,7 @@ state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
box
|
||||
Copyright (C) 2016 yellowtent
|
||||
Copyright (C) 2016 Cloudron UG
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
|
||||
@@ -9,6 +9,10 @@ a complex task.
|
||||
We are building the ultimate platform for self-hosting web apps. The Cloudron allows
|
||||
anyone to effortlessly host web applications on their server on their own terms.
|
||||
|
||||
Support us on
|
||||
[](https://flattr.com/submit/auto?user_id=cloudron&url=https://cloudron.io&title=Cloudron&tags=opensource&category=software)
|
||||
or [pay us a coffee](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8982CKNM46D8U)
|
||||
|
||||
## Features
|
||||
|
||||
* Single click install for apps. Check out the [App Store](https://cloudron.io/appstore.html).
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 14 KiB |
Executable
+193
@@ -0,0 +1,193 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
assertNotEmpty() {
|
||||
: "${!1:? "$1 is not set."}"
|
||||
}
|
||||
|
||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)"
|
||||
export JSON="${SOURCE_DIR}/node_modules/.bin/json"
|
||||
|
||||
INSTANCE_TYPE="t2.micro"
|
||||
BLOCK_DEVICE="DeviceName=/dev/sda1,Ebs={VolumeSize=20,DeleteOnTermination=true,VolumeType=gp2}"
|
||||
SSH_KEY_NAME="id_rsa_yellowtent"
|
||||
|
||||
revision=$(git rev-parse HEAD)
|
||||
ami_name=""
|
||||
server_id=""
|
||||
server_ip=""
|
||||
destroy_server="yes"
|
||||
deploy_env="prod"
|
||||
image_id=""
|
||||
|
||||
args=$(getopt -o "" -l "revision:,name:,no-destroy,env:,region:" -n "$0" -- "$@")
|
||||
eval set -- "${args}"
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
--env) deploy_env="$2"; shift 2;;
|
||||
--revision) revision="$2"; shift 2;;
|
||||
--name) ami_name="$2"; shift 2;;
|
||||
--no-destroy) destroy_server="no"; shift 2;;
|
||||
--region)
|
||||
case "$2" in
|
||||
"us-east-1")
|
||||
image_id="ami-6edd3078"
|
||||
security_group="sg-a5e17fd9"
|
||||
subnet_id="subnet-b8fbc0f1"
|
||||
;;
|
||||
"eu-central-1")
|
||||
image_id="ami-5aee2235"
|
||||
security_group="sg-19f5a770" # everything open on eu-central-1
|
||||
subnet_id=""
|
||||
;;
|
||||
*)
|
||||
echo "Unknown aws region $2"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
export AWS_DEFAULT_REGION="$2" # used by the aws cli tool
|
||||
shift 2
|
||||
;;
|
||||
--) break;;
|
||||
*) echo "Unknown option $1"; exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
# TODO fix this
|
||||
export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY}"
|
||||
export AWS_SECRET_ACCESS_KEY="${AWS_ACCESS_SECRET}"
|
||||
|
||||
readonly ssh_keys="${HOME}/.ssh/id_rsa_yellowtent"
|
||||
readonly SSH="ssh -o IdentitiesOnly=yes -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ${ssh_keys}"
|
||||
|
||||
if [[ ! -f "${ssh_keys}" ]]; then
|
||||
echo "caas ssh key is missing at ${ssh_keys} (pick it up from secrets repo)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${image_id}" ]]; then
|
||||
echo "--region is required (us-east-1 or eu-central-1)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function get_pretty_revision() {
|
||||
local git_rev="$1"
|
||||
local sha1=$(git rev-parse --short "${git_rev}" 2>/dev/null)
|
||||
|
||||
echo "${sha1}"
|
||||
}
|
||||
|
||||
function wait_for_ssh() {
|
||||
echo "=> Waiting for ssh connection"
|
||||
while true; do
|
||||
echo -n "."
|
||||
|
||||
if $SSH ubuntu@${server_ip} echo "hello"; then
|
||||
echo ""
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
}
|
||||
|
||||
now=$(date "+%Y-%m-%d-%H%M%S")
|
||||
pretty_revision=$(get_pretty_revision "${revision}")
|
||||
|
||||
if [[ -z "${ami_name}" ]]; then
|
||||
ami_name="box-${deploy_env}-${pretty_revision}-${now}"
|
||||
fi
|
||||
|
||||
echo "=> Create EC2 instance"
|
||||
id=$(aws ec2 run-instances --image-id "${image_id}" --instance-type "${INSTANCE_TYPE}" --security-group-ids "${security_group}" --block-device-mappings "${BLOCK_DEVICE}" --key-name "${SSH_KEY_NAME}" --subnet-id "${subnet_id}" --associate-public-ip-address \
|
||||
| $JSON Instances \
|
||||
| $JSON 0.InstanceId)
|
||||
|
||||
[[ -z "$id" ]] && exit 1
|
||||
echo "Instance created ID $id"
|
||||
|
||||
echo "=> Waiting for instance to get a public IP"
|
||||
while true; do
|
||||
server_ip=$(aws ec2 describe-instances --instance-ids ${id} \
|
||||
| $JSON Reservations.0.Instances \
|
||||
| $JSON 0.PublicIpAddress)
|
||||
|
||||
if [[ ! -z "${server_ip}" ]]; then
|
||||
echo ""
|
||||
break
|
||||
fi
|
||||
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Got public IP ${server_ip}"
|
||||
|
||||
wait_for_ssh
|
||||
|
||||
echo "=> Fetching cloudron-setup"
|
||||
while true; do
|
||||
|
||||
if $SSH ubuntu@${server_ip} wget "https://cloudron.io/cloudron-setup" -O "cloudron-setup"; then
|
||||
echo ""
|
||||
break
|
||||
fi
|
||||
|
||||
echo -n "."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo "=> Running cloudron-setup"
|
||||
$SSH ubuntu@${server_ip} sudo /bin/bash "cloudron-setup" --env "${deploy_env}" --provider "ami" --skip-reboot
|
||||
|
||||
wait_for_ssh
|
||||
|
||||
echo "=> Removing ssh key"
|
||||
$SSH ubuntu@${server_ip} sudo rm /home/ubuntu/.ssh/authorized_keys /root/.ssh/authorized_keys
|
||||
|
||||
echo "=> Creating AMI"
|
||||
image_id=$(aws ec2 create-image --instance-id "${id}" --name "${ami_name}" | $JSON ImageId)
|
||||
[[ -z "$id" ]] && exit 1
|
||||
echo "Creating AMI with Id ${image_id}"
|
||||
|
||||
echo "=> Waiting for AMI to be created"
|
||||
while true; do
|
||||
state=$(aws ec2 describe-images --image-ids ${image_id} \
|
||||
| $JSON Images \
|
||||
| $JSON 0.State)
|
||||
|
||||
if [[ "${state}" == "available" ]]; then
|
||||
echo ""
|
||||
break
|
||||
fi
|
||||
|
||||
echo -n "."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ "${destroy_server}" == "yes" ]]; then
|
||||
echo "=> Deleting EC2 instance"
|
||||
|
||||
while true; do
|
||||
state=$(aws ec2 terminate-instances --instance-id "${id}" \
|
||||
| $JSON TerminatingInstances \
|
||||
| $JSON 0.CurrentState.Name)
|
||||
|
||||
if [[ "${state}" == "shutting-down" ]]; then
|
||||
echo ""
|
||||
break
|
||||
fi
|
||||
|
||||
echo -n "."
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Done."
|
||||
echo ""
|
||||
echo "New AMI is: ${image_id}"
|
||||
echo ""
|
||||
@@ -10,7 +10,7 @@ readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)"
|
||||
export JSON="${SOURCE_DIR}/node_modules/.bin/json"
|
||||
|
||||
installer_revision=$(git rev-parse HEAD)
|
||||
revision=$(git rev-parse HEAD)
|
||||
box_name=""
|
||||
server_id=""
|
||||
server_ip=""
|
||||
@@ -28,7 +28,7 @@ eval set -- "${args}"
|
||||
while true; do
|
||||
case "$1" in
|
||||
--env) deploy_env="$2"; shift 2;;
|
||||
--revision) installer_revision="$2"; shift 2;;
|
||||
--revision) revision="$2"; shift 2;;
|
||||
--name) box_name="$2"; destroy_server="no"; shift 2;;
|
||||
--no-destroy) destroy_server="no"; shift 2;;
|
||||
--) break;;
|
||||
@@ -73,7 +73,7 @@ function get_pretty_revision() {
|
||||
}
|
||||
|
||||
now=$(date "+%Y-%m-%d-%H%M%S")
|
||||
pretty_revision=$(get_pretty_revision "${installer_revision}")
|
||||
pretty_revision=$(get_pretty_revision "${revision}")
|
||||
|
||||
if [[ -z "${box_name}" ]]; then
|
||||
# if you change this, change the regexp is appstore/janitor.js
|
||||
@@ -138,13 +138,13 @@ cd "${SOURCE_DIR}"
|
||||
git archive --format=tar HEAD | $ssh22 "root@${server_ip}" "cat - > /tmp/box.tar.gz"
|
||||
|
||||
echo "Executing init script"
|
||||
if ! $ssh22 "root@${server_ip}" "/bin/bash /root/initializeBaseUbuntuImage.sh ${installer_revision} caas"; then
|
||||
if ! $ssh22 "root@${server_ip}" "/bin/bash /root/initializeBaseUbuntuImage.sh caas"; then
|
||||
echo "Init script failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Shutting down server with id : ${server_id}"
|
||||
$ssh202 "root@${server_ip}" "shutdown -f now" || true # shutdown sometimes terminates ssh connection immediately making this command fail
|
||||
$ssh22 "root@${server_ip}" "shutdown -f now" || true # shutdown sometimes terminates ssh connection immediately making this command fail
|
||||
|
||||
# wait 10 secs for actual shutdown
|
||||
echo "Waiting for 10 seconds for server to shutdown"
|
||||
|
||||
@@ -31,7 +31,7 @@ function create_droplet() {
|
||||
|
||||
local image_region="sfo1"
|
||||
local ubuntu_image_slug="ubuntu-16-04-x64"
|
||||
local box_size="512mb"
|
||||
local box_size="1gb"
|
||||
|
||||
local data="{\"name\":\"${box_name}\",\"size\":\"${box_size}\",\"region\":\"${image_region}\",\"image\":\"${ubuntu_image_slug}\",\"ssh_keys\":[ \"${ssh_key_id}\" ],\"backups\":false}"
|
||||
|
||||
|
||||
@@ -2,317 +2,125 @@
|
||||
|
||||
set -euv -o pipefail
|
||||
|
||||
readonly USER=yellowtent
|
||||
readonly USER_HOME="/home/${USER}"
|
||||
readonly INSTALLER_SOURCE_DIR="${USER_HOME}/installer"
|
||||
readonly INSTALLER_REVISION="${1:-master}"
|
||||
readonly PROVIDER="${2:-generic}"
|
||||
readonly USER_DATA_FILE="/root/user_data.img"
|
||||
readonly USER_DATA_DIR="/home/yellowtent/data"
|
||||
|
||||
readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
readonly arg_provider="${1:-generic}"
|
||||
readonly arg_infraversionpath="${SOURCE_DIR}/${2:-}"
|
||||
|
||||
function die {
|
||||
echo $1
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ "$(systemd --version 2>&1)" == *"systemd 229"* ]] || die "Expecting systemd to be 229"
|
||||
|
||||
echo "==== Create User ${USER} ===="
|
||||
if ! id "${USER}"; then
|
||||
useradd "${USER}" -m
|
||||
fi
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
echo "=== Upgrade ==="
|
||||
apt-get update
|
||||
apt-get dist-upgrade -y
|
||||
apt-get install -y curl
|
||||
apt-get -o Dpkg::Options::="--force-confdef" update -y
|
||||
apt-get -o Dpkg::Options::="--force-confdef" dist-upgrade -y
|
||||
|
||||
# Setup firewall before everything. docker creates it's own chain and the -X below will remove it
|
||||
# Do NOT use iptables-persistent because it's startup ordering conflicts with docker
|
||||
echo "=== Setting up firewall ==="
|
||||
# clear tables and set default policy
|
||||
iptables -F # flush all chains
|
||||
iptables -X # delete all chains
|
||||
# default policy for filter table
|
||||
iptables -P INPUT ACCEPT # accept by default to allow network drives to persist
|
||||
iptables -P FORWARD ACCEPT # TODO: disable icc and make this as reject
|
||||
iptables -P OUTPUT ACCEPT
|
||||
echo "==> Installing required packages"
|
||||
|
||||
# NOTE: keep these in sync with src/apps.js validatePortBindings
|
||||
# allow ssh, http, https, ping, dns
|
||||
iptables -I INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
|
||||
# caas has ssh on port 202
|
||||
if [[ "${PROVIDER}" == "caas" ]]; then
|
||||
iptables -A INPUT -p tcp -m tcp -m multiport --dports 25,80,202,443,587,993,4190 -j ACCEPT
|
||||
else
|
||||
iptables -A INPUT -p tcp -m tcp -m multiport --dports 25,80,22,443,587,993,4190 -j ACCEPT
|
||||
fi
|
||||
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
|
||||
iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT
|
||||
iptables -A INPUT -p udp --sport 53 -j ACCEPT
|
||||
iptables -A INPUT -s 172.18.0.0/16 -j ACCEPT # required to accept any connections from apps to our IP:<public port>
|
||||
debconf-set-selections <<< 'mysql-server mysql-server/root_password password password'
|
||||
debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password password'
|
||||
|
||||
# loopback
|
||||
iptables -A INPUT -i lo -j ACCEPT
|
||||
iptables -A OUTPUT -o lo -j ACCEPT
|
||||
# this enables automatic security upgrades (https://help.ubuntu.com/community/AutomaticSecurityUpdates)
|
||||
apt-get -y install \
|
||||
acl \
|
||||
awscli \
|
||||
btrfs-tools \
|
||||
build-essential \
|
||||
cron \
|
||||
curl \
|
||||
dmsetup \
|
||||
iptables \
|
||||
logrotate \
|
||||
mysql-server-5.7 \
|
||||
nginx-full \
|
||||
openssh-server \
|
||||
pwgen \
|
||||
rcconf \
|
||||
swaks \
|
||||
unattended-upgrades \
|
||||
unbound
|
||||
|
||||
# prevent DoS
|
||||
# iptables -A INPUT -p tcp --dport 80 -m limit --limit 25/minute --limit-burst 100 -j ACCEPT
|
||||
|
||||
# log dropped incoming. keep this at the end of all the rules
|
||||
iptables -N LOGGING # new chain
|
||||
iptables -A INPUT -j LOGGING # last rule in INPUT chain (log and drop)
|
||||
iptables -A LOGGING -m limit --limit 2/min -j LOG --log-prefix "IPTables Packet Dropped: " --log-level 7
|
||||
iptables -A LOGGING -j DROP
|
||||
|
||||
echo "==== Install btrfs tools ==="
|
||||
apt-get -y install btrfs-tools
|
||||
|
||||
echo "==== Install docker ===="
|
||||
# install docker from binary to pin it to a specific version. the current debian repo does not allow pinning
|
||||
# IMPORTANT: docker 1.11.x breaks the --dns option hack that we use below
|
||||
curl https://get.docker.com/builds/Linux/x86_64/docker-1.10.2 > /usr/bin/docker
|
||||
apt-get -y install aufs-tools
|
||||
chmod +x /usr/bin/docker
|
||||
groupadd docker
|
||||
cat > /etc/systemd/system/docker.socket <<EOF
|
||||
[Unit]
|
||||
Description=Docker Socket for the API
|
||||
PartOf=docker.service
|
||||
|
||||
[Socket]
|
||||
ListenStream=/var/run/docker.sock
|
||||
SocketMode=0660
|
||||
SocketUser=root
|
||||
SocketGroup=docker
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
EOF
|
||||
cat > /etc/systemd/system/docker.service <<EOF
|
||||
[Unit]
|
||||
Description=Docker Application Container Engine
|
||||
After=network.target docker.socket
|
||||
Requires=docker.socket
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/docker daemon -H fd:// --log-driver=journald --exec-opt native.cgroupdriver=cgroupfs --dns 127.0.0.1
|
||||
MountFlags=slave
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=1048576
|
||||
LimitCORE=infinity
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
echo "=== Setup btrfs data ==="
|
||||
if ! grep -q loop.ko /lib/modules/`uname -r`/modules.builtin; then
|
||||
# on scaleway loop is not built-in
|
||||
echo "loop" >> /etc/modules
|
||||
modprobe loop
|
||||
fi
|
||||
truncate -s "8192m" "${USER_DATA_FILE}" # 8gb start (this will get resized dynamically by box-setup.service)
|
||||
mkfs.btrfs -L UserHome "${USER_DATA_FILE}"
|
||||
mkdir -p "${USER_DATA_DIR}"
|
||||
mount -t btrfs -o loop,nosuid "${USER_DATA_FILE}" ${USER_DATA_DIR}
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable docker
|
||||
systemctl start docker
|
||||
|
||||
# give docker sometime to start up and create iptables rules
|
||||
# those rules come in after docker has started, and we want to wait for them to be sure iptables-save has all of them
|
||||
sleep 10
|
||||
|
||||
# Disable forwarding to metadata route from containers
|
||||
iptables -I FORWARD -d 169.254.169.254 -j DROP
|
||||
|
||||
# ubuntu will restore iptables from this file automatically. this is here so that docker's chain is saved to this file
|
||||
mkdir /etc/iptables && iptables-save > /etc/iptables/rules.v4
|
||||
|
||||
echo "=== Enable memory accounting =="
|
||||
if [[ "${PROVIDER}" == "digitalocean" ]] || [[ "${PROVIDER}" == "caas" ]]; then
|
||||
sed -e 's/GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX="console=tty1 root=LABEL=DOROOT notsc clocksource=kvm-clock net.ifnames=0 cgroup_enable=memory swapaccount=1 panic_on_oops=1 panic=5"/' -i /etc/default/grub
|
||||
update-grub
|
||||
elif [[ "${PROVIDER}" == "ec2" ]]; then
|
||||
sed -e 's/GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1 panic_on_oops=1 panic=5"/' -i /etc/default/grub
|
||||
update-grub
|
||||
fi
|
||||
|
||||
# now add the user to the docker group
|
||||
usermod "${USER}" -a -G docker
|
||||
|
||||
echo "==== Install nodejs ===="
|
||||
# Cannot use anything above 4.1.1 - https://github.com/nodejs/node/issues/3803
|
||||
mkdir -p /usr/local/node-4.1.1
|
||||
curl -sL https://nodejs.org/dist/v4.1.1/node-v4.1.1-linux-x64.tar.gz | tar zxvf - --strip-components=1 -C /usr/local/node-4.1.1
|
||||
ln -s /usr/local/node-4.1.1/bin/node /usr/bin/node
|
||||
ln -s /usr/local/node-4.1.1/bin/npm /usr/bin/npm
|
||||
echo "==> Installing node.js"
|
||||
mkdir -p /usr/local/node-6.9.2
|
||||
curl -sL https://nodejs.org/dist/v6.9.2/node-v6.9.2-linux-x64.tar.gz | tar zxvf - --strip-components=1 -C /usr/local/node-6.9.2
|
||||
ln -sf /usr/local/node-6.9.2/bin/node /usr/bin/node
|
||||
ln -sf /usr/local/node-6.9.2/bin/npm /usr/bin/npm
|
||||
apt-get install -y python # Install python which is required for npm rebuild
|
||||
[[ "$(python --version 2>&1)" == "Python 2.7."* ]] || die "Expecting python version to be 2.7.x"
|
||||
|
||||
echo "==== Downloading docker images ===="
|
||||
if [ -f ${SOURCE_DIR}/infra_version.js ]; then
|
||||
images=$(node -e "var i = require('${SOURCE_DIR}/infra_version.js'); console.log(i.baseImages.join(' '), Object.keys(i.images).map(function (x) { return i.images[x].tag; }).join(' '));")
|
||||
# https://docs.docker.com/engine/installation/linux/ubuntulinux/
|
||||
echo "==> Installing Docker"
|
||||
docker_key="-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1
|
||||
|
||||
echo "Pulling images: ${images}"
|
||||
for image in ${images}; do
|
||||
docker pull "${image}"
|
||||
done
|
||||
else
|
||||
echo "No infra_versions.js found, skipping image download"
|
||||
mQINBFWln24BEADrBl5p99uKh8+rpvqJ48u4eTtjeXAWbslJotmC/CakbNSqOb9o
|
||||
ddfzRvGVeJVERt/Q/mlvEqgnyTQy+e6oEYN2Y2kqXceUhXagThnqCoxcEJ3+KM4R
|
||||
mYdoe/BJ/J/6rHOjq7Omk24z2qB3RU1uAv57iY5VGw5p45uZB4C4pNNsBJXoCvPn
|
||||
TGAs/7IrekFZDDgVraPx/hdiwopQ8NltSfZCyu/jPpWFK28TR8yfVlzYFwibj5WK
|
||||
dHM7ZTqlA1tHIG+agyPf3Rae0jPMsHR6q+arXVwMccyOi+ULU0z8mHUJ3iEMIrpT
|
||||
X+80KaN/ZjibfsBOCjcfiJSB/acn4nxQQgNZigna32velafhQivsNREFeJpzENiG
|
||||
HOoyC6qVeOgKrRiKxzymj0FIMLru/iFF5pSWcBQB7PYlt8J0G80lAcPr6VCiN+4c
|
||||
NKv03SdvA69dCOj79PuO9IIvQsJXsSq96HB+TeEmmL+xSdpGtGdCJHHM1fDeCqkZ
|
||||
hT+RtBGQL2SEdWjxbF43oQopocT8cHvyX6Zaltn0svoGs+wX3Z/H6/8P5anog43U
|
||||
65c0A+64Jj00rNDr8j31izhtQMRo892kGeQAaaxg4Pz6HnS7hRC+cOMHUU4HA7iM
|
||||
zHrouAdYeTZeZEQOA7SxtCME9ZnGwe2grxPXh/U/80WJGkzLFNcTKdv+rwARAQAB
|
||||
tDdEb2NrZXIgUmVsZWFzZSBUb29sIChyZWxlYXNlZG9ja2VyKSA8ZG9ja2VyQGRv
|
||||
Y2tlci5jb20+iQI4BBMBAgAiBQJVpZ9uAhsvBgsJCAcDAgYVCAIJCgsEFgIDAQIe
|
||||
AQIXgAAKCRD3YiFXLFJgnbRfEAC9Uai7Rv20QIDlDogRzd+Vebg4ahyoUdj0CH+n
|
||||
Ak40RIoq6G26u1e+sdgjpCa8jF6vrx+smpgd1HeJdmpahUX0XN3X9f9qU9oj9A4I
|
||||
1WDalRWJh+tP5WNv2ySy6AwcP9QnjuBMRTnTK27pk1sEMg9oJHK5p+ts8hlSC4Sl
|
||||
uyMKH5NMVy9c+A9yqq9NF6M6d6/ehKfBFFLG9BX+XLBATvf1ZemGVHQusCQebTGv
|
||||
0C0V9yqtdPdRWVIEhHxyNHATaVYOafTj/EF0lDxLl6zDT6trRV5n9F1VCEh4Aal8
|
||||
L5MxVPcIZVO7NHT2EkQgn8CvWjV3oKl2GopZF8V4XdJRl90U/WDv/6cmfI08GkzD
|
||||
YBHhS8ULWRFwGKobsSTyIvnbk4NtKdnTGyTJCQ8+6i52s+C54PiNgfj2ieNn6oOR
|
||||
7d+bNCcG1CdOYY+ZXVOcsjl73UYvtJrO0Rl/NpYERkZ5d/tzw4jZ6FCXgggA/Zxc
|
||||
jk6Y1ZvIm8Mt8wLRFH9Nww+FVsCtaCXJLP8DlJLASMD9rl5QS9Ku3u7ZNrr5HWXP
|
||||
HXITX660jglyshch6CWeiUATqjIAzkEQom/kEnOrvJAtkypRJ59vYQOedZ1sFVEL
|
||||
MXg2UCkD/FwojfnVtjzYaTCeGwFQeqzHmM241iuOmBYPeyTY5veF49aBJA1gEJOQ
|
||||
TvBR8Q==
|
||||
=Fm3p
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
"
|
||||
echo "$docker_key" | apt-key add -
|
||||
echo "deb https://apt.dockerproject.org/repo ubuntu-xenial main" > /etc/apt/sources.list.d/docker.list
|
||||
apt-get -y update
|
||||
|
||||
# create systemd drop-in file
|
||||
mkdir -p /etc/systemd/system/docker.service.d
|
||||
echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/dockerd -H fd:// --log-driver=journald --exec-opt native.cgroupdriver=cgroupfs --storage-driver=devicemapper" > /etc/systemd/system/docker.service.d/cloudron.conf
|
||||
|
||||
apt-get -y --allow-downgrades install docker-engine=1.12.5-0~ubuntu-xenial # apt-cache madison docker-engine
|
||||
apt-mark hold docker-engine # do not update docker
|
||||
storage_driver=$(docker info | grep "Storage Driver" | sed 's/.*: //')
|
||||
if [[ "${storage_driver}" != "devicemapper" ]]; then
|
||||
echo "Docker is using "${storage_driver}" instead of devicemapper"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==== Install nginx ===="
|
||||
apt-get -y install nginx-full
|
||||
[[ "$(nginx -v 2>&1)" == *"nginx/1.10."* ]] || die "Expecting nginx version to be 1.10.x"
|
||||
echo "==> Enable memory accounting"
|
||||
apt-get -y install grub2
|
||||
sed -e 's/^GRUB_CMDLINE_LINUX="\(.*\)"$/GRUB_CMDLINE_LINUX="\1 cgroup_enable=memory swapaccount=1 panic_on_oops=1 panic=5"/' -i /etc/default/grub
|
||||
update-grub
|
||||
|
||||
echo "==== Install build-essential ===="
|
||||
apt-get -y install build-essential rcconf
|
||||
echo "==> Downloading docker images"
|
||||
if [ ! -f "${arg_infraversionpath}/infra_version.js" ]; then
|
||||
echo "No infra_versions.js found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==== Install mysql ===="
|
||||
debconf-set-selections <<< 'mysql-server mysql-server/root_password password password'
|
||||
debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password password'
|
||||
apt-get -y install mysql-server-5.7
|
||||
[[ "$(mysqld --version 2>&1)" == *"5.7."* ]] || die "Expecting mysql version to be 5.7.x"
|
||||
images=$(node -e "var i = require('${arg_infraversionpath}/infra_version.js'); console.log(i.baseImages.join(' '), Object.keys(i.images).map(function (x) { return i.images[x].tag; }).join(' '));")
|
||||
|
||||
echo "==== Install pwgen and swaks awscli ===="
|
||||
apt-get -y install pwgen swaks awscli
|
||||
echo -e "\tPulling docker images: ${images}"
|
||||
for image in ${images}; do
|
||||
docker pull "${image}"
|
||||
done
|
||||
|
||||
echo "==== Install collectd ==="
|
||||
echo "==> Install collectd"
|
||||
if ! apt-get install -y collectd collectd-utils; then
|
||||
# FQDNLookup is true in default debian config. The box code has a custom collectd.conf that fixes this
|
||||
echo "Failed to install collectd. Presumably because of http://mailman.verplant.org/pipermail/collectd/2015-March/006491.html"
|
||||
sed -e 's/^FQDNLookup true/FQDNLookup false/' -i /etc/collectd/collectd.conf
|
||||
fi
|
||||
update-rc.d -f collectd remove
|
||||
|
||||
# this simply makes it explicit that we run logrotate via cron. it's already part of base ubuntu
|
||||
echo "==== Install logrotate ==="
|
||||
apt-get install -y cron logrotate
|
||||
systemctl enable cron
|
||||
|
||||
echo "=== Prepare installer revision - ${INSTALLER_REVISION}) ==="
|
||||
rm -rf /tmp/box && mkdir -p /tmp/box
|
||||
curl "https://git.cloudron.io/cloudron/box/repository/archive.tar.gz?ref=${INSTALLER_REVISION}" | tar zxvf - --strip-components=1 -C /tmp/box
|
||||
mkdir -p "${INSTALLER_SOURCE_DIR}"
|
||||
cp -rf /tmp/box/installer/* "${INSTALLER_SOURCE_DIR}" && rm -rf /tmp/box
|
||||
echo "${INSTALLER_REVISION}" > "${INSTALLER_SOURCE_DIR}/REVISION"
|
||||
|
||||
cd "${INSTALLER_SOURCE_DIR}" && while ! npm install --production; do sleep 1; done
|
||||
chown "${USER}:${USER}" -R "${INSTALLER_SOURCE_DIR}"
|
||||
|
||||
echo "==== Install cloudron-version tool ===="
|
||||
npm install -g cloudron-version@0.1.1
|
||||
|
||||
echo "==== Install installer systemd script ===="
|
||||
cat > /etc/systemd/system/cloudron-installer.service <<EOF
|
||||
[Unit]
|
||||
Description=Cloudron Installer
|
||||
; journald crashes result in a EPIPE in node. Cannot ignore it as it results in loss of logs.
|
||||
BindsTo=systemd-journald.service
|
||||
After=box-setup.service
|
||||
|
||||
[Service]
|
||||
Type=idle
|
||||
ExecStart="${INSTALLER_SOURCE_DIR}/src/server.js"
|
||||
Environment="DEBUG=installer*,connect-lastmile"
|
||||
; kill any child (installer.sh) as well
|
||||
KillMode=control-group
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# Restore iptables before docker
|
||||
echo "==== Install iptables-restore systemd script ===="
|
||||
cat > /etc/systemd/system/iptables-restore.service <<EOF
|
||||
[Unit]
|
||||
Description=IPTables Restore
|
||||
Before=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/sbin/iptables-restore /etc/iptables/rules.v4
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# Allocate swap files
|
||||
# https://bbs.archlinux.org/viewtopic.php?id=194792 ensures this runs after do-resize.service
|
||||
# On ubuntu ec2 we use cloud-init https://wiki.archlinux.org/index.php/Cloud-init
|
||||
echo "==== Install box-setup systemd script ===="
|
||||
cat > /etc/systemd/system/box-setup.service <<EOF
|
||||
[Unit]
|
||||
Description=Box Setup
|
||||
Before=docker.service collectd.service mysql.service sshd.service nginx.service
|
||||
After=cloud-init.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart="${INSTALLER_SOURCE_DIR}/systemd/box-setup.sh"
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable cloudron-installer
|
||||
systemctl enable iptables-restore
|
||||
systemctl enable box-setup
|
||||
|
||||
# Configure systemd
|
||||
sed -e "s/^#SystemMaxUse=.*$/SystemMaxUse=100M/" \
|
||||
-e "s/^#ForwardToSyslog=.*$/ForwardToSyslog=no/" \
|
||||
-i /etc/systemd/journald.conf
|
||||
|
||||
# When rotating logs, systemd kills journald too soon sometimes
|
||||
# See https://github.com/systemd/systemd/issues/1353 (this is upstream default)
|
||||
sed -e "s/^WatchdogSec=.*$/WatchdogSec=3min/" \
|
||||
-i /lib/systemd/system/systemd-journald.service
|
||||
|
||||
sync
|
||||
|
||||
# Configure time
|
||||
sed -e 's/^#NTP=/NTP=0.ubuntu.pool.ntp.org 1.ubuntu.pool.ntp.org 2.ubuntu.pool.ntp.org 3.ubuntu.pool.ntp.org/' -i /etc/systemd/timesyncd.conf
|
||||
timedatectl set-ntp 1
|
||||
timedatectl set-timezone UTC
|
||||
|
||||
# Give user access to system logs
|
||||
apt-get -y install acl
|
||||
usermod -a -G systemd-journal ${USER}
|
||||
mkdir -p /var/log/journal # in some images, this directory is not created making system log to /run/systemd instead
|
||||
chown root:systemd-journal /var/log/journal
|
||||
systemctl restart systemd-journald
|
||||
setfacl -n -m u:${USER}:r /var/log/journal/*/system.journal
|
||||
|
||||
echo "==== Install ssh ==="
|
||||
apt-get -y install openssh-server
|
||||
# https://stackoverflow.com/questions/4348166/using-with-sed on why ? must be escaped
|
||||
sed -e 's/^#\?PermitRootLogin .*/PermitRootLogin without-password/g' \
|
||||
-e 's/^#\?PermitEmptyPasswords .*/PermitEmptyPasswords no/g' \
|
||||
-e 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/g' \
|
||||
-i /etc/ssh/sshd_config
|
||||
|
||||
# caas has ssh on port 202
|
||||
if [[ "${PROVIDER}" == "caas" ]]; then
|
||||
sed -e 's/^#\?Port .*/Port 202/g' -i /etc/ssh/sshd_config
|
||||
fi
|
||||
|
||||
# DO uses Google nameservers by default. This causes RBL queries to fail (host 2.0.0.127.zen.spamhaus.org)
|
||||
# We do not use dnsmasq because it is not a recursive resolver and defaults to the value in the interfaces file (which is Google DNS!)
|
||||
echo "==== Install unbound DNS ==="
|
||||
apt-get -y install unbound
|
||||
|
||||
# required so we can connect to this machine since port 22 is blocked by iptables by now
|
||||
systemctl reload sshd
|
||||
|
||||
|
||||
@@ -5,18 +5,15 @@
|
||||
require('supererror')({ splatchError: true });
|
||||
|
||||
// remove timestamp from debug() based output
|
||||
require('debug').formatArgs = function formatArgs() {
|
||||
arguments[0] = this.namespace + ' ' + arguments[0];
|
||||
return arguments;
|
||||
require('debug').formatArgs = function formatArgs(args) {
|
||||
args[0] = this.namespace + ' ' + args[0];
|
||||
};
|
||||
|
||||
var appHealthMonitor = require('./src/apphealthmonitor.js'),
|
||||
async = require('async'),
|
||||
config = require('./src/config.js'),
|
||||
ldap = require('./src/ldap.js'),
|
||||
oauthproxy = require('./src/oauthproxy.js'),
|
||||
server = require('./src/server.js'),
|
||||
simpleauth = require('./src/simpleauth.js');
|
||||
server = require('./src/server.js');
|
||||
|
||||
console.log();
|
||||
console.log('==========================================');
|
||||
@@ -35,9 +32,7 @@ console.log();
|
||||
async.series([
|
||||
server.start,
|
||||
ldap.start,
|
||||
simpleauth.start,
|
||||
appHealthMonitor.start,
|
||||
oauthproxy.start
|
||||
], function (error) {
|
||||
if (error) {
|
||||
console.error('Error starting server', error);
|
||||
@@ -51,15 +46,11 @@ var NOOP_CALLBACK = function () { };
|
||||
process.on('SIGINT', function () {
|
||||
server.stop(NOOP_CALLBACK);
|
||||
ldap.stop(NOOP_CALLBACK);
|
||||
simpleauth.stop(NOOP_CALLBACK);
|
||||
oauthproxy.stop(NOOP_CALLBACK);
|
||||
setTimeout(process.exit.bind(process), 3000);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', function () {
|
||||
server.stop(NOOP_CALLBACK);
|
||||
ldap.stop(NOOP_CALLBACK);
|
||||
simpleauth.stop(NOOP_CALLBACK);
|
||||
oauthproxy.stop(NOOP_CALLBACK);
|
||||
setTimeout(process.exit.bind(process), 3000);
|
||||
});
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -318,67 +318,3 @@ cloudron exec
|
||||
> swaks --server "${MAIL_SMTP_SERVER}" -p "${MAIL_SMTP_PORT}" --from "${MAIL_SMTP_USERNAME}@${MAIL_DOMAIN}" --body "Test mail from cloudron app at $(hostname -f)" --auth-user "${MAIL_SMTP_USERNAME}" --auth-password "${MAIL_SMTP_PASSWORD}"
|
||||
```
|
||||
|
||||
## simpleauth
|
||||
|
||||
Simple Auth can be used for authenticating users with a HTTP request. This method of authentication is targeted
|
||||
at applications, which for whatever reason can't use the ldap addon.
|
||||
The response contains an `accessToken` which can then be used to access the [Cloudron API](/references/api.html).
|
||||
|
||||
Exported environment variables:
|
||||
```
|
||||
SIMPLE_AUTH_SERVER= # the simple auth HTTP server
|
||||
SIMPLE_AUTH_PORT= # the simple auth server port
|
||||
SIMPLE_AUTH_URL= # the simple auth server URL. same as "http://SIMPLE_AUTH_SERVER:SIMPLE_AUTH_PORT
|
||||
SIMPLE_AUTH_CLIENT_ID # a client id for identifying the request originator with the auth server
|
||||
```
|
||||
|
||||
This addons provides two REST APIs:
|
||||
|
||||
**POST /api/v1/login**
|
||||
|
||||
Request JSON body:
|
||||
```
|
||||
{
|
||||
"username": "<username> or <email>",
|
||||
"password": "<password>"
|
||||
}
|
||||
```
|
||||
|
||||
Response 200 with JSON body:
|
||||
```
|
||||
{
|
||||
"accessToken": "<accessToken>",
|
||||
"user": {
|
||||
"id": "<userId>",
|
||||
"username": "<username>",
|
||||
"email": "<email>",
|
||||
"admin": <admin boolean>,
|
||||
"displayName": "<display name>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GET /api/v1/logout**
|
||||
|
||||
Request params:
|
||||
```
|
||||
?access_token=<accessToken>
|
||||
```
|
||||
|
||||
Response 200 with JSON body:
|
||||
```
|
||||
{}
|
||||
```
|
||||
|
||||
For debugging, [cloudron exec](https://www.npmjs.com/package/cloudron) can be used to run the `curl` tool within the context of the app:
|
||||
```
|
||||
cloudron exec
|
||||
|
||||
> USERNAME=<enter username>
|
||||
|
||||
> PASSWORD=<enter password>
|
||||
|
||||
> PAYLOAD="{\"clientId\":\"${SIMPLE_AUTH_CLIENT_ID}\", \"username\":\"${USERNAME}\", \"password\":\"${PASSWORD}\"}"
|
||||
|
||||
> curl -H "Content-Type: application/json" -X POST -d "${PAYLOAD}" "${SIMPLE_AUTH_ORIGIN}/api/v1/login"
|
||||
```
|
||||
|
||||
+49
-27
@@ -62,7 +62,7 @@ curl -H "Content-Type: application/json" -H "Authorization: Bearer <token>" http
|
||||
## OAuth
|
||||
|
||||
OAuth authentication is meant to be used by apps. An app can get an OAuth token using the
|
||||
[oauth](addons.html#oauth) or [simpleauth](addons.html#simpleauth) addon.
|
||||
[oauth](addons.html#oauth) addon.
|
||||
|
||||
Tokens obtained via OAuth have a restricted scope wherein they can only access the user's profile.
|
||||
This restriction is so that apps cannot make undesired changes to the user's Cloudron.
|
||||
@@ -151,6 +151,8 @@ If `altDomain` is set, the app can be accessed from `https://<altDomain>`.
|
||||
* `SAMEORIGIN` - allows embedding from the same domain as the app. This is the default.
|
||||
* `ALLOW-FROM https://example.com/` - allows this app to be embedded from example.com
|
||||
|
||||
`memoryLimit` is the maximum memory this app can use (in bytes) including swap. If set to 0, the app uses the `memoryLimit` value set in the manifest. If set to -1, the app gets unlimited memory.
|
||||
|
||||
Read more about the options at [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options).
|
||||
|
||||
Response (200):
|
||||
@@ -197,7 +199,8 @@ Response (200):
|
||||
health: <enum>, // health of the application
|
||||
location: <string>, // subdomain on which app is installed
|
||||
fqdn: <string>, // the FQDN of this app
|
||||
altDomain: <string> // alternate domain from which this app can be reached
|
||||
altDomain: <string>, // alternate domain from which this app can be reached
|
||||
cnameTarget: <string> || null, // If altDomain is set, this contains the CNAME location for the app
|
||||
accessRestriction: null || { // list of users and groups who can access this application
|
||||
users: [ ],
|
||||
groups: [ ]
|
||||
@@ -207,7 +210,8 @@ Response (200):
|
||||
portBindings: { // mapping from application ports to public ports
|
||||
},
|
||||
iconUrl: <url>, // a relative url providing the icon
|
||||
memoryLimit: <number> // memory constraint in bytes
|
||||
memoryLimit: <number>, // memory constraint in bytes
|
||||
sso: <boolean> // Enable single sign-on
|
||||
}
|
||||
```
|
||||
|
||||
@@ -255,6 +259,8 @@ is integrated with Cloudron Authentication.
|
||||
|
||||
`manifest` is the [application manifest](/references/manifest.html).
|
||||
|
||||
For apps that support optional single sign-on, the `sso` field can be used to disable Cloudron authentication. By default, single sign-on is enabled.
|
||||
|
||||
### List apps
|
||||
|
||||
GET `/api/v1/apps/:appId` <scope>admin</scope>
|
||||
@@ -276,7 +282,8 @@ Response (200):
|
||||
health: <enum>, // health of the application
|
||||
location: <string>, // subdomain on which app is installed
|
||||
fqdn: <string>, // the FQDN of this app
|
||||
altDomain: <string> // alternate domain from which this app can be reached
|
||||
altDomain: <string>, // alternate domain from which this app can be reached
|
||||
cnameTarget: <string> || null, // If altDomain is set, this contains the CNAME location for the app
|
||||
accessRestriction: null || { // list of users and groups who can access this application
|
||||
users: [ ],
|
||||
groups: [ ]
|
||||
@@ -447,7 +454,7 @@ POST `/api/v1/apps/:appId/configure` <scope>admin</scope>
|
||||
|
||||
Re-configures an existing app with id `appId`.
|
||||
|
||||
Configuring an app won't preserve existing data. Cloudron apps are written in a way to support reconfiguring
|
||||
Configuring an app preserves existing data. Cloudron apps are written in a way to support reconfiguring
|
||||
any of the parameters listed below without loss of data.
|
||||
|
||||
Request:
|
||||
@@ -652,6 +659,38 @@ curl -L <url> | openssl aes-256-cbc -d -pass "pass:$<backupKey>" | tar -zxf -
|
||||
|
||||
## Cloudron
|
||||
|
||||
### Activate the Cloudron
|
||||
|
||||
POST `/api/v1/cloudron/activate`
|
||||
|
||||
Activates the Cloudron with an admin username and password.
|
||||
|
||||
Request:
|
||||
```
|
||||
{
|
||||
username: <string>, // the admin username
|
||||
password: <string>, // the admin password
|
||||
email: <email> // the admin email
|
||||
}
|
||||
```
|
||||
|
||||
Response (201):
|
||||
```
|
||||
{
|
||||
"token": "771ee724a66aa557f95af06b4e6c27992f9230f6b1d65d5fbaa34cae9318d453",
|
||||
"expires": 1490224113353
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The `token` parameter can be used to make further API calls.
|
||||
|
||||
Curl example to activate the cloudron:
|
||||
|
||||
```
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"username": "girish", "password":"MySecret123#", "email": "girish@cloudron.io" }' https://my.cloudron.info/api/v1/cloudron/activate
|
||||
```
|
||||
|
||||
### Update the Cloudron
|
||||
|
||||
POST `/api/v1/cloudron/update` <scope>admin</scope>
|
||||
@@ -680,8 +719,9 @@ Gets information about an in-progress Cloudron update or backup.
|
||||
|
||||
`update` or `backup` is `null` when there is no such activity in progress.
|
||||
|
||||
```
|
||||
Response (200):
|
||||
|
||||
```
|
||||
{
|
||||
update: null || { percent: <number>, message: <string> },
|
||||
backup: null || { percent: <number>, message: <string> }
|
||||
@@ -804,7 +844,7 @@ Response (200):
|
||||
* user.remove
|
||||
* user.update
|
||||
|
||||
`source` contains information on the originator of the action. For example, for user.login, this contains the IP address, the appId and the authType (ldap or simpleauth or oauth).
|
||||
`source` contains information on the originator of the action. For example, for user.login, this contains the IP address, the appId and the authType (ldap or oauth).
|
||||
|
||||
`data` contains information on the event itself. For example, for user.login, this contains the userId that logged in. For app.install, it contains the manifest and location of the app that was installed.
|
||||
|
||||
@@ -964,24 +1004,6 @@ Response (204):
|
||||
{}
|
||||
```
|
||||
|
||||
### Tutorial
|
||||
|
||||
POST `/api/v1/profile/tutorial` <scope>profile</scope>
|
||||
|
||||
Toggles display of the tutorial when the token owner logs in.
|
||||
|
||||
Request:
|
||||
```
|
||||
{
|
||||
showTutorial: <boolean>
|
||||
}
|
||||
```
|
||||
|
||||
Response (204):
|
||||
```
|
||||
{}
|
||||
```
|
||||
|
||||
## Settings
|
||||
|
||||
### Get auto update pattern
|
||||
@@ -1099,7 +1121,7 @@ This is currently internal API and is documented here for completeness.
|
||||
Response(200):
|
||||
```
|
||||
{
|
||||
"provider": <string> // 'caas' or 'route53' or 'digitalocean'
|
||||
"provider": <string> // 'caas' or 'route53' or 'digitalocean' or 'noop' or 'manual'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1132,7 +1154,7 @@ POST `/api/v1/settings/mail_config` <scope>admin</scope> <scope>internal</scope>
|
||||
|
||||
Sets the email configuration. The Cloudron has a built-in email server for users.
|
||||
This configuration can be used to enable or disable the email server. Note that
|
||||
the Cloudron will always be able to send email on behalf of apps, regardless of
|
||||
the Cloudron will always be able to send email on behalf of apps, regardless of
|
||||
this setting.
|
||||
|
||||
Request:
|
||||
|
||||
@@ -29,7 +29,6 @@ Cloudron provides multiple authentication strategies.
|
||||
|
||||
* OAuth 2.0 provided by the [OAuth addon](/references/addons.html#oauth)
|
||||
* LDAP provided by the [LDAP addon](/references/addons.html#ldap)
|
||||
* Simple Auth provided by [Simple Auth addon](/references/addons.html#simpleauth)
|
||||
|
||||
# Choosing a strategy
|
||||
|
||||
@@ -44,13 +43,6 @@ Applications can be broadly categorized based on their user management as follow
|
||||
|
||||
* No user
|
||||
* Such apps have no concept of logged-in user.
|
||||
* The Cloudron provides a `website visibility` setting that allows a Cloudron admin to optionally
|
||||
install an OAuth proxy in front of such applications. In such a case, a user visiting the website first
|
||||
authenticates with the OAuth proxy and once authenticated is allowed into the application.
|
||||
* When an OAuth proxy is installed, such applications can use the `X-Authenticated-User` header from the
|
||||
[ICAP Extensions](https://tools.ietf.org/html/draft-stecher-icap-subid-00#section-3.4) de facto standard.
|
||||
This value can be used for display purposes or creating meta data for a document.
|
||||
|
||||
|
||||
* Single user
|
||||
* Such apps only have a single user who is usually also the `admin`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Overview
|
||||
|
||||
The application's Dockerfile must specify the FROM base image to be `cloudron/base:0.9.0`.
|
||||
The application's Dockerfile must specify the FROM base image to be `cloudron/base:0.10.0`.
|
||||
|
||||
The base image already contains most popular software packages including node, nginx, apache,
|
||||
ruby, PHP. Using the base image greatly reduces the size of app images.
|
||||
@@ -17,16 +17,16 @@ install it yourself.
|
||||
|
||||
* Apache 2.4.18
|
||||
* Composer 1.2.0
|
||||
* Go 1.5.4, 1.6.3
|
||||
* Go 1.6.4, 1.7.5 (install under `/usr/local/go-<version>`)
|
||||
* Gunicorn 19.4.5
|
||||
* Java 1.8
|
||||
* Maven 3.3.9
|
||||
* Mongo 2.6.10
|
||||
* MySQL Client 5.7.13
|
||||
* MySQL Client 5.7.17
|
||||
* nginx 1.10.0
|
||||
* Node 0.10.40, 0.12.7, 4.2.6, 4.4.7 (installed under `/usr/local/node-<version>`) [more information](#node-js)
|
||||
* Node 0.10.48, 0.12.18, 4.7.3, 6.9.5 (installed under `/usr/local/node-<version>`) [more information](#node-js)
|
||||
* Perl 5.22.1
|
||||
* PHP 7.0.8
|
||||
* PHP 7.0.13
|
||||
* Postgresql client 9.5.4
|
||||
* Python 2.7.12
|
||||
* Redis 3.0.6
|
||||
@@ -41,16 +41,16 @@ The base image can be inspected by installing [Docker](https://docs.docker.com/i
|
||||
|
||||
Once installed, pull down the base image locally using the following command:
|
||||
```
|
||||
docker pull cloudron/base:0.9.0
|
||||
docker pull cloudron/base:0.10.0
|
||||
```
|
||||
|
||||
To inspect the base image:
|
||||
```
|
||||
docker run -ti cloudron/base:0.9.0 /bin/bash
|
||||
docker run -ti cloudron/base:0.10.0 /bin/bash
|
||||
```
|
||||
|
||||
*Note:* Please use `docker 1.9.0` or above to pull the base image. Doing otherwise results in a base
|
||||
image with an incorrect image id. The image id of `cloudron/base:0.9.0` is `d038af182821`.
|
||||
image with an incorrect image id. The image id of `cloudron/base:0.10.0` is `5ec8ca8525be`.
|
||||
|
||||
# The `cloudron` user
|
||||
|
||||
|
||||
+25
-69
@@ -34,7 +34,7 @@ Here is an example manifest:
|
||||
"contactEmail": "support@clourdon.io",
|
||||
"icon": "file://icon.png",
|
||||
"tags": [ "test", "collaboration" ],
|
||||
"mediaLinks": [ "www.youtube.com/watch?v=dQw4w9WgXcQ" ]
|
||||
"mediaLinks": [ "https://images.rapgenius.com/fd0175ef780e2feefb30055be9f2e022.520x343x1.jpg" ]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -47,12 +47,14 @@ Type: object
|
||||
Required: no
|
||||
|
||||
Allowed keys
|
||||
* [email](addons.html#email)
|
||||
* [ldap](addons.html#ldap)
|
||||
* [localstorage](addons.html#localstorage)
|
||||
* [mongodb](addons.html#mongodb)
|
||||
* [mysql](addons.html#mysql)
|
||||
* [oauth](addons.html#oauth)
|
||||
* [postgresql](addons.html#postgresql)
|
||||
* [recvmail](addons.html#recvmail)
|
||||
* [redis](addons.html#redis)
|
||||
* [sendmail](addons.html#sendmail)
|
||||
|
||||
@@ -76,14 +78,14 @@ The `author` field contains the name and email of the app developer (or company)
|
||||
|
||||
Example:
|
||||
```
|
||||
"author": "Cloudron Inc <girish@cloudron.io>"
|
||||
"author": "Cloudron UG <girish@cloudron.io>"
|
||||
```
|
||||
|
||||
## changelog
|
||||
|
||||
Type: markdown string
|
||||
|
||||
Required: no
|
||||
Required: no (required for submitting to the Cloudron Store)
|
||||
|
||||
The `changelog` field contains the changes in this version of the application. This string
|
||||
can be a markdown style bulleted list.
|
||||
@@ -93,26 +95,6 @@ Example:
|
||||
"changelog": "* Add support for IE8 \n* New logo"
|
||||
```
|
||||
|
||||
## configurePath
|
||||
|
||||
Type: path string
|
||||
|
||||
Required: no
|
||||
|
||||
The `configurePath` can be used to specify the absolute path to the configuration / settings
|
||||
page of the app. When this path is present, an absoluted URL is constructed from the app's
|
||||
install location this path and presented to the user in the configuration dialog of the app.
|
||||
|
||||
This is useful for apps that have a main page which does not display a configuration / settings
|
||||
url (i.e) it's hidden for aesthetic reasons. For example, a blogging app like wordpress might
|
||||
keep the admin page url hidden in the main page. Setting the configurationPath makes the
|
||||
configuration url discoverable by the user.
|
||||
|
||||
Example:
|
||||
```
|
||||
"configurePath": "/wp-admin"
|
||||
```
|
||||
|
||||
## contactEmail
|
||||
|
||||
Type: email
|
||||
@@ -127,23 +109,6 @@ Example:
|
||||
"contactEmail": "support@testapp.com"
|
||||
```
|
||||
|
||||
## customAuth
|
||||
|
||||
Type: boolean
|
||||
|
||||
Required: no
|
||||
|
||||
The `customAuth` field can be set to true to indicate that the app does not integrate with
|
||||
Cloudron user management. Use of this flag is strongly discouraged and is only meant to be
|
||||
a stopgap measure until the app integrates with Cloudron user management.
|
||||
|
||||
When set, all access controls for the app are disabled in the Cloudron Admin UI.
|
||||
|
||||
Example:
|
||||
```
|
||||
"customAuth": true
|
||||
```
|
||||
|
||||
## description
|
||||
|
||||
Type: markdown string
|
||||
@@ -167,20 +132,6 @@ Example:
|
||||
"description:": "file://DESCRIPTION.md"
|
||||
```
|
||||
|
||||
## developmentMode
|
||||
|
||||
Type: boolean
|
||||
|
||||
Required: no
|
||||
|
||||
Setting `developmentMode` to true disables readonly rootfs and the default memory limit. In addition,
|
||||
the application *pauses* on start and can be started manually using `cloudron exec`. Note that you
|
||||
cannot submit an app to the store with this field turned on.
|
||||
|
||||
This mode can be used to identify the files being modified by your application - often required to
|
||||
debug situations where your app does not run on a readonly rootfs. Run your app using `cloudron exec`
|
||||
and use `find / -mmin -30` to find file that have been changed or created in the last 30 minutes.
|
||||
|
||||
## healthCheckPath
|
||||
|
||||
Type: url path
|
||||
@@ -203,9 +154,10 @@ Type: positive integer
|
||||
|
||||
Required: yes
|
||||
|
||||
The `httpPort` field contains the TCP port on which your app is listening for HTTP requests. This port
|
||||
is exposed to the world via subdomain/location that the user chooses at installation time. While not
|
||||
required, it is good practice to mark this port as `EXPOSE` in the Dockerfile.
|
||||
The `httpPort` field contains the TCP port on which your app is listening for HTTP requests. This
|
||||
is the HTTP port the Cloudron will use to access your app internally.
|
||||
|
||||
While not required, it is good practice to mark this port as `EXPOSE` in the Dockerfile.
|
||||
|
||||
Cloudron Apps are containerized and thus two applications can listen on the same port. In reality,
|
||||
they are in different network namespaces and do not conflict with each other.
|
||||
@@ -222,7 +174,7 @@ Example:
|
||||
|
||||
Type: local image filename
|
||||
|
||||
Required: no
|
||||
Required: no (required for submitting to the Cloudron Store)
|
||||
|
||||
The `icon` field is used to display the application icon/logo in the Cloudron Store. Icons are expected
|
||||
to be square of size 256x256.
|
||||
@@ -263,16 +215,16 @@ Required: yes
|
||||
|
||||
Type: array of urls
|
||||
|
||||
Required: no
|
||||
Required: no (required for submitting to the Cloudron Store)
|
||||
|
||||
The `mediaLinks` field contains an array of links that the Cloudron Store uses to display a slide show of pictures
|
||||
and videos of the application.
|
||||
The `mediaLinks` field contains an array of links that the Cloudron Store uses to display a slide show of pictures of the application.
|
||||
|
||||
All links are preferably https.
|
||||
They have to be publicly reachable via `https` and should have an aspect ratio of 3 to 1.
|
||||
For example `600px by 200px` (with/height).
|
||||
|
||||
```
|
||||
"mediaLinks": [
|
||||
"www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||
"https://s3.amazonaws.com/cloudron-app-screenshots/org.owncloud.cloudronapp/556f6a1d82d5e27a7c4fca427ebe6386d373304f/2.jpg",
|
||||
"https://images.rapgenius.com/fd0175ef780e2feefb30055be9f2e022.520x343x1.jpg"
|
||||
]
|
||||
```
|
||||
@@ -328,22 +280,26 @@ The intended use of this field is to display some post installation steps that t
|
||||
complete the installation. For example, displaying the default admin credentials and informing the user to
|
||||
to change it.
|
||||
|
||||
## singleUser
|
||||
The message can have the following special tags:
|
||||
* `<sso> ... </sso>` - Content in `sso` blocks are shown if SSO enabled.
|
||||
* `<nosso> ... </nosso>`- Content in `nosso` blocks are shows when SSO is disabled.
|
||||
|
||||
## optionalSso
|
||||
|
||||
Type: boolean
|
||||
|
||||
Required: no
|
||||
|
||||
The `singleUser` field can be set to true for apps that are meant to be used only a single user.
|
||||
The `optionalSso` field can be set to true for apps that can be installed optionally without using the Cloudron user management.
|
||||
|
||||
When set, the Cloudron will display a user selection dialog at installation time. The selected user is the sole user
|
||||
who can access the app.
|
||||
This only applies if any Cloudron auth related addons are used. When set, the Cloudron will not inject the auth related addon environment variables.
|
||||
Any app startup scripts have to be able to deal with missing env variables in this case.
|
||||
|
||||
## tagline
|
||||
|
||||
Type: one-line string
|
||||
|
||||
Required: no
|
||||
Required: no (required for submitting to the Cloudron Store)
|
||||
|
||||
The `tagline` is used by the Cloudron Store to display a single line short description of the application.
|
||||
|
||||
@@ -355,7 +311,7 @@ The `tagline` is used by the Cloudron Store to display a single line short descr
|
||||
|
||||
Type: Array of strings
|
||||
|
||||
Required: no
|
||||
Required: no (required for submitting to the Cloudron Store)
|
||||
|
||||
The `tags` are used by the Cloudron Store for filtering searches by keyword.
|
||||
|
||||
|
||||
+296
-341
@@ -1,128 +1,124 @@
|
||||
# Overview
|
||||
|
||||
The Cloudron platform can be installed on your own cloud server. The self hosted version comes with all the same features as the managed version.
|
||||
The Cloudron platform can be installed on public cloud servers from EC2, Digital Ocean, Hetzner,
|
||||
Linode, OVH, Scaleway, Vultr etc. Cloudron also runs well on a home server or company intranet.
|
||||
|
||||
If you have any questions, join us at our [chat](https://chat.cloudron.io).
|
||||
If you run into any trouble following this guide, ask us at our [chat](https://chat.cloudron.io).
|
||||
|
||||
# Understand
|
||||
|
||||
Before we install anything, it is helpful to understand what the Cloudron is and what exactly it is that you are installing.
|
||||
Before installing the Cloudron, it is helpful to understand Cloudron's design. The Cloudron
|
||||
intends to make self-hosting effortless. It takes care of updates, backups, firewall, dns setup,
|
||||
certificate management etc. All app and user configuration is carried out using the web interface.
|
||||
|
||||
The Cloudron intends to make self-hosting effortless. It takes care of everything - updates, backups, firewall, dns setup,
|
||||
certificate management, just about everything. Once you install it on your server, you will never have to ssh into it again. There is a web interface to manage the users and apps on the server.
|
||||
This approach to self-hosting means that the Cloudron takes complete ownership of the server and
|
||||
only tracks changes that were made via the web interface. Any external changes made to the server
|
||||
(i.e other than via the Cloudron web interface or API) may be lost across updates.
|
||||
|
||||
This approach to self-hosting means that the Cloudron takes complete ownership of your server and
|
||||
you should not use your server for anything other than what is allowed using the web interface. For example,
|
||||
currently the Cloudron is based on Ubunutu 16. When Ubuntu 18 is released, Cloudron will help you migrate your
|
||||
apps and data from 16 to 18 easily. Anything you run or install on the server beside the Cloudron will be lost
|
||||
after an update. If you want to run anything on your server, it's best to make it a Cloudron app (read more on
|
||||
how to create apps in the [packaging guide](/tutorials/packaging.html). This way the app will persist across updates.
|
||||
The Cloudron requires a domain name when it is installed. Apps are installed into subdomains.
|
||||
The `my` subdomain is special and is the location of the Cloudron web interface. For this to
|
||||
work, the Cloudron requires a way to programmatically configure the DNS entries of the domain.
|
||||
Note that the Cloudron will never overwrite _existing_ DNS entries and refuse to install
|
||||
apps on existing subdomains (so, it is safe to reuse an existing domain that runs other services).
|
||||
|
||||
# CLI Tool
|
||||
# Cloud Server
|
||||
|
||||
The [Cloudron tool](https://git.cloudron.io/cloudron/cloudron-cli) is used for managing a Cloudron. It has a `machine`
|
||||
subcommand that can be used to create, update and maintain a self-hosted Cloudron.
|
||||
DigitalOcean and EC2 (Amazon Web Services) are frequently tested by us.
|
||||
|
||||
<br/>
|
||||
<b class="text-danger">The Cloudron CLI tool has to be run on a Laptop or PC, not on the cloud server!</b>
|
||||
<br/>
|
||||
Please use the below links to support us with referrals:
|
||||
* [Amazon EC2](https://aws.amazon.com/ec2/)
|
||||
* [DigitalOcean](https://m.do.co/c/933831d60a1e)
|
||||
|
||||
## Linux & OS X
|
||||
Installing the CLI tool requires node.js and npm. The CLI tool can be installed using the following command:
|
||||
In addition to those, the Cloudron community has successfully installed the platform on those providers:
|
||||
* [Amazon Lightsail](https://amazonlightsail.com/)
|
||||
* [hosttech](https://www.hosttech.ch/?promocode=53619290)
|
||||
* [Linode](https://www.linode.com/?r=f68d816692c49141e91dd4cef3305da457ac0f75)
|
||||
* [OVH](https://www.ovh.com/)
|
||||
* [Rosehosting](https://secure.rosehosting.com/clientarea/?affid=661)
|
||||
* [Scaleway](https://www.scaleway.com/)
|
||||
* [So you Start](https://www.soyoustart.com/)
|
||||
* [Vultr](http://www.vultr.com/?ref=7110116-3B)
|
||||
|
||||
Please let us know if any of them requires tweaks or adjustments.
|
||||
|
||||
# Installing
|
||||
|
||||
## Create server
|
||||
|
||||
Create an `Ubuntu 16.04 (Xenial)` server with at-least `1gb` RAM and 20GB disk space.
|
||||
Do not make any changes to vanilla ubuntu. Be sure to allocate a static IPv4 address
|
||||
for your server.
|
||||
|
||||
Cloudron has a built-in firewall and ports are opened and closed dynamically, as and when
|
||||
apps are installed, re-configured or removed. For this reason, be sure to open all TCP and
|
||||
UDP traffic to the server and leave the traffic management to the Cloudron.
|
||||
|
||||
### Linode
|
||||
|
||||
Since Linode does not manage SSH keys, be sure to add the public key to
|
||||
`/root/.ssh/authorized_keys`.
|
||||
|
||||
### Scaleway
|
||||
|
||||
Use the [boot script](https://github.com/scaleway-community/scaleway-docker/issues/2) to
|
||||
enable memory accouting.
|
||||
|
||||
## Run setup
|
||||
|
||||
SSH into your server and run the following commands:
|
||||
|
||||
```
|
||||
npm install -g cloudron
|
||||
wget https://cloudron.io/cloudron-setup
|
||||
chmod +x cloudron-setup
|
||||
./cloudron-setup --provider <azure|digitalocean|ec2|lightsail|linode|ovh|rosehosting|scaleway|vultr|generic>
|
||||
```
|
||||
|
||||
Depending on your setup, you may need to run this as root.
|
||||
The setup will take around 10-15 minutes.
|
||||
|
||||
On OS X, it is known to work with the `openssl` package from homebrew.
|
||||
**cloudron-setup** takes the following arguments:
|
||||
|
||||
See [#14](https://git.cloudron.io/cloudron/cloudron-cli/issues/14) for more information.
|
||||
* `--provider` is the name of your VPS provider. If the name is not on the list, simply
|
||||
choose `generic`. In most cases, the `generic` provider mostly will work fine.
|
||||
If the Cloudron does not complete initialization, it may mean that
|
||||
we have to add some vendor specific quirks. Please open a
|
||||
[bug report](https://git.cloudron.io/cloudron/box/issues) in that case.
|
||||
|
||||
## Windows
|
||||
Optional arguments for installation:
|
||||
|
||||
The CLI tool does not work on Windows.
|
||||
* `--tls-provider` is the name of the SSL/TLS certificate backend. Defaults to Let's encrypt.
|
||||
Specifying `fallback` will setup the Cloudron to use the fallback wildcard certificate.
|
||||
Initially a self-signed one is provided, which can be overwritten later in the admin interface.
|
||||
This may be useful for non-public installations.
|
||||
|
||||
## Machine subcommands
|
||||
Optional arguments used for update and restore:
|
||||
|
||||
You should now be able to run the `cloudron machine help` command in a shell.
|
||||
* `--version` is the version of Cloudron to install. By default, the setup script installs
|
||||
the latest version. You can set this to an older version when restoring a Cloudron from a backup.
|
||||
|
||||
```
|
||||
create Creates a new Cloudron
|
||||
restore Restores a Cloudron
|
||||
migrate Migrates a Cloudron
|
||||
update Upgrade or updates a Cloudron
|
||||
eventlog Get Cloudron eventlog
|
||||
logs Get Cloudron logs
|
||||
ssh Get remote SSH connection
|
||||
backup Manage Cloudron backups
|
||||
```
|
||||
* `--restore-url` is a backup URL to restore from.
|
||||
|
||||
# AWS EC2
|
||||
## Domain setup
|
||||
|
||||
## Requirements
|
||||
Once the setup script completes, the server will reboot, then visit your server by its
|
||||
IP address (`https://ip`) to complete the installation.
|
||||
|
||||
To run the Cloudron on AWS, first sign up with [Amazon AWS](https://aws.amazon.com/).
|
||||
The setup website will show a certificate warning. Accept the self-signed certificate
|
||||
and proceed to the domain setup.
|
||||
|
||||
The Cloudron uses the following AWS services:
|
||||
Currently, only subdomains of the [Public Suffix List](https://publicsuffix.org/) are supported.
|
||||
For example, `example.com`, `example.co.uk` will work fine. Choosing other non-registrable
|
||||
domain names like `cloudron.example.com` will not work.
|
||||
|
||||
* **EC2** for creating a virtual private server that runs the Cloudron code.
|
||||
* **Route53** for DNS. The Cloudron will manage all app subdomains as well as the email related DNS records automatically.
|
||||
* **S3** to store encrypted Cloudron backups.
|
||||
### Route 53
|
||||
|
||||
The minimum requirements for a Cloudron depends on the apps installed. The absolute minimum required EC2 instance is `t2.small`.
|
||||
Create root or IAM credentials and choose `Route 53` as the DNS provider.
|
||||
|
||||
The Cloudron runs best on instances which do not have a burst mode VCPU.
|
||||
|
||||
The system disk space usage of a Cloudron is around 15GB. This results in a minimum requirement of about 30GB to give some headroom for app installations and user data.
|
||||
|
||||
## Cost Estimation
|
||||
|
||||
Taking the minimal requirements of hosting on EC2, with a backup retention of 2 days, the cost estimation per month is as follows:
|
||||
|
||||
```
|
||||
Route53: 0.90
|
||||
EC2: 19.04
|
||||
EBS: 3.00
|
||||
S3: 1.81
|
||||
-------------------------
|
||||
Total: $ 24.75/mth
|
||||
```
|
||||
|
||||
For custom cost estimation, please use the [AWS Cost Calculator](http://calculator.s3.amazonaws.com/index.html)
|
||||
|
||||
## Setup
|
||||
|
||||
Open the AWS console and create the required resources:
|
||||
|
||||
1. Create a Route53 zone for your domain. Be sure to set the Route53 nameservers for your domain in your name registrar. Note: Only Second Level Domains are supported.
|
||||
For example, `example.com`, `example.co.uk` will work fine. Choosing a domain name at any other level like `cloudron.example.com` will not work.
|
||||
|
||||
2. Create a S3 bucket for backups. The bucket region **must* be the same region as where you intend to create your Cloudron (EC2).
|
||||
|
||||
When creating the S3 bucket, it is important to choose a region. Do **NOT** choose `US Standard`.
|
||||
|
||||
The supported regions are:
|
||||
* US East (N. Virginia) us-east-1
|
||||
* US West (N. California) us-west-1
|
||||
* US West (Oregon) us-west-2
|
||||
* Asia Pacific (Mumbai) ap-south-1
|
||||
* Asia Pacific (Seoul) ap-northeast-2
|
||||
* Asia Pacific (Sydney) ap-southeast-2
|
||||
* Asia Pacific (Tokyo) ap-northeast-1
|
||||
* EU (Frankfurt) eu-central-1
|
||||
* EU (Ireland) eu-west-1
|
||||
* South America (São Paulo) sa-east-1
|
||||
|
||||
3. Create a new SSH key or upload an existing SSH key in the target region (`Key Pairs` in the left pane of the EC2 console).
|
||||
|
||||
4. Create AWS credentials. You can either use root **or** IAM credentials.
|
||||
* For root credentials:
|
||||
* In AWS Console, under your name in the menu bar, click `Security Credentials`
|
||||
* Click on `Access Keys` and create a key pair.
|
||||
* For IAM credentials:
|
||||
* For root credentials:
|
||||
* In AWS Console, under your name in the menu bar, click `Security Credentials`
|
||||
* Click on `Access Keys` and create a key pair.
|
||||
* For IAM credentials:
|
||||
* You can use the following policy to create IAM credentials:
|
||||
|
||||
```
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
@@ -143,7 +139,55 @@ The supported regions are:
|
||||
"Resource": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Digital Ocean
|
||||
|
||||
Create an API token with read+write access and choose `Digital Ocean` as the DNS provider.
|
||||
|
||||
### Other
|
||||
|
||||
If your domain *does not* use Route 53 or Digital Ocean, setup a wildcard (`*`) DNS `A` record that points to the
|
||||
IP of the server created above. If your DNS provider has an API, please open an
|
||||
[issue](https://git.cloudron.io/cloudron/box/issues) and we may be able to support it.
|
||||
|
||||
## Finish Setup
|
||||
|
||||
Once the domain setup is done, the Cloudron will configure the DNS and get a SSL certificate. It will automatically redirect to `https://my.<domain>`.
|
||||
|
||||
# Backups
|
||||
|
||||
The Cloudron creates encrypted backups once a day. Each app is backed up independently and these
|
||||
backups have the prefix `app_`. The platform state is backed up independently with the
|
||||
prefix `box_`.
|
||||
|
||||
By default, backups reside in `/var/backups`. Please note that having backups reside in the same
|
||||
physical machine as the Cloudron server instance is dangerous and it must be changed to
|
||||
an external storage location like `S3` as soon as possible.
|
||||
|
||||
## Amazon S3
|
||||
|
||||
Provide S3 backup credentials in the `Settings` page and leave the endpoint field empty.
|
||||
|
||||
Create a bucket in S3 (You have to have an account at [AWS](https://aws.amazon.com/)). The bucket can be setup to periodically delete old backups by
|
||||
adding a lifecycle rule using the AWS console. S3 supports both permanent deletion
|
||||
or moving objects to the cheaper Glacier storage class based on an age attribute.
|
||||
With the current daily backup schedule a setting of two days should be sufficient
|
||||
for most use-cases.
|
||||
|
||||
* For root credentials:
|
||||
* In AWS Console, under your name in the menu bar, click `Security Credentials`
|
||||
* Click on `Access Keys` and create a key pair.
|
||||
* For IAM credentials:
|
||||
* You can use the following policy to create IAM credentials:
|
||||
|
||||
```
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "s3:*",
|
||||
@@ -151,290 +195,201 @@ The supported regions are:
|
||||
"arn:aws:s3:::<your bucket name>",
|
||||
"arn:aws:s3:::<your bucket name>/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "ec2:*",
|
||||
"Resource": [
|
||||
"*"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"ec2:Region": "<ec2 region>"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Create the Cloudron
|
||||
The `Encryption key` is an arbitrary passphrase used to encrypt the backups. Keep the passphrase safe; it is
|
||||
required to decrypt the backups when restoring the Cloudron.
|
||||
|
||||
Create the Cloudron using the `cloudron machine` command:
|
||||
## Minio S3
|
||||
|
||||
[Minio](https://minio.io/) is a distributed object storage server, providing the same API as Amazon S3.
|
||||
Since Cloudron supports S3, any API compatible solution should be supported as well, if this is not the case, let us know.
|
||||
|
||||
Minio can be setup, by following the [installation instructions](https://docs.minio.io/) on any server, which is reachable by the Cloudron.
|
||||
Do not setup Minio on the same server as the Cloudron, this will inevitably result in data loss, if backups are stored on the same instance.
|
||||
|
||||
Once setup, minio will print the necessary information, like login credentials, region and endpoints in its logs.
|
||||
|
||||
```
|
||||
cloudron machine create ec2 \
|
||||
--region <aws-region> \
|
||||
--type t2.small \
|
||||
--disk-size 30 \
|
||||
--ssh-key <ssh-key-name-or-filepath> \
|
||||
--access-key-id <aws-access-key-id> \
|
||||
--secret-access-key <aws-access-key-secret> \
|
||||
--backup-bucket <bucket-name> \
|
||||
--backup-key '<secret>' \
|
||||
--fqdn <domain>
|
||||
$ ./minio server ./storage
|
||||
|
||||
Endpoint: http://192.168.10.113:9000 http://127.0.0.1:9000
|
||||
AccessKey: GFAWYNJEY7PUSLTHYHT6
|
||||
SecretKey: /fEWk66E7GsPnzE1gohqKDovaytLcxhr0tNWnv3U
|
||||
Region: us-east-1
|
||||
```
|
||||
|
||||
The `--region` is the region where your Cloudron is to be created. For example, `us-west-1` for N. California and `eu-central-1` for Frankfurt. A complete list of available
|
||||
regions is listed <a href="//docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions" target="_blank">here</a>.
|
||||
First create a new bucket for the backups, using the minio commandline tools or the webinterface. The bucket has to have **read and write** permissions.
|
||||
|
||||
The `--disk-size` parameter indicates the volume (hard disk) size to be allocated for the Cloudron.
|
||||
The information to be copied to the Cloudron's backup settings form may look similar to:
|
||||
|
||||
The `--ssh-key` is the path to a PEM file or the private SSH Key. If your key is located as `~/.ssh/id_rsa_<name>`, you can
|
||||
also simply provide the `name` as the argument.
|
||||
<img src="/docs/img/minio_backup_config.png" class="shadow"><br/>
|
||||
|
||||
The `--backup-key '<secret>'` will be used to encrypt all backups prior to uploading to S3. Keep that secret in a safe place, as you need it to restore your Cloudron from a backup! You can generate a random key using `pwgen -1y 64`. Be sure to put single quotes
|
||||
around the `secret` to prevent accidental shell expansion.
|
||||
The `Encryption key` is an arbitrary passphrase used to encrypt the backups. Keep the passphrase safe; it is
|
||||
required to decrypt the backups when restoring the Cloudron.
|
||||
|
||||
**NOTE**: The `cloudron machine create ec2` subcommand will automatically create a corresponding VPC, subnet and security group for your Cloudron, unless `--subnet` and `--security-group` arguments are explicitly passed in. If you want to reuse existing resources, please ensure that the security group does not limit any traffic to the Cloudron since the Cloudron manages its own firewall and that the subnet has an internet gateway setup in the routing table.
|
||||
# Email
|
||||
|
||||
**NOTE**: See `cloudron machine create ec2 --help` for all available options.
|
||||
Cloudron has a built-in email server. By default, it only sends out email on behalf of apps
|
||||
(for example, password reset or notification). You can enable the email server for sending
|
||||
and receiving mail on the `settings` page. This feature is only available if you have setup
|
||||
a DNS provider like Digital Ocean or Route53.
|
||||
|
||||
# DigitalOcean
|
||||
|
||||
<a id="requirements-1"></a>
|
||||
## Requirements
|
||||
|
||||
To run the Cloudron on DigitalOcean, first sign up with [DigitalOcean](https://m.do.co/c/933831d60a1e) (Use this referral link to get $10 credit).
|
||||
|
||||
The minimum requirements for a Cloudron depends on the apps installed. The absolute minimum Droplet required is `1gb`.
|
||||
|
||||
All backups on DigitalOcean Cloudrons are stored locally at `/var/backups`. We recommend to download backups from time to time to a different location using `cloudron machine backup download`.
|
||||
|
||||
<a id="setup-1"></a>
|
||||
## Setup
|
||||
|
||||
Open the DigitalOcean console and do the following:
|
||||
|
||||
1. Create an API token with read+write access.
|
||||
|
||||
2. Upload the SSH key which you intend to use for your Cloudron.
|
||||
|
||||
3. Add the domain you intend to use for your Cloudron.
|
||||
|
||||
* Due to how the DigitalOcean interface works, you have to provide a dummy IPv4 (like `1.2.3.4`) to add a domain.
|
||||
|
||||
* Click on the domain you created and delete the '@' dummy record created above.
|
||||
|
||||
<a id="create-the-cloudron-1"></a>
|
||||
## Create the Cloudron
|
||||
|
||||
Create the Cloudron using the `cloudron machine` command:
|
||||
|
||||
Note: Only Second Level Domains are supported. For example, `example.com`, `example.co.uk` will work fine. Choosing a domain name at any other level like `cloudron.example.com` will not work.
|
||||
|
||||
```
|
||||
cloudron machine create digitalocean \
|
||||
--fqdn <domain> \
|
||||
--region <digitalocean-region> \
|
||||
--token <digitalocean-api-token> \
|
||||
--ssh-key <ssh-key-name-or-filepath> \
|
||||
--backup-key <backup-key>
|
||||
```
|
||||
|
||||
The `--region` is the region where your Cloudron is to be created. For example, `nyc3` for New York and `fra1` for Frankfurt. A complete list of available
|
||||
regions can be obtained <a href="https://developers.digitalocean.com/documentation/v2/#regions" target="_blank">here</a>.
|
||||
|
||||
The `--ssh-key` is the path to a PEM file or the private SSH Key. If your key is located as `~/.ssh/id_rsa_<name>`, you can
|
||||
also simply provide `name` as the argument.
|
||||
|
||||
The `--backup-key '<secret>'` will be used to encrypt all backups. Keep that secret in a safe place, as you need it to restore your Cloudron from a backup! You can generate a random key using `pwgen -1y 64`. Be sure to put single quotes
|
||||
around the `secret` to prevent accidental shell expansion.
|
||||
|
||||
**NOTE**: See `cloudron machine create digitalocean --help` for all available options.
|
||||
|
||||
# Generic
|
||||
|
||||
<a id="requirements-2"></a>
|
||||
## Requirements
|
||||
|
||||
The Cloudron does not support servers other than EC2 and Digital Ocean. However, it is possible to install & run
|
||||
the Cloudron easily on any cloud server (Vultr, Linode, Hetzner, OVH, etc) with SSH access.
|
||||
|
||||
The following requirements must be met:
|
||||
* Create a server with Ubuntu 16.04. Do not make any changes to vanilla ubuntu.
|
||||
* The minimum requirements for a Cloudron depends on the apps installed. The absolute minimum Droplet required is `1gb`.
|
||||
|
||||
**NOTE**: Cloudron created on a generic machine cannot be easily updated or restored.
|
||||
|
||||
<a id="setup-2"></a>
|
||||
## Setup
|
||||
|
||||
* Setup a wildcard DNS entry (`*.domain.com`) for your domain to point to the IP of the server you have created.
|
||||
|
||||
### Linode
|
||||
|
||||
Since Linode does not manage SSH keys, be sure to add the public key to `/root/.ssh/authorized_keys`.
|
||||
|
||||
### Scaleway
|
||||
|
||||
* Use the [boot script](https://github.com/scaleway-community/scaleway-docker/issues/2) to enable memory accouting.
|
||||
|
||||
<a id="create-the-cloudron-2"></a>
|
||||
## Create the Cloudron
|
||||
|
||||
Create the Cloudron using the `cloudron machine` command:
|
||||
|
||||
Note: Only Second Level Domains are supported. For example, `example.com`, `example.co.uk` will work fine. Choosing a domain name at any other level like `cloudron.example.com` will not work.
|
||||
|
||||
```
|
||||
cloudron machine create generic \
|
||||
--ip <ip> \
|
||||
--fqdn <domain> \
|
||||
--ssh-key <ssh-key-name-or-filepath> \
|
||||
--backup-key <backup-key>
|
||||
```
|
||||
|
||||
The `--ip` is the public IP of your server.
|
||||
|
||||
The `--ssh-key` is the path to a PEM file or the private SSH Key. If your key is located as `~/.ssh/id_rsa_<name>`, you can
|
||||
also simply provide `name` as the argument.
|
||||
|
||||
The `--backup-key '<secret>'` will be used to encrypt all backups. Keep that secret in a safe place, as you need it to restore your Cloudron from a backup! You can generate a random key using `pwgen -1y 64`. Be sure to put single quotes
|
||||
around the `secret` to prevent accidental shell expansion.
|
||||
|
||||
# First time setup
|
||||
|
||||
Visit `https://my.<domain>` to do first time setup of your Cloudron.
|
||||
|
||||
Please note the following:
|
||||
|
||||
1. The website should already have a valid TLS certificate. If you see any certificate warnings, it means your Cloudron was not created correctly.
|
||||
2. If you see a login screen, instead of a setup screen, it means that someone else got to your Cloudron first and set it up
|
||||
already! In this unlikely case, simply delete the instance and create a new Cloudron again.
|
||||
|
||||
Once the setup is done, you can access the admin page in the future at `https://my.<domain>`.
|
||||
|
||||
# Backups
|
||||
|
||||
The Cloudron has a backup schedule of creating one once a day. In addition to regularly scheduled backups, a backup is also created if you update the Cloudron or any of the apps (in this case only the app in question will get backed up).
|
||||
|
||||
Since this might result in a lot of backup data on your S3 backup bucket, we recommend adjusting the bucket properties. This can be done adding a lifecycle rule for that bucket, using the AWS console. S3 supports both permanent deletion or moving objects to the cheaper Glacier storage class based on an age attribute. With the current daily backup schedule a setting of two days should be already sufficient for most use-cases.
|
||||
|
||||
If your Cloudron is running, you can list backups using the following command:
|
||||
```
|
||||
cloudron machine backup list <domain>
|
||||
```
|
||||
|
||||
Alternately, you can list the backups by querying S3 using the following command:
|
||||
```
|
||||
cloudron machine backup list --provider ec2 \
|
||||
--region <region> \
|
||||
--access-key-id <access-key-id> \
|
||||
--secret-access-key <secret-access-key> \
|
||||
--backup-bucket <s3 bucket name> \
|
||||
<domain>
|
||||
```
|
||||
|
||||
# Restore
|
||||
|
||||
The Cloudron can restore itself from a backup using the following command:
|
||||
```
|
||||
cloudron machine create ec2 \
|
||||
--backup <backup-id> \
|
||||
--region <aws-region> \
|
||||
--type t2.small \
|
||||
--disk-size 30 \
|
||||
--ssh-key <ssh-key-name> \
|
||||
--access-key-id <aws-access-key-id> \
|
||||
--secret-access-key <aws-access-key-secret> \
|
||||
--backup-bucket <bucket-name> \
|
||||
--backup-key <secret> \
|
||||
--fqdn <domain>
|
||||
```
|
||||
|
||||
The backup id can be obtained by [listing the backup](/references/selfhosting.html#backups). Other arguments are similar to [Cloudron creation](/references/selfhosting.html#create-the-cloudron). Once the new instance has completely restored, you can safely terminate the old Cloudron from the AWS console.
|
||||
|
||||
# Updates
|
||||
|
||||
Apps installed from the Cloudron Store are updated automatically every night.
|
||||
|
||||
The Cloudron platform itself updates in two ways:
|
||||
|
||||
* An **update** is applied onto the running server instance. Such updates are performed every night. You can use the Cloudron UI to perform updates.
|
||||
|
||||
* An **upgrade** requires a new OS image and thus has to be performed using the CLI tool. This process involves creating a new EC2 instance is created using the latest image and all the data and apps are restored. The `cloudron machine update` command can be used when an _upgrade_ is available (you will get a notification in the UI).
|
||||
```
|
||||
cloudron machine update <domain>
|
||||
```
|
||||
Once the upgrade is complete, you can safely terminate the old EC2 instance.
|
||||
|
||||
The Cloudron will always make a complete backup before attempting an update or upgrade. In the unlikely case an update fails, it can be [restored](/references/selfhosting.html#restore).
|
||||
|
||||
# SSH
|
||||
|
||||
If you want to SSH into your Cloudron, you can
|
||||
```
|
||||
ssh -p 202 -i ~/.ssh/ssh_key_name root@my.<domain>
|
||||
```
|
||||
|
||||
If you are unable to connect, verify the following:
|
||||
* Be sure to use the **my.** subdomain (eg. my.foobar.com).
|
||||
* The SSH Key should be in PEM format. If you are using Putty PPK files, follow [this article](http://stackoverflow.com/questions/2224066/how-to-convert-ssh-keypairs-generated-using-puttygenwindows-into-key-pairs-use) to convert it to PEM format.
|
||||
* The SSH Key must have correct permissions (400) set (this is a requirement of the ssh client).
|
||||
|
||||
# Mail
|
||||
|
||||
Your server's IP plays a big role in how emails from our Cloudron get handled. Spammers frequently abuse public IP addresses
|
||||
and as a result your Cloudron might possibly start out with a bad reputation. The good news is that most IP based
|
||||
blacklisting services cool down over time. The Cloudron sets up DNS entries for SPF, DKIM, DMARC automatically and
|
||||
Your server's IP plays a big role in how emails from our Cloudron get handled. Spammers
|
||||
frequently abuse public IP addresses and as a result your Cloudron might possibly start
|
||||
out with a bad reputation. The good news is that most IP based blacklisting services cool
|
||||
down over time. The Cloudron sets up DNS entries for SPF, DKIM, DMARC automatically and
|
||||
reputation should be easy to get back.
|
||||
|
||||
## Checklist
|
||||
|
||||
* Once your Cloudron is ready, setup a Reverse DNS PTR record to be setup for the `my` subdomain.
|
||||
* If you are unable to receive mail, first thing to check is if your VPS provider lets you
|
||||
receive mail on port 25.
|
||||
|
||||
* For AWS/EC2, you can find the request form [here](https://aws-portal.amazon.com/gp/aws/html-forms-controller/contactus/ec2-email-limit-rdns-request.
|
||||
* Digital Ocean - New accounts frequently have port 25 blocked. Write to their support to
|
||||
unblock your server.
|
||||
|
||||
* For Digital Ocean this is automatic. Digital Ocean sets up a PTR record based on the droplet's name. For this reason, it is important that you do not rename your server and keep it at `my.<domain>`.
|
||||
* EC2, Lightsail & Scaleway - Edit your security group to allow email.
|
||||
|
||||
* For Scaleway, edit your security group to allow email. You can also set a PTR record on the interface with your
|
||||
`my.<domain>`.
|
||||
* Setup a Reverse DNS PTR record to be setup for the `my` subdomain.
|
||||
**Note:** PTR records are a feature of your VPS provider and not your domain provider.
|
||||
|
||||
* Check if your IP is listed in any DNSBL list [here](http://multirbl.valli.org/). In most cases, you can apply for removal
|
||||
of your IP by filling out a form at the DNSBL manager site.
|
||||
* You can verify the PTR record [https://mxtoolbox.com/ReverseLookup.aspx](here).
|
||||
|
||||
* Finally, check your spam score at [mail-tester.com](https://www.mail-tester.com/). The Cloudron should get 100%, if not please let
|
||||
us know.
|
||||
* AWS EC2 & Lightsail - Fill the [PTR request form](https://aws-portal.amazon.com/gp/aws/html-forms-controller/contactus/ec2-email-limit-rdns-request).
|
||||
|
||||
# Debugging
|
||||
* Digital Ocean - Digital Ocean sets up a PTR record based on the droplet's name. So, simply rename
|
||||
your droplet to `my.<domain>`. Note that some new Digital Ocean accounts have [port 25 blocked](https://www.digitalocean.com/community/questions/port-25-smtp-external-access).
|
||||
|
||||
To debug the Cloudron CLI tool:
|
||||
* Linode - Follow this [guide](https://www.linode.com/docs/networking/dns/setting-reverse-dns).
|
||||
|
||||
* `DEBUG=* cloudron <cmd>`
|
||||
* Scaleway - Edit your security group to allow email and [reboot the server](https://community.online.net/t/security-group-not-working/2096) for the change to take effect. You can also set a PTR record on the interface with your `my.<domain>`.
|
||||
|
||||
You can also [SSH](#ssh) into your Cloudron and collect logs.
|
||||
* Check if your IP is listed in any DNSBL list [here](http://multirbl.valli.org/). In most cases,
|
||||
you can apply for removal of your IP by filling out a form at the DNSBL manager site.
|
||||
|
||||
* `journalctl -a -u box -u cloudron-installer` to get debug output of box related code.
|
||||
* `docker ps` will give you the list of containers. The addon containers are named as `mail`, `postgresql`, `mysql` etc. If you want to get a specific
|
||||
containers log output, `journalctl -a CONTAINER_ID=<container_id>`.
|
||||
* When using wildcard or manual DNS backends, you have to setup the DMARC, MX records manually.
|
||||
|
||||
# Hotfixing
|
||||
* Finally, check your spam score at [mail-tester.com](https://www.mail-tester.com/). The Cloudron
|
||||
should get 100%, if not please let us know.
|
||||
|
||||
Hotfixing is the process of patching your Cloudron to run the latest git code. This is useful if require a patch urgently and for testing and development. Note that it is ot possible to hotfix between arbitrary git revisions (for example, if there is some
|
||||
db migration involved), so use this with care.
|
||||
# CLI Tool
|
||||
|
||||
To hotfix your cloudron, run the following from the `box` code checkout:
|
||||
The [Cloudron tool](https://git.cloudron.io/cloudron/cloudron-cli) is useful for managing
|
||||
a Cloudron. <b class="text-danger">The Cloudron CLI tool has to be installed & run on a Laptop or PC</b>
|
||||
|
||||
Once installed, you can install, configure, list, backup and restore apps from the command line.
|
||||
|
||||
## Linux & OS X
|
||||
|
||||
Installing the CLI tool requires node.js and npm. The CLI tool can be installed using the following command:
|
||||
|
||||
```
|
||||
cloudron machine hotfix --ssh-key <key> <domain>
|
||||
npm install -g cloudron
|
||||
```
|
||||
|
||||
# Other Providers
|
||||
Depending on your setup, you may need to run this as root.
|
||||
|
||||
Currently, we do not support other cloud server provider. Please let us know at [support@cloudron.io](mailto:support@cloudron.io), if you want to see other providers supported.
|
||||
On OS X, it is known to work with the `openssl` package from homebrew.
|
||||
|
||||
See [#14](https://git.cloudron.io/cloudron/cloudron-cli/issues/14) for more information.
|
||||
|
||||
## Windows
|
||||
|
||||
The CLI tool does not work on Windows. Please contact us on our [chat](https://chat.cloudron.io) if you want to help with Windows support.
|
||||
|
||||
# Updates
|
||||
|
||||
Apps installed from the Cloudron Store are automatically updated every night.
|
||||
|
||||
The Cloudron platform itself updates in two ways: update or upgrade.
|
||||
|
||||
### Update
|
||||
|
||||
An **update** is applied onto the running server instance. Such updates are performed
|
||||
every night. You can also use the Cloudron UI to initiate an update immediately.
|
||||
|
||||
The Cloudron will always make a complete backup before attempting an update. In the unlikely
|
||||
case an update fails, it can be [restored](/references/selfhosting.html#restore).
|
||||
|
||||
### Upgrade
|
||||
|
||||
An **upgrade** requires a new OS image. This process involves creating a new server from scratch
|
||||
with the latest code and restoring it from the last backup.
|
||||
|
||||
To upgrade follow these steps closely:
|
||||
|
||||
* Create a new backup - `cloudron machine backup create`
|
||||
|
||||
* List the latest backup - `cloudron machine backup list`
|
||||
|
||||
* Make the backup available for the new cloudron instance:
|
||||
|
||||
* `S3` - When storing backup ins S3, make the latest box backup public - files starting with `box_` (from v0.94.0) or `backup_`. This can be done from the AWS S3 console as seen here:
|
||||
|
||||
<img src="/docs/img/aws_backup_public.png" class="shadow haze"><br/>
|
||||
|
||||
Copy the new public URL of the latest backup for use as the `--restore-url` below.
|
||||
|
||||
<img src="/docs/img/aws_backup_link.png" class="shadow haze"><br/>
|
||||
|
||||
* `File system` - When storing backups in `/var/backups`, you have to make the box and the app backups available to the new Cloudron instance's `/var/backups`. This can be achieved in a variety of ways depending on the situation: like scp'ing the backup files to the machine before installation, mounting the external backup hard drive into the new Cloudron's `/var/backup` OR downloading a copy of the backup using `cloudron machine backup download` and uploading them to the new machine. After doing so, pass `file:///var/backups/<path to box backup>` as the `--restore-url` below.
|
||||
|
||||
* Create a new Cloudron by following the [installing](/references/selfhosting.html#installing) section.
|
||||
When running the setup script, pass in the `--encryption-key` and `--restore-url` flags.
|
||||
The `--encryption-key` is the backup encryption key. It can be displayed with `cloudron machine info`
|
||||
|
||||
Similar to the initial installation, a Cloudron upgrade looks like:
|
||||
```
|
||||
$ ssh root@newserverip
|
||||
> wget https://cloudron.io/cloudron-setup
|
||||
> chmod +x cloudron-setup
|
||||
> ./cloudron-setup --provider <digitalocean|ec2|generic|scaleway> --domain <example.com> --encryption-key <key> --restore-url <publicS3Url>
|
||||
```
|
||||
|
||||
Note: When upgrading an old version of Cloudron (<= 0.94.0), pass the `--version 0.94.1` flag and then continue updating
|
||||
from that.
|
||||
|
||||
* Finally, once you see the newest version being displayed in your Cloudron webinterface, you can safely delete the old server instance.
|
||||
|
||||
# Restore
|
||||
|
||||
To restore a Cloudron from a specific backup:
|
||||
|
||||
* Select the backup - `cloudron machine backup list`
|
||||
|
||||
* Make the backup public
|
||||
|
||||
* `S3` - Make the box backup publicly readable - files starting with `box_` (from v0.94.0) or `backup_`. This can be done from the AWS S3 console. Once the box has restored, you can make it private again.
|
||||
|
||||
* `File system` - When storing backups in `/var/backups`, you have to make the box and the app backups available to the new Cloudron instance's `/var/backups`. This can be achieved in a variety of ways depending on the situation: like scp'ing the backup files to the new machine before Cloudron installation OR mounting an external backup hard drive into the new Cloudron's `/var/backup` OR downloading a copy of the backup using `cloudron machine backup download` and uploading them to the new machine. After doing so, pass `file:///var/backups/<path to box backup>` as the `--restore-url` below.
|
||||
|
||||
* Create a new Cloudron by following the [installing](/references/selfhosting.html#installing) section.
|
||||
When running the setup script, pass in the `version`, `encryption-key`, `domain` and `restore-url` flags.
|
||||
The `version` field is the version of the Cloudron that the backup corresponds to (it is embedded
|
||||
in the backup file name).
|
||||
|
||||
* Make the box backup private, once the upgrade is complete.
|
||||
|
||||
# Debug
|
||||
|
||||
You can SSH into your Cloudron and collect logs:
|
||||
|
||||
* `journalctl -a -u box` to get debug output of box related code.
|
||||
* `docker ps` will give you the list of containers. The addon containers are named as `mail`, `postgresql`,
|
||||
`mysql` etc. If you want to get a specific container's log output, `journalctl -a CONTAINER_ID=<container_id>`.
|
||||
|
||||
# Alerts
|
||||
|
||||
The Cloudron will notify the Cloudron administrator via email if apps go down, run out of memory, have updates
|
||||
available etc.
|
||||
|
||||
You will have to setup a 3rd party service like [Cloud Watch](https://aws.amazon.com/cloudwatch/) or [UptimeRobot](http://uptimerobot.com/) to monitor the Cloudron itself. You can use `https://my.<domain>/api/v1/cloudron/status`
|
||||
as the health check URL.
|
||||
|
||||
# Help
|
||||
|
||||
If you run into any problems, join us in our [chat](https://chat.cloudron.io) or [email us](mailto:support@cloudron.io).
|
||||
|
||||
If you run into any problems, join us at our [chat](https://chat.cloudron.io) or [email us](mailto:support@cloudron.io).
|
||||
|
||||
@@ -75,7 +75,7 @@ A Dockerfile contains commands to assemble an image.
|
||||
Create a file named `tutorial/Dockerfile` with the following content:
|
||||
|
||||
```dockerfile
|
||||
FROM cloudron/base:0.9.0
|
||||
FROM cloudron/base:0.10.0
|
||||
|
||||
ADD server.js /app/code/server.js
|
||||
|
||||
@@ -171,7 +171,7 @@ Login successful.
|
||||
Build scheduled with id 76cebfdd-7822-4f3d-af17-b3eb393ae604
|
||||
Downloading source
|
||||
Building
|
||||
Step 0 : FROM cloudron/base:0.9.0
|
||||
Step 0 : FROM cloudron/base:0.10.0
|
||||
---> 97583855cc0c
|
||||
Step 1 : ADD server.js /app/code
|
||||
---> b09b97ecdfbc
|
||||
@@ -333,7 +333,7 @@ and modify our Dockerfile to look like this:
|
||||
File `tutorial/Dockerfile`
|
||||
|
||||
```dockerfile
|
||||
FROM cloudron/base:0.9.0
|
||||
FROM cloudron/base:0.10.0
|
||||
|
||||
ADD server.js /app/code/server.js
|
||||
ADD package.json /app/code/package.json
|
||||
|
||||
+34
-20
@@ -5,8 +5,8 @@ This tutorial outlines how to package an existing web application for the Cloudr
|
||||
If you are aware of Docker and Heroku, you should feel at home packaging for the
|
||||
Cloudron. Roughly, the steps involved are:
|
||||
|
||||
* Create a Dockerfile for your application. If your application already has
|
||||
a Dockerfile, you should able to reuse most of it. By virtue of Docker, the Cloudron
|
||||
* Create a Dockerfile for your application. If your application already has a Dockerfile, it
|
||||
is a good starting point for packaging for the Cloudron. By virtue of Docker, the Cloudron
|
||||
is able to run apps written in any language/framework.
|
||||
|
||||
* Create a CloudronManifest.json that provides information like title, author, description
|
||||
@@ -79,27 +79,27 @@ console.log("Server running at port 8000");
|
||||
The Dockerfile contains instructions on how to create an image for your application.
|
||||
|
||||
```Dockerfile
|
||||
FROM cloudron/base:0.9.0
|
||||
FROM cloudron/base:0.10.0
|
||||
|
||||
ADD server.js /app/code/server.js
|
||||
|
||||
CMD [ "/usr/local/node-4.2.1/bin/node", "/app/code/server.js" ]
|
||||
CMD [ "/usr/local/node-4.4.7/bin/node", "/app/code/server.js" ]
|
||||
```
|
||||
|
||||
The `FROM` command specifies that we want to start off with Cloudron's [base image](/references/baseimage.html).
|
||||
All Cloudron apps **must** start from this base image. This approach conserves space on the Cloudron since
|
||||
Docker images tend to be quiet large.
|
||||
Docker images tend to be quite large and also helps us to do a security audit on apps more easily.
|
||||
|
||||
The `ADD` command copies the source code of the app into the directory `/app/code`. There is nothing special
|
||||
about the `/app/code` directory and it is merely a convention we use to store the application code.
|
||||
|
||||
The `CMD` command specifies how to run the server. The base image already contains many different versions of
|
||||
node.js. We use Node 4.2.1 here.
|
||||
node.js. We use Node 4.4.7 here.
|
||||
|
||||
This Dockerfile can be built and run locally as:
|
||||
```
|
||||
docker build -t tutorial .
|
||||
docker run -p 8000:8000 -ti tutorial
|
||||
docker run -p 8000:8000 -t tutorial
|
||||
```
|
||||
|
||||
## Manifest
|
||||
@@ -188,7 +188,7 @@ Build scheduled with id e7706847-f2e3-4ba2-9638-3f334a9453a5
|
||||
Waiting for build to begin, this may take a bit...
|
||||
Downloading source
|
||||
Building
|
||||
Step 1 : FROM cloudron/base:0.9.0
|
||||
Step 1 : FROM cloudron/base:0.10.0
|
||||
---> be9fc6312b2d
|
||||
Step 2 : ADD server.js /app/code/server.js
|
||||
---> 10513e428d7a
|
||||
@@ -271,14 +271,18 @@ You can also execute arbitrary commands:
|
||||
$ cloudron exec env # display the env variables that your app is running with
|
||||
```
|
||||
|
||||
### DevelopmentMode
|
||||
### Debugging
|
||||
|
||||
When debugging complex startup scripts, one can specify `"developmentMode": true,` in the CloudronManifest.json.
|
||||
This will ignore the `RUN` command, specified in the Dockerfile and allows the developer to interactively test
|
||||
the startup scripts using `cloudron exec`.
|
||||
An app can be placed in `debug` mode by passing `--debug` to `cloudron install` or `cloudron configure`.
|
||||
Doing so, runs the app in a non-readonly rootfs and unlimited memory. By default, this will also ignore
|
||||
the `RUN` command specified in the Dockerfile. The developer can then interactively test the app and
|
||||
startup scripts using `cloudron exec`.
|
||||
|
||||
**Note:** that an app running in this mode has full read/write access to the filesystem and all memory limits are lifted.
|
||||
This mode can be used to identify the files being modified by your application - often required to
|
||||
debug situations where your app does not run on a readonly rootfs. Run your app using `cloudron exec`
|
||||
and use `find / -mmin -30` to find file that have been changed or created in the last 30 minutes.
|
||||
|
||||
You can turn off debugging mode using `cloudron configure --no-debug`.
|
||||
|
||||
# Addons
|
||||
|
||||
@@ -331,7 +335,7 @@ See https://git.cloudron.io/cloudron/tutorial-ldap for a simple example of how t
|
||||
For apps that are single user can skip Single Sign-On support by setting the `"singleUser": true`
|
||||
in the manifest. By doing so, the Cloudron will installer will show a dialog to choose a user.
|
||||
|
||||
For app that have no user management at all, the Cloudron implements an `OAuth proxy` that
|
||||
For app that have no user management at all, the Cloudron implements an `OAuth proxy` that
|
||||
optionally lets the Cloudron admin make the app visible only for logged in users.
|
||||
|
||||
# Best practices
|
||||
@@ -385,6 +389,8 @@ field in the manifest.
|
||||
Design your application runtime for concurrent use by 50 users. The Cloudron is not designed for
|
||||
concurrent access by 100s or 1000s of users.
|
||||
|
||||
An app can determine it's memory limit by reading `/sys/fs/cgroup/memory/memory.limit_in_bytes`.
|
||||
|
||||
## Authentication
|
||||
|
||||
Apps should integrate with one of the [authentication strategies](/references/authentication.html).
|
||||
@@ -408,11 +414,20 @@ the `start.sh` script does the following:
|
||||
|
||||
The app's main process must handle SIGTERM and forward it as required to child processes. bash does not
|
||||
automatically forward signals to child processes. For this reason, when using a startup shell script,
|
||||
remember to use exec <app> as the last line. Doing so will replace bash with your program and allows
|
||||
remember to use exec <app> as the last line. Doing so will replace bash with your program and allows
|
||||
your program to handle signals as required.
|
||||
|
||||
# Beta Testing
|
||||
|
||||
## Metadata
|
||||
|
||||
Publishing to the Cloudron Store requires apps to have meta data specified in the `CloudronManifest.json`.
|
||||
|
||||
The `cloudron` tool will notify if any such information is missing, prior to uploading.
|
||||
See more information for each field [here](/references/manifest.html).
|
||||
|
||||
## Upload for Testing
|
||||
|
||||
Once your app is ready, you can upload it to the store for `beta testing` by
|
||||
other Cloudron users. This can be done using:
|
||||
|
||||
@@ -420,9 +435,8 @@ other Cloudron users. This can be done using:
|
||||
cloudron upload
|
||||
```
|
||||
|
||||
The app should now be visible in the Store view of your cloudron under
|
||||
the 'Testing' section. You can check if the icon, description and other details
|
||||
appear correctly.
|
||||
You should now be able to visit `/#/appstore/<appid>?version=<appversion>` on your
|
||||
Cloudron to check if the icon, description and other details appear correctly.
|
||||
|
||||
Other Cloudron users can install your app on their Cloudron's using
|
||||
`cloudron install --appstore-id <appid@version>`.
|
||||
@@ -442,7 +456,7 @@ The cloudron.io team will review the app and publish the app to the store.
|
||||
## Versioning
|
||||
|
||||
To create an update for an app, simply bump up the [semver version](/references/manifest.html#version) field in
|
||||
the manifest and publish a new version to the store.
|
||||
the manifest and publish a new version to the store.
|
||||
|
||||
The Cloudron chooses the next app version to update to based on the following algorithm:
|
||||
* Choose the maximum `patch` version matching the app's current `major` and `minor` version.
|
||||
@@ -461,7 +475,7 @@ The Cloudron admins get notified by email for any major or minor app releases.
|
||||
## Failed updates
|
||||
|
||||
The Cloudron always makes a backup of the app before making an update. Should the
|
||||
update fail, the user can restore to the backup (which will also restore the app's
|
||||
update fail, the user can restore to the backup (which will also restore the app's
|
||||
code to the previous version).
|
||||
|
||||
# Cloudron Button
|
||||
|
||||
+49
-4
@@ -40,7 +40,16 @@ gulp.task('3rdparty', function () {
|
||||
// JavaScript
|
||||
// --------------
|
||||
|
||||
gulp.task('js', ['js-index', 'js-setup', 'js-update'], function () {});
|
||||
if (argv.help || argv.h) {
|
||||
console.log('Supported arguments for "gulp develop":');
|
||||
console.log(' --client-id <clientId>');
|
||||
console.log(' --client-secret <clientSecret>');
|
||||
console.log(' --api-origin <cloudron api uri>');
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
gulp.task('js', ['js-index', 'js-setup', 'js-setupdns', 'js-update'], function () {});
|
||||
|
||||
var oauth = {
|
||||
clientId: argv.clientId || 'cid-webadmin',
|
||||
@@ -55,7 +64,14 @@ console.log(' ClientSecret: %s', oauth.clientSecret);
|
||||
console.log(' Cloudron API: %s', oauth.apiOrigin || 'default');
|
||||
console.log();
|
||||
|
||||
|
||||
gulp.task('js-index', function () {
|
||||
// needs special treatment for error handling
|
||||
var uglifyer = uglify();
|
||||
uglifyer.on('error', function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
gulp.src([
|
||||
'webadmin/src/js/index.js',
|
||||
'webadmin/src/js/client.js',
|
||||
@@ -66,25 +82,53 @@ gulp.task('js-index', function () {
|
||||
.pipe(ejs({ oauth: oauth }, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('index.js', { newLine: ';' }))
|
||||
.pipe(uglify())
|
||||
.pipe(uglifyer)
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-setup', function () {
|
||||
// needs special treatment for error handling
|
||||
var uglifyer = uglify();
|
||||
uglifyer.on('error', function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
gulp.src(['webadmin/src/js/setup.js', 'webadmin/src/js/client.js'])
|
||||
.pipe(ejs({ oauth: oauth }, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('setup.js', { newLine: ';' }))
|
||||
.pipe(uglify())
|
||||
.pipe(uglifyer)
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-setupdns', function () {
|
||||
// needs special treatment for error handling
|
||||
var uglifyer = uglify();
|
||||
uglifyer.on('error', function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
gulp.src(['webadmin/src/js/setupdns.js', 'webadmin/src/js/client.js'])
|
||||
.pipe(ejs({ oauth: oauth }, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('setupdns.js', { newLine: ';' }))
|
||||
.pipe(uglifyer)
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-update', function () {
|
||||
// needs special treatment for error handling
|
||||
var uglifyer = uglify();
|
||||
uglifyer.on('error', function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
gulp.src(['webadmin/src/js/update.js'])
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(uglify())
|
||||
.pipe(uglifyer)
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist/js'))
|
||||
.pipe(gulp.dest('setup/splash/website/js'));
|
||||
@@ -143,6 +187,7 @@ gulp.task('watch', ['default'], function () {
|
||||
gulp.watch(['webadmin/src/templates/*.html'], ['html-templates']);
|
||||
gulp.watch(['webadmin/src/js/update.js'], ['js-update']);
|
||||
gulp.watch(['webadmin/src/js/setup.js', 'webadmin/src/js/client.js'], ['js-setup']);
|
||||
gulp.watch(['webadmin/src/js/setupdns.js', 'webadmin/src/js/client.js'], ['js-setupdns']);
|
||||
gulp.watch(['webadmin/src/js/index.js', 'webadmin/src/js/client.js', 'webadmin/src/js/appstore.js', 'webadmin/src/js/main.js', 'webadmin/src/views/*.js'], ['js-index']);
|
||||
gulp.watch(['webadmin/src/3rdparty/**/*'], ['3rdparty']);
|
||||
});
|
||||
|
||||
Generated
-863
@@ -1,863 +0,0 @@
|
||||
{
|
||||
"name": "installer",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"body-parser": {
|
||||
"version": "1.15.2",
|
||||
"from": "body-parser@>=1.12.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.15.2.tgz",
|
||||
"dependencies": {
|
||||
"bytes": {
|
||||
"version": "2.4.0",
|
||||
"from": "bytes@2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz"
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.2",
|
||||
"from": "content-type@>=1.0.2 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz"
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.0",
|
||||
"from": "depd@>=1.1.0 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz"
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.5.0",
|
||||
"from": "http-errors@>=1.5.0 <1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.0.tgz",
|
||||
"dependencies": {
|
||||
"inherits": {
|
||||
"version": "2.0.1",
|
||||
"from": "inherits@2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.0.1",
|
||||
"from": "setprototypeof@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.1.tgz"
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.3.0",
|
||||
"from": "statuses@>=1.3.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.13",
|
||||
"from": "iconv-lite@0.4.13",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"from": "on-finished@>=2.3.0 <2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"dependencies": {
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"from": "ee-first@1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.2.0",
|
||||
"from": "qs@6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.2.0.tgz"
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.1.7",
|
||||
"from": "raw-body@>=2.1.7 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz",
|
||||
"dependencies": {
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"from": "unpipe@1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.13",
|
||||
"from": "type-is@>=1.6.13 <1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
|
||||
"dependencies": {
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"from": "media-typer@0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.12",
|
||||
"from": "mime-types@>=2.1.11 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.12.tgz",
|
||||
"dependencies": {
|
||||
"mime-db": {
|
||||
"version": "1.24.0",
|
||||
"from": "mime-db@>=1.24.0 <1.25.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.24.0.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"connect-lastmile": {
|
||||
"version": "0.0.13",
|
||||
"from": "connect-lastmile@0.0.13",
|
||||
"resolved": "https://registry.npmjs.org/connect-lastmile/-/connect-lastmile-0.0.13.tgz",
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.1.3",
|
||||
"from": "debug@>=2.1.0 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.1.3.tgz",
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "0.7.0",
|
||||
"from": "ms@0.7.0",
|
||||
"resolved": "http://registry.npmjs.org/ms/-/ms-0.7.0.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.2.0",
|
||||
"from": "debug@>=2.1.1 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "0.7.1",
|
||||
"from": "ms@0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"express": {
|
||||
"version": "4.14.0",
|
||||
"from": "express@>=4.11.2 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.14.0.tgz",
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.3",
|
||||
"from": "accepts@>=1.3.3 <1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
|
||||
"dependencies": {
|
||||
"mime-types": {
|
||||
"version": "2.1.12",
|
||||
"from": "mime-types@>=2.1.11 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.12.tgz",
|
||||
"dependencies": {
|
||||
"mime-db": {
|
||||
"version": "1.24.0",
|
||||
"from": "mime-db@>=1.24.0 <1.25.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.24.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.1",
|
||||
"from": "negotiator@0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"from": "array-flatten@1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz"
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.1",
|
||||
"from": "content-disposition@0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.1.tgz"
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.2",
|
||||
"from": "content-type@>=1.0.2 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz"
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.3.1",
|
||||
"from": "cookie@0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz"
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"from": "cookie-signature@1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz"
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.0",
|
||||
"from": "depd@>=1.1.0 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz"
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.1",
|
||||
"from": "encodeurl@>=1.0.1 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz"
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"from": "escape-html@>=1.0.3 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz"
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.7.0",
|
||||
"from": "etag@>=1.7.0 <1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz"
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "0.5.0",
|
||||
"from": "finalhandler@0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.5.0.tgz",
|
||||
"dependencies": {
|
||||
"statuses": {
|
||||
"version": "1.3.0",
|
||||
"from": "statuses@>=1.3.0 <1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.0.tgz"
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"from": "unpipe@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.3.0",
|
||||
"from": "fresh@0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz"
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"from": "merge-descriptors@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz"
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"from": "methods@>=1.1.2 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz"
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"from": "on-finished@>=2.3.0 <2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"dependencies": {
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"from": "ee-first@1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.1",
|
||||
"from": "parseurl@>=1.3.1 <1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz"
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"from": "path-to-regexp@0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz"
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "1.1.2",
|
||||
"from": "proxy-addr@>=1.1.2 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.2.tgz",
|
||||
"dependencies": {
|
||||
"forwarded": {
|
||||
"version": "0.1.0",
|
||||
"from": "forwarded@>=0.1.0 <0.2.0",
|
||||
"resolved": "http://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz"
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.1.1",
|
||||
"from": "ipaddr.js@1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.1.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.2.0",
|
||||
"from": "qs@6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.2.0.tgz"
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.0",
|
||||
"from": "range-parser@>=1.2.0 <1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz"
|
||||
},
|
||||
"send": {
|
||||
"version": "0.14.1",
|
||||
"from": "send@0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.14.1.tgz",
|
||||
"dependencies": {
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"from": "destroy@>=1.0.4 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz"
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.5.0",
|
||||
"from": "http-errors@>=1.5.0 <1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.0.tgz",
|
||||
"dependencies": {
|
||||
"inherits": {
|
||||
"version": "2.0.1",
|
||||
"from": "inherits@2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.0.1",
|
||||
"from": "setprototypeof@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.3.4",
|
||||
"from": "mime@1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz"
|
||||
},
|
||||
"ms": {
|
||||
"version": "0.7.1",
|
||||
"from": "ms@0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.3.0",
|
||||
"from": "statuses@>=1.3.0 <1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.11.1",
|
||||
"from": "serve-static@>=1.11.1 <1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.11.1.tgz"
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.13",
|
||||
"from": "type-is@>=1.6.13 <1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
|
||||
"dependencies": {
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"from": "media-typer@0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.12",
|
||||
"from": "mime-types@>=2.1.11 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.12.tgz",
|
||||
"dependencies": {
|
||||
"mime-db": {
|
||||
"version": "1.24.0",
|
||||
"from": "mime-db@>=1.24.0 <1.25.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.24.0.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.0",
|
||||
"from": "utils-merge@1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz"
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.0",
|
||||
"from": "vary@>=1.1.0 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"version": "9.0.4",
|
||||
"from": "json@>=9.0.3 <10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json/-/json-9.0.4.tgz"
|
||||
},
|
||||
"morgan": {
|
||||
"version": "1.7.0",
|
||||
"from": "morgan@>=1.5.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.7.0.tgz",
|
||||
"dependencies": {
|
||||
"basic-auth": {
|
||||
"version": "1.0.4",
|
||||
"from": "basic-auth@>=1.0.3 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz"
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.0",
|
||||
"from": "depd@>=1.1.0 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz"
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"from": "on-finished@>=2.3.0 <2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"dependencies": {
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"from": "ee-first@1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"on-headers": {
|
||||
"version": "1.0.1",
|
||||
"from": "on-headers@>=1.0.1 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"version": "2.76.0",
|
||||
"from": "request@>=2.72.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.76.0.tgz",
|
||||
"dependencies": {
|
||||
"aws-sign2": {
|
||||
"version": "0.6.0",
|
||||
"from": "aws-sign2@>=0.6.0 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz"
|
||||
},
|
||||
"aws4": {
|
||||
"version": "1.5.0",
|
||||
"from": "aws4@>=1.2.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.5.0.tgz"
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.11.0",
|
||||
"from": "caseless@>=0.11.0 <0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.5",
|
||||
"from": "combined-stream@>=1.0.5 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
|
||||
"dependencies": {
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"from": "delayed-stream@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.0",
|
||||
"from": "extend@>=3.0.0 <3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"from": "forever-agent@>=0.6.1 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
|
||||
},
|
||||
"form-data": {
|
||||
"version": "2.1.1",
|
||||
"from": "form-data@>=2.1.1 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.1.tgz",
|
||||
"dependencies": {
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"from": "asynckit@>=0.4.0 <0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "2.0.6",
|
||||
"from": "har-validator@>=2.0.6 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"from": "chalk@>=1.1.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "2.2.1",
|
||||
"from": "ansi-styles@>=2.2.1 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz"
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"from": "escape-string-regexp@>=1.0.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
|
||||
},
|
||||
"has-ansi": {
|
||||
"version": "2.0.0",
|
||||
"from": "has-ansi@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "2.0.0",
|
||||
"from": "ansi-regex@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"from": "strip-ansi@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "2.0.0",
|
||||
"from": "ansi-regex@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "2.0.0",
|
||||
"from": "supports-color@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.9.0",
|
||||
"from": "commander@>=2.9.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
|
||||
"dependencies": {
|
||||
"graceful-readlink": {
|
||||
"version": "1.0.1",
|
||||
"from": "graceful-readlink@>=1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"is-my-json-valid": {
|
||||
"version": "2.15.0",
|
||||
"from": "is-my-json-valid@>=2.12.4 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz",
|
||||
"dependencies": {
|
||||
"generate-function": {
|
||||
"version": "2.0.0",
|
||||
"from": "generate-function@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
|
||||
},
|
||||
"generate-object-property": {
|
||||
"version": "1.2.0",
|
||||
"from": "generate-object-property@>=1.1.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
|
||||
"dependencies": {
|
||||
"is-property": {
|
||||
"version": "1.0.2",
|
||||
"from": "is-property@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsonpointer": {
|
||||
"version": "4.0.0",
|
||||
"from": "jsonpointer@>=4.0.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.0.tgz"
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.1",
|
||||
"from": "xtend@>=4.0.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pinkie-promise": {
|
||||
"version": "2.0.1",
|
||||
"from": "pinkie-promise@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
|
||||
"dependencies": {
|
||||
"pinkie": {
|
||||
"version": "2.0.4",
|
||||
"from": "pinkie@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hawk": {
|
||||
"version": "3.1.3",
|
||||
"from": "hawk@>=3.1.3 <3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
|
||||
"dependencies": {
|
||||
"hoek": {
|
||||
"version": "2.16.3",
|
||||
"from": "hoek@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
|
||||
},
|
||||
"boom": {
|
||||
"version": "2.10.1",
|
||||
"from": "boom@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
|
||||
},
|
||||
"cryptiles": {
|
||||
"version": "2.0.5",
|
||||
"from": "cryptiles@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz"
|
||||
},
|
||||
"sntp": {
|
||||
"version": "1.0.9",
|
||||
"from": "sntp@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.1.1",
|
||||
"from": "http-signature@>=1.1.0 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "0.2.0",
|
||||
"from": "assert-plus@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz"
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.3.1",
|
||||
"from": "jsprim@>=1.2.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz",
|
||||
"dependencies": {
|
||||
"extsprintf": {
|
||||
"version": "1.0.2",
|
||||
"from": "extsprintf@1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz"
|
||||
},
|
||||
"json-schema": {
|
||||
"version": "0.2.3",
|
||||
"from": "json-schema@0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz"
|
||||
},
|
||||
"verror": {
|
||||
"version": "1.3.6",
|
||||
"from": "verror@1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sshpk": {
|
||||
"version": "1.10.1",
|
||||
"from": "sshpk@>=1.7.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.1.tgz",
|
||||
"dependencies": {
|
||||
"asn1": {
|
||||
"version": "0.2.3",
|
||||
"from": "asn1@>=0.2.3 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz"
|
||||
},
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"from": "assert-plus@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz"
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.0",
|
||||
"from": "dashdash@>=1.12.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.0.tgz"
|
||||
},
|
||||
"getpass": {
|
||||
"version": "0.1.6",
|
||||
"from": "getpass@>=0.1.1 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz"
|
||||
},
|
||||
"jsbn": {
|
||||
"version": "0.1.0",
|
||||
"from": "jsbn@>=0.1.0 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz"
|
||||
},
|
||||
"tweetnacl": {
|
||||
"version": "0.14.3",
|
||||
"from": "tweetnacl@>=0.14.0 <0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz"
|
||||
},
|
||||
"jodid25519": {
|
||||
"version": "1.0.2",
|
||||
"from": "jodid25519@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz"
|
||||
},
|
||||
"ecc-jsbn": {
|
||||
"version": "0.1.1",
|
||||
"from": "ecc-jsbn@>=0.1.1 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz"
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.0",
|
||||
"from": "bcrypt-pbkdf@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"from": "is-typedarray@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz"
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"from": "isstream@>=0.1.2 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"from": "json-stringify-safe@>=5.0.1 <5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.12",
|
||||
"from": "mime-types@>=2.1.7 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.12.tgz",
|
||||
"dependencies": {
|
||||
"mime-db": {
|
||||
"version": "1.24.0",
|
||||
"from": "mime-db@>=1.24.0 <1.25.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.24.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.7",
|
||||
"from": "node-uuid@>=1.4.7 <1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz"
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.8.2",
|
||||
"from": "oauth-sign@>=0.8.1 <0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz"
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.3.0",
|
||||
"from": "qs@>=6.3.0 <6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.3.0.tgz"
|
||||
},
|
||||
"stringstream": {
|
||||
"version": "0.0.5",
|
||||
"from": "stringstream@>=0.0.4 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.3.1",
|
||||
"from": "tough-cookie@>=2.3.0 <2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.1.tgz"
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.4.3",
|
||||
"from": "tunnel-agent@>=0.4.1 <0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"safetydance": {
|
||||
"version": "0.0.19",
|
||||
"from": "safetydance@0.0.19",
|
||||
"resolved": "https://registry.npmjs.org/safetydance/-/safetydance-0.0.19.tgz"
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.3.0",
|
||||
"from": "semver@>=5.1.0 <6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz"
|
||||
},
|
||||
"superagent": {
|
||||
"version": "0.21.0",
|
||||
"from": "superagent@>=0.21.0 <0.22.0",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-0.21.0.tgz",
|
||||
"dependencies": {
|
||||
"qs": {
|
||||
"version": "1.2.0",
|
||||
"from": "qs@1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-1.2.0.tgz"
|
||||
},
|
||||
"formidable": {
|
||||
"version": "1.0.14",
|
||||
"from": "formidable@1.0.14",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz"
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.2.11",
|
||||
"from": "mime@1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.1.2",
|
||||
"from": "component-emitter@1.1.2",
|
||||
"resolved": "http://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz"
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.0.1",
|
||||
"from": "methods@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.0.1.tgz"
|
||||
},
|
||||
"cookiejar": {
|
||||
"version": "2.0.1",
|
||||
"from": "cookiejar@2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.0.1.tgz"
|
||||
},
|
||||
"reduce-component": {
|
||||
"version": "1.0.1",
|
||||
"from": "reduce-component@1.0.1",
|
||||
"resolved": "http://registry.npmjs.org/reduce-component/-/reduce-component-1.0.1.tgz"
|
||||
},
|
||||
"extend": {
|
||||
"version": "1.2.1",
|
||||
"from": "extend@>=1.2.1 <1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz"
|
||||
},
|
||||
"form-data": {
|
||||
"version": "0.1.3",
|
||||
"from": "form-data@0.1.3",
|
||||
"resolved": "http://registry.npmjs.org/form-data/-/form-data-0.1.3.tgz",
|
||||
"dependencies": {
|
||||
"combined-stream": {
|
||||
"version": "0.0.7",
|
||||
"from": "combined-stream@>=0.0.4 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
|
||||
"dependencies": {
|
||||
"delayed-stream": {
|
||||
"version": "0.0.5",
|
||||
"from": "delayed-stream@0.0.5",
|
||||
"resolved": "http://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"async": {
|
||||
"version": "0.9.2",
|
||||
"from": "async@>=0.9.0 <0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.27-1",
|
||||
"from": "readable-stream@1.0.27-1",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.27-1.tgz",
|
||||
"dependencies": {
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"from": "core-util-is@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"from": "isarray@0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"from": "string_decoder@>=0.10.0 <0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"from": "inherits@>=2.0.1 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"name": "installer",
|
||||
"description": "Cloudron Installer",
|
||||
"version": "0.0.1",
|
||||
"private": "true",
|
||||
"author": {
|
||||
"name": "Cloudron authors"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git"
|
||||
},
|
||||
"engines": [
|
||||
"node >=4.0.0 <=4.1.1"
|
||||
],
|
||||
"dependencies": {
|
||||
"body-parser": "^1.12.0",
|
||||
"connect-lastmile": "0.0.13",
|
||||
"debug": "^2.1.1",
|
||||
"express": "^4.11.2",
|
||||
"json": "^9.0.3",
|
||||
"morgan": "^1.5.1",
|
||||
"request": "^2.72.0",
|
||||
"safetydance": "0.0.19",
|
||||
"semver": "^5.1.0",
|
||||
"superagent": "^0.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"expect.js": "^0.3.1",
|
||||
"istanbul": "^0.3.5",
|
||||
"lodash": "^3.2.0",
|
||||
"mocha": "^2.1.0",
|
||||
"nock": "^0.59.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "NODE_ENV=test ./node_modules/istanbul/lib/cli.js test $1 ./node_modules/mocha/bin/_mocha -- -R spec ./src/test",
|
||||
"precommit": "/bin/true",
|
||||
"prepush": "npm test",
|
||||
"postmerge": "/bin/true"
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/* jslint node: true */
|
||||
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert'),
|
||||
child_process = require('child_process'),
|
||||
debug = require('debug')('installer:installer'),
|
||||
path = require('path'),
|
||||
safe = require('safetydance'),
|
||||
semver = require('semver'),
|
||||
superagent = require('superagent'),
|
||||
util = require('util');
|
||||
|
||||
exports = module.exports = {
|
||||
InstallerError: InstallerError,
|
||||
|
||||
provision: provision,
|
||||
|
||||
_ensureVersion: ensureVersion
|
||||
};
|
||||
|
||||
var INSTALLER_CMD = path.join(__dirname, 'scripts/installer.sh'),
|
||||
SUDO = '/usr/bin/sudo';
|
||||
|
||||
function InstallerError(reason, info) {
|
||||
Error.call(this);
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
|
||||
this.name = this.constructor.name;
|
||||
this.reason = reason;
|
||||
this.message = !info ? reason : (typeof info === 'object' ? JSON.stringify(info) : info);
|
||||
}
|
||||
util.inherits(InstallerError, Error);
|
||||
InstallerError.INTERNAL_ERROR = 1;
|
||||
InstallerError.ALREADY_PROVISIONED = 2;
|
||||
|
||||
// system until file has KillMode=control-group to bring down child processes
|
||||
function spawn(tag, cmd, args, callback) {
|
||||
assert.strictEqual(typeof tag, 'string');
|
||||
assert.strictEqual(typeof cmd, 'string');
|
||||
assert(util.isArray(args));
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var cp = child_process.spawn(cmd, args, { timeout: 0 });
|
||||
cp.stdout.setEncoding('utf8');
|
||||
cp.stdout.on('data', function (data) { debug('%s (stdout): %s', tag, data); });
|
||||
cp.stderr.setEncoding('utf8');
|
||||
cp.stderr.on('data', function (data) { debug('%s (stderr): %s', tag, data); });
|
||||
|
||||
cp.on('error', function (error) {
|
||||
debug('%s : child process errored %s', tag, error.message);
|
||||
callback(error);
|
||||
});
|
||||
|
||||
cp.on('exit', function (code, signal) {
|
||||
debug('%s : child process exited. code: %d signal: %d', tag, code, signal);
|
||||
if (signal) return callback(new Error('Exited with signal ' + signal));
|
||||
if (code !== 0) return callback(new Error('Exited with code ' + code));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function ensureVersion(args, callback) {
|
||||
assert.strictEqual(typeof args, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (!args.data || !args.data.boxVersionsUrl) return callback(new Error('No boxVersionsUrl specified'));
|
||||
|
||||
if (args.sourceTarballUrl) return callback(null, args);
|
||||
|
||||
superagent.get(args.data.boxVersionsUrl).end(function (error, result) {
|
||||
if (error && !error.response) return callback(error);
|
||||
if (result.statusCode !== 200) return callback(new Error(util.format('Bad status: %s %s', result.statusCode, result.text)));
|
||||
|
||||
var versions = safe.JSON.parse(result.text);
|
||||
|
||||
if (!versions || typeof versions !== 'object') return callback(new Error('versions is not in valid format:' + safe.error));
|
||||
|
||||
var requestedVersion = args.data.version;
|
||||
if (!requestedVersion) {
|
||||
var latestVersion = Object.keys(versions).sort(semver.compare).pop();
|
||||
requestedVersion = latestVersion;
|
||||
}
|
||||
|
||||
debug('ensureVersion: Latest version is %s etag:%s', requestedVersion, result.header['etag']);
|
||||
|
||||
if (!versions[requestedVersion]) return callback(new Error('No version available'));
|
||||
if (!versions[requestedVersion].sourceTarballUrl) return callback(new Error('No sourceTarballUrl specified'));
|
||||
|
||||
args.sourceTarballUrl = versions[requestedVersion].sourceTarballUrl;
|
||||
args.data.version = requestedVersion;
|
||||
|
||||
callback(null, args);
|
||||
});
|
||||
}
|
||||
|
||||
function provision(args, callback) {
|
||||
assert.strictEqual(typeof args, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (process.env.NODE_ENV === 'test') return callback(null);
|
||||
|
||||
ensureVersion(args, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
var pargs = [ INSTALLER_CMD ];
|
||||
pargs.push('--sourcetarballurl', result.sourceTarballUrl);
|
||||
pargs.push('--data', JSON.stringify(result.data));
|
||||
|
||||
debug('provision: calling with args %j', pargs);
|
||||
|
||||
// sudo is required for update()
|
||||
spawn('provision', SUDO, pargs, callback);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
readonly BOX_SRC_DIR=/home/yellowtent/box
|
||||
readonly DATA_DIR=/home/yellowtent/data
|
||||
readonly CLOUDRON_CONF=/home/yellowtent/configs/cloudron.conf
|
||||
|
||||
readonly script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly json="${script_dir}/../../node_modules/.bin/json"
|
||||
readonly curl="curl --fail --connect-timeout 20 --retry 10 --retry-delay 2 --max-time 300"
|
||||
|
||||
readonly is_update=$([[ -f "${CLOUDRON_CONF}" ]] && echo "yes" || echo "no")
|
||||
|
||||
# create a provision file for testing. %q escapes args. %q is reused as much as necessary to satisfy $@
|
||||
(echo -e "#!/bin/bash\n"; printf "%q " "${script_dir}/installer.sh" "$@") > /home/yellowtent/provision.sh
|
||||
chmod +x /home/yellowtent/provision.sh
|
||||
|
||||
arg_source_tarball_url=""
|
||||
arg_data=""
|
||||
|
||||
args=$(getopt -o "" -l "sourcetarballurl:,data:" -n "$0" -- "$@")
|
||||
eval set -- "${args}"
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
--sourcetarballurl) arg_source_tarball_url="$2";;
|
||||
--data) arg_data="$2";;
|
||||
--) break;;
|
||||
*) echo "Unknown option $1"; exit 1;;
|
||||
esac
|
||||
|
||||
shift 2
|
||||
done
|
||||
|
||||
box_src_tmp_dir=$(mktemp -dt box-src-XXXXXX)
|
||||
echo "Downloading box code from ${arg_source_tarball_url} to ${box_src_tmp_dir}"
|
||||
|
||||
while true; do
|
||||
if $curl -L "${arg_source_tarball_url}" | tar -zxf - -C "${box_src_tmp_dir}"; then break; fi
|
||||
echo "Failed to download source tarball, trying again"
|
||||
sleep 5
|
||||
done
|
||||
while true; do
|
||||
# for reasons unknown, the dtrace package will fail. but rebuilding second time will work
|
||||
if cd "${box_src_tmp_dir}" && npm rebuild; then break; fi
|
||||
echo "Failed to rebuild, trying again"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ "${is_update}" == "yes" ]]; then
|
||||
echo "Setting up update splash screen"
|
||||
"${box_src_tmp_dir}/setup/splashpage.sh" --data "${arg_data}" # show splash from new code
|
||||
${BOX_SRC_DIR}/setup/stop.sh # stop the old code
|
||||
fi
|
||||
|
||||
# switch the codes
|
||||
rm -rf "${BOX_SRC_DIR}"
|
||||
mv "${box_src_tmp_dir}" "${BOX_SRC_DIR}"
|
||||
chown -R yellowtent.yellowtent "${BOX_SRC_DIR}"
|
||||
|
||||
# create a start file for testing. %q escapes args
|
||||
(echo -e "#!/bin/bash\n"; printf "%q " "${BOX_SRC_DIR}/setup/start.sh" --data "${arg_data}") > /home/yellowtent/setup_start.sh
|
||||
chmod +x /home/yellowtent/setup_start.sh
|
||||
|
||||
echo "Calling box setup script"
|
||||
"${BOX_SRC_DIR}/setup/start.sh" --data "${arg_data}"
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/* jslint node: true */
|
||||
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert'),
|
||||
debug = require('debug')('installer:server'),
|
||||
express = require('express'),
|
||||
http = require('http'),
|
||||
HttpError = require('connect-lastmile').HttpError,
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess,
|
||||
installer = require('./installer.js'),
|
||||
json = require('body-parser').json,
|
||||
lastMile = require('connect-lastmile'),
|
||||
morgan = require('morgan');
|
||||
|
||||
exports = module.exports = {
|
||||
start: start,
|
||||
stop: stop
|
||||
};
|
||||
|
||||
var gHttpServer = null; // update server; used for updates
|
||||
|
||||
function update(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (!req.body.sourceTarballUrl || typeof req.body.sourceTarballUrl !== 'string') return next(new HttpError(400, 'No sourceTarballUrl provided'));
|
||||
if (!req.body.data || typeof req.body.data !== 'object') return next(new HttpError(400, 'No data provided'));
|
||||
|
||||
debug('provision: received from box %j', req.body);
|
||||
|
||||
installer.provision(req.body, function (error) {
|
||||
if (error) console.error(error);
|
||||
});
|
||||
|
||||
next(new HttpSuccess(202, { }));
|
||||
}
|
||||
|
||||
function start(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('Starting update server');
|
||||
|
||||
var app = express();
|
||||
|
||||
var router = new express.Router();
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') app.use(morgan('dev', { immediate: false }));
|
||||
|
||||
app.use(json({ strict: true }))
|
||||
.use(router)
|
||||
.use(lastMile());
|
||||
|
||||
router.post('/api/v1/installer/update', update);
|
||||
|
||||
gHttpServer = http.createServer(app);
|
||||
gHttpServer.on('error', console.error);
|
||||
|
||||
gHttpServer.listen(2020, '127.0.0.1', callback);
|
||||
}
|
||||
|
||||
function stop(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('Stopping update server');
|
||||
|
||||
if (!gHttpServer) return callback(null);
|
||||
|
||||
gHttpServer.close(callback);
|
||||
gHttpServer = null;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
start(function (error) {
|
||||
if (error) console.error(error);
|
||||
});
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
/* jslint node:true */
|
||||
/* global it:false */
|
||||
/* global describe:false */
|
||||
/* global before:false */
|
||||
/* global after:false */
|
||||
|
||||
'use strict';
|
||||
|
||||
var expect = require('expect.js'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
nock = require('nock'),
|
||||
os = require('os'),
|
||||
request = require('superagent'),
|
||||
server = require('../server.js'),
|
||||
installer = require('../installer.js'),
|
||||
_ = require('lodash');
|
||||
|
||||
var EXTERNAL_SERVER_URL = 'https://localhost:4443';
|
||||
var INTERNAL_SERVER_URL = 'http://localhost:2020';
|
||||
var APPSERVER_ORIGIN = 'http://appserver';
|
||||
var FQDN = os.hostname();
|
||||
|
||||
describe('Server', function () {
|
||||
this.timeout(5000);
|
||||
|
||||
before(function (done) {
|
||||
var user_data = JSON.stringify({ apiServerOrigin: APPSERVER_ORIGIN }); // user_data is a string
|
||||
var scope = nock('http://169.254.169.254')
|
||||
.persist()
|
||||
.get('/metadata/v1.json')
|
||||
.reply(200, JSON.stringify({ user_data: user_data }), { 'Content-Type': 'application/json' });
|
||||
done();
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
nock.cleanAll();
|
||||
done();
|
||||
});
|
||||
|
||||
describe('starts and stop', function () {
|
||||
it('starts', function (done) {
|
||||
server.start(done);
|
||||
});
|
||||
|
||||
it('stops', function (done) {
|
||||
server.stop(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update (internal server)', function () {
|
||||
before(function (done) {
|
||||
server.start(done);
|
||||
});
|
||||
after(function (done) {
|
||||
server.stop(done);
|
||||
});
|
||||
|
||||
it('does not respond to provision', function (done) {
|
||||
request.post(INTERNAL_SERVER_URL + '/api/v1/installer/provision').send({ }).end(function (error, result) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(result.statusCode).to.equal(404);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not respond to restore', function (done) {
|
||||
request.post(INTERNAL_SERVER_URL + '/api/v1/installer/restore').send({ }).end(function (error, result) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(result.statusCode).to.equal(404);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var data = {
|
||||
sourceTarballUrl: "https://foo.tar.gz",
|
||||
|
||||
data: {
|
||||
token: 'sometoken',
|
||||
apiServerOrigin: APPSERVER_ORIGIN,
|
||||
webServerOrigin: 'https://somethingelse.com',
|
||||
fqdn: 'www.something.com',
|
||||
tlsKey: 'key',
|
||||
tlsCert: 'cert',
|
||||
boxVersionsUrl: 'https://versions.json',
|
||||
version: '0.1'
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(data).forEach(function (key) {
|
||||
it('fails due to missing ' + key, function (done) {
|
||||
var dataCopy = _.merge({ }, data);
|
||||
delete dataCopy[key];
|
||||
|
||||
request.post(INTERNAL_SERVER_URL + '/api/v1/installer/update').send(dataCopy).end(function (error, result) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(result.statusCode).to.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds', function (done) {
|
||||
request.post(INTERNAL_SERVER_URL + '/api/v1/installer/update').send(data).end(function (error, result) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(result.statusCode).to.equal(202);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureVersion', function () {
|
||||
before(function () {
|
||||
process.env.NODE_ENV = undefined;
|
||||
});
|
||||
|
||||
after(function () {
|
||||
process.env.NODE_ENV = 'test';
|
||||
});
|
||||
|
||||
it ('fails without data', function (done) {
|
||||
installer._ensureVersion({}, function (error) {
|
||||
expect(error).to.be.an(Error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it ('fails without boxVersionsUrl', function (done) {
|
||||
installer._ensureVersion({ data: {}}, function (error) {
|
||||
expect(error).to.be.an(Error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it ('succeeds with sourceTarballUrl', function (done) {
|
||||
var data = {
|
||||
sourceTarballUrl: 'sometarballurl',
|
||||
data: {
|
||||
boxVersionsUrl: 'http://foobar/versions.json'
|
||||
}
|
||||
};
|
||||
|
||||
installer._ensureVersion(data, function (error, result) {
|
||||
expect(error).to.equal(null);
|
||||
expect(result).to.eql(data);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it ('succeeds without sourceTarballUrl', function (done) {
|
||||
var versions = {
|
||||
'0.1.0': {
|
||||
sourceTarballUrl: 'sometarballurl1'
|
||||
},
|
||||
'0.2.0': {
|
||||
sourceTarballUrl: 'sometarballurl2'
|
||||
}
|
||||
};
|
||||
|
||||
var scope = nock('http://foobar')
|
||||
.get('/versions.json')
|
||||
.reply(200, JSON.stringify(versions), { 'Content-Type': 'application/json' });
|
||||
|
||||
var data = {
|
||||
data: {
|
||||
boxVersionsUrl: 'http://foobar/versions.json'
|
||||
}
|
||||
};
|
||||
|
||||
installer._ensureVersion(data, function (error, result) {
|
||||
expect(error).to.equal(null);
|
||||
expect(result.sourceTarballUrl).to.equal(versions['0.2.0'].sourceTarballUrl);
|
||||
expect(result.data.boxVersionsUrl).to.equal(data.data.boxVersionsUrl);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var dbm = require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
var url = require('url');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs'),
|
||||
async = require('async'),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE users ADD COLUMN resetToken VARCHAR(128) DEFAULT ""', function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('DELETE FROM tokens', [], function (error) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE authcodes ADD COLUMN expiresAt BIGINT NOT NULL', function (error) {
|
||||
@@ -13,4 +12,4 @@ exports.down = function(db, callback) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE appPortBindings ADD COLUMN environmentVariable VARCHAR(128) NOT NULL', function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE appPortBindings DROP COLUMN containerPort', function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('DELETE FROM tokens', [], function (error) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps DROP COLUMN version', function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps DROP COLUMN healthy, ADD COLUMN health VARCHAR(128)', [], function (error) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps ADD COLUMN lastBackupId VARCHAR(128)', function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps ADD COLUMN createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP', function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
// everyday at 1am
|
||||
@@ -8,5 +7,4 @@ exports.up = function(db, callback) {
|
||||
|
||||
exports.down = function(db, callback) {
|
||||
db.runSql('DELETE * FROM settings WHERE name="autoupdate_pattern"', [ ], callback);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
'use strict';
|
||||
|
||||
var safe = require('safetydance');
|
||||
var type = dbm.dataType;
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
var tz = safe.fs.readFileSync('/etc/timezone', 'utf8');
|
||||
@@ -12,4 +12,3 @@ exports.up = function(db, callback) {
|
||||
exports.down = function(db, callback) {
|
||||
db.runSql('DELETE * FROM settings WHERE name="time_zone"', [ ], callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps ADD COLUMN lastManifestJson VARCHAR(2048)', function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps CHANGE lastManifestJson lastBackupConfigJson VARCHAR(2048)', [], function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps ADD COLUMN oldConfigJson VARCHAR(2048)', function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('DELETE FROM settings', [ ], callback);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps ADD COLUMN oauthProxy BOOLEAN DEFAULT 0', function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps CHANGE accessRestriction accessRestrictionJson VARCHAR(2048)', [], function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps MODIFY manifestJson TEXT', [], function (error) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE users ADD COLUMN displayName VARCHAR(512) DEFAULT ""', function (error) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps ADD COLUMN memoryLimit BIGINT DEFAULT 0', function (error) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
var cmd = "CREATE TABLE groups(" +
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
var cmd = "CREATE TABLE IF NOT EXISTS groupMembers(" +
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var async = require('async');
|
||||
|
||||
var ADMIN_GROUP_ID = 'admin'; // see groups.js
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
var cmd = "CREATE TABLE backups(" +
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE backups ADD COLUMN configJson TEXT', function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE backups DROP COLUMN configJson', function (error) {
|
||||
@@ -14,4 +13,3 @@ exports.down = function(db, callback) {
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE backups CHANGE filename id VARCHAR(128)', [], function (error) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE users MODIFY username VARCHAR(254) UNIQUE', [], function (error) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var dbm = dbm || require('db-migrate');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps ADD COLUMN altDomain VARCHAR(256)', function (error) {
|
||||
if (error) console.error(error);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
var cmd = "CREATE TABLE eventlog(" +
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE users ADD COLUMN showTutorial BOOLEAN DEFAULT 0', function (error) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
var cmd = 'CREATE TABLE mailboxes(' +
|
||||
'name VARCHAR(128) NOT NULL,' +
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
|
||||
// imports mailbox entries for existing users
|
||||
exports.up = function(db, callback) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps DROP COLUMN lastBackupConfigJson', function (error) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps MODIFY installationProgress TEXT', [], function (error) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dbm = dbm || require('db-migrate');
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps ADD COLUMN xFrameOptions VARCHAR(512)', function (error) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var dbm = global.dbm || require('db-migrate');
|
||||
var type = dbm.dataType;
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.all('SELECT id FROM users', function (error, results) {
|
||||
@@ -14,4 +13,3 @@ exports.up = function(db, callback) {
|
||||
exports.down = function(db, callback) {
|
||||
db.runSql('DELETE * FROM settings WHERE name="mail_config"', [ ], callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
var dbm = dbm || require('db-migrate');
|
||||
var async = require('async');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
async.series([
|
||||
@@ -71,4 +71,3 @@ exports.down = function(db, callback) {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps ADD COLUMN sso BOOLEAN DEFAULT 1', function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps DROP COLUMN sso', function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps DROP COLUMN oauthProxy', function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps ADD COLUMN oauthProxy BOOLEAN DEFAULT 0', function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE users DROP COLUMN showTutorial', function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(db, callback) {
|
||||
db.runSql('ALTER TABLE users ADD COLUMN showTutorial BOOLEAN DEFAULT 0', function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps ADD COLUMN debugModeJson TEXT', function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(db, callback) {
|
||||
db.runSql('ALTER TABLE apps DROP COLUMN debugModeJson ', function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE backups MODIFY dependsOn TEXT', [], function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(db, callback) {
|
||||
db.runSql('ALTER TABLE backups MODIFY dependsOn VARCHAR(4096)', [], function (error) {
|
||||
if (error) console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
@@ -19,7 +19,6 @@ CREATE TABLE IF NOT EXISTS users(
|
||||
modifiedAt VARCHAR(512) NOT NULL,
|
||||
admin INTEGER NOT NULL,
|
||||
displayName VARCHAR(512) DEFAULT '',
|
||||
showTutorial BOOLEAN DEFAULT 0,
|
||||
PRIMARY KEY(id));
|
||||
|
||||
CREATE TABLE IF NOT EXISTS groups(
|
||||
@@ -61,13 +60,14 @@ CREATE TABLE IF NOT EXISTS apps(
|
||||
manifestJson TEXT,
|
||||
httpPort INTEGER, // this is the nginx proxy port and not manifest.httpPort
|
||||
location VARCHAR(128) NOT NULL UNIQUE,
|
||||
dnsRecordId VARCHAR(512),
|
||||
dnsRecordId VARCHAR(512), // tracks any id that we got back to track dns updates (unused)
|
||||
accessRestrictionJson TEXT, // { users: [ ], groups: [ ] }
|
||||
oauthProxy BOOLEAN DEFAULT 0,
|
||||
createdAt TIMESTAMP(2) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
memoryLimit BIGINT DEFAULT 0,
|
||||
altDomain VARCHAR(256),
|
||||
xFrameOptions VARCHAR(512),
|
||||
sso BOOLEAN DEFAULT 1, // whether user chose to enable SSO
|
||||
debugModeJson TEXT, // options for development mode
|
||||
|
||||
lastBackupId VARCHAR(128), // tracks last valid backup, can be removed
|
||||
|
||||
@@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS backups(
|
||||
creationTime TIMESTAMP,
|
||||
version VARCHAR(128) NOT NULL, /* app version or box version */
|
||||
type VARCHAR(16) NOT NULL, /* 'box' or 'app' */
|
||||
dependsOn VARCHAR(4096), /* comma separate list of objects this backup depends on */
|
||||
dependsOn TEXT, /* comma separate list of objects this backup depends on */
|
||||
state VARCHAR(16) NOT NULL,
|
||||
|
||||
PRIMARY KEY (filename));
|
||||
|
||||
Generated
+4508
-2460
File diff suppressed because it is too large
Load Diff
+10
-12
@@ -13,11 +13,11 @@
|
||||
"node >=4.0.0 <=4.1.1"
|
||||
],
|
||||
"dependencies": {
|
||||
"async": "^1.2.1",
|
||||
"async": "^2.1.4",
|
||||
"aws-sdk": "^2.1.46",
|
||||
"body-parser": "^1.13.1",
|
||||
"checksum": "^0.1.1",
|
||||
"cloudron-manifestformat": "^2.4.3",
|
||||
"cloudron-manifestformat": "^2.8.0",
|
||||
"connect-ensure-login": "^0.1.1",
|
||||
"connect-lastmile": "^0.1.0",
|
||||
"connect-timeout": "^1.5.0",
|
||||
@@ -25,15 +25,17 @@
|
||||
"cookie-session": "^1.1.0",
|
||||
"cron": "^1.0.9",
|
||||
"csurf": "^1.6.6",
|
||||
"db-migrate": "^0.9.2",
|
||||
"db-migrate": "^0.10.0-beta.20",
|
||||
"db-migrate-mysql": "^1.1.10",
|
||||
"debug": "^2.2.0",
|
||||
"dockerode": "^2.2.10",
|
||||
"ejs": "^2.2.4",
|
||||
"ejs-cli": "^1.2.0",
|
||||
"express": "^4.12.4",
|
||||
"express-rate-limit": "^2.6.0",
|
||||
"express-session": "^1.11.3",
|
||||
"gulp-sass": "^3.0.0",
|
||||
"hat": "0.0.3",
|
||||
"ini": "^1.3.4",
|
||||
"json": "^9.0.3",
|
||||
"ldapjs": "^1.0.0",
|
||||
"mime": "^1.3.4",
|
||||
@@ -58,19 +60,16 @@
|
||||
"proxy-middleware": "^0.13.0",
|
||||
"safetydance": "^0.1.1",
|
||||
"semver": "^4.3.6",
|
||||
"showdown": "^1.6.0",
|
||||
"split": "^1.0.0",
|
||||
"superagent": "^1.8.3",
|
||||
"supererror": "^0.7.1",
|
||||
"tail-stream": "https://registry.npmjs.org/tail-stream/-/tail-stream-0.2.1.tgz",
|
||||
"tldjs": "^1.6.2",
|
||||
"underscore": "^1.7.0",
|
||||
"ursa": "^0.9.3",
|
||||
"valid-url": "^1.0.9",
|
||||
"validator": "^4.9.0",
|
||||
"x509": "^0.2.4"
|
||||
"validator": "^4.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"apidoc": "*",
|
||||
"bootstrap-sass": "^3.3.3",
|
||||
"deep-extend": "^0.4.1",
|
||||
"del": "^1.1.1",
|
||||
@@ -80,7 +79,7 @@
|
||||
"gulp-concat": "^2.4.3",
|
||||
"gulp-cssnano": "^2.1.0",
|
||||
"gulp-ejs": "^1.0.0",
|
||||
"gulp-sass": "^2.0.1",
|
||||
"gulp-sass": "^3.0.0",
|
||||
"gulp-serve": "^1.0.0",
|
||||
"gulp-sourcemaps": "^1.5.2",
|
||||
"gulp-uglify": "^1.1.0",
|
||||
@@ -88,10 +87,9 @@
|
||||
"istanbul": "*",
|
||||
"js2xmlparser": "^1.0.0",
|
||||
"mocha": "*",
|
||||
"nock": "^3.4.0",
|
||||
"nock": "^9.0.2",
|
||||
"node-sass": "^3.0.0-alpha.0",
|
||||
"request": "^2.65.0",
|
||||
"sinon": "^1.12.2",
|
||||
"yargs": "^3.15.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
+206
-58
@@ -2,21 +2,58 @@
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
if [[ ${EUID} -ne 0 ]]; then
|
||||
echo "This script should be run as root." > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $(lsb_release -rs) != "16.04" ]]; then
|
||||
echo "Cloudron requires Ubuntu 16.04" > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# change this to a hash when we make a upgrade release
|
||||
readonly INSTALLER_REVISION=master
|
||||
readonly INIT_BASESYSTEM_SCRIPT_URL="https://git.cloudron.io/cloudron/box/raw/${INSTALLER_REVISION}/baseimage/initializeBaseUbuntuImage.sh"
|
||||
readonly INSTALLER_SOURCE_DIR="/home/yellowtent/installer"
|
||||
readonly LOG_FILE="/var/log/cloudron-setup.log"
|
||||
readonly DATA_FILE="/root/cloudron-install-data.json"
|
||||
readonly MINIMUM_DISK_SIZE_GB="19" # this is the size of "/" and required to fit in docker images 19 is a safe bet for different reporting on 20GB min
|
||||
readonly MINIMUM_MEMORY="974" # this is mostly reported for 1GB main memory (DO 992, EC2 990, Linode 989, Serverdiscounter.com 974)
|
||||
|
||||
readonly curl="curl --fail --connect-timeout 20 --retry 10 --retry-delay 2 --max-time 2400"
|
||||
|
||||
# copied from cloudron-resize-fs.sh
|
||||
readonly physical_memory=$(LC_ALL=C free -m | awk '/Mem:/ { print $2 }')
|
||||
readonly disk_device="$(for d in $(find /dev -type b); do [ "$(mountpoint -d /)" = "$(mountpoint -x $d)" ] && echo $d && break; done)"
|
||||
readonly disk_size_bytes=$(LC_ALL=C fdisk -l ${disk_device} | grep "Disk ${disk_device}" | awk '{ printf $5 }')
|
||||
readonly disk_size_gb=$((${disk_size_bytes}/1024/1024/1024))
|
||||
|
||||
# verify the system has minimum requirements met
|
||||
if [[ "${physical_memory}" -lt "${MINIMUM_MEMORY}" ]]; then
|
||||
echo "Error: Cloudron requires atleast 1GB physical memory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${disk_size_gb}" -lt "${MINIMUM_DISK_SIZE_GB}" ]]; then
|
||||
echo "Error: Cloudron requires atleast 20GB disk space (Disk space on ${disk_device} is ${disk_size_gb}GB)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
initBaseImage="true"
|
||||
# provisioning data
|
||||
domain=""
|
||||
provider="generic"
|
||||
restoreKey=""
|
||||
provider=""
|
||||
encryptionKey=""
|
||||
restoreUrl=""
|
||||
tlsProvider="letsencrypt-prod"
|
||||
dnsProvider="manual"
|
||||
tlsProvider="le-prod"
|
||||
versionsUrl="https://s3.amazonaws.com/prod-cloudron-releases/versions.json"
|
||||
version="latest"
|
||||
requestedVersion="latest"
|
||||
apiServerOrigin="https://api.cloudron.io"
|
||||
dataJson=""
|
||||
prerelease="false"
|
||||
sourceTarballUrl=""
|
||||
rebootServer="true"
|
||||
|
||||
args=$(getopt -o "" -l "domain:,help,provider:,restore-key:,restore-url:,tls-provider:,version:,versions-url:" -n "$0" -- "$@")
|
||||
args=$(getopt -o "" -l "domain:,help,skip-baseimage-init,data:,provider:,encryption-key:,restore-url:,tls-provider:,version:,versions-url:,api-server:,dns-provider:,env:,prerelease,skip-reboot,source-url:" -n "$0" -- "$@")
|
||||
eval set -- "${args}"
|
||||
|
||||
while true; do
|
||||
@@ -24,89 +61,200 @@ while true; do
|
||||
--domain) domain="$2"; shift 2;;
|
||||
--help) echo "See https://cloudron.io/references/selfhosting.html on how to install Cloudron"; exit 0;;
|
||||
--provider) provider="$2"; shift 2;;
|
||||
--restore-key) restoreKey="$2"; shift 2;;
|
||||
--encryption-key) encryptionKey="$2"; shift 2;;
|
||||
--restore-url) restoreUrl="$2"; shift 2;;
|
||||
--tls-provider) tlsProvider="$2"; shift 2;;
|
||||
--version) version="$2"; shift 2;;
|
||||
--dns-provider) dnsProvider="$2"; shift 2;;
|
||||
--version) requestedVersion="$2"; shift 2;;
|
||||
--env)
|
||||
if [[ "$2" == "dev" ]]; then
|
||||
apiServerOrigin="https://api.dev.cloudron.io"
|
||||
versionsUrl="https://s3.amazonaws.com/dev-cloudron-releases/versions.json"
|
||||
tlsProvider="le-staging"
|
||||
prerelease="true"
|
||||
elif [[ "$2" == "staging" ]]; then
|
||||
apiServerOrigin="https://api.staging.cloudron.io"
|
||||
versionsUrl="https://s3.amazonaws.com/staging-cloudron-releases/versions.json"
|
||||
tlsProvider="le-staging"
|
||||
prerelease="true"
|
||||
fi
|
||||
shift 2;;
|
||||
--versions-url) versionsUrl="$2"; shift 2;;
|
||||
--api-server) apiServerOrigin="$2"; shift 2;;
|
||||
--skip-baseimage-init) initBaseImage="false"; shift;;
|
||||
--skip-reboot) rebootServer="false"; shift;;
|
||||
--data) dataJson="$2"; shift 2;;
|
||||
--prerelease) prerelease="true"; shift;;
|
||||
--source-url) sourceTarballUrl="$2"; version="0.0.1+custom"; shift 2;;
|
||||
--) break;;
|
||||
*) echo "Unknown option $1"; exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${domain}" ]]; then
|
||||
echo "--domain is required"
|
||||
exit 1
|
||||
# validate arguments in the absence of data
|
||||
if [[ -z "${dataJson}" ]]; then
|
||||
if [[ -z "${provider}" ]]; then
|
||||
echo "--provider is required (azure, digitalocean, ec2, lightsail, linode, ovh, scaleway, vultr or generic)"
|
||||
exit 1
|
||||
elif [[ \
|
||||
"${provider}" != "ami" && \
|
||||
"${provider}" != "azure" && \
|
||||
"${provider}" != "digitalocean" && \
|
||||
"${provider}" != "ec2" && \
|
||||
"${provider}" != "lightsail" && \
|
||||
"${provider}" != "linode" && \
|
||||
"${provider}" != "ovh" && \
|
||||
"${provider}" != "rosehosting" && \
|
||||
"${provider}" != "scaleway" && \
|
||||
"${provider}" != "vultr" && \
|
||||
"${provider}" != "generic" \
|
||||
]]; then
|
||||
echo "--provider must be one of: azure, digitalocean, ec2, lightsail, linode, ovh, rosehosting, scaleway, vultr or generic"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${tlsProvider}" != "fallback" && "${tlsProvider}" != "le-prod" && "${tlsProvider}" != "le-staging" ]]; then
|
||||
echo "--tls-provider must be one of: le-prod, le-staging, fallback"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${dnsProvider}" ]]; then
|
||||
echo "--dns-provider is required (noop, manual)"
|
||||
exit 1
|
||||
elif [[ "${dnsProvider}" != "noop" && "${dnsProvider}" != "manual" ]]; then
|
||||
echo "--dns-provider must be one of : manual, noop"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "##############################################"
|
||||
echo " Cloudron Setup (${version}) "
|
||||
echo " Cloudron Setup (${requestedVersion}) "
|
||||
echo "##############################################"
|
||||
echo ""
|
||||
echo " Follow setup logs in a second terminal with:"
|
||||
echo " $ tail -f ${LOG_FILE}"
|
||||
echo ""
|
||||
echo " Join us at https://chat.cloudron.io for any questions."
|
||||
echo ""
|
||||
|
||||
echo "=> Downloading initialization script"
|
||||
if ! curl -s "${INIT_BASESYSTEM_SCRIPT_URL}" > /tmp/initializeBaseUbuntuImage.sh; then
|
||||
echo "Could not download initialization script"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${initBaseImage}" == "true" ]]; then
|
||||
echo "=> Updating apt and installing script dependencies"
|
||||
if ! apt-get update &>> "${LOG_FILE}"; then
|
||||
echo "Could not update package repositories"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=> Installing dependencies ... (this takes some time)"
|
||||
if ! /bin/bash /tmp/initializeBaseUbuntuImage.sh "${INSTALLER_REVISION}" "${provider}" &>> "${LOG_FILE}"; then
|
||||
echo "Init script failed. See ${LOG_FILE} for details"
|
||||
exit 1
|
||||
if ! apt-get install curl python3 ubuntu-standard -y &>> "${LOG_FILE}"; then
|
||||
echo "Could not install setup dependencies (curl)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
rm /tmp/initializeBaseUbuntuImage.sh
|
||||
|
||||
echo "=> Checking version"
|
||||
NPM_BIN=$(npm bin -g 2>/dev/null)
|
||||
if ! version=$(${NPM_BIN}/cloudron-version --out version --versions-url "${versionsUrl}" --version "${version}"); then
|
||||
echo "No such version ${version}"
|
||||
exit 1
|
||||
fi
|
||||
if ! sourceTarballUrl=$(${NPM_BIN}/cloudron-version --out tarballUrl --versions-url "${versionsUrl}" --version "${version}"); then
|
||||
echo "No source code for version ${version}"
|
||||
exit 1
|
||||
if [[ "${sourceTarballUrl}" == "" ]]; then
|
||||
releaseJson=$($curl -s "${versionsUrl}")
|
||||
if [[ "$requestedVersion" == "latest" ]]; then
|
||||
pre=$([[ "${prerelease}" == "true" ]] && echo "null" || echo "-pre")
|
||||
version=$(echo "${releaseJson}" | python3 -c "import json,sys,collections;obj=json.load(sys.stdin, object_pairs_hook=collections.OrderedDict);latest=list(v for v in obj if '${pre}' not in v)[-1];print(latest)")
|
||||
else
|
||||
version="${requestedVersion}"
|
||||
fi
|
||||
if ! sourceTarballUrl=$(echo "${releaseJson}" | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj[sys.argv[1]]["sourceTarballUrl"])' "${version}"); then
|
||||
echo "No source code for version ${requestedVersion}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# start the update server
|
||||
systemctl start cloudron-installer
|
||||
|
||||
data=$(cat <<EOF
|
||||
{
|
||||
"boxVersionsUrl": "${versionsUrl}",
|
||||
"fqdn": "${domain}",
|
||||
"provider": "${provider}",
|
||||
"restore": {
|
||||
"url": "${restoreUrl}",
|
||||
"key": "${restoreKey}"
|
||||
},
|
||||
"tlsConfig": {
|
||||
"provider": "${tlsProvider}"
|
||||
},
|
||||
"version": "${version}"
|
||||
}
|
||||
# Build data
|
||||
if [[ -z "${dataJson}" ]]; then
|
||||
if [[ -z "${restoreUrl}" ]]; then
|
||||
data=$(cat <<EOF
|
||||
{
|
||||
"boxVersionsUrl": "${versionsUrl}",
|
||||
"fqdn": "${domain}",
|
||||
"provider": "${provider}",
|
||||
"apiServerOrigin": "${apiServerOrigin}",
|
||||
"tlsConfig": {
|
||||
"provider": "${tlsProvider}"
|
||||
},
|
||||
"dnsConfig": {
|
||||
"provider": "${dnsProvider}"
|
||||
},
|
||||
"backupConfig" : {
|
||||
"provider": "filesystem",
|
||||
"backupFolder": "/var/backups",
|
||||
"key": "${encryptionKey}"
|
||||
},
|
||||
"updateConfig": {
|
||||
"prerelease": ${prerelease}
|
||||
},
|
||||
"version": "${version}"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
)
|
||||
else
|
||||
data=$(cat <<EOF
|
||||
{
|
||||
"boxVersionsUrl": "${versionsUrl}",
|
||||
"fqdn": "${domain}",
|
||||
"provider": "${provider}",
|
||||
"apiServerOrigin": "${apiServerOrigin}",
|
||||
"restore": {
|
||||
"url": "${restoreUrl}",
|
||||
"key": "${encryptionKey}"
|
||||
},
|
||||
"version": "${version}"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
fi
|
||||
else
|
||||
data="${dataJson}"
|
||||
fi
|
||||
|
||||
echo "=> Run installer.sh for version ${version} with ${sourceTarballUrl} ... (this takes some time)"
|
||||
if ! ${INSTALLER_SOURCE_DIR}/src/scripts/installer.sh --sourcetarballurl "${sourceTarballUrl}" --data "${data}" &>> "${LOG_FILE}"; then
|
||||
echo "=> Downloading version ${version} ..."
|
||||
box_src_tmp_dir=$(mktemp -dt box-src-XXXXXX)
|
||||
|
||||
if ! $curl -sL "${sourceTarballUrl}" | tar -zxf - -C "${box_src_tmp_dir}"; then
|
||||
echo "Could not download source tarball. See ${LOG_FILE} for details"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${initBaseImage}" == "true" ]]; then
|
||||
echo -n "=> Installing base dependencies and downloading docker images (this takes some time) ..."
|
||||
if ! /bin/bash "${box_src_tmp_dir}/baseimage/initializeBaseUbuntuImage.sh" "${provider}" "../src" &>> "${LOG_FILE}"; then
|
||||
echo "Init script failed. See ${LOG_FILE} for details"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "=> Installing version ${version} (this takes some time) ..."
|
||||
echo "${data}" > "${DATA_FILE}"
|
||||
if ! /bin/bash "${box_src_tmp_dir}/scripts/installer.sh" --data-file "${DATA_FILE}" &>> "${LOG_FILE}"; then
|
||||
echo "Failed to install cloudron. See ${LOG_FILE} for details"
|
||||
exit 1
|
||||
fi
|
||||
rm "${DATA_FILE}"
|
||||
|
||||
echo -n "=> Waiting for cloudron to be ready"
|
||||
echo -n "=> Waiting for cloudron to be ready (this takes some time) ..."
|
||||
while true; do
|
||||
echo -n "."
|
||||
if journalctl -u box -a | grep "platformReady: configured, resuming tasks" >/dev/null; then
|
||||
break
|
||||
if status=$($curl -q -f "http://localhost:3000/api/v1/cloudron/status" 2>/dev/null); then
|
||||
[[ -z "$domain" ]] && break # with no domain, we are up and running
|
||||
[[ "$status" == *"\"tls\": true"* ]] && break # with a domain, wait for the cert
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Visit https://my.${domain} to finish setup"
|
||||
echo ""
|
||||
if [[ -n "${domain}" ]]; then
|
||||
echo -e "\n\nVisit https://my.${domain} to finish setup once the server has rebooted.\n"
|
||||
else
|
||||
echo -e "\n\nVisit https://<IP> to finish setup once the server has rebooted.\n"
|
||||
fi
|
||||
|
||||
if [[ "${rebootServer}" == "true" ]]; then
|
||||
echo -e "\n\nRebooting this server now to let bootloader changes take effect.\n"
|
||||
systemctl reboot
|
||||
fi
|
||||
|
||||
@@ -2,55 +2,37 @@
|
||||
|
||||
set -eu
|
||||
|
||||
assertNotEmpty() {
|
||||
: "${!1:? "$1 is not set."}"
|
||||
}
|
||||
|
||||
# Only GNU getopt supports long options. OS X comes bundled with the BSD getopt
|
||||
# brew install gnu-getopt to get the GNU getopt on OS X
|
||||
[[ $(uname -s) == "Darwin" ]] && GNU_GETOPT="/usr/local/opt/gnu-getopt/bin/getopt" || GNU_GETOPT="getopt"
|
||||
readonly GNU_GETOPT
|
||||
|
||||
args=$(${GNU_GETOPT} -o "" -l "revision:,output:,publish,no-upload" -n "$0" -- "$@")
|
||||
args=$(${GNU_GETOPT} -o "" -l "revision:,output:" -n "$0" -- "$@")
|
||||
eval set -- "${args}"
|
||||
|
||||
readonly RELEASE_TOOL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../release" && pwd)"
|
||||
readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
delete_bundle="yes"
|
||||
commitish="HEAD"
|
||||
publish="no"
|
||||
upload="yes"
|
||||
bundle_file=""
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
--revision) commitish="$2"; shift 2;;
|
||||
--output) bundle_file="$2"; delete_bundle="no"; shift 2;;
|
||||
--no-upload) upload="no"; shift;;
|
||||
--publish) publish="yes"; shift;;
|
||||
--output) bundle_file="$2"; shift 2;;
|
||||
--) break;;
|
||||
*) echo "Unknown option $1"; exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "${upload}" == "no" && "${publish}" == "yes" ]]; then
|
||||
echo "Cannot publish without uploading"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
readonly TMPDIR=${TMPDIR:-/tmp} # why is this not set on mint?
|
||||
|
||||
assertNotEmpty AWS_DEV_ACCESS_KEY
|
||||
assertNotEmpty AWS_DEV_SECRET_KEY
|
||||
|
||||
if ! $(cd "${SOURCE_DIR}" && git diff --exit-code >/dev/null); then
|
||||
echo "You have local changes, stash or commit them to proceed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$(node --version)" != "v4.1.1" ]]; then
|
||||
echo "This script requires node 4.1.1"
|
||||
if [[ "$(node --version)" != "v6.9.2" ]]; then
|
||||
echo "This script requires node 6.9.2"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -101,23 +83,5 @@ echo "Create final tarball"
|
||||
echo "Cleaning up ${bundle_dir}"
|
||||
rm -rf "${bundle_dir}"
|
||||
|
||||
if [[ "${upload}" == "yes" ]]; then
|
||||
echo "Uploading bundle to S3"
|
||||
# That special header is needed to allow access with singed urls created with different aws credentials than the ones the file got uploaded
|
||||
s3cmd --multipart-chunk-size-mb=5 --ssl --acl-public --access_key="${AWS_DEV_ACCESS_KEY}" --secret_key="${AWS_DEV_SECRET_KEY}" --no-mime-magic put "${bundle_file}" "s3://dev-cloudron-releases/box-${version}.tar.gz"
|
||||
|
||||
versions_file_url="https://dev-cloudron-releases.s3.amazonaws.com/box-${version}.tar.gz"
|
||||
echo "The URL for the versions file is: ${versions_file_url}"
|
||||
|
||||
if [[ "${publish}" == "yes" ]]; then
|
||||
echo "Publishing to dev"
|
||||
${RELEASE_TOOL_DIR}/release create --env dev --code "${versions_file_url}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${delete_bundle}" == "no" ]]; then
|
||||
echo "Tarball preserved at ${bundle_file}"
|
||||
else
|
||||
rm "${bundle_file}"
|
||||
fi
|
||||
echo "Tarball saved at ${bundle_file}"
|
||||
|
||||
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
if [[ ${EUID} -ne 0 ]]; then
|
||||
echo "This script should be run as root." > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
|
||||
readonly USER=yellowtent
|
||||
readonly BOX_SRC_DIR=/home/${USER}/box
|
||||
readonly CLOUDRON_CONF=/home/yellowtent/configs/cloudron.conf
|
||||
|
||||
readonly script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly box_src_tmp_dir="$(realpath ${script_dir}/..)"
|
||||
|
||||
readonly is_update=$([[ -f "${CLOUDRON_CONF}" ]] && echo "yes" || echo "no")
|
||||
|
||||
arg_data=""
|
||||
|
||||
args=$(getopt -o "" -l "data:,data-file:" -n "$0" -- "$@")
|
||||
eval set -- "${args}"
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
--data) arg_data="$2"; shift 2;;
|
||||
--data-file) arg_data=$(cat $2); shift 2;;
|
||||
--) break;;
|
||||
*) echo "Unknown option $1"; exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
for try in `seq 1 10`; do
|
||||
# for reasons unknown, the dtrace package will fail. but rebuilding second time will work
|
||||
|
||||
# We need --unsafe-perm as we run as root and the folder is owned by root,
|
||||
# however by default npm drops privileges for npm rebuild
|
||||
# https://docs.npmjs.com/misc/config#unsafe-perm
|
||||
if cd "${box_src_tmp_dir}" && npm rebuild --unsafe-perm; then break; fi
|
||||
echo "Failed to rebuild, trying again"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ ${try} -eq 10 ]]; then
|
||||
echo "npm rebuild failed"
|
||||
exit 4
|
||||
fi
|
||||
|
||||
if ! id "${USER}" 2>/dev/null; then
|
||||
useradd "${USER}" -m
|
||||
fi
|
||||
|
||||
if [[ "${is_update}" == "yes" ]]; then
|
||||
echo "Setting up update splash screen"
|
||||
"${box_src_tmp_dir}/setup/splashpage.sh" --data "${arg_data}" || true # show splash from new code
|
||||
${BOX_SRC_DIR}/setup/stop.sh # stop the old code
|
||||
fi
|
||||
|
||||
# ensure we are not inside the source directory, which we will remove now
|
||||
cd /root
|
||||
|
||||
echo "==> installer: switching the box code"
|
||||
rm -rf "${BOX_SRC_DIR}"
|
||||
mv "${box_src_tmp_dir}" "${BOX_SRC_DIR}"
|
||||
chown -R "${USER}:${USER}" "${BOX_SRC_DIR}"
|
||||
|
||||
echo "==> installer: calling box setup script"
|
||||
"${BOX_SRC_DIR}/setup/start.sh" --data "${arg_data}"
|
||||
@@ -1,57 +0,0 @@
|
||||
This document gives the design of this setup code.
|
||||
|
||||
box code should be delivered in the form of a (docker) container.
|
||||
This is not the case currently but we want to do structure the code
|
||||
in spirit that way.
|
||||
|
||||
### container.sh
|
||||
This contains code that essential goes into Dockerfile.
|
||||
|
||||
This file contains static configuration over a base image. Currently,
|
||||
the yellowtent user is created in the installer base image but it
|
||||
could very well be placed here.
|
||||
|
||||
The idea is that the installer would simply remove the old box container
|
||||
and replace it with a new one for an update.
|
||||
|
||||
Because we do not package things as Docker yet, we should be careful
|
||||
about the code here. We have to expect remains of an older setup code.
|
||||
For example, older systemd or nginx configs might be around.
|
||||
|
||||
The config directory is _part_ of the container and is not a VOLUME.
|
||||
Which is to say that the files will be nuked from one update to the next.
|
||||
|
||||
The data directory is a VOLUME. Contents of this directory are expected
|
||||
to survive an update. This is a good place to place config files that
|
||||
are "dynamic" and need to survive restarts. For example, the infra
|
||||
version (see below) or the mysql/postgresql data etc.
|
||||
|
||||
### start.sh
|
||||
* It is called in 3 modes - new, update, restore.
|
||||
|
||||
* The first thing this does is to do the static container.sh setup.
|
||||
|
||||
* It then downloads any box restore data and restores the box db from the
|
||||
backup.
|
||||
|
||||
* It then proceeds to call the db-migrate script.
|
||||
|
||||
* It then does dynamic configuration like setting up nginx, collectd.
|
||||
|
||||
* It then setups up the cloud infra (setup_infra.sh) and creates cloudron.conf.
|
||||
|
||||
* box services are then started
|
||||
|
||||
setup_infra.sh
|
||||
This setups containers like graphite, mail and the addons containers.
|
||||
|
||||
Containers are relaunched based on the INFRA_VERSION. The script compares
|
||||
the version here with the version in the file DATA_DIR/INFRA_VERSION.
|
||||
|
||||
If they match, the containers are not recreated and nothing is to be done.
|
||||
nginx, collectd configs are part of data already and containers are running.
|
||||
|
||||
If they do not match, it deletes all containers (including app containers) and starts
|
||||
them all afresh. Important thing here is that, DATA_DIR is never removed across
|
||||
updates. So, it is only the containers being recreated and not the data.
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
json="${script_dir}/../node_modules/.bin/json"
|
||||
source_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
json="${source_dir}/../node_modules/.bin/json"
|
||||
|
||||
# IMPORTANT: Fix cloudron.js:doUpdate if you add/remove any arg. keep these sorted for readability
|
||||
arg_api_server_origin=""
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
# This file can be used in Dockerfile
|
||||
|
||||
readonly container_files="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/container"
|
||||
|
||||
readonly CONFIG_DIR="/home/yellowtent/configs"
|
||||
readonly DATA_DIR="/home/yellowtent/data"
|
||||
|
||||
########## create config directory
|
||||
rm -rf "${CONFIG_DIR}"
|
||||
sudo -u yellowtent mkdir "${CONFIG_DIR}"
|
||||
|
||||
########## systemd
|
||||
rm -f /etc/systemd/system/janitor.*
|
||||
cp -r "${container_files}/systemd/." /etc/systemd/system/
|
||||
systemctl daemon-reload
|
||||
systemctl enable cloudron.target
|
||||
|
||||
########## sudoers
|
||||
rm -f /etc/sudoers.d/yellowtent
|
||||
cp "${container_files}/sudoers" /etc/sudoers.d/yellowtent
|
||||
|
||||
########## collectd
|
||||
rm -rf /etc/collectd
|
||||
ln -sfF "${DATA_DIR}/collectd" /etc/collectd
|
||||
|
||||
########## apparmor docker profile
|
||||
cp "${container_files}/docker-cloudron-app.apparmor" /etc/apparmor.d/docker-cloudron-app
|
||||
systemctl restart apparmor
|
||||
|
||||
########## nginx
|
||||
# link nginx config to system config
|
||||
unlink /etc/nginx 2>/dev/null || rm -rf /etc/nginx
|
||||
ln -s "${DATA_DIR}/nginx" /etc/nginx
|
||||
|
||||
########## mysql
|
||||
cp "${container_files}/mysql.cnf" /etc/mysql/mysql.cnf
|
||||
|
||||
########## Enable services
|
||||
update-rc.d -f collectd defaults
|
||||
|
||||
+8
-3
@@ -5,12 +5,17 @@ set -eu -o pipefail
|
||||
readonly SETUP_WEBSITE_DIR="/home/yellowtent/setup/website"
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly BOX_SRC_DIR="/home/yellowtent/box"
|
||||
readonly box_src_dir="$(realpath ${script_dir}/..)"
|
||||
readonly DATA_DIR="/home/yellowtent/data"
|
||||
readonly ADMIN_LOCATION="my" # keep this in sync with constants.js
|
||||
|
||||
echo "Setting up nginx update page"
|
||||
|
||||
if [[ ! -f "${DATA_DIR}/nginx/applications/admin.conf" ]]; then
|
||||
echo "No admin.conf found. This Cloudron has no domain yet. Skip splash setup"
|
||||
exit
|
||||
fi
|
||||
|
||||
source "${script_dir}/argparser.sh" "$@" # this injects the arg_* variables used below
|
||||
|
||||
# keep this is sync with config.js appFqdn()
|
||||
@@ -28,11 +33,11 @@ existing_infra="none"
|
||||
if [[ "${arg_retire_reason}" != "" || "${existing_infra}" != "${current_infra}" ]]; then
|
||||
echo "Showing progress bar on all subdomains in retired mode or infra update. retire: ${arg_retire_reason} existing: ${existing_infra} current: ${current_infra}"
|
||||
rm -f ${DATA_DIR}/nginx/applications/*
|
||||
${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \
|
||||
${box_src_dir}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \
|
||||
-O "{ \"vhost\": \"~^(.+)\$\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"splash\", \"sourceDir\": \"${SETUP_WEBSITE_DIR}\", \"certFilePath\": \"cert/host.cert\", \"keyFilePath\": \"cert/host.key\", \"xFrameOptions\": \"SAMEORIGIN\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
|
||||
else
|
||||
echo "Show progress bar only on admin domain for normal update"
|
||||
${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \
|
||||
${box_src_dir}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \
|
||||
-O "{ \"vhost\": \"${admin_fqdn}\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"splash\", \"sourceDir\": \"${SETUP_WEBSITE_DIR}\", \"certFilePath\": \"cert/host.cert\", \"keyFilePath\": \"cert/host.key\", \"xFrameOptions\": \"SAMEORIGIN\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
|
||||
fi
|
||||
|
||||
|
||||
+218
-123
@@ -2,149 +2,257 @@
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
echo "==== Cloudron Start ===="
|
||||
echo "==> Cloudron Start"
|
||||
|
||||
readonly USER="yellowtent"
|
||||
readonly BOX_SRC_DIR="/home/${USER}/box"
|
||||
readonly DATA_DIR="/home/${USER}/data"
|
||||
readonly CONFIG_DIR="/home/${USER}/configs"
|
||||
readonly SETUP_PROGRESS_JSON="/home/yellowtent/setup/website/progress.json"
|
||||
readonly ADMIN_LOCATION="my" # keep this in sync with constants.js
|
||||
readonly DATA_FILE="/root/user_data.img"
|
||||
readonly HOME_DIR="/home/${USER}"
|
||||
readonly BOX_SRC_DIR="${HOME_DIR}/box"
|
||||
readonly DATA_DIR="${HOME_DIR}/data" # app and platform data
|
||||
readonly BOX_DATA_DIR="${HOME_DIR}/boxdata" # box data
|
||||
readonly CONFIG_DIR="${HOME_DIR}/configs"
|
||||
readonly SETUP_PROGRESS_JSON="${HOME_DIR}/setup/website/progress.json"
|
||||
|
||||
readonly curl="curl --fail --connect-timeout 20 --retry 10 --retry-delay 2 --max-time 2400"
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
source "${script_dir}/argparser.sh" "$@" # this injects the arg_* variables used below
|
||||
|
||||
# keep this is sync with config.js appFqdn()
|
||||
admin_fqdn=$([[ "${arg_is_custom_domain}" == "true" ]] && echo "${ADMIN_LOCATION}.${arg_fqdn}" || echo "${ADMIN_LOCATION}-${arg_fqdn}")
|
||||
admin_origin="https://${admin_fqdn}"
|
||||
|
||||
readonly is_update=$([[ -f "${CONFIG_DIR}/cloudron.conf" ]] && echo "true" || echo "false")
|
||||
|
||||
set_progress() {
|
||||
local percent="$1"
|
||||
local message="$2"
|
||||
|
||||
echo "==== ${percent} - ${message} ===="
|
||||
echo "==> ${percent} - ${message}"
|
||||
(echo "{ \"update\": { \"percent\": \"${percent}\", \"message\": \"${message}\" }, \"backup\": {} }" > "${SETUP_PROGRESS_JSON}") 2> /dev/null || true # as this will fail in non-update mode
|
||||
}
|
||||
|
||||
set_progress "1" "Create container"
|
||||
$script_dir/container.sh
|
||||
|
||||
set_progress "5" "Adjust system settings"
|
||||
set_progress "20" "Configuring host"
|
||||
sed -e 's/^#NTP=/NTP=0.ubuntu.pool.ntp.org 1.ubuntu.pool.ntp.org 2.ubuntu.pool.ntp.org 3.ubuntu.pool.ntp.org/' -i /etc/systemd/timesyncd.conf
|
||||
timedatectl set-ntp 1
|
||||
timedatectl set-timezone UTC
|
||||
hostnamectl set-hostname "${arg_fqdn}"
|
||||
|
||||
set_progress "10" "Ensuring directories"
|
||||
echo "==> Setting up firewall"
|
||||
iptables -t filter -N CLOUDRON || true
|
||||
iptables -t filter -F CLOUDRON # empty any existing rules
|
||||
|
||||
# NOTE: keep these in sync with src/apps.js validatePortBindings
|
||||
# allow ssh, http, https, ping, dns
|
||||
iptables -t filter -I CLOUDRON -m state --state RELATED,ESTABLISHED -j ACCEPT
|
||||
# caas has ssh on port 202
|
||||
if [[ "${arg_provider}" == "caas" ]]; then
|
||||
iptables -A CLOUDRON -p tcp -m tcp -m multiport --dports 25,80,202,443,587,993,4190 -j ACCEPT
|
||||
else
|
||||
iptables -A CLOUDRON -p tcp -m tcp -m multiport --dports 25,80,22,443,587,993,4190 -j ACCEPT
|
||||
fi
|
||||
iptables -t filter -A CLOUDRON -p icmp --icmp-type echo-request -j ACCEPT
|
||||
iptables -t filter -A CLOUDRON -p icmp --icmp-type echo-reply -j ACCEPT
|
||||
iptables -t filter -A CLOUDRON -p udp --sport 53 -j ACCEPT
|
||||
iptables -t filter -A CLOUDRON -s 172.18.0.0/16 -j ACCEPT # required to accept any connections from apps to our IP:<public port>
|
||||
iptables -t filter -A CLOUDRON -i lo -j ACCEPT # required for localhost connections (mysql)
|
||||
|
||||
# log dropped incoming. keep this at the end of all the rules
|
||||
iptables -t filter -A CLOUDRON -m limit --limit 2/min -j LOG --log-prefix "IPTables Packet Dropped: " --log-level 7
|
||||
iptables -t filter -A CLOUDRON -j DROP
|
||||
|
||||
if ! iptables -t filter -C INPUT -j CLOUDRON 2>/dev/null; then
|
||||
iptables -t filter -I INPUT -j CLOUDRON
|
||||
fi
|
||||
|
||||
# so it gets restored across reboot
|
||||
mkdir -p /etc/iptables && iptables-save > /etc/iptables/rules.v4
|
||||
|
||||
echo "==> Configuring docker"
|
||||
cp "${script_dir}/start/docker-cloudron-app.apparmor" /etc/apparmor.d/docker-cloudron-app
|
||||
systemctl enable apparmor
|
||||
systemctl restart apparmor
|
||||
|
||||
usermod ${USER} -a -G docker
|
||||
temp_file=$(mktemp)
|
||||
# create systemd drop-in. some apps do not work with aufs
|
||||
echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/dockerd -H fd:// --log-driver=journald --exec-opt native.cgroupdriver=cgroupfs --storage-driver=devicemapper --dns=172.18.0.1 --dns-search=." > "${temp_file}"
|
||||
|
||||
systemctl enable docker
|
||||
# restart docker if options changed
|
||||
if [[ ! -f /etc/systemd/system/docker.service.d/cloudron.conf ]] || ! diff -q /etc/systemd/system/docker.service.d/cloudron.conf "${temp_file}" >/dev/null; then
|
||||
mkdir -p /etc/systemd/system/docker.service.d
|
||||
mv "${temp_file}" /etc/systemd/system/docker.service.d/cloudron.conf
|
||||
systemctl daemon-reload
|
||||
systemctl restart docker
|
||||
fi
|
||||
docker network create --subnet=172.18.0.0/16 cloudron || true
|
||||
|
||||
# caas has ssh on port 202 and we disable password login
|
||||
if [[ "${arg_provider}" == "caas" ]]; then
|
||||
# https://stackoverflow.com/questions/4348166/using-with-sed on why ? must be escaped
|
||||
sed -e 's/^#\?PermitRootLogin .*/PermitRootLogin without-password/g' \
|
||||
-e 's/^#\?PermitEmptyPasswords .*/PermitEmptyPasswords no/g' \
|
||||
-e 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/g' \
|
||||
-e 's/^#\?Port .*/Port 202/g' \
|
||||
-i /etc/ssh/sshd_config
|
||||
|
||||
# required so we can connect to this machine since port 22 is blocked by iptables by now
|
||||
systemctl reload sshd
|
||||
fi
|
||||
|
||||
echo "==> Setup btrfs data"
|
||||
if [[ ! -d "${DATA_DIR}" ]]; then
|
||||
echo "==> Mounting loopback btrfs"
|
||||
truncate -s "8192m" "${DATA_FILE}" # 8gb start (this will get resized dynamically by cloudron-resize-fs.service)
|
||||
mkfs.btrfs -L UserDataHome "${DATA_FILE}"
|
||||
mkdir -p "${DATA_DIR}"
|
||||
mount -t btrfs -o loop,nosuid "${DATA_FILE}" ${DATA_DIR}
|
||||
fi
|
||||
|
||||
# keep these in sync with paths.js
|
||||
[[ "${is_update}" == "false" ]] && btrfs subvolume create "${DATA_DIR}/box"
|
||||
mkdir -p "${DATA_DIR}/box/appicons"
|
||||
mkdir -p "${DATA_DIR}/box/certs"
|
||||
mkdir -p "${DATA_DIR}/box/mail/dkim/${arg_fqdn}"
|
||||
mkdir -p "${DATA_DIR}/box/acme" # acme keys
|
||||
echo "==> Ensuring directories"
|
||||
if ! btrfs subvolume show "${DATA_DIR}/mail" &> /dev/null; then
|
||||
# Migrate mail data to new format
|
||||
docker stop mail || true # otherwise the move below might fail if mail container writes in the middle
|
||||
rm -rf "${DATA_DIR}/mail" # this used to be mail container's run directory
|
||||
btrfs subvolume create "${DATA_DIR}/mail"
|
||||
[[ -d "${DATA_DIR}/box/mail" ]] && mv "${DATA_DIR}/box/mail/"* "${DATA_DIR}/mail"
|
||||
rm -rf "${DATA_DIR}/box/mail"
|
||||
fi
|
||||
mkdir -p "${DATA_DIR}/graphite"
|
||||
mkdir -p "${DATA_DIR}/mail/dkim"
|
||||
|
||||
mkdir -p "${DATA_DIR}/mysql"
|
||||
mkdir -p "${DATA_DIR}/postgresql"
|
||||
mkdir -p "${DATA_DIR}/mongodb"
|
||||
mkdir -p "${DATA_DIR}/snapshots"
|
||||
mkdir -p "${DATA_DIR}/addons"
|
||||
mkdir -p "${DATA_DIR}/addons/mail"
|
||||
mkdir -p "${DATA_DIR}/collectd/collectd.conf.d"
|
||||
mkdir -p "${DATA_DIR}/acme" # acme challenges
|
||||
mkdir -p "${DATA_DIR}/acme"
|
||||
|
||||
mkdir -p "${BOX_DATA_DIR}"
|
||||
if btrfs subvolume show "${DATA_DIR}/box" &> /dev/null; then
|
||||
# Migrate box data out of data volume
|
||||
mv "${DATA_DIR}/box/"* "${BOX_DATA_DIR}"
|
||||
btrfs subvolume delete "${DATA_DIR}/box"
|
||||
fi
|
||||
mkdir -p "${BOX_DATA_DIR}/appicons"
|
||||
mkdir -p "${BOX_DATA_DIR}/certs"
|
||||
mkdir -p "${BOX_DATA_DIR}/acme" # acme keys
|
||||
|
||||
echo "==> Configuring journald"
|
||||
sed -e "s/^#SystemMaxUse=.*$/SystemMaxUse=100M/" \
|
||||
-e "s/^#ForwardToSyslog=.*$/ForwardToSyslog=no/" \
|
||||
-i /etc/systemd/journald.conf
|
||||
|
||||
# When rotating logs, systemd kills journald too soon sometimes
|
||||
# See https://github.com/systemd/systemd/issues/1353 (this is upstream default)
|
||||
sed -e "s/^WatchdogSec=.*$/WatchdogSec=3min/" \
|
||||
-i /lib/systemd/system/systemd-journald.service
|
||||
|
||||
# Give user access to system logs
|
||||
usermod -a -G systemd-journal ${USER}
|
||||
mkdir -p /var/log/journal # in some images, this directory is not created making system log to /run/systemd instead
|
||||
chown root:systemd-journal /var/log/journal
|
||||
systemctl daemon-reload
|
||||
systemctl restart systemd-journald
|
||||
setfacl -n -m u:${USER}:r /var/log/journal/*/system.journal
|
||||
|
||||
echo "==> Creating config directory"
|
||||
rm -rf "${CONFIG_DIR}" && mkdir "${CONFIG_DIR}"
|
||||
|
||||
echo "==> Setting up unbound"
|
||||
# DO uses Google nameservers by default. This causes RBL queries to fail (host 2.0.0.127.zen.spamhaus.org)
|
||||
# We do not use dnsmasq because it is not a recursive resolver and defaults to the value in the interfaces file (which is Google DNS!)
|
||||
# We listen on 0.0.0.0 because there is no way control ordering of docker (which creates the 172.18.0.0/16) and unbound
|
||||
echo -e "server:\n\tinterface: 0.0.0.0\n\taccess-control: 127.0.0.1 allow\n\taccess-control: 172.18.0.1/16 allow\n\tcache-max-negative-ttl: 30" > /etc/unbound/unbound.conf.d/cloudron-network.conf
|
||||
|
||||
echo "==> Adding systemd services"
|
||||
cp -r "${script_dir}/start/systemd/." /etc/systemd/system/
|
||||
systemctl daemon-reload
|
||||
systemctl enable unbound
|
||||
systemctl enable cloudron.target
|
||||
systemctl enable iptables-restore
|
||||
|
||||
# For logrotate
|
||||
systemctl enable --now cron
|
||||
|
||||
# ensure unbound runs
|
||||
systemctl restart unbound
|
||||
|
||||
echo "==> Configuring sudoers"
|
||||
rm -f /etc/sudoers.d/${USER}
|
||||
cp "${script_dir}/start/sudoers" /etc/sudoers.d/${USER}
|
||||
|
||||
echo "==> Configuring collectd"
|
||||
rm -rf /etc/collectd
|
||||
ln -sfF "${DATA_DIR}/collectd" /etc/collectd
|
||||
cp "${script_dir}/start/collectd.conf" "${DATA_DIR}/collectd/collectd.conf"
|
||||
systemctl restart collectd
|
||||
|
||||
echo "==> Configuring nginx"
|
||||
# link nginx config to system config
|
||||
unlink /etc/nginx 2>/dev/null || rm -rf /etc/nginx
|
||||
ln -s "${DATA_DIR}/nginx" /etc/nginx
|
||||
mkdir -p "${DATA_DIR}/nginx/applications"
|
||||
mkdir -p "${DATA_DIR}/nginx/cert"
|
||||
cp "${script_dir}/start/nginx/nginx.conf" "${DATA_DIR}/nginx/nginx.conf"
|
||||
cp "${script_dir}/start/nginx/mime.types" "${DATA_DIR}/nginx/mime.types"
|
||||
if ! grep -q "^Restart=" /etc/systemd/system/multi-user.target.wants/nginx.service; then
|
||||
# default nginx service file does not restart on crash
|
||||
echo -e "\n[Service]\nRestart=always\n" >> /etc/systemd/system/multi-user.target.wants/nginx.service
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
systemctl start nginx
|
||||
|
||||
# bookkeep the version as part of data
|
||||
echo "{ \"version\": \"${arg_version}\", \"boxVersionsUrl\": \"${arg_box_versions_url}\" }" > "${DATA_DIR}/box/version"
|
||||
echo "{ \"version\": \"${arg_version}\", \"boxVersionsUrl\": \"${arg_box_versions_url}\" }" > "${BOX_DATA_DIR}/version"
|
||||
|
||||
# remove old snapshots. if we do want to keep this around, we will have to fix the chown -R below
|
||||
# which currently fails because these are readonly fs
|
||||
echo "Cleaning up snapshots"
|
||||
echo "==> Cleaning up snapshots"
|
||||
find "${DATA_DIR}/snapshots" -mindepth 1 -maxdepth 1 | xargs --no-run-if-empty btrfs subvolume delete
|
||||
|
||||
# restart mysql to make sure it has latest config
|
||||
# wait for all running mysql jobs
|
||||
while true; do
|
||||
if ! systemctl list-jobs | grep mysql; then break; fi
|
||||
echo "Waiting for mysql jobs..."
|
||||
sleep 1
|
||||
done
|
||||
systemctl restart mysql
|
||||
if [[ ! -f /etc/mysql/mysql.cnf ]] || ! diff -q "${script_dir}/start/mysql.cnf" /etc/mysql/mysql.cnf >/dev/null; then
|
||||
# wait for all running mysql jobs
|
||||
cp "${script_dir}/start/mysql.cnf" /etc/mysql/mysql.cnf
|
||||
while true; do
|
||||
if ! systemctl list-jobs | grep mysql; then break; fi
|
||||
echo "Waiting for mysql jobs..."
|
||||
sleep 1
|
||||
done
|
||||
systemctl restart mysql
|
||||
else
|
||||
systemctl start mysql
|
||||
fi
|
||||
|
||||
readonly mysql_root_password="password"
|
||||
mysqladmin -u root -ppassword password password # reset default root password
|
||||
mysql -u root -p${mysql_root_password} -e 'CREATE DATABASE IF NOT EXISTS box'
|
||||
|
||||
if [[ -n "${arg_restore_url}" ]]; then
|
||||
set_progress "15" "Downloading restore data"
|
||||
set_progress "30" "Downloading restore data"
|
||||
|
||||
echo "Downloading backup: ${arg_restore_url} and key: ${arg_restore_key}"
|
||||
echo "==> Downloading backup: ${arg_restore_url} and key: ${arg_restore_key}"
|
||||
|
||||
while true; do
|
||||
if $curl -L "${arg_restore_url}" | openssl aes-256-cbc -d -pass "pass:${arg_restore_key}" | tar -zxf - -C "${DATA_DIR}/box"; then break; fi
|
||||
if $curl -L "${arg_restore_url}" | openssl aes-256-cbc -d -pass "pass:${arg_restore_key}" \
|
||||
| tar -zxf - --overwrite --transform="s,^box/\?,boxdata/," --transform="s,^mail/\?,data/mail/," --show-transformed-names -C "${HOME_DIR}"; then break; fi
|
||||
echo "Failed to download data, trying again"
|
||||
done
|
||||
|
||||
set_progress "21" "Setting up MySQL"
|
||||
if [[ -f "${DATA_DIR}/box/box.mysqldump" ]]; then
|
||||
echo "Importing existing database into MySQL"
|
||||
mysql -u root -p${mysql_root_password} box < "${DATA_DIR}/box/box.mysqldump"
|
||||
set_progress "35" "Setting up MySQL"
|
||||
if [[ -f "${BOX_DATA_DIR}/box.mysqldump" ]]; then
|
||||
echo "==> Importing existing database into MySQL"
|
||||
mysql -u root -p${mysql_root_password} box < "${BOX_DATA_DIR}/box.mysqldump"
|
||||
fi
|
||||
fi
|
||||
|
||||
set_progress "25" "Migrating data"
|
||||
set_progress "40" "Migrating data"
|
||||
sudo -u "${USER}" -H bash <<EOF
|
||||
set -eu
|
||||
cd "${BOX_SRC_DIR}"
|
||||
BOX_ENV=cloudron DATABASE_URL=mysql://root:${mysql_root_password}@localhost/box "${BOX_SRC_DIR}/node_modules/.bin/db-migrate" up
|
||||
EOF
|
||||
|
||||
set_progress "28" "Setup collectd"
|
||||
cp "${script_dir}/start/collectd.conf" "${DATA_DIR}/collectd/collectd.conf"
|
||||
systemctl restart collectd
|
||||
|
||||
set_progress "30" "Setup nginx"
|
||||
mkdir -p "${DATA_DIR}/nginx/applications"
|
||||
cp "${script_dir}/start/nginx/nginx.conf" "${DATA_DIR}/nginx/nginx.conf"
|
||||
cp "${script_dir}/start/nginx/mime.types" "${DATA_DIR}/nginx/mime.types"
|
||||
|
||||
# generate these for update code paths as well to overwrite splash
|
||||
admin_cert_file="${DATA_DIR}/nginx/cert/host.cert"
|
||||
admin_key_file="${DATA_DIR}/nginx/cert/host.key"
|
||||
if [[ -f "${DATA_DIR}/box/certs/${admin_fqdn}.cert" && -f "${DATA_DIR}/box/certs/${admin_fqdn}.key" ]]; then
|
||||
admin_cert_file="${DATA_DIR}/box/certs/${admin_fqdn}.cert"
|
||||
admin_key_file="${DATA_DIR}/box/certs/${admin_fqdn}.key"
|
||||
fi
|
||||
${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/appconfig.ejs" \
|
||||
-O "{ \"vhost\": \"${admin_fqdn}\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"admin\", \"sourceDir\": \"${BOX_SRC_DIR}\", \"certFilePath\": \"${admin_cert_file}\", \"keyFilePath\": \"${admin_key_file}\", \"xFrameOptions\": \"SAMEORIGIN\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
|
||||
|
||||
mkdir -p "${DATA_DIR}/nginx/cert"
|
||||
if [[ -f "${DATA_DIR}/box/certs/host.cert" && -f "${DATA_DIR}/box/certs/host.key" ]]; then
|
||||
cp "${DATA_DIR}/box/certs/host.cert" "${DATA_DIR}/nginx/cert/host.cert"
|
||||
cp "${DATA_DIR}/box/certs/host.key" "${DATA_DIR}/nginx/cert/host.key"
|
||||
else
|
||||
if [[ -z "${arg_tls_cert}" || -z "${arg_tls_key}" ]]; then
|
||||
echo "Creating fallback certs"
|
||||
openssl req -x509 -newkey rsa:2048 -keyout "${DATA_DIR}/nginx/cert/host.key" -out "${DATA_DIR}/nginx/cert/host.cert" -days 3650 -subj "/CN=${arg_fqdn}" -nodes
|
||||
else
|
||||
echo "${arg_tls_cert}" > "${DATA_DIR}/nginx/cert/host.cert"
|
||||
echo "${arg_tls_key}" > "${DATA_DIR}/nginx/cert/host.key"
|
||||
fi
|
||||
fi
|
||||
|
||||
set_progress "33" "Changing ownership"
|
||||
chown "${USER}:${USER}" -R "${DATA_DIR}/nginx" "${DATA_DIR}/collectd" "${DATA_DIR}/addons" "${DATA_DIR}/acme"
|
||||
# during updates, do not trample mail ownership behind the the mail container's back
|
||||
find "${DATA_DIR}/box" -mindepth 1 -maxdepth 1 -not -path "${DATA_DIR}/box/mail" -print0 | xargs -0 chown -R "${USER}:${USER}"
|
||||
chown "${USER}:${USER}" "${DATA_DIR}/box"
|
||||
chown "${USER}:${USER}" -R "${DATA_DIR}/box/mail/dkim" # this is owned by box currently since it generates the keys
|
||||
chown "${USER}:${USER}" "${DATA_DIR}/INFRA_VERSION" || true
|
||||
chown "${USER}:${USER}" "${DATA_DIR}"
|
||||
|
||||
set_progress "65" "Creating cloudron.conf"
|
||||
sudo -u yellowtent -H bash <<EOF
|
||||
set -eu
|
||||
echo "Creating cloudron.conf"
|
||||
echo "==> Creating cloudron.conf"
|
||||
cat > "${CONFIG_DIR}/cloudron.conf" <<CONF_END
|
||||
{
|
||||
"version": "${arg_version}",
|
||||
@@ -166,69 +274,56 @@ cat > "${CONFIG_DIR}/cloudron.conf" <<CONF_END
|
||||
"appBundle": ${arg_app_bundle}
|
||||
}
|
||||
CONF_END
|
||||
# pass these out-of-band because they have new lines which interfere with json
|
||||
if [[ -n "${arg_tls_cert}" && -n "${arg_tls_key}" ]]; then
|
||||
echo "${arg_tls_cert}" > "${CONFIG_DIR}/host.cert"
|
||||
echo "${arg_tls_key}" > "${CONFIG_DIR}/host.key"
|
||||
fi
|
||||
|
||||
echo "Creating config.json for webadmin"
|
||||
echo "==> Creating config.json for webadmin"
|
||||
cat > "${BOX_SRC_DIR}/webadmin/dist/config.json" <<CONF_END
|
||||
{
|
||||
"webServerOrigin": "${arg_web_server_origin}"
|
||||
}
|
||||
CONF_END
|
||||
EOF
|
||||
|
||||
# Add Backup Configuration
|
||||
echo "==> Changing ownership"
|
||||
chown "${USER}:${USER}" -R "${CONFIG_DIR}"
|
||||
chown "${USER}:${USER}" -R "${DATA_DIR}/nginx" "${DATA_DIR}/collectd" "${DATA_DIR}/addons" "${DATA_DIR}/acme"
|
||||
chown "${USER}:${USER}" -R "${BOX_DATA_DIR}"
|
||||
chown "${USER}:${USER}" -R "${DATA_DIR}/mail/dkim" # this is owned by box currently since it generates the keys
|
||||
chown "${USER}:${USER}" "${DATA_DIR}/INFRA_VERSION" 2>/dev/null || true
|
||||
chown "${USER}:${USER}" "${DATA_DIR}"
|
||||
|
||||
echo "==> Adding automated configs"
|
||||
if [[ ! -z "${arg_backup_config}" ]]; then
|
||||
echo "Add Backup Config"
|
||||
|
||||
mysql -u root -p${mysql_root_password} \
|
||||
-e "REPLACE INTO settings (name, value) VALUES (\"backup_config\", '$arg_backup_config')" box
|
||||
fi
|
||||
|
||||
# Add DNS Configuration
|
||||
if [[ ! -z "${arg_dns_config}" ]]; then
|
||||
echo "Add DNS Config"
|
||||
|
||||
mysql -u root -p${mysql_root_password} \
|
||||
-e "REPLACE INTO settings (name, value) VALUES (\"dns_config\", '$arg_dns_config')" box
|
||||
fi
|
||||
|
||||
# Add Update Configuration
|
||||
if [[ ! -z "${arg_update_config}" ]]; then
|
||||
echo "Add Update Config"
|
||||
|
||||
mysql -u root -p${mysql_root_password} \
|
||||
-e "REPLACE INTO settings (name, value) VALUES (\"update_config\", '$arg_update_config')" box
|
||||
fi
|
||||
|
||||
# Add TLS Configuration
|
||||
if [[ ! -z "${arg_tls_config}" ]]; then
|
||||
echo "Add TLS Config"
|
||||
|
||||
mysql -u root -p${mysql_root_password} \
|
||||
-e "REPLACE INTO settings (name, value) VALUES (\"tls_config\", '$arg_tls_config')" box
|
||||
fi
|
||||
|
||||
# The domain might have changed, therefor we have to update the record
|
||||
# !!! This needs to be in sync with the webadmin, specifically login_callback.js
|
||||
echo "Add webadmin api cient"
|
||||
readonly ADMIN_SCOPES="cloudron,developer,profile,users,apps,settings"
|
||||
mysql -u root -p${mysql_root_password} \
|
||||
-e "REPLACE INTO clients (id, appId, type, clientSecret, redirectURI, scope) VALUES (\"cid-webadmin\", \"Settings\", \"built-in\", \"secret-webadmin\", \"${admin_origin}\", \"${ADMIN_SCOPES}\")" box
|
||||
echo "==> Generating dhparams (takes forever)"
|
||||
if [[ ! -f "${BOX_DATA_DIR}/dhparams.pem" ]]; then
|
||||
openssl dhparam -out "${BOX_DATA_DIR}/dhparams.pem" 2048
|
||||
fi
|
||||
|
||||
echo "Add SDK api client"
|
||||
mysql -u root -p${mysql_root_password} \
|
||||
-e "REPLACE INTO clients (id, appId, type, clientSecret, redirectURI, scope) VALUES (\"cid-sdk\", \"SDK\", \"built-in\", \"secret-sdk\", \"${admin_origin}\", \"*,roleSdk\")" box
|
||||
|
||||
echo "Add cli api client"
|
||||
mysql -u root -p${mysql_root_password} \
|
||||
-e "REPLACE INTO clients (id, appId, type, clientSecret, redirectURI, scope) VALUES (\"cid-cli\", \"Cloudron Tool\", \"built-in\", \"secret-cli\", \"${admin_origin}\", \"*,roleSdk\")" box
|
||||
|
||||
set_progress "80" "Starting Cloudron"
|
||||
set_progress "60" "Starting Cloudron"
|
||||
systemctl start cloudron.target
|
||||
|
||||
sleep 2 # give systemd sometime to start the processes
|
||||
|
||||
set_progress "85" "Reloading nginx"
|
||||
nginx -s reload
|
||||
|
||||
set_progress "100" "Done"
|
||||
|
||||
set_progress "90" "Almost done"
|
||||
|
||||
@@ -12,22 +12,19 @@ disk_device="$(for d in $(find /dev -type b); do [ "$(mountpoint -d /)" = "$(mou
|
||||
|
||||
existing_swap=$(cat /proc/meminfo | grep SwapTotal | awk '{ printf "%.0f", $2/1024 }')
|
||||
|
||||
# allow root access over ssh
|
||||
sed -e 's/.* \(ssh-rsa.*\)/\1/' -i /root/.ssh/authorized_keys
|
||||
|
||||
# all sizes are in mb
|
||||
readonly physical_memory=$(free -m | awk '/Mem:/ { print $2 }')
|
||||
readonly physical_memory=$(LC_ALL=C free -m | awk '/Mem:/ { print $2 }')
|
||||
readonly swap_size=$((${physical_memory} - ${existing_swap})) # if you change this, fix enoughResourcesAvailable() in client.js
|
||||
readonly app_count=$((${physical_memory} / 200)) # estimated app count
|
||||
readonly disk_size_gb=$(fdisk -l ${disk_device} | grep "Disk ${disk_device}" | awk '{ printf "%.0f", $3 }')
|
||||
readonly disk_size=$((disk_size_gb * 1024))
|
||||
readonly system_size=10240 # 10 gigs for system libs, apps images, installer, box code and tmp
|
||||
readonly disk_size_bytes=$(LC_ALL=C fdisk -l ${disk_device} | grep "Disk ${disk_device}" | awk '{ printf $5 }') # can't rely on fdisk human readable units, using bytes instead
|
||||
readonly disk_size=$((${disk_size_bytes}/1024/1024))
|
||||
readonly system_size=10240 # 10 gigs for system libs, apps images, installer, box code, data and tmp
|
||||
readonly ext4_reserved=$((disk_size * 5 / 100)) # this can be changes using tune2fs -m percent /dev/vda1
|
||||
|
||||
echo "Disk device: ${disk_device}"
|
||||
echo "Physical memory: ${physical_memory}"
|
||||
echo "Estimated app count: ${app_count}"
|
||||
echo "Disk size: ${disk_size}"
|
||||
echo "Disk size: ${disk_size}M"
|
||||
|
||||
# Allocate swap for general app usage
|
||||
if [[ ! -f "${APPS_SWAP_FILE}" && ${swap_size} -gt 0 ]]; then
|
||||
@@ -41,6 +38,7 @@ else
|
||||
echo "Apps Swap file already exists"
|
||||
fi
|
||||
|
||||
# see start.sh for the initial default size of 8gb. On small disks the calculation might be lower than 8gb resulting in a failure to resize here.
|
||||
echo "Resizing data volume"
|
||||
home_data_size=$((disk_size - system_size - swap_size - ext4_reserved))
|
||||
echo "Resizing up btrfs user data to size ${home_data_size}M"
|
||||
@@ -50,4 +48,3 @@ umount "${USER_DATA_DIR}" || true
|
||||
truncate -s "${home_data_size}m" "${USER_DATA_FILE}" # this will shrink it if the file had existed. this is useful when running this script on a live system
|
||||
mount -t btrfs -o loop,nosuid "${USER_DATA_FILE}" ${USER_DATA_DIR}
|
||||
btrfs filesystem resize max "${USER_DATA_DIR}"
|
||||
|
||||
@@ -5,8 +5,12 @@ map $http_upgrade $connection_upgrade {
|
||||
}
|
||||
|
||||
server {
|
||||
<% if (vhost) { %>
|
||||
listen 443;
|
||||
server_name <%= vhost %>;
|
||||
<% } else { %>
|
||||
listen 443 default_server;
|
||||
<% } %>
|
||||
|
||||
ssl on;
|
||||
# paths are relative to prefix and not to this file
|
||||
@@ -21,12 +25,22 @@ server {
|
||||
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # don't use SSLv3 ref: POODLE
|
||||
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
|
||||
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains";
|
||||
# ciphers according to https://weakdh.org/sysadmin.html
|
||||
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_dhparam /home/yellowtent/boxdata/dhparams.pem;
|
||||
add_header Strict-Transport-Security "max-age=15768000";
|
||||
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options
|
||||
add_header X-Frame-Options "<%= xFrameOptions %>";
|
||||
|
||||
# https://github.com/twitter/secureheaders
|
||||
# https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#tab=Compatibility_Matrix
|
||||
# https://wiki.mozilla.org/Security/Guidelines/Web_Security
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Download-Options "noopen";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
add_header X-Permitted-Cross-Domain-Policies "none";
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_intercept_errors on;
|
||||
proxy_read_timeout 3500;
|
||||
@@ -43,9 +57,10 @@ server {
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
|
||||
# only serve up the status page if we get proxy gateway errors
|
||||
error_page 502 503 504 @appstatus;
|
||||
location @appstatus {
|
||||
return 307 <%= adminOrigin %>/appstatus.html?referrer=https://$host$request_uri;
|
||||
root <%= sourceDir %>/webadmin/dist;
|
||||
error_page 502 503 504 /appstatus.html;
|
||||
location /appstatus.html {
|
||||
internal;
|
||||
}
|
||||
|
||||
location / {
|
||||
@@ -80,9 +95,6 @@ server {
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
<% } else if ( endpoint === 'oauthproxy' ) { %>
|
||||
proxy_pass http://127.0.0.1:3003;
|
||||
proxy_set_header X-Cloudron-Proxy-Port <%= port %>;
|
||||
<% } else if ( endpoint === 'app' ) { %>
|
||||
proxy_pass http://127.0.0.1:<%= port %>;
|
||||
<% } else if ( endpoint === 'splash' ) { %>
|
||||
|
||||
@@ -57,35 +57,6 @@ http {
|
||||
}
|
||||
}
|
||||
|
||||
# This server handles the naked domain for custom domains.
|
||||
# It can also be used for wildcard subdomain 404. This feature is not used by the Cloudron itself
|
||||
# because box always sets up DNS records for app subdomains.
|
||||
server {
|
||||
listen 443 default_server;
|
||||
ssl on;
|
||||
ssl_certificate cert/host.cert;
|
||||
ssl_certificate_key cert/host.key;
|
||||
|
||||
error_page 404 = @fallback;
|
||||
location @fallback {
|
||||
internal;
|
||||
root /home/yellowtent/box/webadmin/dist;
|
||||
rewrite ^/$ /nakeddomain.html break;
|
||||
}
|
||||
|
||||
location / {
|
||||
internal;
|
||||
root /home/yellowtent/box/webadmin/dist;
|
||||
rewrite ^/$ /nakeddomain.html break;
|
||||
}
|
||||
|
||||
# required for /api/v1/cloudron/avatar
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
client_max_body_size 1m;
|
||||
}
|
||||
}
|
||||
|
||||
include applications/*.conf;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,3 +33,9 @@ yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/retire.sh
|
||||
|
||||
Defaults!/home/yellowtent/box/src/scripts/rmbackup.sh env_keep="HOME BOX_ENV"
|
||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/rmbackup.sh
|
||||
|
||||
Defaults!/home/yellowtent/box/src/scripts/update.sh env_keep="HOME BOX_ENV"
|
||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/update.sh
|
||||
|
||||
Defaults!/home/yellowtent/box/src/scripts/authorized_keys.sh env_keep="HOME BOX_ENV"
|
||||
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/authorized_keys.sh
|
||||
@@ -4,6 +4,9 @@ OnFailure=crashnotifier@%n.service
|
||||
StopWhenUnneeded=true
|
||||
; journald crashes result in a EPIPE in node. Cannot ignore it as it results in loss of logs.
|
||||
BindsTo=systemd-journald.service
|
||||
After=mysql.service nginx.service
|
||||
; As cloudron-resize-fs is a one-shot, the Wants= automatically ensures that the service *finishes*
|
||||
Wants=cloudron-resize-fs.service
|
||||
|
||||
[Service]
|
||||
Type=idle
|
||||
@@ -0,0 +1,16 @@
|
||||
# Allocate swap files
|
||||
# https://bbs.archlinux.org/viewtopic.php?id=194792 ensures this runs after do-resize.service
|
||||
# On ubuntu ec2 we use cloud-init https://wiki.archlinux.org/index.php/Cloud-init
|
||||
|
||||
[Unit]
|
||||
Description=Cloudron FS Resizer
|
||||
Before=docker.service collectd.service mysql.service sshd.service nginx.service
|
||||
After=cloud-init.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart="/home/yellowtent/box/setup/start/cloudron-resize-fs.sh"
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user