Compare commits

...

2602 Commits

Author SHA1 Message Date
Girish Ramakrishnan
060d9e88ef Pass manifest to backupApp 2016-06-14 11:32:29 -07:00
Girish Ramakrishnan
9cf497a87d 0.15.2 changes 2016-06-13 23:31:35 -07:00
Girish Ramakrishnan
b174765992 delete unused addonsa fter backup 2016-06-13 23:07:41 -07:00
Girish Ramakrishnan
9c2d217176 fix typo 2016-06-13 23:04:43 -07:00
Girish Ramakrishnan
3197349058 Fix app backup before updates
we were passing the current manifest to the backup code which meant that
the app version and manifest was incorrect.
2016-06-13 21:19:29 -07:00
Girish Ramakrishnan
5f3378878e remove lastBackupConfig 2016-06-13 19:19:28 -07:00
Girish Ramakrishnan
53cd45496b parse the response 2016-06-13 18:28:51 -07:00
Girish Ramakrishnan
942339435a return config correctly 2016-06-13 18:04:22 -07:00
Girish Ramakrishnan
2bd6519795 add assert 2016-06-13 18:02:57 -07:00
Girish Ramakrishnan
1763c36a0b restore from the backup's config.json
To summarize what we are doing is that restore is simply getting old data and
old code. Config is not changed. If config is required, then it has to come
in the restore REST parameter. Otherwise, there is too much magic.

https://blog.smartserver.io/2016/06/13/app-restore/
2016-06-13 16:54:59 -07:00
Girish Ramakrishnan
2c0eb33625 use apps.getAppConfig when generating config.json 2016-06-13 15:11:49 -07:00
Girish Ramakrishnan
040b9993c7 refactor code into getAppConfig 2016-06-13 15:07:15 -07:00
Girish Ramakrishnan
8f21126697 add a way to get the restore config (config.json) 2016-06-13 15:04:27 -07:00
Girish Ramakrishnan
716d29165c store altDomain in backupConfig 2016-06-13 13:14:04 -07:00
Girish Ramakrishnan
a2ec308155 pass the lastBackupId explicity as the backup to restore to 2016-06-13 10:13:54 -07:00
Girish Ramakrishnan
b82610ba00 pass data argument to restore 2016-06-13 10:08:58 -07:00
Johannes Zellner
ed4674cd14 Disable the trial popup 2016-06-13 16:51:28 +02:00
Johannes Zellner
4e9dc75a37 Replace DatabaseError with ClientsError where applicable 2016-06-13 14:43:56 +02:00
Johannes Zellner
f284b4cd83 Use clients.get() instead of clientdb.get() 2016-06-13 13:51:14 +02:00
Johannes Zellner
15cf83b37c Fixup the built-in client setup for tests 2016-06-13 13:48:43 +02:00
Johannes Zellner
0eff8911ee Do not use DatabaseError in routes clients.js 2016-06-13 13:29:39 +02:00
Girish Ramakrishnan
814a0ce3a6 wait for 10mins before sending out emails about app being down 2016-06-11 18:36:38 -07:00
Girish Ramakrishnan
b3e1c221b7 bump infra to force a reconfigure for existing pr cloudrons 2016-06-11 13:49:55 -07:00
Girish Ramakrishnan
dc31946e50 move webdav block outside location
when inside location, nginx is redirecting to 127.0.0.1 (no clue why)
2016-06-11 12:05:16 -07:00
Girish Ramakrishnan
36bbb98970 0.15.1 changes 2016-06-10 12:40:48 -07:00
Girish Ramakrishnan
ea4cea9733 add tag on blur 2016-06-10 12:35:57 -07:00
Girish Ramakrishnan
7c06937a57 Add @fqdn hint to email aliases 2016-06-10 12:04:40 -07:00
Girish Ramakrishnan
597704d3ed remove the plain input email aliases 2016-06-10 11:58:25 -07:00
Girish Ramakrishnan
63290b9936 Final fixes to taginput 2016-06-10 11:58:00 -07:00
Girish Ramakrishnan
324222b040 Fix template code style 2016-06-09 16:11:57 -07:00
Girish Ramakrishnan
f37b92da04 taginput: remove autocomplete. we don't use it 2016-06-09 10:34:42 -07:00
Girish Ramakrishnan
0de3b8fbdb taginput: add tag input control for email aliases 2016-06-09 10:34:29 -07:00
Johannes Zellner
f0cb3f94cb Fixup the token ui to allow removal of user added clients 2016-06-09 16:17:14 +02:00
Johannes Zellner
1508a5c6b9 Add tokendb.delByClientId() 2016-06-09 15:42:52 +02:00
Johannes Zellner
9b9db6acf1 Only the rest api shall not allow to remove those 2016-06-09 15:35:46 +02:00
Johannes Zellner
001bf94773 Remove unused require 2016-06-09 15:35:20 +02:00
Johannes Zellner
0160c12965 Allow to distinguish between built-in auth clients and external ones 2016-06-09 15:35:00 +02:00
Johannes Zellner
d08397336d Add test for addon auth clients deletion 2016-06-09 15:12:30 +02:00
Johannes Zellner
880754877d Prevent the rest api to delete addon auth clients 2016-06-09 14:44:38 +02:00
Johannes Zellner
984a191e4c Use the variable correctly 2016-06-09 14:24:53 +02:00
Girish Ramakrishnan
cdca43311b wording and other minor fixes 2016-06-08 17:06:27 -07:00
Girish Ramakrishnan
020b47841a 0.15.1 changes 2016-06-08 16:42:00 -07:00
Girish Ramakrishnan
3f602c8a04 verifyWithUsername and not as id 2016-06-08 15:54:22 -07:00
Girish Ramakrishnan
dea0c5642d bump mail container 2016-06-08 13:16:44 -07:00
Girish Ramakrishnan
3d2b75860b minor fixes to session ui 2016-06-08 13:04:22 -07:00
Girish Ramakrishnan
0da754a14b fix singular/plural 2016-06-08 12:52:19 -07:00
Girish Ramakrishnan
3d408c8c90 fix wording 2016-06-08 12:43:15 -07:00
Girish Ramakrishnan
40348a5132 Merge branch '0.15' 2016-06-08 11:56:24 -07:00
Girish Ramakrishnan
9a177d9e46 fix typo 2016-06-08 10:14:59 -07:00
Girish Ramakrishnan
6f1df9980d minor wording changes 2016-06-08 09:58:30 -07:00
Girish Ramakrishnan
0c9d331f47 more changes 2016-06-08 08:46:18 -07:00
Girish Ramakrishnan
f9db24e162 Fix autoupdate detection logic
We should be comparing existing manifest ports with new manifest ports.
The user chosen bindings itself doesn't matter.
2016-06-08 08:45:40 -07:00
Johannes Zellner
385bf3561b Remove unused function in platform.js 2016-06-08 15:06:03 +02:00
Johannes Zellner
4304f20fe0 Fix some log output 2016-06-08 15:05:43 +02:00
Johannes Zellner
509083265f fix setup_infra.sh to accomodate ifconfig differences 2016-06-08 15:01:28 +02:00
Johannes Zellner
1b9dbd06c8 Fix typo 2016-06-08 14:55:14 +02:00
Johannes Zellner
d6482414bb Fixup the clientdb tests 2016-06-08 14:49:54 +02:00
Johannes Zellner
194b9b35bd We now have 3 built-in api clients 2016-06-08 14:48:03 +02:00
Johannes Zellner
6b9acb4722 Preserve the built-in clients on db clean 2016-06-08 14:47:21 +02:00
Johannes Zellner
08c3cb9376 Insert the default auth clients for tests 2016-06-08 14:37:41 +02:00
Johannes Zellner
79631ba996 Provide a fallback for the redirect uri 2016-06-08 14:30:42 +02:00
Johannes Zellner
4776a005a5 Remove redundant client TYPE_*s 2016-06-08 14:09:06 +02:00
Johannes Zellner
e954df2120 Issue developer tokens with cid-cli 2016-06-08 13:39:31 +02:00
Johannes Zellner
526a62a20e Fix the async iterator 2016-06-08 13:35:32 +02:00
Johannes Zellner
e2432d002f Count activeTokens correctly 2016-06-08 13:31:17 +02:00
Johannes Zellner
6e4d6d1099 Do not show application token listing if there is nothing to show 2016-06-08 13:03:59 +02:00
Johannes Zellner
fc2d1d61d7 Also logout the webadmin session 2016-06-08 13:03:21 +02:00
Johannes Zellner
4c4ae08b44 Allow users to see issued tokens and revoke them all in one 2016-06-08 12:56:48 +02:00
Johannes Zellner
401c0e1b44 Special handling for better ui for sdk tokens 2016-06-08 11:55:40 +02:00
Johannes Zellner
e431bd6040 Fix typo 2016-06-08 11:36:01 +02:00
Johannes Zellner
a69cd204d6 Handle sdk and cli clients just like the webadmin 2016-06-08 11:33:08 +02:00
Johannes Zellner
3c3de6205e Add test case for blocking cid-webadmin deletion 2016-06-08 11:27:10 +02:00
Johannes Zellner
16444f775d Prevent deletion of the built-in clients 2016-06-08 11:24:02 +02:00
Johannes Zellner
2676658b5d Add auth client cid-sdk and cid-cli 2016-06-08 11:20:06 +02:00
Girish Ramakrishnan
fbb8a842c1 do not require password 2016-06-08 00:07:41 -07:00
Girish Ramakrishnan
62b586e8dd fix require path 2016-06-07 20:57:39 -07:00
Girish Ramakrishnan
313d98ef70 add a route to check for updates quickly 2016-06-07 20:24:41 -07:00
Girish Ramakrishnan
06448f146d fix typo 2016-06-07 20:09:53 -07:00
Girish Ramakrishnan
064d950f87 add new tests for field validation 2016-06-07 16:00:02 -07:00
Girish Ramakrishnan
3236ce9cd6 check if both are null 2016-06-07 15:36:45 -07:00
Johannes Zellner
f74b22645f Token view is now admin only 2016-06-07 22:56:27 +02:00
Johannes Zellner
3540f2c197 Move token management to separate view for admins only 2016-06-07 22:54:53 +02:00
Girish Ramakrishnan
3231fe7874 use user.get which already set admin flag 2016-06-07 10:03:08 -07:00
Girish Ramakrishnan
dc8fd2eab3 do not use userdb directly 2016-06-07 10:01:14 -07:00
Girish Ramakrishnan
3ae388602c fix wording 2016-06-07 09:27:42 -07:00
Johannes Zellner
733187f3c4 Also show redirectURI for developers to use 2016-06-07 16:21:33 +02:00
Johannes Zellner
02d2a7058e Remove whitespace in scope input 2016-06-07 16:15:53 +02:00
Johannes Zellner
25003bcf40 Add placeholder text in scope input 2016-06-07 16:12:56 +02:00
Johannes Zellner
234caa60eb Add form validation for scopes 2016-06-07 16:10:33 +02:00
Johannes Zellner
a0227b6043 Remove now unused localhost test client
We can now simply use the regular APIs to do local development against a Cloudron
2016-06-07 16:03:50 +02:00
Johannes Zellner
46ac6c4918 Offset the footer in the console views 2016-06-07 15:58:53 +02:00
Johannes Zellner
4afde79297 Fix error message 2016-06-07 15:56:22 +02:00
Johannes Zellner
17d48f3fce Specify the expiration on the client. Currently this is 100 years.
I am not sure if this is the best approach, or if we should introduce a magic value instead.
2016-06-07 15:54:32 +02:00
Johannes Zellner
facdabcc8d Mention the token expiration in the ui 2016-06-07 15:54:09 +02:00
Johannes Zellner
691803f10b Allow optional expiresAt to be set on token creation 2016-06-07 15:47:13 +02:00
Johannes Zellner
8144c6d086 Patch up the token delete button with the route 2016-06-07 15:38:42 +02:00
Johannes Zellner
290ab6cc7d Fix typo 2016-06-07 15:38:30 +02:00
Johannes Zellner
8e5af17e5d Add route to delete a single token 2016-06-07 15:34:27 +02:00
Johannes Zellner
d9d94faf75 Refresh the client on token actions 2016-06-07 15:15:56 +02:00
Johannes Zellner
0201ab19e4 Pass in the client object 2016-06-07 14:47:56 +02:00
Johannes Zellner
721fe74f3c The route creates the subobject 2016-06-07 14:47:47 +02:00
Johannes Zellner
96eeb247a1 Add rest api to create a new token for a client 2016-06-07 14:29:37 +02:00
Johannes Zellner
6261231593 add ui for token generation 2016-06-07 14:19:20 +02:00
Johannes Zellner
d62d2b17fe Select tokens and secrets with single click 2016-06-07 14:10:20 +02:00
Johannes Zellner
89cef4f050 Show tokens for admins 2016-06-07 14:07:41 +02:00
Johannes Zellner
8602e033c5 Only show active clients for non-admins 2016-06-07 13:46:45 +02:00
Johannes Zellner
3598d89b12 ?all is gone in clients route 2016-06-07 13:17:02 +02:00
Johannes Zellner
ffd552583c Patch up the client remove ui 2016-06-07 13:12:53 +02:00
Johannes Zellner
9eabc9d266 Fixup the wording 2016-06-07 12:50:52 +02:00
Johannes Zellner
edf8cd736e Add modal for client removal 2016-06-07 12:48:12 +02:00
Johannes Zellner
c5ebe2c2bf Fixup the panel padding 2016-06-07 12:48:00 +02:00
Johannes Zellner
5d0ccc0dd7 Show correct token count 2016-06-07 12:37:18 +02:00
Johannes Zellner
4147455654 Fetch tokens for each client separately 2016-06-07 12:37:04 +02:00
Johannes Zellner
f3436a99a2 Fixup the client list details 2016-06-07 12:28:33 +02:00
Johannes Zellner
70d569e2e8 List all oauth clients in webadmin 2016-06-07 12:26:14 +02:00
Johannes Zellner
684625fbaf Remove unused clients.getAllWithDetailsByUserId() 2016-06-07 12:25:48 +02:00
Johannes Zellner
c8b9ae542c Simply return oauth clients instead of join with tokendb 2016-06-07 12:15:25 +02:00
Johannes Zellner
af29c1ba86 Handle external api client requests separately 2016-06-07 12:00:18 +02:00
Johannes Zellner
207e81345f Log event for external login 2016-06-07 11:59:54 +02:00
Johannes Zellner
d880731351 Support ?all query param for oauth clients get route 2016-06-07 11:18:30 +02:00
Johannes Zellner
e603cfe96e Add clients.getAllWithDetails() 2016-06-07 11:17:29 +02:00
Johannes Zellner
5b93a2870f Add clientdb.getAllWithTokenCount() 2016-06-07 11:17:14 +02:00
Johannes Zellner
1214300800 Consistent code styling 2016-06-07 11:08:35 +02:00
Johannes Zellner
8159334cbf Avoid more inlining 2016-06-07 10:55:36 +02:00
Johannes Zellner
78135c807a Be consistent in client.js add -> create 2016-06-07 10:49:11 +02:00
Johannes Zellner
bfa33e4d8e Fixup a linter issue 2016-06-07 10:48:36 +02:00
Johannes Zellner
8b23174769 add client.addOAuthClient() 2016-06-07 10:45:50 +02:00
Johannes Zellner
a078c94b97 Ensure autofocus is handled 2016-06-07 10:43:33 +02:00
Johannes Zellner
c86392cd60 Add modal dialog to create API clients 2016-06-07 10:42:54 +02:00
Johannes Zellner
f0e9256d46 Avoid style inlining 2016-06-07 10:10:58 +02:00
Girish Ramakrishnan
0cd4e4f03a update now takes appStoreId 2016-06-04 20:51:17 -07:00
Girish Ramakrishnan
1766da9174 update code path now takes appStoreId 2016-06-04 20:05:29 -07:00
Girish Ramakrishnan
dbdcf1ec27 pass data object to update 2016-06-04 19:12:36 -07:00
Girish Ramakrishnan
c916ea2589 fix style 2016-06-04 18:56:53 -07:00
Girish Ramakrishnan
5540b5f545 remove unused require 2016-06-04 18:55:31 -07:00
Girish Ramakrishnan
1e38190e68 setting falsy values for cert/key removes it 2016-06-04 18:30:05 -07:00
Girish Ramakrishnan
8f3553090f make args optional in configure 2016-06-04 18:07:06 -07:00
Girish Ramakrishnan
cc0f5a1f03 fix configure arg insanity 2016-06-04 16:32:27 -07:00
Girish Ramakrishnan
a1c531d2a8 better type checking in configure and make accessRestriction optional 2016-06-04 16:27:50 -07:00
Girish Ramakrishnan
57cb3b04d7 generate 2048-bit keys 2016-06-04 15:59:53 -07:00
Girish Ramakrishnan
a49cf98a8d do not allow appId to be set
this is some legacy code
2016-06-04 13:40:43 -07:00
Girish Ramakrishnan
da6cab8dd6 we return 400 now 2016-06-04 13:32:41 -07:00
Girish Ramakrishnan
3b7cfdd7db better type checking 2016-06-04 13:31:18 -07:00
Girish Ramakrishnan
f9251c8b37 sending manifest is now redundant 2016-06-04 13:23:13 -07:00
Girish Ramakrishnan
4068ff5f21 add TODO note to validate accessRestriction 2016-06-04 13:20:10 -07:00
Girish Ramakrishnan
ee073c91a3 return BAD_FIELD if app was not found 2016-06-04 13:15:38 -07:00
Girish Ramakrishnan
9e8742ca87 download manifest from appstore when appStoreId is provided 2016-06-04 01:07:43 -07:00
Girish Ramakrishnan
7f99fe2399 appStoreId has empty string default 2016-06-03 23:58:09 -07:00
Girish Ramakrishnan
bfe8df35df toLowerCase in one place 2016-06-03 23:54:46 -07:00
Girish Ramakrishnan
e2848d3e08 fix apps.install insane arg list 2016-06-03 23:35:55 -07:00
Girish Ramakrishnan
bc823b4a75 make checkManifestConstraints return AppsError 2016-06-03 22:19:09 -07:00
Girish Ramakrishnan
c24f780722 make validateAccessRestriction and validateMemory return AppsError 2016-06-03 22:16:55 -07:00
Girish Ramakrishnan
0d51ec9920 make validatePortBindings return AppsError 2016-06-03 22:15:02 -07:00
Girish Ramakrishnan
e07e544029 make validateHostname return AppsError 2016-06-03 22:14:08 -07:00
Girish Ramakrishnan
5aff55c5ca typo when stashing altDomain 2016-06-03 19:50:01 -07:00
Girish Ramakrishnan
5ebc29746d fix failing tests 2016-06-03 19:14:16 -07:00
Girish Ramakrishnan
8fc44e6bc9 remove redundant checks 2016-06-03 19:08:47 -07:00
Girish Ramakrishnan
44f4872134 remove dead comments 2016-06-03 17:55:05 -07:00
Girish Ramakrishnan
49dd584a41 return expiresAt as ISO-string for API consistency 2016-06-03 10:11:09 -07:00
Girish Ramakrishnan
6d8f1f90d4 add note to change expires to TIMESTAMP type 2016-06-03 10:07:30 -07:00
Girish Ramakrishnan
c1ded66c1a make download_url a post route 2016-06-03 09:23:15 -07:00
Johannes Zellner
4df49a82e5 Some clientdb.TYPE_ oversight in clients.js 2016-06-03 15:28:04 +02:00
Johannes Zellner
92e6ee9539 The clientSecret is now only ever created in the clients.js 2016-06-03 15:11:08 +02:00
Johannes Zellner
3ad2a2a5ca Fixup the unit tests 2016-06-03 15:07:44 +02:00
Johannes Zellner
226537de04 Move client TYPE_* to clients.js 2016-06-03 15:05:00 +02:00
Johannes Zellner
41b324eb2d Remove clientdb usage in addons.js 2016-06-03 14:56:45 +02:00
Johannes Zellner
1360729e97 Don't use clientdb directly from auth.js and apptask.js 2016-06-03 14:52:59 +02:00
Johannes Zellner
725e1debcc Provide getByAppIdAndType() by clients.js 2016-06-03 14:47:06 +02:00
Johannes Zellner
201efa70b7 use clients instead of clientdb in oauth2.js 2016-06-03 14:38:58 +02:00
Johannes Zellner
c52d0369fa Provide better feedback on invalid scopes 2016-06-03 13:53:33 +02:00
Johannes Zellner
b4dfad3aa3 Fixup the unit tests after removing PREFIX_USER 2016-06-03 13:09:26 +02:00
Johannes Zellner
7667cdc66d PREFIX_USER finally gone 2016-06-03 13:01:23 +02:00
Johannes Zellner
3a9a667890 Make all token grants without PREFIX_USER 2016-06-03 13:01:05 +02:00
Johannes Zellner
304cfed5a9 Result of password setting is now a plain token identifier 2016-06-03 13:00:07 +02:00
Johannes Zellner
778c583a52 Activation hands out a token without PREFIX_USER now 2016-06-03 12:59:13 +02:00
Johannes Zellner
f988bb4d14 Do not use PREFIX_USER for token managment 2016-06-03 12:58:39 +02:00
Johannes Zellner
7057f1aaa2 All token identifiers are now plain user ids 2016-06-03 12:54:59 +02:00
Johannes Zellner
e06f5f88b8 Remove the token types 2016-06-03 12:54:34 +02:00
Johannes Zellner
03cd3f0b6f Remove attached tokenType on req.user 2016-06-03 12:53:11 +02:00
Johannes Zellner
615f875169 Remove PREFIX_DEV for developer tokens 2016-06-03 12:52:10 +02:00
Johannes Zellner
f27ba04a00 Add test case for developer tokens 2016-06-03 11:11:11 +02:00
Johannes Zellner
3e0006a327 Allow tokens with SCOPE_ROLE_SDK through without a password 2016-06-03 11:10:59 +02:00
Johannes Zellner
558ca42ae8 Issue developer tokens with SCOPE_ROLE_SDK 2016-06-03 11:10:22 +02:00
Johannes Zellner
9d8a803185 Handle scope roles in scope checks 2016-06-03 11:09:48 +02:00
Johannes Zellner
105047b0c4 Add SCOPE_ROLE_SDK 2016-06-03 11:08:35 +02:00
Johannes Zellner
e335aa5dee Check for sdk token instead of token type DEV 2016-06-03 10:17:52 +02:00
Johannes Zellner
10163733db Separate the scope checking 2016-06-03 10:10:58 +02:00
Girish Ramakrishnan
251fad8514 add test for groupIds in listing api 2016-06-03 00:14:52 -07:00
Girish Ramakrishnan
036740f97b filter out correct fields in the route code 2016-06-03 00:04:17 -07:00
Girish Ramakrishnan
f4958d936c return groupIds in get user route 2016-06-03 00:00:11 -07:00
Girish Ramakrishnan
80ca69a128 user.update does not need the user object 2016-06-02 23:53:06 -07:00
Girish Ramakrishnan
097d23c412 move logic to model code 2016-06-02 23:29:43 -07:00
Girish Ramakrishnan
13a1213b0d make group listing API return member userIds 2016-06-02 21:07:33 -07:00
Girish Ramakrishnan
76fe2bf531 add note to fix precision at some point 2016-06-02 19:43:23 -07:00
Girish Ramakrishnan
50c4e4c91e log event only after lock is acquired 2016-06-02 19:26:58 -07:00
Girish Ramakrishnan
46441d1814 cloudron.update is not exposed 2016-06-02 19:23:21 -07:00
Girish Ramakrishnan
a4e73be834 pass auditSource for certificate renewal 2016-06-02 18:54:45 -07:00
Girish Ramakrishnan
6be0d0814d pass auditSource from cron.js 2016-06-02 18:51:50 -07:00
Girish Ramakrishnan
e30d71921e pass auditSource for app autoupdater 2016-06-02 18:49:56 -07:00
Girish Ramakrishnan
a49c78f32c make box autoupdate generate eventlog 2016-06-02 18:47:09 -07:00
Girish Ramakrishnan
b077223e58 fix scope name 2016-06-02 17:49:54 -07:00
Girish Ramakrishnan
d2864dfe56 rename root scope to cloudron scope (for lack of better scope name) 2016-06-02 16:51:14 -07:00
Girish Ramakrishnan
6d08af35a8 give developer token root scope 2016-06-02 15:58:40 -07:00
Girish Ramakrishnan
54f9d653f7 fix error messages 2016-06-02 14:41:21 -07:00
Girish Ramakrishnan
8d65f93fa4 return error.message 2016-06-02 14:40:29 -07:00
Girish Ramakrishnan
462440bb30 do not check for password in profile route
This is already checked by the verifyPassword middleware based on
the token type.

When using dev tokens, this check barfs for lack of password field
even when none is required.
2016-06-02 14:26:01 -07:00
Girish Ramakrishnan
65261dc4d5 add time_zone setter route 2016-06-02 13:54:07 -07:00
Girish Ramakrishnan
54ead09aac make the name API work
currently this only works for the main webadmin (and not for
nakeddomain, error etc) but that's fine.
2016-06-02 13:25:02 -07:00
Girish Ramakrishnan
28b3550214 use error.message 2016-06-02 13:00:23 -07:00
Girish Ramakrishnan
e2e70da4c5 restrict length to 32 2016-06-02 12:51:49 -07:00
Johannes Zellner
7326ea27ca Only set username and displayName after successful update 2016-06-02 21:12:02 +02:00
Girish Ramakrishnan
1fe00f7f80 do not use verbs in resource url 2016-06-02 12:01:48 -07:00
Girish Ramakrishnan
e9e9d6000d remove token check for user.update to work with dev tokens 2016-06-02 11:29:59 -07:00
Girish Ramakrishnan
6dccb3655f add no groups available message in edit user dialog 2016-06-02 10:55:34 -07:00
Girish Ramakrishnan
c3113bd74d go back to step2 if activation fails 2016-06-02 10:40:06 -07:00
Girish Ramakrishnan
e79119b72a 0.15.0 changes 2016-06-02 10:32:10 -07:00
Johannes Zellner
086cfdc1e6 Disabled form fields are not POSTed
I did not know about that fact, one has to use readonly
2016-06-02 16:12:32 +02:00
Johannes Zellner
1f091d3b4b We have to let angular know 2016-06-02 16:06:15 +02:00
Johannes Zellner
892fa4b2ec We still require the username to be sent always 2016-06-02 16:01:25 +02:00
Johannes Zellner
a87b4b207c Adhere to already set username in user setup view 2016-06-02 15:47:58 +02:00
Johannes Zellner
bdd14022d6 Report user conflict message all the way through the rest routes 2016-06-02 15:41:07 +02:00
Johannes Zellner
3d40cf03b1 Pass down the reason why the user conflicts 2016-06-02 15:39:21 +02:00
Johannes Zellner
594be7dbbd Allow the userdb code to distinguish between username or email duplicates 2016-06-02 15:34:27 +02:00
Johannes Zellner
a52e2ffc23 Distinguish between username and email conflict 2016-06-02 15:19:35 +02:00
Johannes Zellner
8eeee712aa Remove unused require 2016-06-02 14:14:16 +02:00
Johannes Zellner
0f62faa198 All our tokens are now representing an user with a profile 2016-06-02 14:13:57 +02:00
Johannes Zellner
bfd66cf309 Remove unused token PREFIX_APP 2016-06-02 14:07:41 +02:00
Johannes Zellner
c2f7d61e34 Remove unused token TYPE_APP 2016-06-02 14:07:19 +02:00
Johannes Zellner
d5d5e356ae Add error handling for invalid usernames 2016-06-02 13:52:44 +02:00
Johannes Zellner
531752cd43 Use placeholder for description in username and displayName fields 2016-06-02 13:52:33 +02:00
Johannes Zellner
9eac56578c Allow admins to set the username and displayName optionally on user creation 2016-06-02 13:32:45 +02:00
Johannes Zellner
d06398dbfd Move webdav nginx fixes into app endpoint
Not sure if this will now still work with oauth proxy though.
2016-06-02 09:49:01 +02:00
Girish Ramakrishnan
60ce6b69ee profile updates must be POST 2016-06-02 00:31:41 -07:00
Girish Ramakrishnan
4fcc7fe99f updateUser is POST 2016-06-02 00:27:06 -07:00
Girish Ramakrishnan
82cd215ffa merge bad fields and pass error.message correctly in REST responses 2016-06-02 00:12:21 -07:00
Girish Ramakrishnan
1dcea84068 fix typo 2016-06-01 23:43:21 -07:00
Girish Ramakrishnan
4107252bfe developer mode is true by default 2016-06-01 23:13:12 -07:00
Girish Ramakrishnan
9cc6cb56f7 fix error message 2016-06-01 19:38:42 -07:00
Girish Ramakrishnan
48b99a4203 enable developer mode by default
also emphasize on the api aspect
2016-06-01 18:57:15 -07:00
Girish Ramakrishnan
824767adbb enabled -> isEnabled 2016-06-01 18:21:02 -07:00
Girish Ramakrishnan
3d84880d92 keep limits in sync with the nginx config 2016-06-01 18:09:23 -07:00
Girish Ramakrishnan
dfa08469d6 set timeouts explicitly 2016-06-01 17:33:28 -07:00
Girish Ramakrishnan
d798073d95 fix comment of default_server 2016-06-01 17:28:15 -07:00
Girish Ramakrishnan
41632b8c11 fix favicon of naked domain 2016-06-01 17:27:39 -07:00
Girish Ramakrishnan
6ccc46717e remove unused middleware 2016-06-01 16:59:51 -07:00
Girish Ramakrishnan
2495caf2eb remove unused redis module 2016-06-01 16:55:58 -07:00
Girish Ramakrishnan
ae9c104a8b remove unused bytes module 2016-06-01 16:54:25 -07:00
Girish Ramakrishnan
683f371778 remove serve-favicon
favicon is served up as /api/v1/cloudron/avatar
2016-06-01 16:52:28 -07:00
Girish Ramakrishnan
eb29bdd575 document keepalive_timeout 2016-06-01 16:51:52 -07:00
Girish Ramakrishnan
b13de298bf Add some REST api tests 2016-06-01 16:33:18 -07:00
Johannes Zellner
47978436c2 Set Destination header for webdav in nginx proxy 2016-06-01 18:49:50 +02:00
Johannes Zellner
71b5cc4702 Fix invite mail wording 2016-06-01 18:45:31 +02:00
Girish Ramakrishnan
5a9e32d41a hide aliases field if no username is set 2016-06-01 06:33:29 -07:00
Girish Ramakrishnan
b03e4db8d5 check for null username 2016-05-31 21:38:51 -07:00
Girish Ramakrishnan
663ff2410a user.update must become a post route 2016-05-31 11:51:56 -07:00
Girish Ramakrishnan
f763759008 return empty groupIds 2016-05-31 11:49:59 -07:00
Girish Ramakrishnan
69aa11d6c6 send pretty json 2016-05-31 11:14:59 -07:00
Girish Ramakrishnan
65041743c5 update to connect-lastmile@0.1.0 2016-05-31 10:48:37 -07:00
Girish Ramakrishnan
76214d3d7a no variable named infra_version 2016-05-30 19:45:59 -07:00
Girish Ramakrishnan
be83a967fc node require will not work without json extension 2016-05-30 17:03:56 -07:00
Girish Ramakrishnan
119e095710 actually change ownership 2016-05-30 15:51:52 -07:00
Girish Ramakrishnan
5df3a41988 INFRA_VERSION may not exist 2016-05-30 14:48:41 -07:00
Girish Ramakrishnan
a34b611e20 make INFRA_VERSION writable by yellowtent user 2016-05-30 12:52:39 -07:00
Girish Ramakrishnan
75c1731443 do not add app mailboxes to database
a) we don't allow .app pattern in database for aliases and mailboxes
b) the addons already know about app names separately
2016-05-30 01:38:43 -07:00
Girish Ramakrishnan
9e36b7abf4 load addon vars for existing infra case 2016-05-30 01:06:41 -07:00
Girish Ramakrishnan
b37226d4d1 fix ui issues 2016-05-30 00:07:58 -07:00
Girish Ramakrishnan
311efe5d10 test: add test for getting aliases 2016-05-29 23:23:03 -07:00
Girish Ramakrishnan
ebdd6d8a31 add missing require 2016-05-29 23:15:55 -07:00
Girish Ramakrishnan
3ee9f70113 return alias info in mailbox response 2016-05-29 23:14:24 -07:00
Girish Ramakrishnan
adfc069e16 allow user alias to be set for custom domains 2016-05-29 23:02:01 -07:00
Girish Ramakrishnan
31fd0d711a add setAliases 2016-05-29 22:45:48 -07:00
Girish Ramakrishnan
a6a852cfae remove bogus debug 2016-05-29 22:01:21 -07:00
Girish Ramakrishnan
e9b3e22e86 check version of existing infra 2016-05-29 21:58:04 -07:00
Girish Ramakrishnan
564d61bcf5 fix typo 2016-05-29 21:31:49 -07:00
Girish Ramakrishnan
5582ac7402 add some debugs 2016-05-29 21:28:55 -07:00
Girish Ramakrishnan
a05b6ad78d delete mailbox on user delete 2016-05-29 21:02:51 -07:00
Girish Ramakrishnan
ec71390d0b autocreate mailbox when username is available 2016-05-29 19:14:01 -07:00
Girish Ramakrishnan
68a3862ee5 add create and remove mailbox 2016-05-29 18:56:40 -07:00
Girish Ramakrishnan
a9f70d8363 add mailbox search endpoint 2016-05-29 18:24:54 -07:00
Girish Ramakrishnan
e91539d79a add a todo 2016-05-29 18:08:16 -07:00
Girish Ramakrishnan
5546bfbf0e add mailbox ldap auth point 2016-05-29 17:25:23 -07:00
Girish Ramakrishnan
803d47b426 refactor authenticate path into a middleware 2016-05-29 17:16:52 -07:00
Girish Ramakrishnan
e4c0192243 rename to appUserBind since it is tailored for apps 2016-05-29 17:07:48 -07:00
Girish Ramakrishnan
d5b5289e0c Add mailbox importer for existing users and apps
this should prevent conflicts of mailboxes from the get-go.
2016-05-28 02:07:43 -07:00
Girish Ramakrishnan
2909aad72a use async.series 2016-05-28 01:59:48 -07:00
Girish Ramakrishnan
cafbb31e78 push aliases to mail container on startup 2016-05-28 01:53:25 -07:00
Girish Ramakrishnan
080128539c set/unset aliases on the mail container 2016-05-28 01:33:20 -07:00
Girish Ramakrishnan
cf93a99a4e add a note about mailboxes 2016-05-27 22:28:56 -07:00
Girish Ramakrishnan
ce927bfa22 alias
also remove id since it's not useful for mailbox case (not like
mailbox can be renamed and we need a fixed it)
2016-05-27 22:20:08 -07:00
Girish Ramakrishnan
6993a9c7e7 add more mailbox route test 2016-05-27 18:23:14 -07:00
Girish Ramakrishnan
84d04cce16 initial mailboxes route 2016-05-27 18:17:57 -07:00
Girish Ramakrishnan
f735fd8172 add mailboxes tests 2016-05-27 17:43:25 -07:00
Girish Ramakrishnan
53e28db1d6 add note on accessRestriction 2016-05-27 11:10:36 -07:00
Girish Ramakrishnan
77457d1ea9 initial mailbox db and model code 2016-05-27 10:36:47 -07:00
Girish Ramakrishnan
161b7cf76b add mailboxes table 2016-05-26 21:08:20 -07:00
Girish Ramakrishnan
01b6defd24 mount mail container /run into data 2016-05-26 15:18:10 -07:00
Girish Ramakrishnan
badc524ff2 '-' has special meaning haraka
so do '.app' instead
2016-05-26 10:58:30 -07:00
Girish Ramakrishnan
b3f53099f0 allow only alpha numerals in username 2016-05-25 21:36:20 -07:00
Girish Ramakrishnan
a28560cdc0 0.14.2 changes 2016-05-24 20:17:20 -07:00
Girish Ramakrishnan
4afdf50736 finally finally finally the tests are working 2016-05-24 20:04:37 -07:00
Girish Ramakrishnan
078e36f07f turns out we cannot use sudo since it asks for password 2016-05-24 19:59:47 -07:00
Girish Ramakrishnan
3b8e15a61c check for node in path 2016-05-24 18:32:15 -07:00
Girish Ramakrishnan
67682c5d27 check for test image 2016-05-24 17:44:19 -07:00
Girish Ramakrishnan
48e3b8ebf9 provide a dummy config for tests 2016-05-24 17:36:54 -07:00
Girish Ramakrishnan
2072dedf66 bump mail addon version 2016-05-24 16:46:54 -07:00
Girish Ramakrishnan
51f43ecc27 use infra_version.js in checkInstall 2016-05-24 16:46:27 -07:00
Girish Ramakrishnan
2347a7ced2 admin email is a platform property 2016-05-24 16:36:56 -07:00
Girish Ramakrishnan
b2cadaf95c load vars files after the platform is created 2016-05-24 16:28:59 -07:00
Girish Ramakrishnan
957f787701 setup mail addon root credentials 2016-05-24 16:18:21 -07:00
Girish Ramakrishnan
ad48067bb2 setup_infra now uses infra_version.js
INFRA_VERSION is now removed. Note that DATA_DIR/INFRA_VERSION
still exists.
2016-05-24 16:16:03 -07:00
Girish Ramakrishnan
12b6c46558 use infra_version.js in splashpage 2016-05-24 13:31:45 -07:00
Girish Ramakrishnan
b4ba17c599 use the constant 2016-05-24 13:23:41 -07:00
Girish Ramakrishnan
7fb28662c1 remove old images in platform.js 2016-05-24 13:23:41 -07:00
Girish Ramakrishnan
4845db538a use infra_version.js in platform.js 2016-05-24 13:23:41 -07:00
Girish Ramakrishnan
8429985253 use infra_version.js in addons.js 2016-05-24 13:23:41 -07:00
Girish Ramakrishnan
aff9ff47bc use infra_version.js in baseimage script 2016-05-24 13:23:38 -07:00
Girish Ramakrishnan
9b3077eca3 add infra_version.js 2016-05-24 13:02:38 -07:00
Girish Ramakrishnan
39396cb3ab call callback if provided 2016-05-24 11:39:05 -07:00
Girish Ramakrishnan
364f0ead51 debug out the cmd 2016-05-24 11:26:11 -07:00
Girish Ramakrishnan
5ac1d5575c platform.js: minor refactor 2016-05-24 10:58:18 -07:00
Girish Ramakrishnan
e5a030baff move platform cleanup bits to javascript 2016-05-24 10:52:55 -07:00
Girish Ramakrishnan
a100837e69 Add helpers to restore/reconfigure all apps 2016-05-24 10:44:45 -07:00
Girish Ramakrishnan
ffacf17a42 add paths.INFRA_VERSION_FILE 2016-05-24 10:26:08 -07:00
Girish Ramakrishnan
f5d37b6443 add ini module 2016-05-24 10:25:33 -07:00
Girish Ramakrishnan
d71d09c1ba Add shell.execSync 2016-05-24 10:22:39 -07:00
Girish Ramakrishnan
c1a2444dfa move container creation to platform.js 2016-05-24 09:40:26 -07:00
Girish Ramakrishnan
ef40aae3ba set adminEmail to no-reply@localhost for tests 2016-05-24 00:54:38 -07:00
Girish Ramakrishnan
9570086c87 add config.smtpPort 2016-05-24 00:53:42 -07:00
Girish Ramakrishnan
57a823a698 make tests work 2016-05-24 00:44:01 -07:00
Girish Ramakrishnan
ec0ee07b17 test: email works now 2016-05-23 23:17:38 -07:00
Girish Ramakrishnan
3d7545133e wait 30 secs in the beginning 2016-05-23 23:16:02 -07:00
Girish Ramakrishnan
bcc752469a remove containers after the test 2016-05-23 22:47:40 -07:00
Girish Ramakrishnan
da85f4c096 stop ldap server in test 2016-05-23 21:59:06 -07:00
Girish Ramakrishnan
3b740a5651 make tests work 2016-05-23 20:41:00 -07:00
Girish Ramakrishnan
7eb202f19a test: use the test-app instead of duplicating the checks in the tests 2016-05-23 20:17:11 -07:00
Girish Ramakrishnan
8dbd4c8527 use 1024 bit keys
Stacked error: error:04075070:rsa routines:RSA_sign:digest too big for rsa key
2016-05-23 19:31:57 -07:00
Girish Ramakrishnan
88f2ce554d bump to mail 0.13.1 with the auth fix 2016-05-23 18:57:59 -07:00
Girish Ramakrishnan
57888659a6 Update test app version 2016-05-23 18:31:12 -07:00
Girish Ramakrishnan
ebdefa7f18 test: oauth addon 2016-05-23 17:34:25 -07:00
Girish Ramakrishnan
569150f602 tests: create a self-signed cert 2016-05-23 16:40:18 -07:00
Girish Ramakrishnan
6ccb806628 make apps-test pass 2016-05-23 16:31:02 -07:00
Girish Ramakrishnan
ae807b28b6 test: let server start the infra
otherwise, deps like dkim keys need to be setup in tests as well
2016-05-23 15:53:51 -07:00
Girish Ramakrishnan
00726b01e2 pass -no-run-if-empty instead 2016-05-23 15:50:36 -07:00
Girish Ramakrishnan
f5b777ab33 add route tests for username 2016-05-23 15:00:21 -07:00
Girish Ramakrishnan
d84e584222 add some username tests 2016-05-23 14:56:09 -07:00
Girish Ramakrishnan
31e452e1cc test: mixed case reserved name 2016-05-23 14:52:29 -07:00
Girish Ramakrishnan
e015b9bd7a 0.14.1 changes 2016-05-23 12:29:49 -07:00
Girish Ramakrishnan
10e0cbcebc do not set allowHalfOpen (otherwise we have to end socket ourself) 2016-05-23 10:50:04 -07:00
Girish Ramakrishnan
2768c3a336 acme: configure prod based on caas or acme 2016-05-23 09:48:17 -07:00
Girish Ramakrishnan
37512c4cac Wrap the stdin stream to indicate EOF
The docker exec protocol supports half-closing to signal that the stdin
is finished. The CLI tool tried to do this by closing it's half of the
socket when the stdin finished. Unfortunately, this does not work because
nginx immediately terminates a half-close :/ Node itself has no problem.

http://mailman.nginx.org/pipermail/nginx/2008-September/007388.html
seems to support the hypothesis. Basically, for HTTP and websockets
there is no notion of half-close.

Websocket protocol itself has no half-close as well:
http://www.lenholgate.com/blog/2011/07/websockets---i-miss-the-tcp-half-close.html
http://doc.akka.io/docs/akka/2.4.5/scala/http/client-side/websocket-support.html

The fix is to implement our own protocol that wrap stdin. We put a length
header for every payload. When we hit EOF, the length is set to 0. The server
sees this 0 length header and closes the exec container socket.
2016-05-22 22:27:49 -07:00
Girish Ramakrishnan
0aaaa866e4 Add a whole bunch of magic for docker.exec to work 2016-05-22 00:27:32 -07:00
Girish Ramakrishnan
53cb7fe687 debug out cmd 2016-05-19 15:54:35 -07:00
Girish Ramakrishnan
da42f2f00c fix boolean logic 2016-05-19 15:54:35 -07:00
Girish Ramakrishnan
27d2daae93 leave a note in nginx config 2016-05-19 12:27:54 -07:00
Girish Ramakrishnan
42cc8249f8 reserve usernames with -app in them 2016-05-18 21:45:02 -07:00
Girish Ramakrishnan
de055492ef set username restriction to 2 chars 2016-05-18 11:05:45 -07:00
Girish Ramakrishnan
efa3ccaffe fix crash because of missing error handling 2016-05-18 10:00:32 -07:00
Girish Ramakrishnan
32e238818a make script more robust 2016-05-17 18:16:18 -07:00
Girish Ramakrishnan
2c1083d58b log error if the curl parsing fails 2016-05-17 17:15:10 -07:00
Girish Ramakrishnan
517b967fe9 enable sieve 2016-05-17 14:04:57 -07:00
Girish Ramakrishnan
3c4ca8e9c8 reserve more usernames 2016-05-17 12:47:10 -07:00
Girish Ramakrishnan
4ec043836b 0.14.0 changes 2016-05-17 09:36:28 -07:00
Girish Ramakrishnan
7ec93b733b setup restore paths for recvmail and email addon 2016-05-17 09:27:59 -07:00
Girish Ramakrishnan
a81262afb5 remove unused var 2016-05-17 08:47:57 -07:00
Girish Ramakrishnan
266603bb19 Do not barf on rmi 2016-05-16 16:56:17 -07:00
Girish Ramakrishnan
6dcecaaf55 log the ldap source 2016-05-16 14:31:57 -07:00
Girish Ramakrishnan
099eb2bca4 use port 2525 2016-05-16 12:52:36 -07:00
Girish Ramakrishnan
b92ed8d079 cn can also be the cloudron email
cn can be:
1. username
2. username@fqdn
3. user@personalemail.com
2016-05-16 12:21:15 -07:00
Girish Ramakrishnan
0838ce4ef8 fix casing 2016-05-16 12:13:23 -07:00
Girish Ramakrishnan
cc8767274a Bind 587 with 2525 as well
MSA (587) and MTA (25) are the same protocol. Only difference is
that 587 does relaying with AUTH. Haraka has been configured to
make this distinction.
2016-05-16 08:32:30 -07:00
Girish Ramakrishnan
dfaed79e31 fix mail container name 2016-05-16 08:18:33 -07:00
Girish Ramakrishnan
9dc1a95992 SENDIMAGE -> IMAGE 2016-05-15 21:51:04 -07:00
Girish Ramakrishnan
5be05529c2 remove unused ldap ou 2016-05-15 21:25:56 -07:00
Girish Ramakrishnan
6ff7786f04 use the send addon service api 2016-05-15 21:23:44 -07:00
Girish Ramakrishnan
a833b65ef3 add mail container link once 2016-05-15 21:18:43 -07:00
Girish Ramakrishnan
83a252bd20 there is only mail container now 2016-05-15 21:15:53 -07:00
Girish Ramakrishnan
7ef3805dbc docker data in test dir requires sudo 2016-05-13 22:38:36 -07:00
Girish Ramakrishnan
e5d906a065 create fake cert/key for tests to pass 2016-05-13 22:35:13 -07:00
Girish Ramakrishnan
b5e4e9fed6 bump postgresql 2016-05-13 22:13:58 -07:00
Girish Ramakrishnan
3ccb72f891 tests finally work 2016-05-13 22:05:05 -07:00
Girish Ramakrishnan
45cd4ba349 fix name 2016-05-13 22:03:40 -07:00
Girish Ramakrishnan
5b9b21c469 create recvmail link 2016-05-13 22:03:34 -07:00
Girish Ramakrishnan
ed55ad1c6f change image name 2016-05-13 21:34:04 -07:00
Girish Ramakrishnan
560f460a32 rename to sendmail 2016-05-13 20:48:31 -07:00
Girish Ramakrishnan
aa116ce58c fix tests 2016-05-13 20:21:09 -07:00
Girish Ramakrishnan
3f0e2024e4 pass db name and password for tests 2016-05-13 19:35:20 -07:00
Girish Ramakrishnan
d9c5b2b642 setup and teardown recvmail addon 2016-05-13 18:58:48 -07:00
Girish Ramakrishnan
5322ed054d reserve 4190 for sieve 2016-05-13 18:48:05 -07:00
Girish Ramakrishnan
39c4954371 remove isIncomingMailEnabled. always enable for now
also, custom domain === we will take over domain completely (setup
mx and all)
2016-05-13 18:44:08 -07:00
Girish Ramakrishnan
78ad49bd74 hack for settings test 2016-05-13 18:27:00 -07:00
Girish Ramakrishnan
f56c960b92 use latest manifestformat (for recvmail, email) 2016-05-13 17:59:11 -07:00
Girish Ramakrishnan
8e077660c4 start_addons is redundant 2016-05-13 17:57:56 -07:00
Girish Ramakrishnan
1b8b4900a2 parametrize data_dir for tests 2016-05-13 17:57:56 -07:00
Girish Ramakrishnan
27ddcb9758 use the internal hostnames for email addon
The public ones are for the user to configure their MUA. This
things can have ssl disabled safely as well.
2016-05-13 08:28:13 -07:00
Girish Ramakrishnan
fdb951c9e5 set MAIL_DOMAIN in email addon as well 2016-05-12 23:34:35 -07:00
Girish Ramakrishnan
0f2037513b remove recvmail bind 2016-05-12 21:48:42 -07:00
Girish Ramakrishnan
9da4e038bd all lower case 2016-05-12 18:54:13 -07:00
Girish Ramakrishnan
ae3e0177bb make script more robust 2016-05-12 18:16:26 -07:00
Girish Ramakrishnan
0751974624 Add a hack to test addon patching 2016-05-12 16:41:58 -07:00
Girish Ramakrishnan
b8242c82d6 create bind point for recvmail 2016-05-12 14:33:02 -07:00
Girish Ramakrishnan
442c02fa1b set mailAlternateAddress to username@fqdn
This is mostly to keep haraka's rcpt_to.ldap happy. That plugin
could do with some love.
2016-05-12 14:32:15 -07:00
Girish Ramakrishnan
d5306052bb refactor code for readability 2016-05-12 13:36:53 -07:00
Girish Ramakrishnan
8543dbe3be create a new ou for addons 2016-05-12 13:20:57 -07:00
Girish Ramakrishnan
bf42b735d1 fix the smtp port 2016-05-12 09:11:29 -07:00
Girish Ramakrishnan
a2ba3989d0 use manifestformat 2.4.0 2016-05-12 08:57:12 -07:00
Girish Ramakrishnan
6f36d79358 implement email addon 2016-05-12 08:54:59 -07:00
Girish Ramakrishnan
1da24564b3 reserve postman subdomain 2016-05-11 15:04:22 -07:00
Girish Ramakrishnan
da61d5c0f1 add ou=recvmail for dovecot 2016-05-11 14:26:34 -07:00
Girish Ramakrishnan
79da7b31c7 use latest base image 2016-05-11 13:14:11 -07:00
Girish Ramakrishnan
631b238b63 use MAIL_LOCATION for mx record 2016-05-11 09:59:12 -07:00
Girish Ramakrishnan
ff5ca617b1 use ADMIN_LOCATION 2016-05-11 09:58:31 -07:00
Girish Ramakrishnan
e16125c67e set MAIL_DOMAIN and MAIL_SERVER_NAME 2016-05-11 09:49:20 -07:00
Girish Ramakrishnan
646ba096c3 start recvmail addon in setup_infra 2016-05-11 08:55:51 -07:00
Girish Ramakrishnan
8be3b4c281 add mx records 2016-05-11 08:50:33 -07:00
Girish Ramakrishnan
5afff5eecc open 25 (inbound smtp) and 587 (inbound submission) 2016-05-11 08:48:50 -07:00
Girish Ramakrishnan
84206738e1 open port 993 (imap) 2016-05-11 08:47:59 -07:00
Girish Ramakrishnan
8b2e4ce700 do another infra bump for existing cloudrons 2016-05-10 16:53:13 -07:00
Girish Ramakrishnan
776f184dbc wait randomly instead 2016-05-10 16:51:20 -07:00
Girish Ramakrishnan
a54466f8c2 Fix apptask error because of multiple collectd restarts
Everyone gets in a rush to restart collectd and apptask update/restore
fails during infra updates
2016-05-10 09:52:55 -07:00
Girish Ramakrishnan
f36641b443 better error message 2016-05-10 09:50:57 -07:00
Girish Ramakrishnan
36eb107b83 0.13.4 changes 2016-05-10 09:16:16 -07:00
Girish Ramakrishnan
fa16ae9a0c Use mail container 0.12.0 that contains the restart fix 2016-05-10 09:05:06 -07:00
Girish Ramakrishnan
517b36b3f0 fix typo 2016-05-09 23:58:35 -07:00
Girish Ramakrishnan
83a28afc8f addons are started by box code now 2016-05-09 23:56:02 -07:00
Girish Ramakrishnan
e76c7de259 bump test app version 2016-05-09 23:40:59 -07:00
Girish Ramakrishnan
fcda4a771c play around with some text 2016-05-09 18:47:11 -07:00
Girish Ramakrishnan
76d8f16e22 0.13.3 changes 2016-05-08 01:17:03 -07:00
Girish Ramakrishnan
1b3cd1f373 remove tls since server does not offer it anymore 2016-05-08 01:15:24 -07:00
Girish Ramakrishnan
62b020e96d add note 2016-05-07 02:34:52 -07:00
Girish Ramakrishnan
bc78f4a6d8 fix user to match adminEmail 2016-05-07 01:32:14 -07:00
Girish Ramakrishnan
a8e458e935 Load certs into etc 2016-05-06 21:46:22 -07:00
Johannes Zellner
e4747ef50c Rework the tutorial 2016-05-06 21:32:34 +02:00
Johannes Zellner
0d6637de27 Avoid circular dependencies with apps and certificates 2016-05-06 18:44:37 +02:00
Johannes Zellner
28e513a434 Fix eventlog tests 2016-05-06 18:05:28 +02:00
Girish Ramakrishnan
e73174685b add note on settings route 2016-05-06 08:42:27 -07:00
Johannes Zellner
3af95508f5 eventlog getAllPaged is now getByQueryPaged 2016-05-06 17:27:52 +02:00
Johannes Zellner
4fa8ab596b Show busy state in eventlogs 2016-05-06 17:23:39 +02:00
Johannes Zellner
54c9bb7409 Add filter bar for event log view 2016-05-06 17:18:47 +02:00
Johannes Zellner
4c7dc5056d Add more query options for eventlog api 2016-05-06 16:49:17 +02:00
Johannes Zellner
e986a67d39 Fixup all the unit tests 2016-05-06 15:16:22 +02:00
Johannes Zellner
da8de173a6 Remove appdb.getBySubdomain() 2016-05-06 14:52:33 +02:00
Johannes Zellner
cbc906f8d1 Remove apps.getBySubdomain() 2016-05-06 14:52:06 +02:00
Johannes Zellner
c7958f8e1d Remove unused /api/v1/subdomains/:subdomain 2016-05-06 14:51:02 +02:00
Johannes Zellner
b88ee8143a Rename welcome -> tutorial 2016-05-06 14:41:20 +02:00
Johannes Zellner
e413f7ba9b Handle tutorial walkthrough 2016-05-06 14:38:17 +02:00
Johannes Zellner
7e1055ae44 Show tutorial for admins 2016-05-06 14:23:21 +02:00
Johannes Zellner
c61ce40362 Add /api/v1/profile/tutorial route tests 2016-05-06 14:06:54 +02:00
Johannes Zellner
e48156dceb postprocess showTutorial to ensure we deal with a boolean 2016-05-06 14:05:47 +02:00
Johannes Zellner
f3811e3df9 Remove all unused vars in profile test 2016-05-06 14:03:24 +02:00
Johannes Zellner
0d40b1b80d Fix the test wording in profile password change tests 2016-05-06 14:02:21 +02:00
Johannes Zellner
8b92c8f7ae Remove unused checkMails() in profile tests 2016-05-06 13:57:46 +02:00
Johannes Zellner
d41eb81b3d Add new profile/ route to set the showTutorial field 2016-05-06 13:56:40 +02:00
Johannes Zellner
3adf91afed Add setShowTutorial() api to users.js 2016-05-06 13:56:26 +02:00
Johannes Zellner
18f05de8ae use users.showTutorial field in userdb 2016-05-06 13:56:05 +02:00
Johannes Zellner
b0f4396389 Add showTutorial field to users table 2016-05-06 13:55:03 +02:00
Johannes Zellner
bf99475dbd Introduce basic welcome tutorial flow 2016-05-06 13:06:12 +02:00
Girish Ramakrishnan
d50fa70f47 pass -out 2016-05-05 21:26:13 -07:00
Girish Ramakrishnan
0e655cadb0 generate dkim keys before dns setup
Two things require DKIM keys
1. the mail addon
2. the DNS TXT record
2016-05-05 21:15:10 -07:00
Girish Ramakrishnan
496e1c3dc1 fix path to INFRA_VERSION 2016-05-05 18:37:17 -07:00
Girish Ramakrishnan
325252699e set MAIL_FROM more smartly 2016-05-05 18:33:22 -07:00
Girish Ramakrishnan
2d43e22285 fix typo 2016-05-05 15:26:32 -07:00
Girish Ramakrishnan
9e673c3890 supply a bogus username/password 2016-05-05 15:24:52 -07:00
Girish Ramakrishnan
c3c18e8a4b reserve more ports 2016-05-05 15:00:07 -07:00
Girish Ramakrishnan
cb1bd58cb9 do not export submission port just yet 2016-05-05 14:53:07 -07:00
Girish Ramakrishnan
0bdff14c9f r -> ro 2016-05-05 13:54:57 -07:00
Girish Ramakrishnan
c4ae9526af look for fallback cert in nginx cert dir 2016-05-05 13:52:08 -07:00
Girish Ramakrishnan
8d79ac9ae0 provide tls cert and key to mail server
haraka requires tls certs for:
1. supporting AUTH
2. port 587 support (MSA)

currently, we just reuse the cert for the admin domain. Otherwise,
we have to setup dns etc to get a new cert. While doable, its' not
necessary right now.
2016-05-05 13:18:17 -07:00
Girish Ramakrishnan
7d4ed5bafc Revert "x"
This reverts commit a1554b9cc1642037e9fbd0d261722c908f499aab.

committed by mistake
2016-05-05 12:57:04 -07:00
Johannes Zellner
85db8f398b Ensure we require an admin for settings routes
The cloudron name and avatar already have public routes.
2016-05-05 12:48:21 +02:00
Girish Ramakrishnan
636b71ce6f Add MAIL_FROM env 2016-05-05 00:28:08 -07:00
Girish Ramakrishnan
b46008f0b1 add sendmail ou bind
this will be used by haraka to authenticate the apps
2016-05-05 00:26:43 -07:00
Girish Ramakrishnan
9d9bd42cd2 x 2016-05-05 00:09:16 -07:00
Girish Ramakrishnan
0f6a2a42f2 use latest mysql image 2016-05-04 23:09:30 -07:00
Girish Ramakrishnan
fc8bf82993 Add getters for fallback and admin cert 2016-05-04 17:37:21 -07:00
Girish Ramakrishnan
e56192913d reserved smtp and imap locations 2016-05-04 15:54:21 -07:00
Girish Ramakrishnan
2d08ce441f rename arg_fqdn to fqdn 2016-05-04 15:54:21 -07:00
Girish Ramakrishnan
87497c2047 pass fqdn as arg 2016-05-04 15:54:21 -07:00
Girish Ramakrishnan
32a0bf6fd2 add --check arg to setup_infra.sh 2016-05-04 15:54:21 -07:00
Girish Ramakrishnan
291e625785 skip infra setup for tests (the test does it itself) 2016-05-04 15:54:21 -07:00
Girish Ramakrishnan
6bcfd33e10 boot2docker is dead 2016-05-04 15:54:21 -07:00
Girish Ramakrishnan
b4c15b1719 Let the box code initialize the infrastructure
This is done because:
1. The box code can install certs for addons (like mail addon) when
   required.

2. The box code initialize/teardown addons on demand. This is not planned
   currently.
2016-05-04 15:54:21 -07:00
Girish Ramakrishnan
920626192c fix error message 2016-05-04 09:28:50 -07:00
Johannes Zellner
778371b818 Only send out mails if the admin group has changed 2016-05-04 13:55:14 +02:00
Girish Ramakrishnan
d2a1cea1e8 0.13.2 changes 2016-05-03 23:48:45 -07:00
Girish Ramakrishnan
5683cefe89 provide auditSource when autoupdating app 2016-05-03 23:36:27 -07:00
Girish Ramakrishnan
126d64ffa8 move backup event in backupBoxAndApps
the updater uses this code route
2016-05-03 18:36:52 -07:00
Girish Ramakrishnan
7262eb208f add route to get the timezone 2016-05-03 12:10:21 -07:00
Girish Ramakrishnan
f57f8f5e58 fix typo 2016-05-03 12:09:58 -07:00
Girish Ramakrishnan
00ad1308aa handle empty time_zone 2016-05-03 12:09:22 -07:00
Girish Ramakrishnan
2cc37a9c31 0.13.1 changes 2016-05-03 11:50:52 -07:00
Girish Ramakrishnan
13b0093f20 telize has been discontinued. use freegeoip 2016-05-03 11:50:31 -07:00
Girish Ramakrishnan
5617baea50 eventlog tests 2016-05-02 14:54:25 -07:00
Girish Ramakrishnan
da79e4f229 only admin can view activity logs 2016-05-02 14:54:20 -07:00
Girish Ramakrishnan
6c1bd522e6 add pagination 2016-05-02 11:50:15 -07:00
Girish Ramakrishnan
7fca9decc1 fixes to eventlog 2016-05-02 11:07:55 -07:00
Girish Ramakrishnan
7cf08b6a4d add start event
this signals that an update worked
2016-05-02 10:01:23 -07:00
Girish Ramakrishnan
ffedbdfa13 various minor fixes to eventlog 2016-05-02 10:01:23 -07:00
Johannes Zellner
43e207a301 Merge branch 'v0.12.7-hotfix' 2016-05-02 17:34:56 +02:00
Johannes Zellner
bea6019dc4 Changes for 0.12.7 2016-05-02 14:20:59 +02:00
Johannes Zellner
9042e9e1a9 Fix usage of the password change route in webadmin 2016-05-02 14:14:44 +02:00
Johannes Zellner
b855dee4cb Fix crash by providing required eventsource 2016-05-02 13:43:12 +02:00
Girish Ramakrishnan
b322f6805f move authType into source 2016-05-01 21:53:44 -07:00
Girish Ramakrishnan
ccc119ddec add appLocation to user login 2016-05-01 21:47:35 -07:00
Girish Ramakrishnan
994cbaa22a add event log in model code 2016-05-01 21:38:20 -07:00
Girish Ramakrishnan
d7a34bbf68 remove profile action 2016-05-01 20:14:21 -07:00
Girish Ramakrishnan
1f31fe6f8f make user.remove and user.update add eventlog 2016-05-01 20:11:11 -07:00
Girish Ramakrishnan
37bdd2672b make user.create take auditSource 2016-05-01 20:01:34 -07:00
Girish Ramakrishnan
63702f836a move developer eventlogs to model code 2016-05-01 13:46:33 -07:00
Girish Ramakrishnan
5898428a6c use req.connection.remoteAddress 2016-05-01 13:29:11 -07:00
Girish Ramakrishnan
f4a6c64956 make cloudron.activate take an auditSource 2016-05-01 13:27:57 -07:00
Girish Ramakrishnan
f9d4d3014d remove reboot (user cannot initiate anyway) 2016-05-01 13:23:31 -07:00
Girish Ramakrishnan
8254337552 make cloudron.updateToLatest take an auditSource 2016-05-01 13:17:35 -07:00
Girish Ramakrishnan
d811115f21 update schema.sql 2016-05-01 13:17:27 -07:00
Girish Ramakrishnan
fec388b648 move backup eventlog to model 2016-05-01 13:17:23 -07:00
Girish Ramakrishnan
a969e323a6 what if cron was a username 2016-05-01 11:48:29 -07:00
Girish Ramakrishnan
09584ac29c fix failing test 2016-04-30 23:26:45 -07:00
Girish Ramakrishnan
7967610f3f add user login to event log 2016-04-30 23:18:14 -07:00
Girish Ramakrishnan
727332fe66 save the userId as well 2016-04-30 23:04:54 -07:00
Girish Ramakrishnan
09595d1c43 make tests pass 2016-04-30 22:35:46 -07:00
Girish Ramakrishnan
c4ad6c803f add certificate renew event 2016-04-30 22:27:33 -07:00
Girish Ramakrishnan
fd1a00d280 better text for action 2016-04-30 20:25:20 -07:00
Girish Ramakrishnan
5c2a650681 add activity view 2016-04-30 20:25:20 -07:00
Girish Ramakrishnan
43051cea3b add app update event 2016-04-30 20:25:20 -07:00
Girish Ramakrishnan
3d50a251ee store email in USER_ADD event 2016-04-30 20:25:20 -07:00
Girish Ramakrishnan
219df8babd pick up correct ip 2016-04-30 19:08:24 -07:00
Girish Ramakrishnan
d3d9706a70 make response plural 2016-04-30 18:57:43 -07:00
Girish Ramakrishnan
8a3ad6c964 store the activation username 2016-04-30 14:32:35 -07:00
Girish Ramakrishnan
90719cd4d9 do not store entire manifest 2016-04-30 14:28:59 -07:00
Girish Ramakrishnan
30445ddab9 more changes 2016-04-30 14:14:02 -07:00
Girish Ramakrishnan
157fbc89b8 add eventlog route 2016-04-30 14:08:44 -07:00
Girish Ramakrishnan
71219c6af7 add eventlog hooks 2016-04-30 14:05:19 -07:00
Girish Ramakrishnan
934abafbd4 make actions seem like commands 2016-04-30 13:34:06 -07:00
Girish Ramakrishnan
bc6e896507 add eventlog test 2016-04-30 13:02:57 -07:00
Girish Ramakrishnan
ca8731c282 add source to events table 2016-04-30 12:56:23 -07:00
Girish Ramakrishnan
c511019d79 remove jslint hint 2016-04-30 11:53:46 -07:00
Girish Ramakrishnan
992c4ee847 add some event enums 2016-04-30 11:49:51 -07:00
Girish Ramakrishnan
8c427553ba add eventlogdb tests 2016-04-30 10:16:27 -07:00
Girish Ramakrishnan
c1df22f079 use JSON type (nice for querying) 2016-04-30 10:15:33 -07:00
Girish Ramakrishnan
7673ecde2f Add eventlog model file 2016-04-29 23:58:29 -07:00
Girish Ramakrishnan
a9d0cf66fd Add eventlogdb 2016-04-29 23:58:24 -07:00
Girish Ramakrishnan
db89784af8 add eventlog table 2016-04-29 23:58:20 -07:00
Girish Ramakrishnan
4143f903ad 0.13.0 changes 2016-04-29 20:50:55 -07:00
Girish Ramakrishnan
12820db4a5 fix typo in mysql config 2016-04-29 20:20:52 -07:00
Girish Ramakrishnan
8837cc5a3c update nginx version 2016-04-29 19:38:06 -07:00
Girish Ramakrishnan
f2545e3def bump systemd to 229 2016-04-29 19:18:31 -07:00
Girish Ramakrishnan
7a72bf3f78 select mysql 5.7 2016-04-29 19:12:20 -07:00
Girish Ramakrishnan
87351f04ef use 16.04
among other things this will bump the mysql version bringing us
json type
2016-04-29 19:11:00 -07:00
Girish Ramakrishnan
4a04e0b52f use recommendation from raymii.org 2016-04-28 09:59:03 -07:00
Girish Ramakrishnan
5945bce00e add misssing arg 2016-04-26 16:21:26 -07:00
Girish Ramakrishnan
d2a3925e04 add altDomain to install route 2016-04-26 14:45:58 -07:00
Girish Ramakrishnan
9c9f82e2c5 fix usage of waitForDns 2016-04-26 11:09:14 -07:00
Girish Ramakrishnan
8fd3ff0ccc 0.12.6 changes 2016-04-26 09:53:33 -07:00
Girish Ramakrishnan
dec2fdb6bb merge altDomain into location field 2016-04-25 21:22:09 -07:00
Girish Ramakrishnan
c581d2a52c stash altDomain in oldConfig 2016-04-25 21:22:08 -07:00
Girish Ramakrishnan
e6e748e30d fix cname answer 2016-04-25 16:16:14 -07:00
Girish Ramakrishnan
36fddacf5c add more debugs 2016-04-25 16:06:50 -07:00
Girish Ramakrishnan
183c1608a6 it is fine for one or more ns to be dead 2016-04-25 15:58:42 -07:00
Girish Ramakrishnan
51c8f65e8d wait for altDomain on install as well
restore calls install when there is no lastBackupId
2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
0da6e9a5b9 fix casing 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
31c7a17684 Infinity does not work 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
a1e2cd438e return altDomain in response 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
6b0e00e28b use app.fqdn 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
19948851e0 return altDomain as the app.fqdn if present 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
bfe4f75881 display altDomain as the title of the app 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
7f13594f01 fix two typos 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
4fafac035e remove options in waitForDns 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
ca41e6acfd remove attempt module 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
9893dd6640 make waitfordns get the zone itself 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
8f7e4c2053 Make waitForDns wait for cname 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
d037b13401 wait for alt domain dns 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
83c955d25b add a label for alt domain 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
0bb6d969a4 inject altDomain in the APP_ORIGIN and APP_DOMAIN env vars 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
6062a5bdd2 move exports to top because sysinfo backend source the error class 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
70ab492efa remove jslint header 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
aab035f7b9 use the acme backend when using altDomain 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
0e825272ae ensureCertificate now takes app object 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
46fee9e431 use config.adminFqdn instead 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
0789c96992 altDomain ui 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
a4adc581fa make nginx.configure/unconfigure use altDomain 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
500fb452e7 use altDomain when present to configure certs and nginx 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
e11b762ea1 use vhost instead 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
f5d1726352 remove jslint header 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
3d5aa9fd23 pass altDomain in configure route 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
ef12740060 add altDomain to appdb fields 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
415902d68e add altDomain to apps table 2016-04-25 10:52:12 -07:00
Girish Ramakrishnan
0ef0e010a3 use defines for role names 2016-04-25 10:30:56 -07:00
Girish Ramakrishnan
2d27da89d2 validate individual scopes 2016-04-25 10:26:47 -07:00
Girish Ramakrishnan
9d8def8349 update bytes ejs-cli dockerode morgan superagent ursa validator x509 2016-04-22 22:26:32 -07:00
Girish Ramakrishnan
2533111bfa more 0.12.5 changes 2016-04-20 19:41:45 -07:00
Girish Ramakrishnan
20d6da8230 add debugs 2016-04-20 19:40:58 -07:00
Girish Ramakrishnan
f159cacfbb Use same timestamp for archive and config
This fixes a very curious case:
1. App has backup.
2. App dies.
3. Box backs up. This make it reuse the backup. But it generates wrong config file timestamp.
4. Box cannot update anymore. This is because the backup of app fails - it tries to reuse
   the backup and that fails with AccessDenied because the timestamp above is wrong!
2016-04-20 19:37:00 -07:00
Girish Ramakrishnan
5e9ea98b66 ignore apps in errored state 2016-04-20 19:05:49 -07:00
Girish Ramakrishnan
d87b7dcb75 fix typo 2016-04-20 12:56:35 -07:00
Girish Ramakrishnan
6eea2fef9a retry fetching icon
e2e randomly fails with EAI_AGAIN
2016-04-20 00:40:22 -07:00
Girish Ramakrishnan
34fd5f14a5 0.12.5 changes 2016-04-19 21:52:24 -07:00
Girish Ramakrishnan
a4e73e747c fix crash mail subject 2016-04-19 19:12:47 -07:00
Girish Ramakrishnan
eadff099eb send logs when apptask fails 2016-04-19 18:40:46 -07:00
Girish Ramakrishnan
15653cb3f8 rename to logcollector 2016-04-19 18:13:05 -07:00
Girish Ramakrishnan
2f8dc35c5d rename to sendFailureLogs 2016-04-19 18:06:11 -07:00
Girish Ramakrishnan
a97720d204 rename to failure_notification 2016-04-19 18:04:45 -07:00
Girish Ramakrishnan
73898505b0 remove jslint header 2016-04-19 16:59:12 -07:00
Girish Ramakrishnan
88b4b6a38b use the crashnotifier module 2016-04-19 16:47:21 -07:00
Girish Ramakrishnan
3da82e3a63 rename to crashnotifierservice 2016-04-19 16:45:05 -07:00
Girish Ramakrishnan
dad1585704 send crash notification on apptask crash 2016-04-19 16:43:58 -07:00
Girish Ramakrishnan
e81dbdb36c add crashnotifier module 2016-04-19 16:42:05 -07:00
Girish Ramakrishnan
ee2478e500 collect last 300 lines 2016-04-19 16:39:28 -07:00
Girish Ramakrishnan
0f7a6964a4 0.12.4 changes 2016-04-19 16:24:37 -07:00
Girish Ramakrishnan
5fa974ffe6 wait for 30 seconds in taskmanager instead
problem: because the apps are not inserted into appdb, the cloudron starts out
with an empty view. apps appear suddenly after 30 seconds.

besides, it makes more sense because 30 secs is not really tied to first run
2016-04-19 13:56:41 -07:00
Girish Ramakrishnan
e1b7198a29 reverse setTimeout args 2016-04-19 12:28:36 -07:00
Girish Ramakrishnan
37d6354627 wait for 30 seconds for the addons to start up
The platform sometimes takes time to start up (especially in 1GB droplet).
This means that apps like wordpress begin auto installing and they fail
since mysql has not started yet.
2016-04-19 12:15:25 -07:00
Girish Ramakrishnan
6ab3e04fc1 0.12.3 changes 2016-04-19 12:11:59 -07:00
Girish Ramakrishnan
b1987868be Set sn attribute only if non-empty
sn and givenName have as their superior the name attribute, which is of DirectoryString syntax,
that is, the syntax is 1.3.6.1.4.1.1466.115.121.1.15. Attributes which are of syntax
DirectoryString are not allowed to be null, that is, a DirectoryString is required to have
at least one character.

http://stackoverflow.com/questions/15027094/how-to-filter-null-or-empty-attributes-from-an-active-directory-query

This fixes a crash in paperwork which relies on this.
2016-04-19 12:03:03 -07:00
Girish Ramakrishnan
72eb3007c4 tmp -> obj 2016-04-19 12:00:34 -07:00
Girish Ramakrishnan
6c1da45ad1 skip the not automated part 2016-04-19 10:49:46 -07:00
Girish Ramakrishnan
b9857cdb65 use async.retry 2016-04-18 22:06:49 -07:00
Girish Ramakrishnan
d5c251115c typo 2016-04-18 18:57:29 -07:00
Girish Ramakrishnan
64c66e248b add mongodb test 2016-04-18 18:12:56 -07:00
Girish Ramakrishnan
bb53c4f331 fix setup of mongodb 2016-04-18 18:05:23 -07:00
Girish Ramakrishnan
3215d4a3c9 move the exports to the top 2016-04-18 16:30:58 -07:00
Girish Ramakrishnan
68c4d77494 0.12.2 changes 2016-04-18 15:24:33 -07:00
Girish Ramakrishnan
44bf299e10 Merge remote-tracking branch 'origin/users' 2016-04-18 15:19:38 -07:00
Girish Ramakrishnan
6b1e14b464 add option to buffer stdout 2016-04-18 15:02:31 -07:00
Girish Ramakrishnan
8dcde84c3c remove memorystream 2016-04-18 14:56:47 -07:00
Girish Ramakrishnan
a0deedb958 fixup backup and restore to use docker.execContainer 2016-04-18 14:56:01 -07:00
Girish Ramakrishnan
a2096bec18 use options.stdout to pass back result 2016-04-18 12:22:42 -07:00
Girish Ramakrishnan
4f82bcec43 make execContainer take options arg 2016-04-18 11:42:34 -07:00
Girish Ramakrishnan
491356ce8d fix teardownMongoDb 2016-04-18 11:29:11 -07:00
Girish Ramakrishnan
6c99105a7e Make teardown commands use docker.execContainer 2016-04-18 11:25:16 -07:00
Girish Ramakrishnan
71f847776b fix up all the seutp code to use docker.execContainer 2016-04-18 11:15:21 -07:00
Girish Ramakrishnan
87c5371603 use docker exec instead of dockerode exec in mysql
this way we can check the exit code of the exec process.
preivously, we were only wait for the stream to end.
2016-04-18 11:06:09 -07:00
Girish Ramakrishnan
01d676628d rename docker variable 2016-04-18 10:37:33 -07:00
Girish Ramakrishnan
60badce935 add docker.execContainer 2016-04-18 10:32:22 -07:00
Johannes Zellner
182ae6bf1f Add some description to the upgrade dialog 2016-04-18 17:21:26 +02:00
Johannes Zellner
c62ef9e156 Implement upgrade request dialog
This is currently merely a placeholder for some real upgrade ui
2016-04-18 17:21:26 +02:00
Johannes Zellner
96383a1fae Support upgrade_request feedback type 2016-04-18 17:11:36 +02:00
Johannes Zellner
5e9542ee76 Offer upgrade on install if resources are low 2016-04-18 16:16:44 +02:00
Johannes Zellner
cc28d49df4 That api already returns the user array 2016-04-18 15:13:22 +02:00
Johannes Zellner
18f3733d6e Simplify the password change logic
We now can use verifyPassword and this makes
user.changePassword() route obsolete
2016-04-17 19:17:03 +02:00
Johannes Zellner
87dcf42c7e Remove redundant client api listUsers() 2016-04-17 18:42:56 +02:00
Johannes Zellner
32d8627045 Password change api is now in /profile 2016-04-17 18:41:13 +02:00
Johannes Zellner
6a607f9565 Adjust user route tests 2016-04-17 18:39:00 +02:00
Johannes Zellner
c623770b44 Group /users routes better 2016-04-17 18:38:49 +02:00
Johannes Zellner
69f3620b22 remove unused user route functions 2016-04-17 18:27:11 +02:00
Johannes Zellner
21110bb2e0 Enable profile password change tests 2016-04-17 17:51:37 +02:00
Johannes Zellner
fabe55622e Fix the first bunch of profile tests 2016-04-17 16:49:09 +02:00
Johannes Zellner
73e079cc6c Add initial profile route tests 2016-04-17 16:42:45 +02:00
Johannes Zellner
a7d22a1972 Add specific user profile routes 2016-04-17 16:22:39 +02:00
Girish Ramakrishnan
5c1970b37f Fix crash where portBindings is set to undefined 2016-04-15 21:27:42 -07:00
Girish Ramakrishnan
db065bd0fc 0.12.1 changes 2016-04-15 18:28:02 -07:00
Girish Ramakrishnan
db6d8deec4 fix another typo 2016-04-15 18:25:46 -07:00
Girish Ramakrishnan
414b21f29a add sysadmin route test 2016-04-15 12:33:54 -07:00
Girish Ramakrishnan
c4c7668b5a Use new env vars 2016-04-15 11:57:51 -07:00
Girish Ramakrishnan
b9fa87cca2 cloudron.backup -> backups.backup 2016-04-15 11:57:51 -07:00
Girish Ramakrishnan
218c9099fd more CHANGES 2016-04-15 11:09:25 -07:00
Girish Ramakrishnan
916d97f7bd reserve the no-reply mailbox 2016-04-15 11:09:25 -07:00
Johannes Zellner
109f777c00 Fix focus in setup wizard 2016-04-15 14:49:10 +02:00
Johannes Zellner
4bf3a78227 Add display name input in setup wizard 2016-04-15 12:18:41 +02:00
Girish Ramakrishnan
c03e69232e Remove admin name (already set in cloudron.conf now) 2016-04-14 20:39:05 -07:00
Girish Ramakrishnan
91a016ee91 Change the admin email to no-reply 2016-04-14 19:56:54 -07:00
Girish Ramakrishnan
8256f97e9d use latest mail image 2016-04-14 19:37:34 -07:00
Girish Ramakrishnan
d095899aef add note that admin@fqdn is reserved as well 2016-04-14 13:34:41 -07:00
Johannes Zellner
6293c0aede Add test for reserved username 'admin' 2016-04-14 16:30:31 +02:00
Johannes Zellner
101ce62ef3 Move username and email lowercasing to where it belongs
Fixes #592
2016-04-14 16:25:48 +02:00
Girish Ramakrishnan
9f443e2d07 should ideally use shutdown commands at some point (for mongodb) 2016-04-13 20:53:07 -07:00
Girish Ramakrishnan
0a30585a05 bump mongodb (handles mongod crash recovery) 2016-04-13 20:35:20 -07:00
Girish Ramakrishnan
ed78bd05c8 reserve the "admin" username 2016-04-13 16:50:20 -07:00
Girish Ramakrishnan
c24d7e7b3c do not crash on duplicate email 2016-04-13 14:47:35 -07:00
Girish Ramakrishnan
389d2be82d CLI is not a mode 2016-04-13 11:11:04 -07:00
Girish Ramakrishnan
38b85e6006 set givenName and sn in ldap response 2016-04-13 10:52:25 -07:00
Johannes Zellner
de2cde7333 Test oauth with mixed case username and email 2016-04-13 12:48:02 +02:00
Johannes Zellner
08410569c0 Actually fix the correct thing in the janitor tests 2016-04-13 12:43:18 +02:00
Johannes Zellner
be3b08a7b4 Test case-insensitive developer login 2016-04-13 12:39:50 +02:00
Johannes Zellner
2724cfd0ad Test simpleauth with uppercase username input 2016-04-13 12:30:55 +02:00
Johannes Zellner
d7c8cf5e0e Ensure ldap filter values are treated lowercase only 2016-04-13 12:28:44 +02:00
Johannes Zellner
11f89da3a0 Ensure username and email are treated lower case in the database layer 2016-04-13 12:15:49 +02:00
Johannes Zellner
a803af2300 Revert "add get route for user"
This route is already there, the reason is, that the users api works off
the :userId but the profile api works off the req.user coming from the used
access token.

This reverts commit dbef4d71be5a68239133ab9b6e0fc1fd88ee27cd.
2016-04-13 11:36:49 +02:00
Johannes Zellner
6991402a8c Fix typo 2016-04-13 11:33:01 +02:00
Johannes Zellner
259798a8f2 Ensure auth code expiration is calculated at the right time 2016-04-13 11:32:30 +02:00
Johannes Zellner
d83395ecfb Also test grant type token access tokens 2016-04-13 11:28:10 +02:00
Johannes Zellner
6d3dd452be Test that oauth tokens are actually usable after issuing 2016-04-13 11:03:35 +02:00
Johannes Zellner
40bee79e3d Fix oversight to store userId as user.username for auth codes 2016-04-13 10:45:11 +02:00
Girish Ramakrishnan
95de25560b add profile scope to developer tokens 2016-04-12 19:08:56 -07:00
Girish Ramakrishnan
79eee94a5e Fix setup link path 2016-04-12 18:40:00 -07:00
Girish Ramakrishnan
82651a33c7 typo 2016-04-12 18:16:52 -07:00
Girish Ramakrishnan
212a0ffcd9 add get route for user 2016-04-12 18:13:37 -07:00
Girish Ramakrishnan
115ed12c36 check that app patch releases does not send email 2016-04-12 13:49:49 -07:00
Girish Ramakrishnan
53268b67dc test: it does not send mail for box patch releases 2016-04-12 13:45:11 -07:00
Girish Ramakrishnan
40dd12ba68 verify emails are sent in updatechecker test 2016-04-12 13:24:23 -07:00
Girish Ramakrishnan
7a111e29ad test updatechecker emails 2016-04-12 13:15:40 -07:00
Girish Ramakrishnan
065c65317d create owner in app update checker test 2016-04-12 13:13:16 -07:00
Girish Ramakrishnan
91a5d711f4 test: create owner 2016-04-12 13:12:17 -07:00
Girish Ramakrishnan
9071ea6c5e test: fix prerelease version 2016-04-12 13:01:42 -07:00
Girish Ramakrishnan
34521735da skip email notification for patch releases 2016-04-12 12:30:13 -07:00
Girish Ramakrishnan
b7f6dfb197 remove verbose from tar 2016-04-10 22:49:39 -07:00
Girish Ramakrishnan
fa330b4652 remove redundant debug 2016-04-10 22:44:43 -07:00
Girish Ramakrishnan
3bdbcff811 add debug 2016-04-10 22:34:55 -07:00
Girish Ramakrishnan
ea3bd6d71d remove trailing comma 2016-04-10 22:29:09 -07:00
Girish Ramakrishnan
d5cc96b1ff clean up backups code 2016-04-10 22:24:01 -07:00
Girish Ramakrishnan
4ed368cdd8 remove getBackupUrl 2016-04-10 22:12:06 -07:00
Girish Ramakrishnan
5229222014 getBackupCredentials is never used 2016-04-10 22:09:29 -07:00
Girish Ramakrishnan
9b0aa331e1 remove unused function 2016-04-10 22:08:11 -07:00
Girish Ramakrishnan
70cc073b1c only add to backupdb when the backup succeeded 2016-04-10 21:55:08 -07:00
Girish Ramakrishnan
29502fd8af remove unused exports 2016-04-10 21:52:01 -07:00
Girish Ramakrishnan
8d75fcfe67 typo 2016-04-10 21:46:01 -07:00
Girish Ramakrishnan
b2668579d6 pass appid to backup script 2016-04-10 21:41:53 -07:00
Girish Ramakrishnan
ba663faa64 fix debug 2016-04-10 21:39:45 -07:00
Girish Ramakrishnan
8db76f6b70 backup swap is not required anymore 2016-04-10 20:55:59 -07:00
Girish Ramakrishnan
322e9faee7 rework backup code
move all the backup code into backups.js
2016-04-10 20:41:08 -07:00
Girish Ramakrishnan
af9d489395 backup apps use aws-cli 2016-04-10 18:47:25 -07:00
Girish Ramakrishnan
4565291c1c use aws-cli to upload box backups 2016-04-10 18:22:05 -07:00
Girish Ramakrishnan
be127ec313 fix failing test 2016-04-10 17:15:23 -07:00
Girish Ramakrishnan
8b3a44b33c Add getBackupCredentials to backups API 2016-04-10 11:01:59 -07:00
Girish Ramakrishnan
08b5d7003d expose getBackupCredentials from storage api 2016-04-10 10:55:59 -07:00
Girish Ramakrishnan
60cc4c988f bump mysql addon 2016-04-09 02:34:54 -07:00
Girish Ramakrishnan
68219748ec oops, bump postgresql 2016-04-09 01:07:46 -07:00
Girish Ramakrishnan
cfb56d7eee install aws-cli tool (for backups) 2016-04-08 23:58:07 -07:00
Girish Ramakrishnan
4690616230 Add 0.12.0 changes proactively 2016-04-08 23:57:05 -07:00
Girish Ramakrishnan
96d625b866 bump the postgresql addon (required for gitlab) 2016-04-08 23:46:39 -07:00
Johannes Zellner
2e281f8554 Only directly callback if the config is not empty
apiServerOrigin is always set if the config was set
2016-04-08 17:29:14 +02:00
Johannes Zellner
5da5d86bc8 Pass billing through from the appstore to the cloudron config 2016-04-08 17:27:22 +02:00
Johannes Zellner
103c0bd688 Add initial upgrade button version
This is currently always hidden
2016-04-08 13:57:24 +02:00
Girish Ramakrishnan
275d8c2121 fix user create response 2016-04-06 10:20:32 -07:00
Girish Ramakrishnan
4c964bcaf8 set userid correctly in tokendb 2016-04-06 10:11:48 -07:00
Girish Ramakrishnan
e6c2c77f03 set username for predictability 2016-04-06 09:18:00 -07:00
Girish Ramakrishnan
819095b465 order for predictable tests 2016-04-06 09:08:59 -07:00
Girish Ramakrishnan
1453fd3c54 order by username to make tests deterministic 2016-04-06 08:46:42 -07:00
Johannes Zellner
867278a0b6 Use verifyWithUsername() instead of verify() in simpleauth 2016-04-06 08:57:55 +02:00
Girish Ramakrishnan
382fca3cf2 minor rewording in password reset 2016-04-05 23:08:57 -07:00
Girish Ramakrishnan
f210501e12 create -> set 2016-04-05 23:06:17 -07:00
Girish Ramakrishnan
499921e3af more changes 2016-04-05 18:45:34 -07:00
Girish Ramakrishnan
db19df9395 Bump infra version to force app reconfigure
Required for collectd profiles to be regenerated
2016-04-05 17:36:30 -07:00
Girish Ramakrishnan
6e2067bfe7 fix memory.stat path 2016-04-05 17:24:54 -07:00
Girish Ramakrishnan
8eb1b374ef cgroups are not in system.slice anymore 2016-04-05 17:12:50 -07:00
Girish Ramakrishnan
1734555974 handle case where box is not activated 2016-04-05 12:23:27 -07:00
Girish Ramakrishnan
7136de4d08 add debugs 2016-04-05 12:07:37 -07:00
Girish Ramakrishnan
21e8bc1ce5 javascript much 2016-04-05 12:06:15 -07:00
Girish Ramakrishnan
13020be6e6 default app bundle to null, if absent 2016-04-05 12:00:33 -07:00
Girish Ramakrishnan
3b922ff8b2 Some 0.11.2 changes 2016-04-05 11:07:32 -07:00
Girish Ramakrishnan
69402d0079 check activation state for existing cloudrons without a first run file 2016-04-05 10:56:27 -07:00
Johannes Zellner
99850f1161 Support ldap DNs with userId, username and email 2016-04-05 16:32:12 +02:00
Johannes Zellner
b205212bf2 Explicitly verifyWithUsername() and offer a verify() userId based 2016-04-05 16:27:04 +02:00
Johannes Zellner
baf586b028 Add missing 'else' 2016-04-05 16:25:05 +02:00
Johannes Zellner
94faa3575c Ensure we lowercase all emails
This ensures the uniqueness of that field
2016-04-05 11:15:50 +02:00
Johannes Zellner
544c1474d1 Allow multiple empty usernames in the db 2016-04-05 10:54:09 +02:00
Johannes Zellner
bb25279878 Fixup some bugs in the user handling ui 2016-04-05 10:11:04 +02:00
Johannes Zellner
4939f526d5 Fixup the user.js tests 2016-04-05 09:28:41 +02:00
Johannes Zellner
68af03f401 Fixup ldap tests 2016-04-05 09:28:41 +02:00
Johannes Zellner
f744fee708 password change route also now takes the userId 2016-04-05 09:28:41 +02:00
Johannes Zellner
c7ceb29845 Remove unused setAdmin() from webclient 2016-04-05 09:28:41 +02:00
Johannes Zellner
56d9d5913d Fixup the user route tests 2016-04-05 09:28:41 +02:00
Johannes Zellner
f7887228d3 Fix oauth session view tests and simpleauth tests 2016-04-05 09:28:41 +02:00
Johannes Zellner
73ed0384ea Fixup group rest api tests 2016-04-05 09:28:41 +02:00
Johannes Zellner
3051d4c22a This is actually a callback, doh 2016-04-05 09:28:41 +02:00
Johannes Zellner
b32a0bcfad Do not allow empty username on createOwner() 2016-04-05 09:28:41 +02:00
Johannes Zellner
61c79aab23 Add asserts for user.createOwner() 2016-04-05 09:28:41 +02:00
Johannes Zellner
9740ffd504 Remove displayName setting in user edit view 2016-04-05 09:28:41 +02:00
Johannes Zellner
435ec2365b fix sendError() args 2016-04-05 09:28:41 +02:00
Johannes Zellner
ff3562b0e8 Show error page for invalid reset tokens 2016-04-05 09:28:41 +02:00
Johannes Zellner
3be5511e33 Ensure we pass the resetToken on error 2016-04-05 09:28:41 +02:00
Johannes Zellner
c8604e95ab Prevent password reset for not activated user 2016-04-05 09:28:41 +02:00
Johannes Zellner
bbaf4c77fd This is ejs not angular 2016-04-05 09:28:41 +02:00
Johannes Zellner
1c9fc3f3dc Make account setup view prettier 2016-04-05 09:28:41 +02:00
Johannes Zellner
577959f281 Ensure browser autofill is disabled 2016-04-05 09:28:41 +02:00
Johannes Zellner
8af01f2955 Give basic form feedback for account creation 2016-04-05 09:28:41 +02:00
Johannes Zellner
c73213b2f2 Handle username conflict in account setup 2016-04-05 09:28:41 +02:00
Johannes Zellner
36f3f4b8f4 Remove superflous ' 2016-04-05 09:28:41 +02:00
Johannes Zellner
31bd5cdee3 Fix typo, userdb.del() wants an id 2016-04-05 09:28:41 +02:00
Johannes Zellner
fd0326efb1 Allow to use username or password for user deletion form 2016-04-05 09:28:41 +02:00
Johannes Zellner
65c6806109 Send full user information on deletion, not just the uuid 2016-04-05 09:28:41 +02:00
Johannes Zellner
1b7406784e Fix the user invitation to use userId 2016-04-05 09:28:41 +02:00
Johannes Zellner
8cbf83058f Adjust the welcome mail 2016-04-05 09:27:32 +02:00
Johannes Zellner
e058e22cae Fixup the client tests with userid change 2016-04-05 09:27:32 +02:00
Johannes Zellner
c84674529b Calm down the app polling a bit 2016-04-05 09:27:32 +02:00
Johannes Zellner
a0098a8883 Adjust email templates, as we do not have a username set now 2016-04-05 09:27:32 +02:00
Johannes Zellner
f6547c9b71 Ensure we render a useful string for access restriction users 2016-04-05 09:27:32 +02:00
Johannes Zellner
6dc17183ee Do not use google font cdn 2016-04-05 09:27:32 +02:00
Johannes Zellner
bba3dd5ec0 Fetch users and groups in apps view 2016-04-05 09:27:32 +02:00
Johannes Zellner
9eec6c2e9d Add an extra postprocess in client.js 2016-04-05 09:27:32 +02:00
Johannes Zellner
c235b82660 Fallback to email if username is not set for single user apps 2016-04-05 09:27:32 +02:00
Johannes Zellner
67ac0fcd5a Add missing ajax-loader.gif for slick carousel 2016-04-05 09:27:32 +02:00
Johannes Zellner
87ca147e65 Show user and email in user selection for app permission 2016-04-05 09:27:32 +02:00
Johannes Zellner
0cf2bfb792 Use user.id for session serialization 2016-04-05 09:27:32 +02:00
Johannes Zellner
a112e614e6 Actually query by username instead of just delegate to get() in getByUsername() 2016-04-05 09:27:32 +02:00
Johannes Zellner
0b1dcd2940 Use userdb.getByUsername() instead of get() 2016-04-05 09:27:32 +02:00
Johannes Zellner
951934f275 Remove unused require 2016-04-05 09:27:32 +02:00
Johannes Zellner
78518ff5f6 Hide username and displayName when adding an account 2016-04-05 09:27:32 +02:00
Johannes Zellner
b8d0c01187 fix typo 2016-04-05 09:27:32 +02:00
Johannes Zellner
572e5c4938 Adjust the user add form to not require a username 2016-04-05 09:27:32 +02:00
Johannes Zellner
e4fabd20c1 Do not require a username to be present when creating a user 2016-04-05 09:27:32 +02:00
Johannes Zellner
726d154890 Make user id a uuid.v4() and allow empty usernames 2016-04-05 09:27:32 +02:00
Johannes Zellner
7a5ac1a2f5 Add POST account/setup to distinguish between setup and password reset 2016-04-05 09:27:32 +02:00
Johannes Zellner
c90a8041e2 Move password/setup.html -> account/setup.html 2016-04-05 09:27:32 +02:00
Johannes Zellner
18b91b5fa0 Rename password setup to account setup 2016-04-05 09:27:32 +02:00
Johannes Zellner
f058c266d2 Add username and display name form fields on account setup 2016-04-05 09:27:32 +02:00
Johannes Zellner
e0114c87ac Also update the user record when username and email is sent 2016-04-05 09:27:32 +02:00
Johannes Zellner
c98275000b Optionally support username and email in password setter route 2016-04-05 09:27:32 +02:00
Girish Ramakrishnan
553509c462 implement installation of app bundle 2016-04-04 23:03:13 -07:00
Girish Ramakrishnan
306bef96b4 remove dead DNS_IN_SYNC 2016-04-04 22:14:05 -07:00
Girish Ramakrishnan
497eaea65e bump expiry to 60 mins 2016-04-04 16:02:13 -07:00
Girish Ramakrishnan
8aacc503a6 Revert "getRestoreUrl now uses caas restore api"
This reverts commit f9fc9325a8995dc0a9cb1dfcf22fb27eca697a89.

For now, we can simply assume that caas is s3 based.
2016-04-04 15:57:32 -07:00
Girish Ramakrishnan
ec160fe45f make getBackupUrl return id as well 2016-04-04 12:45:09 -07:00
Girish Ramakrishnan
82c74e6787 add backupdb tests 2016-04-04 12:41:17 -07:00
Girish Ramakrishnan
bbff195863 rename filename to id 2016-04-04 12:20:56 -07:00
Girish Ramakrishnan
e528dbcfc0 creationTime is redundant 2016-04-04 12:13:54 -07:00
Girish Ramakrishnan
0467e80c71 remove unused require 2016-04-04 12:13:25 -07:00
Girish Ramakrishnan
c9ef0056e0 rename getSignedUploadUrl to getBackupUrl 2016-04-04 12:01:47 -07:00
Girish Ramakrishnan
efb228cf5e getRestoreUrl now uses caas restore api 2016-04-04 11:57:29 -07:00
Girish Ramakrishnan
af700827c5 info is not passed anymore 2016-04-04 11:44:24 -07:00
Girish Ramakrishnan
3135783fe3 rename getSignedDownloadUrl to getRestoreUrl 2016-04-04 11:43:56 -07:00
Girish Ramakrishnan
496f530b9f sessionToken is required in credentials (when signing) 2016-04-04 11:23:38 -07:00
Girish Ramakrishnan
f44c2707f0 install swaks in base image 2016-04-04 09:50:19 -07:00
Johannes Zellner
9fbbddc3eb Show setupLink properly 2016-04-04 18:41:51 +02:00
Johannes Zellner
5afb16aa98 Show dialog with setupLink on invite 2016-04-04 18:41:51 +02:00
Johannes Zellner
8f2b0bae5e Receive the resetToken in the webadmin 2016-04-04 18:41:51 +02:00
Johannes Zellner
fcfd1dceac Deliver the resetToken when an invite was sent 2016-04-04 18:41:51 +02:00
Girish Ramakrishnan
d839f0b762 remove redundant session token 2016-04-03 23:23:23 -07:00
Girish Ramakrishnan
16a65fb185 drop configJson
The initial idea was to store exactly where the backups are stored.
But this only causes problems for migrations where the bucket might
change and clones where the prefix (box.id) changes.

Thus, it's best to leave the url creation to the caas side. (That
has to be done in another change)
2016-04-03 22:55:08 -07:00
Girish Ramakrishnan
aaeb355183 add version 0.11.1 changes 2016-04-03 11:34:47 -07:00
Girish Ramakrishnan
c236072c4c add comment 2016-04-02 18:04:58 -07:00
Girish Ramakrishnan
5d92cff638 backup config.json first because tarball takes lot of time and leads to token expiration 2016-04-02 18:01:49 -07:00
Girish Ramakrishnan
1b539b8d22 upload as binary 2016-04-02 17:58:10 -07:00
Girish Ramakrishnan
a21a913f34 delete snapshot on failure path 2016-04-02 17:57:15 -07:00
Girish Ramakrishnan
357f6f0552 use same region as what we uploaded to 2016-04-02 13:32:14 -07:00
Girish Ramakrishnan
b16aa4c007 check for region as well 2016-04-02 13:31:12 -07:00
Girish Ramakrishnan
1fed5ee353 0.11.0 changes 2016-04-01 23:38:35 -07:00
Girish Ramakrishnan
29077abf7c pass back the changeId 2016-04-01 23:21:10 -07:00
Girish Ramakrishnan
f5c7116573 Add 0.10.4 changelog 2016-04-01 13:59:07 -07:00
Girish Ramakrishnan
42fc2d446c do not set the session_token header
this seems to be part of url now in signature v4
2016-04-01 13:26:25 -07:00
Girish Ramakrishnan
9ef04dc67f more 0.10.3 changes 2016-03-31 10:48:08 -07:00
Girish Ramakrishnan
3ea2070cdb pass filename (it is not part of the config!) 2016-03-31 09:53:56 -07:00
Girish Ramakrishnan
fc11484b51 pick region from apiConfig if present 2016-03-31 09:48:38 -07:00
Girish Ramakrishnan
b4ddfa94cc rename to apiConfig 2016-03-31 09:48:13 -07:00
Girish Ramakrishnan
9e7ae1a4f7 we really need a better linter 2016-03-31 09:38:40 -07:00
Girish Ramakrishnan
d27159275b pick region as well 2016-03-31 09:34:57 -07:00
Girish Ramakrishnan
6c2ae756f1 fix usage of backupInfo 2016-03-31 09:23:41 -07:00
Girish Ramakrishnan
92e4433dff make backupdb.get return app backups 2016-03-31 09:12:12 -07:00
Girish Ramakrishnan
c4cbd9f4e4 mailer: check for the correct SPF record 2016-03-31 08:44:31 -07:00
Johannes Zellner
f413afb835 Support email login in simple auth 2016-03-31 16:59:44 +02:00
Johannes Zellner
915c37a72f Add tests for displayName change 2016-03-31 16:24:28 +02:00
Johannes Zellner
1ddb3a58da Also send displayName in simpleAuth 2016-03-31 16:24:28 +02:00
Girish Ramakrishnan
a4aa5bbc59 fix linting 2016-03-31 00:51:38 -07:00
Girish Ramakrishnan
39cc5d07d1 use the backupdb config to determine bucket and prefix 2016-03-31 00:50:56 -07:00
Girish Ramakrishnan
f3a05931df fix typo 2016-03-30 23:43:04 -07:00
Girish Ramakrishnan
df39384056 do not save backup secrets in database 2016-03-30 23:39:48 -07:00
Girish Ramakrishnan
47c5cad239 fix typo 2016-03-30 17:06:50 -07:00
Girish Ramakrishnan
ec380aa41e each change really needs to be in separate line 2016-03-30 15:34:09 -07:00
Girish Ramakrishnan
7d1a663a87 0.10.3 changes 2016-03-30 15:26:52 -07:00
Girish Ramakrishnan
ba69316c14 add note that filename is reused as id 2016-03-30 15:17:04 -07:00
Girish Ramakrishnan
c097651a88 store backup configuration as part of backups table 2016-03-30 15:04:39 -07:00
Girish Ramakrishnan
22b8154a39 0.11.0 changes 2016-03-30 11:39:50 -07:00
Girish Ramakrishnan
9e8179a235 up link is relative 2016-03-29 14:02:53 -07:00
Girish Ramakrishnan
3fbeb2a1c1 more 0.10.2 changes 2016-03-29 13:24:26 -07:00
Girish Ramakrishnan
2c4cf0a505 Download intermediate cert following the 'up' Link 2016-03-29 12:51:05 -07:00
Girish Ramakrishnan
adab544e99 Version 0.10.2 changes 2016-03-28 10:55:20 -07:00
Girish Ramakrishnan
ae8a371597 add adminFqdn in the spf record
For custom domains, we do not set the A record for the naked domain
(because the user might be using it for his own). This means that
a:domain.com will not work.

The solution is to simply use the admin domain.
2016-03-27 23:05:29 -07:00
Girish Ramakrishnan
ead076bd9f add MAIL_SMTP_PASSWORD 2016-03-25 23:14:09 -07:00
Girish Ramakrishnan
f8c683f451 Disallow updating an app with mismatching manifest id
Story so far:
1. App installed from store. appStoreId is set to manifest.id.
2. User installed a custom built app with a custom manifest.id using cloudron install --app <id>. The appStoreId is still set.
3. When we make a new release, it overrides the users install.

The fix (for now) is:
1. Do not allow mismatching ids to start with.
2. When forced, it is allowed but appStoreId is cleared so as to not get any auto updates.

This leaves the user vulnerable to 'cloudron uninstall' simply autoselecting this new app.
For this, they have to simply disable CLI mode for now.

There is also a corner case where:
1. Dev installs from app store
2. Dev compiles from source and updates on top of app store install with --app <id>
3. Dev find out that his installation has auto-updated the next day.
2016-03-25 11:46:25 -07:00
Johannes Zellner
b56bc08e9a Allow to use email and username for ldap bind 2016-03-24 21:03:04 +01:00
Girish Ramakrishnan
daadbfa23f fix wording 2016-03-23 12:00:30 -07:00
Girish Ramakrishnan
a215443c56 do not renew apps without any cert
autoRenew was mistakenly reconfiguring app without a cert (this
is the common case for apps in non-custom domain)
2016-03-23 08:49:08 -07:00
girish@cloudron.io
4e22c6d5ac minor nakedomain fixes 2016-03-21 15:07:10 -07:00
girish@cloudron.io
d43810fea9 add comment on why we add naked domain for custom domains 2016-03-21 13:50:26 -07:00
girish@cloudron.io
f5ab63e8ec naked domain page styling 2016-03-21 13:49:11 -07:00
girish@cloudron.io
b1f172ed17 trim the output string 2016-03-21 08:25:10 -07:00
Girish Ramakrishnan
413f9231b3 fix formatting 2016-03-20 12:12:22 -07:00
Girish Ramakrishnan
11513f9428 send a message for cert renewal status 2016-03-19 20:40:03 -07:00
Girish Ramakrishnan
5042741435 renew cert every 12 hours 2016-03-19 20:30:01 -07:00
Girish Ramakrishnan
75ed9c4a63 Check for key file instead of csr file
1) csr file in older backups got corrupt
2) new key results in a new cert request in LE (for rate limits)
2016-03-19 18:49:55 -07:00
Girish Ramakrishnan
8c36f3aab4 add debug for fallback case 2016-03-19 18:37:05 -07:00
Girish Ramakrishnan
7aa5e8720a 0.10.1 changes 2016-03-19 14:17:28 -07:00
Girish Ramakrishnan
14ef71002f write the DER cert properly into the csr file 2016-03-19 14:07:58 -07:00
Girish Ramakrishnan
ea87841e77 merge fallback cert job into renewal
this is becase we need to reconfigure for the case where we got a
renewed cert (but the app was switched to fallback cert at some point)
2016-03-19 13:54:52 -07:00
Girish Ramakrishnan
091e424c0e Fix description 2016-03-19 13:37:58 -07:00
Girish Ramakrishnan
20629ea078 fix linter errors 2016-03-19 13:22:38 -07:00
Girish Ramakrishnan
b1b6a9ae65 reconfigure admin using configureAdmin 2016-03-19 12:54:11 -07:00
Girish Ramakrishnan
7ddbf7b652 refactor expiry check 2016-03-19 12:50:31 -07:00
Girish Ramakrishnan
3d088aa9c4 fix debug message 2016-03-19 12:31:48 -07:00
Girish Ramakrishnan
f329e0da92 fix typo 2016-03-19 12:14:23 -07:00
Girish Ramakrishnan
a18737882b run more aggressively in test mode 2016-03-19 12:12:39 -07:00
Girish Ramakrishnan
a58a458950 do not abbrev 2016-03-19 12:11:28 -07:00
Girish Ramakrishnan
44c5f84c56 Fix usage of isExpiringSync 2016-03-19 12:06:13 -07:00
Girish Ramakrishnan
d6b92ee301 remove Job suffix 2016-03-19 10:25:19 -07:00
Girish Ramakrishnan
c769a12c45 set the box version for test for pass 2016-03-19 10:23:12 -07:00
Girish Ramakrishnan
017c32c3dd fix certificate renewal
Do the whole acme flow for certificate renewal. the idea here is
simply reuse the key and the csr. In this case, it does not count
as a new certificate issuance.

https://github.com/diafygi/letsencrypt-nosudo/issues/55
2016-03-19 02:44:05 -07:00
Girish Ramakrishnan
5d54c9e668 check my domain for expiry and falling back 2016-03-18 23:43:56 -07:00
Girish Ramakrishnan
adaaca5ceb switch expired certs of domains to use fallback cert
1) nginx won't reload when using expired certs
2) this is the only way the user can use the app now
2016-03-18 23:26:57 -07:00
Girish Ramakrishnan
4a73e1490e Refactor code to take hours 2016-03-18 23:00:02 -07:00
Girish Ramakrishnan
f31a7a5061 use fallback certs if renewal fails 2016-03-17 12:20:02 -07:00
Girish Ramakrishnan
3499a4cc6c move requiresOAuthProxy to nginx
we have 3 levels
    * routes, cron, apptask
    * everything else where everyone calls everyone :-)
    * the db layer
2016-03-17 11:38:29 -07:00
girish@cloudron.io
42796b12dc update safetydance to 0.1.1 2016-03-14 22:50:48 -07:00
girish@cloudron.io
20ac040dde cert: check expiry correctly 2016-03-14 22:50:06 -07:00
girish@cloudron.io
7f2b3eb835 acme: disable renewal via url fetch for now
this does not seem to work.

From cf85854177:

// RenewCertificate attempts to renew an existing certificate.
// Let's Encrypt may return the same certificate. You should load your
// current x509.Certificate and use the Equal method to compare to the "new"
// certificate. If it's identical, you'll need to run NewCertificate and/or
// start a new certificate flow.
2016-03-14 22:22:57 -07:00
girish@cloudron.io
2b562f76ea le: handle renewal upto 30 days in advance 2016-03-14 22:18:43 -07:00
Girish Ramakrishnan
b942033512 acme: debug output the domain 2016-03-14 16:21:03 -07:00
Girish Ramakrishnan
fa4a8c2036 add debug for successful renewal 2016-03-14 15:55:51 -07:00
Johannes Zellner
27febbf1e9 The blue cloud is gone 2016-03-11 16:48:56 -08:00
girish@cloudron.io
8da2eb36cc fix email wording 2016-03-09 18:37:02 -08:00
girish@cloudron.io
cbb34005c6 restoreKey -> filename 2016-03-09 14:23:42 -08:00
girish@cloudron.io
efc1627648 more changes 2016-03-09 09:34:57 -08:00
girish@cloudron.io
f513dcdf3b Add 0.10.0 changelog 2016-03-09 09:29:17 -08:00
girish@cloudron.io
61a52d8888 dist-upgrade to update more aggressively 2016-03-09 09:29:07 -08:00
Johannes Zellner
4cfc187063 Add sender name to admin email 2016-03-09 07:41:50 +01:00
Johannes Zellner
065af03e5f Stop docker proxy in ldap tests 2016-03-09 07:34:44 +01:00
Johannes Zellner
c4eeebdfbe Enable admin change test 2016-03-09 06:18:39 +01:00
Johannes Zellner
b1004de358 Notify admins about newly added admin 2016-03-09 06:16:21 +01:00
Girish Ramakrishnan
fbca0fef38 fix missing assert 2016-03-08 18:51:40 -08:00
Girish Ramakrishnan
d658530e66 fix failing tests 2016-03-08 18:44:51 -08:00
Girish Ramakrishnan
21d4cc9cb2 getAllPaged -> getPaged 2016-03-08 18:10:39 -08:00
Girish Ramakrishnan
e2b7ec3ffd store filename with tar.gz extension 2016-03-08 16:47:53 -08:00
Girish Ramakrishnan
8014e2eaf8 add route to download backup 2016-03-08 16:28:42 -08:00
girish@cloudron.io
a10ed73af2 get zoneName using tldjs 2016-03-08 09:52:13 -08:00
girish@cloudron.io
8b2903015d list app backups from db 2016-03-08 08:57:28 -08:00
girish@cloudron.io
d157bf30f3 remove box backups from the database 2016-03-08 08:52:20 -08:00
girish@cloudron.io
7996b32022 add backups to the database
ideally, these should be done _after_ the backup is successful and not when
the backup url is generated.

we had a discussion on why need backupdb to start with. Some rationale includes:
1. we can use it as a FK constraint (like make sure you delete backups when you delete app)
2. have labels for backups
3. dependancy relation from box backup to apps
4. s3 reverse sort is little painful and requires us to get all items in bulk and sort in JS
   (also requires us to change our backup filename format)
5. any metadata storage requires database

The cons include:
1. s3 and this db go out of sync
2. db entry is useless if s3 file is missing
2016-03-08 08:42:00 -08:00
girish@cloudron.io
4b77703902 export getByAppIdPaged 2016-03-07 17:52:13 -08:00
Girish Ramakrishnan
4dd82d10ad backup: ensure same timestamp for app data and config 2016-03-07 12:13:54 -08:00
Girish Ramakrishnan
83d05c99d3 mount manually instead of fstab because of race
I cannot figure how to make the box-setup.service run before the mounting
of a specific mount point. adding a dep on mount.target locks up the system.
2016-03-07 10:48:09 -08:00
Girish Ramakrishnan
b0acdfb908 use truncate instead of fallocate 2016-03-07 10:44:35 -08:00
Girish Ramakrishnan
b062dab65c mysql also uses the data partition 2016-03-07 10:38:59 -08:00
Girish Ramakrishnan
eadcdeee1c not being mounted is the normal case 2016-03-07 10:37:26 -08:00
Girish Ramakrishnan
9de6f9c1c2 add backupdb
mostly same code as the appstore side
2016-03-07 09:30:44 -08:00
Girish Ramakrishnan
89f54245f7 Add backups table 2016-03-07 09:27:10 -08:00
Girish Ramakrishnan
5fbd1dae30 bump the mysql memory limit
we hit this memory limit often in phabricator backup. this is all
very crude but should suffice for now.
2016-03-05 18:35:28 -08:00
girish@cloudron.io
486ced0946 fix LDAP debug 2016-03-04 17:52:27 -08:00
girish@cloudron.io
d1c1fb8786 fix ldap debug ("ldap" already appears as part of debug) 2016-03-04 17:51:18 -08:00
Johannes Zellner
57ff8b6770 fix feedback test 2016-03-04 22:27:18 +01:00
Johannes Zellner
d12d8f5c0b Properly extract referrers, which contain queries on their own and are not properly encoded 2016-03-03 15:06:14 +01:00
Johannes Zellner
17deac756b Also log app manifest id for alive apps 2016-03-03 09:30:46 +01:00
Johannes Zellner
f7bb3bac98 Log app manifest id in healthmonitor 2016-03-03 09:30:46 +01:00
girish@cloudron.io
744c721000 use docker 1.10.2 (untested) 2016-03-01 10:13:44 -08:00
girish@cloudron.io
0500bae221 install aufs tools
https://github.com/docker/docker/issues/915
2016-03-01 10:13:04 -08:00
girish@cloudron.io
a7b5b49d96 fix language 2016-02-26 10:14:37 -08:00
Johannes Zellner
93ef1919c2 Hide superuser checkbox for the user himself 2016-02-26 18:08:56 +01:00
girish@cloudron.io
254d6ac92e handle singular case as well 2016-02-26 08:43:59 -08:00
girish@cloudron.io
3a12265f42 Do not preallocate data volume
This is not tested but will get tested in the next upgrade
2016-02-26 08:27:13 -08:00
Johannes Zellner
71eeb47f0f Hide groups in user listing if the screen is too tiny 2016-02-26 13:10:29 +01:00
Johannes Zellner
5ea5023d97 Better encapsulate the form related functions in install view 2016-02-26 12:50:05 +01:00
Johannes Zellner
1148e21cd4 Add more changes 2016-02-26 12:29:52 +01:00
Johannes Zellner
e9a2b2a7cf Disable group access control and show info if there are no groups 2016-02-26 11:59:18 +01:00
Johannes Zellner
7a34f40611 Allow to specify accessRestrictions on app install 2016-02-26 11:47:20 +01:00
Johannes Zellner
c630de1003 Fetch groups in appstore.js 2016-02-26 11:24:01 +01:00
Johannes Zellner
74da8f5af8 Add 0.9.3 changes 2016-02-26 11:19:45 +01:00
girish@cloudron.io
b758be5ae2 0.9.2 changes 2016-02-26 11:17:36 +01:00
Johannes Zellner
c585be4eec Adjust group length error message 2016-02-26 11:07:31 +01:00
Johannes Zellner
3ebc569438 Avoid using superuser in the ui, but describe what it is 2016-02-26 11:03:39 +01:00
Johannes Zellner
5a2cf3cbfe Move superuser checkbox at the bottom of the form 2016-02-26 11:02:43 +01:00
Johannes Zellner
715c5f9f61 Do not set admin group multiple times 2016-02-26 10:56:33 +01:00
Johannes Zellner
6843fda601 Show group names and make sure we don't break layout right away 2016-02-26 10:53:41 +01:00
Johannes Zellner
a78f3b1db3 Give more space to some views 2016-02-26 10:52:47 +01:00
girish@cloudron.io
1419108a86 umount is for unmounting 2016-02-25 20:13:16 -08:00
girish@cloudron.io
7a8b457ce9 truncate to shrink the file if required 2016-02-25 19:26:46 -08:00
girish@cloudron.io
10967ff8ce allow 1.2 times RAM
This is basically to allow 2 phabricators and another small app with
no warning on a 4gb droplet :-)
2016-02-25 18:34:28 -08:00
girish@cloudron.io
1fdfd3681c Revert "Display group ids"
This reverts commit d80ce25363061f95cb14e223efd4ab9828739eea.

Didn't mean to commit this
2016-02-25 18:11:25 -08:00
girish@cloudron.io
187d4f9ca2 round memory to nearest GB
os.totalmem returns some close-to-GB number. Because of this two
apps with 2GB don't install on 4GB.
2016-02-25 17:19:39 -08:00
girish@cloudron.io
6b67e64bf1 Display group ids 2016-02-25 16:33:58 -08:00
girish@cloudron.io
7ae6061d72 Edit -> Save 2016-02-25 15:24:50 -08:00
Johannes Zellner
e96b9c3e3f Give the user the perception he gets what he pays for 2016-02-26 00:18:47 +01:00
Johannes Zellner
c9ca05a703 Do not offer admin group for access restriction
The same can be achieved with a new group and it just
keeps the superuser/admin out of the way here. In any case
admins can always access all apps.
2016-02-26 00:13:09 +01:00
Johannes Zellner
23e5bed247 Fix the group numbering to ignore admin group 2016-02-26 00:02:38 +01:00
Johannes Zellner
bae0d728b3 Only show group count to not break layout and allow quickedit 2016-02-26 00:01:25 +01:00
Johannes Zellner
5cd1c7d714 add hand selector 2016-02-26 00:01:01 +01:00
Johannes Zellner
d430e902bf Separate superuser checkbox from the other groups in user edit 2016-02-25 23:20:55 +01:00
Johannes Zellner
4fb89de34f Remove 'this is you' 2016-02-25 22:29:06 +01:00
Johannes Zellner
7cd3bb31e1 Add new support type for failing erroring apps 2016-02-25 21:38:37 +01:00
Girish Ramakrishnan
2857158543 do not set memoryLimit 2016-02-25 11:05:19 -08:00
Johannes Zellner
82a347ea4b Add tooltips for superusers 2016-02-25 16:07:31 +01:00
Johannes Zellner
b5c7f978a2 Do not show admin group in group listing 2016-02-25 15:54:30 +01:00
Johannes Zellner
625da29fce Show admins with an icon instead of a group tag 2016-02-25 15:53:36 +01:00
Johannes Zellner
b82b183df6 Show alternative text for non admins, when no apps are available for this user 2016-02-25 15:38:46 +01:00
Johannes Zellner
ce36fadf2b Fix bug for non admins to view the appstore 2016-02-25 15:34:44 +01:00
Johannes Zellner
2429599733 Change text from installed to your applications 2016-02-25 15:34:33 +01:00
Johannes Zellner
261a0a1728 Add account ui to change displayName 2016-02-25 15:09:52 +01:00
Johannes Zellner
d8def61f67 Encapsulate the business logic in the account controller 2016-02-25 14:58:26 +01:00
Johannes Zellner
2732af24c1 Some code cleanups and bugfixes for the accounts view 2016-02-25 14:46:53 +01:00
Johannes Zellner
3d48da0e8d Remove unused function in client to change email 2016-02-25 14:34:35 +01:00
Johannes Zellner
d3b8bd1314 Remove password field for user email change 2016-02-25 14:34:16 +01:00
Johannes Zellner
f600ebcf19 Remove password entry from user edit form 2016-02-25 14:15:48 +01:00
Johannes Zellner
160467e199 Do not require password for user profile changes 2016-02-25 14:03:42 +01:00
Johannes Zellner
384c410e7c Do not require a password for user profile changes 2016-02-25 13:54:44 +01:00
Johannes Zellner
84c4187fa9 Test normal users accessing the user api 2016-02-25 13:53:18 +01:00
Johannes Zellner
4f7fd9177c Allow user details only for the same user or admins 2016-02-25 13:44:53 +01:00
Johannes Zellner
b5b0ab7475 Require admin rights for user listing 2016-02-25 13:43:15 +01:00
Johannes Zellner
a0d7406b3c Do not allow normal users to get group listings or details 2016-02-25 13:34:01 +01:00
Johannes Zellner
7165be0513 Warn the user about installing too many apps, but give an override button 2016-02-25 12:41:15 +01:00
Johannes Zellner
9c995277f7 Fixup the apps unit tests 2016-02-25 12:20:18 +01:00
Johannes Zellner
aa693e529b Only list apps where a user has access to 2016-02-25 12:20:11 +01:00
Johannes Zellner
63013c7297 Just check for .admin flag in the user object 2016-02-25 11:42:25 +01:00
Johannes Zellner
c8db6419d8 Admins are not special cased in apps.js app listing
This is done in the route
2016-02-25 11:41:14 +01:00
Johannes Zellner
93c1ddd982 Always amend the admin flag for further use 2016-02-25 11:40:48 +01:00
Johannes Zellner
df102ec374 Add basic getAllByUser() app tests 2016-02-25 11:28:45 +01:00
Johannes Zellner
9688e4c124 Add apps.getAllByUser() 2016-02-25 11:28:29 +01:00
Johannes Zellner
00d277b1c3 Make the error dialog generic not only for app install errors 2016-02-24 18:36:40 +01:00
Johannes Zellner
0fb44bfbc1 Forward the error.message instead of making a new Error object
That leads to only Internal Error
2016-02-24 18:12:31 +01:00
Johannes Zellner
c167bd8996 Set error in installationProgress also on uninstallation errors 2016-02-24 17:53:21 +01:00
Johannes Zellner
a3737c3797 Report access denied errors in route53 backend 2016-02-23 17:29:28 +01:00
Johannes Zellner
8fcb0b46a5 add 0.9.1 changes 2016-02-21 14:51:51 +01:00
Johannes Zellner
f5189e0a56 Force the user to set at least one access restriction group 2016-02-19 18:02:51 +01:00
Johannes Zellner
86f14b0149 Thanks JS for being so value focused... 2016-02-19 17:23:09 +01:00
Johannes Zellner
30913006e3 Remove all occurances of oauthProxy in the webadmin 2016-02-19 16:50:25 +01:00
Johannes Zellner
81bd4f2ea5 Remove console.log() 2016-02-19 16:29:40 +01:00
Johannes Zellner
351ddcb218 Fixup the unit tests 2016-02-19 16:29:29 +01:00
Johannes Zellner
dd18f9741a Dynamically detect oauth proxy needs in apptask 2016-02-19 16:18:47 +01:00
Johannes Zellner
cdce6e605d Adjust to new apps api 2016-02-19 16:14:02 +01:00
Johannes Zellner
d4480ec407 Remove oauthProxy usage in the database wrapper 2016-02-19 16:12:58 +01:00
Johannes Zellner
85c92ab0b4 Remove oauthProxy from the apps.js api 2016-02-19 16:07:49 +01:00
Johannes Zellner
230c24d6c6 Adjust the route unit tests, to remove oauthProxy 2016-02-19 16:01:08 +01:00
Johannes Zellner
07c935dfec Remove oauthProxy from client side api wrapper 2016-02-19 16:00:48 +01:00
Johannes Zellner
eab3bda8e1 Remove oauthProxy from the apps rest routes 2016-02-19 15:54:01 +01:00
Johannes Zellner
f731c1ed0b Dynamically detect if an oauth proxy should be used for an app 2016-02-19 15:44:15 +01:00
Johannes Zellner
edec3601f4 Do not rely on oauthProxy property of app object in nginx configuration code 2016-02-19 15:43:39 +01:00
Johannes Zellner
9e87fd0440 Add apps.requiresOAuthProxy() 2016-02-19 15:03:36 +01:00
Johannes Zellner
8cb304e1c9 Ensure app ordering by location 2016-02-19 13:36:39 +01:00
Johannes Zellner
a24335d68b Remove oauth proxy setting ui 2016-02-19 12:04:05 +01:00
Johannes Zellner
78d1ed7aa5 Special case the admin button in apps access control UI 2016-02-18 18:26:24 +01:00
Johannes Zellner
deb30e440a Disable ejs debug flag 2016-02-18 18:00:23 +01:00
Johannes Zellner
86ef9074b1 Add access restriction tests for ldap auth 2016-02-18 17:40:53 +01:00
Johannes Zellner
1a13128ae1 Ensure accessRestriction is either an object or an empty string 2016-02-18 17:28:00 +01:00
Johannes Zellner
b41642552d The ldap property is part of req.connection 2016-02-18 16:40:30 +01:00
Johannes Zellner
f5570c2e63 Enable the apps group access control ui 2016-02-18 16:14:45 +01:00
Johannes Zellner
b0d11ddcab Adhere to access control on ldap user bind 2016-02-18 16:04:53 +01:00
Johannes Zellner
804464c304 Add apps.getByIpAddress() 2016-02-18 15:43:46 +01:00
Johannes Zellner
ecf7f442ba Add docker.getContainerIdByIp() 2016-02-18 15:39:27 +01:00
Johannes Zellner
9ddd3aeb07 Show app id and fix naked domain in debugApp() 2016-02-18 12:51:25 +01:00
Johannes Zellner
864e3ff217 Give form feedback if group name is invalid
Fixes #583
2016-02-15 13:09:58 +01:00
Johannes Zellner
9bf1fe3b7d Show naked_domain for healthtask summary 2016-02-14 17:42:52 +01:00
Johannes Zellner
b32a48c212 Add changelog for 0.9.0 2016-02-14 13:19:12 +01:00
Johannes Zellner
22a3dd7653 Disable advanced accessControl ui
This is working, from a configuration standpoint.
However not all auth methods support this yet, so
we hide it until that is done, otherwise it is just
confusing
2016-02-14 13:15:09 +01:00
Johannes Zellner
132b463e0a Hide memoryLimit ui 2016-02-14 13:14:03 +01:00
Johannes Zellner
7aefe5226a Properly layout the group labels 2016-02-14 13:12:04 +01:00
Johannes Zellner
656c1bfd3a Make Admin group members visible 2016-02-14 13:11:49 +01:00
Johannes Zellner
e237b609f5 Make admin group buttons red 2016-02-14 13:08:50 +01:00
Johannes Zellner
057b9e954e Support btn-admin 2016-02-14 13:08:40 +01:00
Johannes Zellner
f79c00d9be Special case admin group button in user profile 2016-02-14 12:51:58 +01:00
Johannes Zellner
5f96d862ab Move default memory limit to constants.js 2016-02-14 12:13:49 +01:00
Johannes Zellner
79199bf023 Ensure we stash and restore the memoryLimit 2016-02-14 12:10:22 +01:00
girish@cloudron.io
beec4dddca my -> our 2016-02-13 11:22:47 -08:00
Johannes Zellner
7c243cb219 Warn the user on group deletion if it still has members 2016-02-13 12:42:41 +01:00
Johannes Zellner
754e33af2a do not allow removing the admin group 2016-02-13 12:42:41 +01:00
Johannes Zellner
63cab7d751 Allow non-empty groups to be deleted 2016-02-13 12:42:41 +01:00
Johannes Zellner
503714a10b Special case admin group in group listing 2016-02-13 12:42:41 +01:00
girish@cloudron.io
ada5be6ae0 Add 0.9.0 changes 2016-02-13 03:27:21 -08:00
girish@cloudron.io
2112494b43 bump mysql image version 2016-02-13 03:26:29 -08:00
girish@cloudron.io
c0b45ad71e update manifestformat to 2.3.0 2016-02-12 17:40:00 -08:00
girish@cloudron.io
5669d387af check app state before exec 2016-02-12 12:32:58 -08:00
Johannes Zellner
957f20a9a8 Always store the memory limit in the app db record and adjust on update if needed 2016-02-11 18:14:16 +01:00
Johannes Zellner
71bfc1cbda Ensure we never go below minimum memoryLimit 2016-02-11 18:13:42 +01:00
Johannes Zellner
489ea3a980 Add memoryLimit validation 2016-02-11 17:39:15 +01:00
Johannes Zellner
8c6f655628 Ensure we deal with byte values for memoryLimit 2016-02-11 17:29:00 +01:00
Johannes Zellner
75d22d7988 Introduce memoryLimit to apps routes 2016-02-11 17:00:21 +01:00
Johannes Zellner
a7bf043a9e Improve memory limit ui 2016-02-11 16:31:11 +01:00
Johannes Zellner
402385faca Remove unused linter options 2016-02-11 14:35:51 +01:00
Johannes Zellner
cdd82fa456 Add access restriction by group ui to app configure dialog 2016-02-11 14:14:51 +01:00
Johannes Zellner
2f7d99f3f6 Do not allow to remove the user from the admin group 2016-02-11 13:43:02 +01:00
Johannes Zellner
e4799991ec Remove reference to non-existing css selector form-signin 2016-02-11 12:53:35 +01:00
Johannes Zellner
66167e74dc Cleanup the user forms 2016-02-11 12:50:02 +01:00
Johannes Zellner
5643d49bef Check if user deletion actually affected a row 2016-02-11 12:07:43 +01:00
Johannes Zellner
81ec26e45c Ensure we can delete users which belong to a group 2016-02-11 12:02:35 +01:00
Johannes Zellner
72c5ebcc06 Add user api tests for adding/removing from admin group 2016-02-11 11:39:19 +01:00
Johannes Zellner
ecf7575dd3 UserError.NOT_ALLOWED is not unused 2016-02-11 11:32:48 +01:00
Johannes Zellner
98a7f44dc1 Check for last admin not required anymore
This is now prevented by the fact that an admin
cannot remove itself from the admin group. There
remains a race, just like before, where two admins could
trigger an admin group removal of the other admin in parallel
and the calls are in a state after admin flag check of
the used tokens. This can only be prevented with a db constraint
in the end.
2016-02-11 11:30:21 +01:00
Johannes Zellner
5fce9c8d1f Do not allow an admin remove itself from admins group 2016-02-11 11:29:04 +01:00
Johannes Zellner
0ea89fccb8 Remove admin api route tests 2016-02-11 11:26:35 +01:00
Johannes Zellner
2c2922d725 Make tests succeed for now
We still have to bring back the sending of email when admins are changed
2016-02-11 11:26:35 +01:00
Johannes Zellner
fbeefeca7d setGroups() has no result 2016-02-11 11:26:35 +01:00
Johannes Zellner
163ceef527 Remove the admin toggle route 2016-02-11 11:26:35 +01:00
Girish Ramakrishnan
db5cc1f694 dropping groupMembers table makes not much sense 2016-02-10 08:57:06 -08:00
Johannes Zellner
a3b9a7365c No need to check for admin, the whole view is admin only 2016-02-10 17:35:40 +01:00
Johannes Zellner
213b2a2802 show groups of each user 2016-02-10 17:34:29 +01:00
Johannes Zellner
229d09bb9e Give the group buttons some space 2016-02-10 17:26:51 +01:00
Johannes Zellner
f127680c8c Fixup the group delete dialog title 2016-02-10 17:23:42 +01:00
Johannes Zellner
f767f7f1b9 We do not actually allow group edit 2016-02-10 17:22:04 +01:00
Johannes Zellner
acb1afa955 Make delete action the last one 2016-02-10 17:20:24 +01:00
Johannes Zellner
d132109925 Fixup the modal dialog css selector 2016-02-10 17:16:50 +01:00
Johannes Zellner
820e417026 angular requires special treatment for DELETE 2016-02-10 17:12:58 +01:00
Johannes Zellner
94bd0c606b Add group removal ui 2016-02-10 16:59:24 +01:00
Johannes Zellner
9a8328e6db We should really use camelCase 2016-02-10 16:43:23 +01:00
Johannes Zellner
5c75d64a07 Do not forget to reset the busy state 2016-02-10 16:41:32 +01:00
Johannes Zellner
a8001995c8 Add business logic for group adding 2016-02-10 16:37:58 +01:00
Johannes Zellner
9ba4d52fb7 Add Client.createGroup() 2016-02-10 16:37:46 +01:00
Johannes Zellner
0e613a1cab Ensure focus 2016-02-10 16:25:38 +01:00
Johannes Zellner
cf3d503a74 Add group add form 2016-02-10 16:24:25 +01:00
Johannes Zellner
1ab46a96f9 Reset the user edit form password error 2016-02-10 15:18:36 +01:00
Johannes Zellner
1a3164ef32 Add ui components for group management 2016-02-10 15:15:09 +01:00
Johannes Zellner
bd62efcff5 That api is of course a PUT api 2016-02-10 15:01:51 +01:00
Johannes Zellner
7fc37b7c70 Allow admins to edit other users 2016-02-10 14:48:54 +01:00
Johannes Zellner
8ddccae15a Save the groups on user edit 2016-02-10 14:47:49 +01:00
Johannes Zellner
675d7c8730 Add Client.setGroups() 2016-02-10 14:47:35 +01:00
Johannes Zellner
ba35d4a313 remove unused class 2016-02-10 14:40:29 +01:00
Johannes Zellner
c1280ddcc2 Make group buttons toggle able 2016-02-10 14:39:49 +01:00
Johannes Zellner
36ded4c06a Group -> Groups 2016-02-10 14:26:17 +01:00
Johannes Zellner
9fb276019e Add ui elements for group selection 2016-02-10 14:26:04 +01:00
Johannes Zellner
19982b1815 Add Client.getGroups() 2016-02-10 14:25:08 +01:00
Johannes Zellner
459d5b8f60 Adjust user action buttons 2016-02-10 14:07:37 +01:00
Johannes Zellner
8ba5dc2352 Fix indentation 2016-02-10 13:55:49 +01:00
Johannes Zellner
8c73a7c7c2 Send admin flag with user profile 2016-02-10 13:35:16 +01:00
Johannes Zellner
e78dd41e88 Replace deprecated gulp-minify-css with gulp-cssnano 2016-02-10 13:13:08 +01:00
Johannes Zellner
59ecb056d0 Fixup the oauth tests to set memoryLimit 2016-02-10 12:49:02 +01:00
Johannes Zellner
11b17fec3a Ensure groupMembers table is created first 2016-02-10 12:38:00 +01:00
Johannes Zellner
5ea81d0fd3 Ensure default minimum memory limit 2016-02-10 12:30:19 +01:00
Johannes Zellner
19cbd1f394 Ensure we never go below 256mb memoryLimit 2016-02-10 12:30:19 +01:00
Johannes Zellner
1b7265f866 Fixup the merge 2016-02-10 12:28:57 +01:00
Johannes Zellner
1cdb64e78d Use memoryLimit from app object instead of manifest in docker.js 2016-02-10 12:25:26 +01:00
Johannes Zellner
eec8708249 Set memory limit on app installation to the default or the one specified in the manifest 2016-02-10 12:25:26 +01:00
Johannes Zellner
ab003bf81f adjust appdb.js and unit tests to support memoryLimit 2016-02-10 12:25:26 +01:00
Johannes Zellner
2d60901b6e memoryLimit has to be BIGINT 2016-02-10 12:25:26 +01:00
Johannes Zellner
3fc9bde4f4 Make memoryLimit step in 8s 2016-02-10 12:25:26 +01:00
Johannes Zellner
4fc0df31fe Add apps.memoryLimit 2016-02-10 12:25:26 +01:00
Girish Ramakrishnan
3ac326e766 try upto 5 minutes to download the tarball
DO/S3 can be really slow at times
2016-02-09 21:07:03 -08:00
Girish Ramakrishnan
4770f9ddf6 add hasAccessTo tests 2016-02-09 21:07:03 -08:00
girish@cloudron.io
7e60fd554a add some failing groups for good measure 2016-02-09 18:55:42 -08:00
girish@cloudron.io
c1cd7ac129 fix typo 2016-02-09 18:53:14 -08:00
girish@cloudron.io
aab62263a7 add accessRestriction group test in oauth2 2016-02-09 18:52:27 -08:00
girish@cloudron.io
79889a0aac test simple auth accessRestriction 2016-02-09 18:40:20 -08:00
Girish Ramakrishnan
f413bfb3a0 Add route to set the users groups 2016-02-09 16:43:32 -08:00
Girish Ramakrishnan
2b0791f4a3 rename vars 2016-02-09 16:19:00 -08:00
Girish Ramakrishnan
d95339534f rename test file 2016-02-09 16:17:01 -08:00
Girish Ramakrishnan
82cf667f3b Add groups route tests 2016-02-09 15:26:46 -08:00
girish@cloudron.io
e20b3f75e4 Handle NOT_EMPTY group deletion 2016-02-09 13:45:28 -08:00
girish@cloudron.io
6cca7b3e0e initial rest API for groups 2016-02-09 13:34:36 -08:00
girish@cloudron.io
0b814af206 Add api to get groups 2016-02-09 13:33:30 -08:00
girish@cloudron.io
bfdabf9272 check groups property in accessRestriction 2016-02-09 13:03:52 -08:00
girish@cloudron.io
60988ff7f3 make hasAccessTo take a callback 2016-02-09 12:48:21 -08:00
girish@cloudron.io
3649fd0c31 drop the gid: prefix for group id
like the username, id and name is same in groups.
2016-02-09 12:28:50 -08:00
girish@cloudron.io
00c5aa041f clear database in backups test 2016-02-09 12:21:36 -08:00
girish@cloudron.io
4569b67007 make users and admins groups as reserved 2016-02-09 12:16:30 -08:00
girish@cloudron.io
1fb26bc441 make startAppTask take a callback 2016-02-09 12:14:04 -08:00
girish@cloudron.io
e6d23a9701 stop previous task explicitly
there is a race:
1. task is running
2. new task is created overwriting the installationState
3. new task kills the old task of step 1. this results in installationState getting overwritten by 'error' because of the sigkill
4. new task that is launched loses the installationState that was step in 2.
2016-02-09 12:09:20 -08:00
girish@cloudron.io
0785266741 kill immediately. by default, it sends SIGTERM 2016-02-09 12:03:21 -08:00
girish@cloudron.io
e752949752 make all tests work after group changes 2016-02-09 11:29:32 -08:00
girish@cloudron.io
199eb2b3e1 set the admin flag in user object 2016-02-09 09:25:17 -08:00
Girish Ramakrishnan
49cbea93fb fix ldap test 2016-02-09 08:52:16 -08:00
girish@cloudron.io
451c410547 make user test pass 2016-02-08 21:17:21 -08:00
girish@cloudron.io
f6541720c4 pass owner flag in createUser 2016-02-08 21:05:02 -08:00
girish@cloudron.io
5e5435e869 send email for userAded 2016-02-08 20:51:20 -08:00
girish@cloudron.io
0d4f113d7d add groupIds to user object 2016-02-08 20:38:50 -08:00
girish@cloudron.io
14fab0992f make user test mostly work 2016-02-08 16:53:20 -08:00
girish@cloudron.io
d7eb004bc1 remove admin arg from user.create 2016-02-08 16:36:45 -08:00
girish@cloudron.io
c34f3ee653 null invitor is ok 2016-02-08 16:36:26 -08:00
girish@cloudron.io
96d595de39 fix database test 2016-02-08 16:25:29 -08:00
girish@cloudron.io
b1f4508313 remove admin references from userdb 2016-02-08 16:18:51 -08:00
girish@cloudron.io
52ce59faaf createUser does not take admin anymore 2016-02-08 16:14:43 -08:00
girish@cloudron.io
85085ae0b2 implement getAllAdmins based on groups 2016-02-08 16:10:44 -08:00
girish@cloudron.io
c14cf9c260 migrate admin flag to group membership 2016-02-08 16:07:44 -08:00
girish@cloudron.io
a47c6f0774 make requires alphabetical 2016-02-08 15:17:54 -08:00
girish@cloudron.io
888955bd9b add groups.isMember 2016-02-08 10:53:01 -08:00
girish@cloudron.io
6abf5e2c44 group members: add/remove/get 2016-02-08 10:48:21 -08:00
girish@cloudron.io
b1935c3550 restrict group names 2016-02-08 09:41:25 -08:00
girish@cloudron.io
e39d7750c5 add group membership table 2016-02-08 08:55:37 -08:00
girish@cloudron.io
1d83a48a1a make group id distinct from name 2016-02-08 08:44:18 -08:00
Girish Ramakrishnan
802ee6c456 more group tests 2016-02-07 20:49:55 -08:00
Girish Ramakrishnan
278085ba22 initial tests for adding group 2016-02-07 20:34:05 -08:00
Girish Ramakrishnan
b945a8a04c add groups model and db code 2016-02-07 20:25:08 -08:00
Girish Ramakrishnan
7ef92071c5 add groups table 2016-02-07 20:11:37 -08:00
girish@cloudron.io
c16ab95193 reword CLI message 2016-02-04 23:13:29 -08:00
Girish Ramakrishnan
c5e2d9a9cc download new app image as the first thing in update
this will reduce downtime.
2016-02-04 22:49:22 -08:00
Girish Ramakrishnan
07df76b25e Change developer mode text to cli 2016-02-04 22:49:22 -08:00
girish@cloudron.io
5b264565db set the target for cloudron links 2016-02-04 18:22:53 -08:00
girish@cloudron.io
a3561bd040 Make Cloudron a link and fix copyright 2016-02-04 17:47:31 -08:00
girish@cloudron.io
6e4f47e807 0.8.1 changes 2016-02-04 15:20:40 -08:00
Johannes Zellner
471965dc66 Add initial still disabled slider to app configure 2016-02-04 16:45:00 +01:00
Johannes Zellner
3b109ea2e7 Include bootstrap-slider in our angular app 2016-02-04 16:44:40 +01:00
Johannes Zellner
6011526d5e Add bootstrap-slider assets 2016-02-04 16:44:03 +01:00
Johannes Zellner
1395d2971b Send app update changelog with email
Fixes #579
2016-02-04 15:31:40 +01:00
Johannes Zellner
e9d6badae7 Changelog is just a flat text, no array 2016-02-04 15:17:18 +01:00
Johannes Zellner
65ddc7f24c Show changelog in app update ui 2016-02-04 15:17:18 +01:00
girish@cloudron.io
fa871c7ada some apache configs require Host header to be set 2016-02-03 20:18:59 -08:00
girish@cloudron.io
8652d6c136 Add 0.8.0 changes 2016-02-02 08:51:13 -08:00
girish@cloudron.io
16d976a145 use multidb version of mysql addon 2016-02-02 08:46:09 -08:00
girish@cloudron.io
fa1f5cc454 call the multi methods if multipleDatabases is set 2016-02-02 08:41:41 -08:00
Johannes Zellner
84c3b367d5 Actually wait for apps to be stopped 2016-01-29 17:24:18 +01:00
Johannes Zellner
793aa6512d Rework parts of the apps tests to be more reliable
No need to create and tear down the addons everytime.
Docker proxy can also be just injected once.
Wait explicitly for apptasks to be terminated.
2016-01-29 16:27:37 +01:00
Johannes Zellner
98ab99ab34 Add callback to config._reset() for convenience 2016-01-29 16:27:04 +01:00
Johannes Zellner
24a826bdd1 Reduce from 20 to 10 sec wait for addons
Since that is reliable already with 10 on my laptop
I assume this is fine for any other more modern machine
2016-01-29 16:26:15 +01:00
Johannes Zellner
05245f5fc7 Add a way to wait and stop pending tasks 2016-01-29 16:25:31 +01:00
Johannes Zellner
b718c8d044 Cleanup config before and after apps tests 2016-01-29 14:30:40 +01:00
Johannes Zellner
2888a85081 Remove cloudron side migrate api
This is some old api when we had a migrate view in the webadmin
This is entirely handled on the appstore for now.
2016-01-29 14:17:33 +01:00
Johannes Zellner
307262244a Also cleanup the config file on config._reset() 2016-01-29 14:17:10 +01:00
Johannes Zellner
9a875634f8 Improve error message in mailer 2016-01-29 12:40:34 +01:00
Johannes Zellner
4af33486ae Add missing fi in shell script 2016-01-29 12:31:59 +01:00
Johannes Zellner
befa898f18 Do not show app version, but make it available as a tooltip on the title for us 2016-01-29 12:29:35 +01:00
Johannes Zellner
18525e1236 Show app website in appstore install dialog
Fixes #580
2016-01-29 12:26:42 +01:00
Johannes Zellner
28ffd01cf4 Invoke BACKUP_APP_CMD with the additional backupConfig.url 2016-01-29 11:55:52 +01:00
Johannes Zellner
09c7aa4440 Remove whitespace 2016-01-29 11:54:58 +01:00
Johannes Zellner
ea4862d351 Strictly assert on app 2016-01-29 11:54:40 +01:00
Johannes Zellner
3e4d62329e Also copy the backup config json 2016-01-29 11:54:15 +01:00
Johannes Zellner
d12366576b Add backup config url to backupapp.sh 2016-01-29 11:44:24 +01:00
Johannes Zellner
7b1d906494 Add backups.getAppBackupConfigUrl() 2016-01-29 11:44:24 +01:00
Johannes Zellner
0972c88b8b Set the correct environment for the installer.sh 2016-01-28 16:36:34 +01:00
girish@cloudron.io
9464a26a7e put version in the mail 2016-01-27 10:35:48 -08:00
Johannes Zellner
10f1ad5cfe Until we can distinguish by the return data, mark both fields red 2016-01-26 19:44:33 +01:00
Johannes Zellner
fd11eb8da0 Ensure the user add form is reset on show 2016-01-26 19:40:57 +01:00
girish@cloudron.io
62d5e99802 Version 0.7.2 changes 2016-01-26 10:34:09 -08:00
girish@cloudron.io
48305f0e95 call retire after sending off the http response 2016-01-26 10:29:32 -08:00
girish@cloudron.io
8170b490f2 add retire.sh
this is a sudo script that retires the box
2016-01-26 09:37:25 -08:00
girish@cloudron.io
072962bbc3 add internal route to retire 2016-01-26 08:46:48 -08:00
girish@cloudron.io
33bc1cf7d9 Add cloudron.retire 2016-01-26 08:39:42 -08:00
Johannes Zellner
85df9d1472 We use 'wrong password' now everywhere 2016-01-26 16:09:14 +01:00
Johannes Zellner
109ba3bf56 Do not perform any client side password validation in user delete form 2016-01-26 16:06:42 +01:00
Johannes Zellner
8083362e71 Ensure we enforce the password pattern for setup view 2016-01-26 15:55:02 +01:00
Johannes Zellner
9b4c385a64 Ensure we send proper password requirements on password reset 2016-01-26 15:21:03 +01:00
girish@cloudron.io
ee9c8ba4eb fix dead comment 2016-01-25 20:15:54 -08:00
girish@cloudron.io
000a64d54a remove installer.retire 2016-01-25 17:52:35 -08:00
girish@cloudron.io
eba74d77a6 remove installer certs
the installer is no more a public server
2016-01-25 17:47:57 -08:00
girish@cloudron.io
714a1bcb1d installer: remove provisioning server
the box will retire itself
2016-01-25 17:46:24 -08:00
girish@cloudron.io
02d17dc2e4 fix typo 2016-01-25 16:51:14 -08:00
girish@cloudron.io
4b54e776cc reset update info after short-circuit 2016-01-25 16:46:54 -08:00
girish@cloudron.io
ba6f05b119 Clear the progress for web ui to work 2016-01-25 16:35:03 -08:00
girish@cloudron.io
1d9ae120dc strip prerelease from box version and not the released version 2016-01-25 16:27:22 -08:00
girish@cloudron.io
3ce841e050 add test for checkDiskSpace 2016-01-25 16:03:12 -08:00
girish@cloudron.io
436fc2ba13 checking capacity does not work well for /
Just check if we have around 1.25GB left.

/root (data image file) is 26G
/home (our code) is 1G
/apps.swap is 2G
/backup.swap is 1G
/var is 6.2G (docker)

Rest of the system is 1.5G

Fixes #577
2016-01-25 15:56:46 -08:00
girish@cloudron.io
77d652fc2b add test for config.setVersion 2016-01-25 15:12:22 -08:00
girish@cloudron.io
ac3681296e short-circuit updates from prerelease to release version
Fixes #575
2016-01-25 14:48:12 -08:00
girish@cloudron.io
5254d3325f add comment on fields on box update info object 2016-01-25 13:57:59 -08:00
girish@cloudron.io
ce0a24a95d comment out public graphite paths 2016-01-25 12:51:37 -08:00
Johannes Zellner
1bb596bf58 Add changes for 0.7.1 2016-01-25 16:40:57 +01:00
Johannes Zellner
c384ac6080 Use local cache when looking up apps 2016-01-25 16:25:25 +01:00
Johannes Zellner
61c2ce0f47 Keep appstore url bar up-to-date with the selected app
This allows sharing links directly

Fixes #526
2016-01-25 16:21:44 +01:00
Johannes Zellner
7a71315d33 Adjust angular's to allow non reload path changes 2016-01-25 16:21:20 +01:00
Johannes Zellner
0a658e5862 Ensure the focus is setup correctly 2016-01-25 15:30:29 +01:00
Johannes Zellner
5f8c99aa0e There is no body here 2016-01-25 15:29:52 +01:00
Johannes Zellner
4c6f1e4b4a Allow admins or users to operate on themselves 2016-01-25 15:29:52 +01:00
Johannes Zellner
226ae627f9 Move updateUser() to where it belongs 2016-01-25 15:29:52 +01:00
Johannes Zellner
27a02aa918 Make user edit form submit with enter 2016-01-25 15:29:52 +01:00
Johannes Zellner
3c43503df8 Add businesslogic for user edit form 2016-01-25 14:58:12 +01:00
Johannes Zellner
35c926d504 Ensure we actually update the correct user, not the user holding the token 2016-01-25 14:58:02 +01:00
Johannes Zellner
ea18ca5c60 Fix copy and paste error in the user add form 2016-01-25 14:46:51 +01:00
Johannes Zellner
55a56355d5 Add email change fields to user edit form 2016-01-25 14:28:47 +01:00
Johannes Zellner
dc83ba2686 Require displayName in updateUser() 2016-01-25 14:26:42 +01:00
Johannes Zellner
62615dfd0f Make email in user change optional 2016-01-25 14:12:09 +01:00
Johannes Zellner
a6998550a7 Add displayName change unit tests 2016-01-25 14:08:35 +01:00
Johannes Zellner
3b199170be Support changing the displayName 2016-01-25 14:08:11 +01:00
Johannes Zellner
1f93787a63 Also send displayName for users 2016-01-25 13:36:51 +01:00
Johannes Zellner
199c5b926a Show tooltip for user action buttons 2016-01-25 13:31:49 +01:00
Johannes Zellner
d9ad7085c3 Shorten the user action buttons 2016-01-25 13:28:48 +01:00
Johannes Zellner
df12f31800 Add ui components for user edit 2016-01-25 13:28:30 +01:00
Johannes Zellner
ad205da3db Match any loop back device
This is just a quick fix and will potentially match all
loop* devices.
2016-01-25 13:06:38 +01:00
Johannes Zellner
34aab65db3 Use the first part of the dn to get the common name in ldap
It is no must to have the first part named 'cn' but the first
part is always the id we want to verify
2016-01-25 11:31:57 +01:00
Johannes Zellner
63c06a508e Make /api available on just the IP
We might want to also show something else than
the naked domain placeholder page when just
accessing the ip
2016-01-24 12:08:10 +01:00
Girish Ramakrishnan
a2899c9c65 add appupdate tests 2016-01-24 00:44:46 -08:00
Girish Ramakrishnan
ff6d5e9efc complete the box update tests 2016-01-24 00:06:32 -08:00
Girish Ramakrishnan
f48fe0a7c0 Get started with updatechecker tests 2016-01-23 22:38:46 -08:00
Girish Ramakrishnan
5f6c8ca520 fix crash 2016-01-23 13:37:21 -08:00
Girish Ramakrishnan
0eaa3a8d94 clear update info so that we use the latest settings 2016-01-23 11:08:01 -08:00
Girish Ramakrishnan
8ad190fa83 make updateConfig a provision argument 2016-01-23 10:15:09 -08:00
girish@cloudron.io
70f096c820 check settingsdb whether to notify for app prerelease
fixes #573
2016-01-23 05:56:08 -08:00
girish@cloudron.io
2840251862 check if updateInfo is null earlier 2016-01-23 05:37:22 -08:00
girish@cloudron.io
b43966df22 code without callback is hard to read 2016-01-23 05:35:57 -08:00
girish@cloudron.io
cc22285beb Check settingsdb whether to notify for prereleases
part of #573
2016-01-23 05:33:16 -08:00
girish@cloudron.io
b72d48b49f set default update config 2016-01-23 05:07:12 -08:00
girish@cloudron.io
3a6b9c23c6 settings: add update config 2016-01-23 05:06:09 -08:00
girish@cloudron.io
b2da364345 fix typo in comment 2016-01-22 17:58:38 -08:00
girish@cloudron.io
de7a6abc50 Check for out of disk space
Fixes #567
2016-01-22 17:46:23 -08:00
girish@cloudron.io
10f74349ca collectd: disable vmem plugin 2016-01-22 15:44:46 -08:00
girish@cloudron.io
05a771c365 collectd: disable process plugin 2016-01-22 15:43:47 -08:00
girish@cloudron.io
cfa2089d7b collectd: Remove ping metric 2016-01-22 15:36:13 -08:00
girish@cloudron.io
d56abd94a9 collectd uses the data lo partition that is resized by box-setup.sh 2016-01-22 15:06:43 -08:00
girish@cloudron.io
2f20ff8def use loop1 instead of loop0
box-setup.sh always resizes the loopback partition on reboot. This
means that it always be loop1.
2016-01-22 15:03:23 -08:00
girish@cloudron.io
9706daf330 Just track ext4 and btrfs file systems 2016-01-22 14:33:02 -08:00
girish@cloudron.io
a246b3e90c box-setup needs to run after mounting to prevent race in script 2016-01-22 14:21:36 -08:00
girish@cloudron.io
e28e1b239f fix comment 2016-01-22 14:21:20 -08:00
girish@cloudron.io
4aead483de This hack is not needed in 15.10 anymore
collectd is still same version in 15.10 but it collects info correctly
as df-vda1 now.
2016-01-22 14:00:40 -08:00
girish@cloudron.io
f8cc6e471e 0.7.0 changes 2016-01-22 11:02:04 -08:00
girish@cloudron.io
6b9ed9472d upgrade to 15.10 2016-01-22 10:46:13 -08:00
girish@cloudron.io
a763b08c41 pin packages
fixes #558
2016-01-22 10:46:13 -08:00
Johannes Zellner
178f904143 Put installer.sh location in README 2016-01-22 13:03:31 +01:00
Girish Ramakrishnan
bb88fa3620 Restart node processes if journald crashes
Note that we cannot simply ignore EPIPE in the node programs.
Doing so results in no logs anymore :-( This is supposedly
fixed in systemd 228.

Fixes #550
2016-01-21 22:13:19 -08:00
Girish Ramakrishnan
1e1249d8e0 Give journald more time to sync
Part of #550
2016-01-21 21:43:49 -08:00
girish@cloudron.io
bcb0e61bfc Kill child processes
On Unix, child processes are not killed when parent dies.

Each process is part of a process group (pgid). When pgid == pid,
it is the process group leader.

node creates child processes with the parent as the group leader
(detached = false).

You can send a signal to entire group using kill(-pgid), as in,
negative value in argument. Systemd can be made to do this by
setting the KillMode=control-group.

Unrelated: Process groups reside inside session groups. Each session
group has a controlling terminal. Only one process in the session
group has access to the terminal. Process group is basically like
a bash pipeline. A session group is the entire login session with only
one process having terminal access at a time.

Fixes #543
2016-01-21 17:44:17 -08:00
girish@cloudron.io
022ff89836 Add 0.6.6 changes 2016-01-21 15:57:22 -08:00
girish@cloudron.io
b9d4b8f6e8 Remove docker images by tag
docker pull previously used to pull down all tags.
    docker pull tag1 # might pull down tag2, tag3 if they are all same!
    docker rm tag1 # oops, tag2 and tag3 still hold on to the image

However, the above feature was not possible with registry v2 (some
technical stuff to do with each tag being separately signed). As a result,
this feature was removed from v1 as well - https://github.com/docker/docker/pull/10571

This means we can now do:
    docker pull tag1 # nice
    docker rm tag1 # image goes away if no one else is using it

references:
https://github.com/docker/docker/issues/8689
https://github.com/docker/docker/pull/8193 (added this feature to v1)
https://github.com/docker/docker/issues/8141 (the request)
https://github.com/docker/docker/pull/10571 (removes the v1 feature)

Fixes #563
2016-01-21 15:53:51 -08:00
Johannes Zellner
0f5ce651cc Show errors if passwords do not match for reset and setup 2016-01-21 16:33:51 +01:00
Johannes Zellner
6b8d5f92de Set meaningful page title for oauth rendered pages 2016-01-21 16:19:38 +01:00
Johannes Zellner
55e556c725 Also provide client side password validation for password setup and reset forms 2016-01-21 16:08:51 +01:00
Johannes Zellner
19bb0a6ec2 Move form feedback below in setup screen 2016-01-21 16:03:46 +01:00
Johannes Zellner
290132f432 Add warning in password.js to update the UI parts 2016-01-21 16:00:12 +01:00
Johannes Zellner
4a8be8e62d Add changes for 0.6.5 2016-01-21 15:57:48 +01:00
Johannes Zellner
23b61aef0c Use client side pattern validation for setup password 2016-01-21 15:52:24 +01:00
Johannes Zellner
24cc433a3d Also take care of the developermode toggle form 2016-01-21 15:17:49 +01:00
Johannes Zellner
e014b7de81 It should be called 'Wrong password' 2016-01-21 15:11:42 +01:00
Johannes Zellner
0895a2bdea Same procedure for the app uninstall dialog 2016-01-21 15:09:22 +01:00
Johannes Zellner
03ca4887ba Streamline the app restore password validation 2016-01-21 15:06:22 +01:00
Johannes Zellner
9eeb17c397 Fixup app configure password validation 2016-01-21 15:03:51 +01:00
Johannes Zellner
6a5da2745a Simplify password validation in email edit 2016-01-21 14:59:39 +01:00
Johannes Zellner
e1111ba2bb Simplify password validation for cloudron update 2016-01-21 14:57:21 +01:00
Johannes Zellner
d186084835 Use password regexp instead of min-max to do client side validation also 2016-01-21 14:47:21 +01:00
Johannes Zellner
06c2ba9fa9 Fixup email edit form with password changes 2016-01-21 14:34:36 +01:00
Johannes Zellner
b82e5fd8c6 Remove console.log() 2016-01-21 14:29:04 +01:00
Johannes Zellner
6e1f96a832 Set min and max length for all password fields 2016-01-21 14:26:24 +01:00
Johannes Zellner
f68135c7aa Fixup password requirement feedback in account settings 2016-01-21 14:03:24 +01:00
Johannes Zellner
f48cbb457b Call reset avatar to trigger favicon change 2016-01-20 17:15:13 +01:00
Johannes Zellner
8d192dc992 Add Client.resetAvatar() 2016-01-20 17:14:50 +01:00
Johannes Zellner
b70324aa24 Give favicon an id 2016-01-20 17:14:36 +01:00
Johannes Zellner
390afaf614 Remove dead code 2016-01-20 16:56:14 +01:00
Johannes Zellner
5112322e7d Ensure the avatar is always updated in all places
Fixes #549
2016-01-20 16:55:44 +01:00
Johannes Zellner
2cb498d500 Improve singleUser configure dialog
Fixes #565
2016-01-20 16:27:43 +01:00
Johannes Zellner
2bd6e02cdc Do not show oauth proxy settings for singleUser apps 2016-01-20 16:23:31 +01:00
Johannes Zellner
85423cbc20 Actually send displayName instead of name in cloudron activation tests 2016-01-20 16:14:44 +01:00
Johannes Zellner
1c0d027bd3 Fix error message if displayName has wrong type 2016-01-20 16:14:21 +01:00
Johannes Zellner
5a8a023039 Fixup all the route tests with new password requirement 2016-01-20 16:06:51 +01:00
Johannes Zellner
196b059cfb Immediately set icon fallback to avoid flickering 2016-01-20 16:01:39 +01:00
Johannes Zellner
2d930b9c3d Explicitly set icon urls to null if we dont have them 2016-01-20 16:01:21 +01:00
Johannes Zellner
a5ba3faa49 Correctly report password errors 2016-01-20 15:41:29 +01:00
Johannes Zellner
02ba91f1bb Move password generation into separate file and ensure we generate strong passwords 2016-01-20 15:33:11 +01:00
Johannes Zellner
bfa917e057 Add password strength unit tests 2016-01-20 14:50:06 +01:00
Johannes Zellner
909dd0725a Fix copy and paste error 2016-01-20 14:49:45 +01:00
Johannes Zellner
74860f2d16 Fix tests for password strength change 2016-01-20 14:39:08 +01:00
Johannes Zellner
132ebb4e74 Require strong passwords
Fixes #568
2016-01-20 14:38:41 +01:00
Johannes Zellner
698158cd93 Change some of the mail added email text 2016-01-20 13:14:19 +01:00
Johannes Zellner
5bfc684f1b Fix crash due to missing event 2016-01-20 13:10:16 +01:00
Johannes Zellner
c944c9b65b Add changelog for 0.6.4 2016-01-20 12:43:40 +01:00
Johannes Zellner
d61698b894 Send user_added email instead of generic user event to admins
Fixes #569
2016-01-20 12:40:56 +01:00
Johannes Zellner
a4d32009ad Make it clear why this if condition is there 2016-01-20 12:39:28 +01:00
Johannes Zellner
3007875e35 Add user_added.ejs email template
This allows us to also send an invitation link to admins
in case the user was not invited already.
2016-01-20 12:38:07 +01:00
Johannes Zellner
b4aad138fc Fixup the update badge for mobile 2016-01-20 12:00:19 +01:00
Johannes Zellner
8df7eb2acb Check if the versions for app updates match
Fixes #566
2016-01-20 11:56:46 +01:00
girish@cloudron.io
18cab6f861 initialize displayName from activation link 2016-01-20 00:16:48 -08:00
girish@cloudron.io
b2071c65d8 Fix typo 2016-01-20 00:05:06 -08:00
girish@cloudron.io
402dba096e webadmin: display name for users 2016-01-19 23:58:52 -08:00
girish@cloudron.io
abf0c81de4 provide displayName in createAdmin route 2016-01-19 23:58:08 -08:00
girish@cloudron.io
613985a17c Set default displayName as empty 2016-01-19 23:47:29 -08:00
girish@cloudron.io
bfc9801699 provide displayName in ldap response when available 2016-01-19 23:47:24 -08:00
girish@cloudron.io
ee705eb979 Add displayName to create user and activate routes 2016-01-19 23:34:49 -08:00
girish@cloudron.io
67b94c7fde give message for development mode 2016-01-19 10:20:24 -08:00
Johannes Zellner
77e5d3f4bb Retry checking for app start state in test 2016-01-19 16:45:58 +01:00
Johannes Zellner
30618b8644 add missing argument 2016-01-19 14:03:01 +01:00
Johannes Zellner
57a2613286 Remove leftover js-error target reference in gulpfile 2016-01-19 13:36:32 +01:00
Johannes Zellner
e15bd89ba2 Add route to list application backups 2016-01-19 13:35:28 +01:00
Johannes Zellner
d2ed816f44 Add apps.listBackups() 2016-01-19 13:35:18 +01:00
Johannes Zellner
e51234928b Add FIXME for selfhost backup listing 2016-01-19 13:32:11 +01:00
Johannes Zellner
3aa668aea3 Fixup tests 2016-01-19 12:42:19 +01:00
Johannes Zellner
870edab78a Set empty displayName for users 2016-01-19 12:40:50 +01:00
Johannes Zellner
ebc9d9185d Use displayName in userdb 2016-01-19 12:39:54 +01:00
Johannes Zellner
093150d4e3 Add displayName to users table 2016-01-19 12:37:22 +01:00
Johannes Zellner
de80a6692d Remove unused error.js the code is inline 2016-01-19 11:22:06 +01:00
Johannes Zellner
c28f564a47 Remove unused gulp target for error.js 2016-01-19 11:21:43 +01:00
Johannes Zellner
eb6a09c2bd Try to fetch the cloudron status to offer a way to reload 2016-01-19 11:20:32 +01:00
Johannes Zellner
19f404e092 Changes for 0.6.3 2016-01-19 11:00:29 +01:00
girish@cloudron.io
55799ebb2d cli tool demuxes stream now 2016-01-18 21:36:05 -08:00
girish@cloudron.io
fdf4d8fdcf maybe stream is duplex 2016-01-18 13:39:18 -08:00
girish@cloudron.io
6dc11edafe make exec route more debugging friedly
allow upto 30 minutes of idle connection
2016-01-18 12:49:06 -08:00
girish@cloudron.io
c82ca1c69d disable http server timeout 2016-01-18 12:28:53 -08:00
girish@cloudron.io
7ef3d55cbf add tty option to exec 2016-01-18 11:39:09 -08:00
Johannes Zellner
44e4f53827 Change user creation api to require the invite flag 2016-01-18 16:53:51 +01:00
Johannes Zellner
643e490cbb Allow to specify if a new user gets invited immediately 2016-01-18 16:44:11 +01:00
Johannes Zellner
e61498c3b6 Ensure the avatar is also based on the apiOrigin 2016-01-18 16:35:25 +01:00
Johannes Zellner
bb6b61d810 Remove unused Client.prototype.getAppLogUrl() 2016-01-18 16:29:33 +01:00
Johannes Zellner
cff173c2e6 Ensure all api calls are absolute 2016-01-18 16:29:13 +01:00
Johannes Zellner
226501d103 Clear user remove form 2016-01-18 16:25:42 +01:00
Johannes Zellner
c5b8b0e3db Split up userAdd and sendInvite mailer calls 2016-01-18 16:11:00 +01:00
Johannes Zellner
46878e4363 Add button to trigger the invite email 2016-01-18 15:45:54 +01:00
Johannes Zellner
f77682365e Add client.sendInvite() 2016-01-18 15:45:44 +01:00
Johannes Zellner
d9850fa660 Add send invite route 2016-01-18 15:37:03 +01:00
Johannes Zellner
9258585746 add user.sendInvite() with tests 2016-01-18 15:16:18 +01:00
Johannes Zellner
e635aaaa58 Fix oauth2 tests 2016-01-18 14:31:25 +01:00
Johannes Zellner
d0d6725df5 Remove obsolete comments 2016-01-18 14:19:38 +01:00
Johannes Zellner
61f4fea9c3 Check for emails sent in users tests 2016-01-18 14:19:20 +01:00
Johannes Zellner
66d59c1d6c Do not require the invitor in the invite mail 2016-01-18 14:18:57 +01:00
Johannes Zellner
f9725965e2 Add test hooks to check if mails are queued 2016-01-18 14:00:35 +01:00
Johannes Zellner
4629739a14 Fix ldap tests 2016-01-18 13:56:59 +01:00
Johannes Zellner
e9b3a1e99c Fixup user tests 2016-01-18 13:50:54 +01:00
Johannes Zellner
8ac27b9dc7 Adjust api to set a flag if invitiation should be sent on user creation 2016-01-18 13:48:10 +01:00
Johannes Zellner
2edd434474 Support more notification types 2016-01-18 13:39:27 +01:00
Johannes Zellner
bebf480321 Use appdb.exists() instead of a apps.get() 2016-01-17 16:05:47 +01:00
Johannes Zellner
10c09d9def Fix wrong conditional in appdb 2016-01-17 16:01:17 +01:00
Johannes Zellner
6ce6b96e5c Fix linter issues 2016-01-17 15:59:11 +01:00
Johannes Zellner
16a9cae80e Allow to specify the restore id 2016-01-17 15:50:20 +01:00
Johannes Zellner
e865e2ae6d Employ hack to ensure chrome and firefox do not autocomplete
http://stackoverflow.com/questions/12374442/chrome-browser-ignoring-autocomplete-off
2016-01-16 17:38:18 +01:00
Johannes Zellner
06363a43f9 Offset the status bar 2016-01-16 16:48:47 +01:00
girish@cloudron.io
09a88e6a1c Version 0.6.2 changes 2016-01-15 18:12:20 -08:00
girish@cloudron.io
28baef8929 Go back to using docker exec in cloudron exec
The main issue is that multiple cloudron exec sessions do not
share the same rootfs. Which makes it annoying to debug.

We also have some nginx timeout which drops you out of exec
now and then resulting in loss of all state.
2016-01-15 15:24:46 -08:00
girish@cloudron.io
9b061a4c7c make the command work 2016-01-15 14:50:13 -08:00
girish@cloudron.io
0b542dfbdf Pause app container in developmentMode
This allows us to share the network namespace with the app container
2016-01-15 14:34:15 -08:00
girish@cloudron.io
d3b039ebd8 support developmentMode flag
- disables readonly rootfs
- disables memory limit
2016-01-15 11:28:43 -08:00
Johannes Zellner
c22924eed7 Use user.js instead of userdb.js in mailer 2016-01-15 16:33:13 +01:00
Johannes Zellner
033ccb121f Add tests for user.getAllAdmins() 2016-01-15 16:33:13 +01:00
Johannes Zellner
ecd91e8f2a Add user.getAllAdmins() 2016-01-15 16:33:13 +01:00
girish@cloudron.io
1cdb954967 0.6.1 changes 2016-01-14 13:30:08 -08:00
girish@cloudron.io
f309f87f55 use no-reply for naked domain apps 2016-01-14 12:56:35 -08:00
girish@cloudron.io
989ab3094d Set initial progress so that tools can wait on it 2016-01-14 11:34:49 -08:00
girish@cloudron.io
70ac18d139 add internal route to update the cloudron
need to way to trigger updates of cloudron using the caas tool
2016-01-14 11:13:02 -08:00
girish@cloudron.io
8f43236e2e adjust update mail text 2016-01-14 11:02:05 -08:00
girish@cloudron.io
38416d46a6 insist on node 4.1.1
this is probably FUD but I think we get npm rebuild issues when
trying to rebuild packages of a newer node to an older node.
2016-01-14 10:55:53 -08:00
girish@cloudron.io
fb96b00922 just keep rebuilding
Jan 14 07:03:27 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:03:27 GMT installer:installer provision (stdout):
Jan 14 07:03:27 rudolf.cloudron.me server.js[541]: > bson@0.2.22 install
/tmp/box-src-a4tklr/node_modules/db-migrate/node_modules/mongodb/node_modules/bson

Jan 14 07:03:27 rudolf.cloudron.me server.js[541]: > (node-gyp rebuild
2> builderror.log) || (exit 0)
Jan 14 07:03:31 rudolf.cloudron.me ntpdate[1344]: step time server
91.189.89.199 offset 0.000661 sec
Jan 14 07:03:31 rudolf.cloudron.me systemd[1]: Time has been changed
Jan 14 07:03:44 rudolf.cloudron.me kernel: IPTables Packet Dropped:
IN=eth0 OUT= MAC=04:01:9a:dd:a9:01:84:b5:9c:fa:08:30:08:00
SRC=79.174.70.237 DST=178.62.202.80 LEN=40 TOS=0x00 PREC=0x00 TTL=248
ID=54321 PROTO=TCP SPT=49152 DPT=22 WINDOW=65535 RES=0x00 SYN URGP=0
Jan 14 07:04:02 rudolf.cloudron.me kernel: IPTables Packet Dropped:
IN=eth0 OUT= MAC=04:01:9a:dd:a9:01:84:b5:9c:fa:08:30:08:00
SRC=124.6.36.197 DST=178.62.202.80 LEN=638 TOS=0x00 PREC=0x00 TTL=59
ID=61522 DF PROTO=TCP SPT=443 DPT=58535 WINDOW=95 RES=0x00 ACK PSH URGP=0
Jan 14 07:04:08 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:04:08 GMT installer:installer provision (stdout):
Jan 14 07:04:08 rudolf.cloudron.me server.js[541]: > kerberos@0.0.11
install
/tmp/box-src-a4tklr/node_modules/db-migrate/node_modules/mongodb/node_modules/kerberos

Jan 14 07:04:08 rudolf.cloudron.me server.js[541]: > (node-gyp rebuild
2> builderror.log) || (exit 0)
Jan 14 07:04:47 rudolf.cloudron.me kernel: IPTables Packet Dropped:
IN=eth0 OUT= MAC=04:01:9a:dd:a9:01:84:b5:9c:fa:08:30:08:00
SRC=58.218.205.83 DST=178.62.202.80 LEN=40 TOS=0x00 PREC=0x00 TTL=112
ID=256 PROTO=TCP SPT=49127 DPT=5555 WINDOW=512 RES=0x00 SYN URGP=0
Jan 14 07:05:18 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 91.207.136.55:123 (2.ubuntu.pool.ntp.org).
Jan 14 07:05:28 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 194.190.168.1:123 (2.ubuntu.pool.ntp.org).
Jan 14 07:05:49 rudolf.cloudron.me kernel: IPTables Packet Dropped:
IN=eth0 OUT= MAC=04:01:9a:dd:a9:01:84:b5:9c:fa:08:30:08:00
SRC=218.77.79.38 DST=178.62.202.80 LEN=40 TOS=0x00 PREC=0x00 TTL=240
ID=54321 PROTO=TCP SPT=44094 DPT=3306 WINDOW=65535 RES=0x00 SYN URGP=0
Jan 14 07:06:02 rudolf.cloudron.me kernel: IPTables Packet Dropped:
IN=eth0 OUT= MAC=04:01:9a:dd:a9:01:84:b5:9c:fa:08:30:08:00
SRC=124.6.36.197 DST=178.62.202.80 LEN=638 TOS=0x00 PREC=0x00 TTL=59
ID=61523 DF PROTO=TCP SPT=443 DPT=58535 WINDOW=95 RES=0x00 ACK PSH URGP=0
Jan 14 07:06:21 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:06:21 GMT installer:installer provision (stdout):
Jan 14 07:06:21 rudolf.cloudron.me server.js[541]: > sqlite3@3.1.1
install /tmp/box-src-a4tklr/node_modules/db-migrate/node_modules/sqlite3
Jan 14 07:06:21 rudolf.cloudron.me server.js[541]: > node-pre-gyp
install --fallback-to-build
Jan 14 07:06:21 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:06:21 GMT installer:installer provision (stdout): [sqlite3] Success:
"/tmp/box-src-a4tklr/node_modules/db-migrate/node_modules/sqlite3/lib/binding/node-v46-linux-x64/node_sqlite3.node"
already installed
Jan 14 07:06:21 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:06:21 GMT installer:installer provision (stdout): Pass
--update-binary to reinstall or --build-from-source to recompile
Jan 14 07:06:23 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:06:23 GMT installer:installer provision (stdout):
Jan 14 07:06:23 rudolf.cloudron.me server.js[541]: >
dtrace-provider@0.2.8 install
/tmp/box-src-a4tklr/node_modules/ldapjs/node_modules/dtrace-provider
Jan 14 07:06:23 rudolf.cloudron.me server.js[541]: > node-gyp rebuild
Jan 14 07:07:47 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 91.206.16.3:123 (2.ubuntu.pool.ntp.org).
Jan 14 07:07:57 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 46.254.216.12:123 (2.ubuntu.pool.ntp.org).
Jan 14 07:08:02 rudolf.cloudron.me kernel: IPTables Packet Dropped:
IN=eth0 OUT= MAC=04:01:9a:dd:a9:01:84:b5:9c:fa:08:30:08:00
SRC=124.6.36.197 DST=178.62.202.80 LEN=638 TOS=0x00 PREC=0x00 TTL=59
ID=61524 DF PROTO=TCP SPT=443 DPT=58535 WINDOW=95 RES=0x00 ACK PSH URGP=0
Jan 14 07:08:08 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 85.255.214.66:123 (3.ubuntu.pool.ntp.org).
Jan 14 07:08:18 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 83.98.201.134:123 (3.ubuntu.pool.ntp.org).
Jan 14 07:08:28 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 194.171.167.130:123 (3.ubuntu.pool.ntp.org).
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): gyp
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): WARN
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): install
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):  got an error,
rolling back install

<...>

07:08:31 GMT installer:installer provision (stderr):  Error: connect
ETIMEDOUT 104.20.23.46:443
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): gyp
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): stack
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):      at
Object.exports._errnoException (util.js:837:11)
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): gyp
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): stack
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):      at
exports._exceptionWithHostPort (util.js:860:20)
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): gyp
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): stack
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):      at
TCPConnectWrap.afterConnect [as oncomplete] (net.js:1060:14)
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): gyp
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): System Linux
3.19.0-31-generic
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: gyp ERR! command
"/usr/local/node-4.1.1/bin/node"
"/usr/local/node-4.1.1/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js"
"rebuild"
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: gyp ERR! cwd
/tmp/box-src-a4tklr/node_modules/ldapjs/node_modules/dtrace-provider
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: gyp ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): node -v
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):  v4.1.1
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): gyp
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): node-gyp -v
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):  v3.0.3
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: gyp ERR! not ok
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): npm
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):  Linux
3.19.0-31-generic
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): npm
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): argv
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
"/usr/local/node-4.1.1/bin/node" "/usr/bin/npm" "rebuild"
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): npm ERR! node v4.1.1
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! npm  v2.14.4
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! code ELIFECYCLE
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR!
dtrace-provider@0.2.8 install: `node-gyp rebuild`
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! Exit status 1
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! Failed at
the dtrace-provider@0.2.8 install script 'node-gyp rebuild'.
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):  This is most
likely a problem with the dtrace-provider package,
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! not with npm
itself.
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! Tell the
author that this fails on your system:
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR!     node-gyp
rebuild
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! You can get
their info via:
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR!     npm
owner ls dtrace-provider
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! There is
likely additional logging output above.
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): npm
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):  Please include the
following file with any support request:
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR!
/tmp/box-src-a4tklr/npm-debug.log
Jan 14 07:08:31 rudolf.cloudron.me sudo[1284]: pam_unix(sudo:session):
session closed for user root
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision : child process exited. code:
1 signal: 0
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: [Error: Exited with
code 1]
2016-01-14 10:51:21 -08:00
Johannes Zellner
0601ea2f39 Show actionable notification in case we miss aws credentials
Fixes #553
2016-01-14 16:01:10 +01:00
Johannes Zellner
a92d4f2af7 add some comments on how to use notifications 2016-01-14 15:56:23 +01:00
Johannes Zellner
658305c969 Tweak the notification appearance 2016-01-14 15:54:32 +01:00
Johannes Zellner
8aed2be19b Support action scopes 2016-01-14 15:54:14 +01:00
Johannes Zellner
263f6e49d8 Use custom notification template 2016-01-14 15:53:47 +01:00
Johannes Zellner
d822e38016 Ensure templates are available 2016-01-14 15:53:32 +01:00
Johannes Zellner
6cf78f19bb Add custom notification template to make actionable 2016-01-14 15:53:16 +01:00
Johannes Zellner
4a3319406c Add Client api to show notifications 2016-01-14 15:08:27 +01:00
Johannes Zellner
cb4418b973 Set default values for the notifications 2016-01-14 15:07:41 +01:00
Johannes Zellner
5a797124b3 Update the angular notification library 2016-01-14 15:07:10 +01:00
Johannes Zellner
63430fbce6 Adjust update email text to let the user know about auto updates
So far the emails make no sense in case the user only clicks the update
link after the Cloudron has already auto updated. We might want to
adjust the text, once the user can disable auto updates, or set the
preferred update time.
2016-01-14 13:55:02 +01:00
girish@cloudron.io
bc2085139e ensure no trailing slash in redis password
Fixes #539
2016-01-13 19:42:28 -08:00
girish@cloudron.io
f98c710f5b use password generator module 2016-01-13 19:02:15 -08:00
girish@cloudron.io
eb7101deff setupToken is property of wizard
fixes #559
2016-01-13 18:41:08 -08:00
girish@cloudron.io
826f50da7e expose wizard in SetupController 2016-01-13 18:04:07 -08:00
girish@cloudron.io
4e94c8ea56 updateContact gets 202 and not 200 2016-01-13 16:46:01 -08:00
girish@cloudron.io
3120eca721 status api should set provider 2016-01-13 16:09:36 -08:00
girish@cloudron.io
26c9bcbc28 fix this and that 2016-01-13 15:00:33 -08:00
girish@cloudron.io
7a2e73a5d6 acme: update account with owner email
fixes #544
2016-01-13 14:21:59 -08:00
girish@cloudron.io
cd35ab5932 acme: update contact information before getting a cert
part of #544

there were two approaches considered:
1. pipe through owner email from appstore. this requires to save this
   value in settingsdb and we need to remember this in case user changes
   the email. another issue is that selfhost installer tooling needs to
   require this new value.

2. simply update owner email each time. this is the chosen approach.
2016-01-13 14:06:31 -08:00
girish@cloudron.io
efaacdb534 Add getOwner 2016-01-13 12:37:56 -08:00
girish@cloudron.io
5eb3c208f1 allow email to be configured 2016-01-13 12:15:27 -08:00
Johannes Zellner
8347b62c1b Mention lets encrypt in the ssl settings 2016-01-13 16:24:15 +01:00
Johannes Zellner
48c3c7b4dc Also hide the webadmin domain cert ui 2016-01-13 16:17:46 +01:00
Johannes Zellner
faefe078af Make links in app description go to a new page 2016-01-13 16:16:40 +01:00
Johannes Zellner
66eb0481b5 Hide app per app certificate upload fields
Fixes #555
2016-01-13 16:00:55 +01:00
Johannes Zellner
0a0fc130d4 Fix linter errors 2016-01-13 16:00:55 +01:00
Johannes Zellner
44afd7b657 No need to check for version equality for the update badge 2016-01-13 14:50:05 +01:00
Johannes Zellner
165b572a5f Fixup the app grid icon action layout 2016-01-13 14:49:00 +01:00
Johannes Zellner
1a30e622cc Only register app updates for apps where the available version is actually bigger
Fixes #533
2016-01-13 14:48:52 +01:00
Johannes Zellner
aec3238e42 Add changlog for 0.5.0 2016-01-13 11:51:21 +01:00
Johannes Zellner
249868dba7 Add CHANGES file 2016-01-13 11:51:13 +01:00
Johannes Zellner
f82e714b3c Remove executable flag for scripts not intended to be called directly 2016-01-13 10:58:20 +01:00
Johannes Zellner
baa7eae77c Move images script to appstore since it is specific to caas 2016-01-13 10:45:23 +01:00
Johannes Zellner
c69542a34d cleanup outdated README.md 2016-01-13 10:42:45 +01:00
Johannes Zellner
9d45892603 Move baseimage relevant scripts to baseimage/ 2016-01-13 10:30:01 +01:00
Johannes Zellner
e65f247b99 Fix path to initializeBaseUbuntuImage.sh in installer.sh 2016-01-12 16:53:36 +01:00
Johannes Zellner
600f061e47 The box tarball is now public, no need for a signed url 2016-01-12 16:50:34 +01:00
Johannes Zellner
8ac0e9e751 Provide sourceTarballUrl with update info 2016-01-12 16:50:14 +01:00
Johannes Zellner
772787fc22 Do not ignore scripts/ for export 2016-01-12 16:15:21 +01:00
Johannes Zellner
985b33b65b Add missing dev modules for images tool 2016-01-12 12:55:10 +01:00
Johannes Zellner
ef7c5c2d2b Remove vultr caas support 2016-01-12 12:55:10 +01:00
Johannes Zellner
0d49aafb54 Move installer/images scripts to scripts/ 2016-01-12 12:55:10 +01:00
Johannes Zellner
98aae5ddc6 Correctly copy installer files 2016-01-11 15:37:40 +01:00
Johannes Zellner
070e8606fa Use json from box/ 2016-01-11 15:20:29 +01:00
Johannes Zellner
511b2848c3 Fixup the paths in the createImage script 2016-01-11 15:14:07 +01:00
Johannes Zellner
7dd96d9a75 Fixup create box tarball script paths 2016-01-11 14:53:18 +01:00
Johannes Zellner
7d80f69ee8 Use the unified tarball instead of separate installer 2016-01-11 14:49:49 +01:00
Johannes Zellner
85491cb7b5 Merge branch 'selfhost' of ../installer into selfhost 2016-01-11 14:45:33 +01:00
Johannes Zellner
3c0b88a1ee Move to subfolder installer/ 2016-01-11 14:42:20 +01:00
Johannes Zellner
33c072f544 Support box and installer in one tarball 2016-01-11 14:25:04 +01:00
Johannes Zellner
a8d08bca3f Merge branch 'master' of ssh://gitlab.smartserver.io:6000/yellowtent/cloudron-installer 2016-01-11 12:53:52 +01:00
Johannes Zellner
d7ddc56ab3 Not applicable for ubuntu 15.10 2016-01-11 11:15:04 +01:00
Johannes Zellner
b562cd5c73 Fix typo when specifying the provisionEnv environment var 2016-01-08 13:15:07 +01:00
Johannes Zellner
2549a41eb3 Add a valid hostname entry in /etc/hosts if not found
Using 127.0.1.1 as per http://www.debian.org/doc/manuals/debian-reference/ch05.en.html#_the_hostname_resolution
2016-01-06 16:52:02 +01:00
Johannes Zellner
464f0fc231 add some unit tests for ensureVersion 2016-01-06 16:02:42 +01:00
Johannes Zellner
5914fd9fb7 Return the whole object, not just the string 2016-01-06 14:52:21 +01:00
Johannes Zellner
2e03217551 Make sysinfo provider selection explicit 2016-01-06 14:34:23 +01:00
Johannes Zellner
e0dc974f87 Add provider argument 2016-01-06 10:52:35 +01:00
Girish Ramakrishnan
fdf1ed829d Revert "use 15.10 as base image"
This reverts commit 50807f3046fdf715cb3bf2afc08436f64995f36a.

15.10 requires more work
2016-01-05 20:32:09 -08:00
Girish Ramakrishnan
ba90490ad9 Simply remove the old sudoers file that we installed
This is alternate fix to 743b8e757b
2016-01-05 20:24:05 -08:00
Girish Ramakrishnan
910be97f54 return calling callback 2016-01-05 16:16:25 -08:00
Girish Ramakrishnan
e162582045 Add collectd hack 2016-01-05 15:38:56 -08:00
Girish Ramakrishnan
cfd197d26c region is not required 2016-01-05 14:06:16 -08:00
Girish Ramakrishnan
aa0486bc2b use 15.10 as base image 2016-01-05 13:27:18 -08:00
Johannes Zellner
97a1fc62ae This rule is obsolete
It should protect the metadata from apps, but that is already
covered with the FORWARD dropping rule below
2016-01-05 21:13:45 +01:00
Johannes Zellner
fd9dcd065a Bring back the sleep 10 to wait for docker's iptable rules
See comment in code for further details
2016-01-05 21:11:43 +01:00
Johannes Zellner
59997560eb Do not remove all files from /etc/sudoers.d/
On DO with caas, there are no other files initially, but
on the ec2 ubuntu images, the files have set the rules for the
ubuntu user to be able to sudo without password, which we want to
keep
2016-01-05 17:06:01 +01:00
Johannes Zellner
026e71cc6e Default provider is caas 2016-01-05 15:58:16 +01:00
Johannes Zellner
98ecc24425 Allow metadata access for selfhosters for now 2016-01-05 15:57:50 +01:00
Johannes Zellner
3886006343 Implement ec2 sysinfo backend 2016-01-05 14:33:33 +01:00
Johannes Zellner
6d539c9203 Fix sysinfo api usage 2016-01-05 13:18:56 +01:00
Johannes Zellner
5f778e61dd Use new getIP() api in certificates.js 2016-01-05 12:23:07 +01:00
Johannes Zellner
9860489f05 Use new getIP() api in cloudron.js 2016-01-05 12:16:48 +01:00
Johannes Zellner
21ca8ac883 Use new getIP() api in apptask 2016-01-05 12:16:39 +01:00
Johannes Zellner
ec93becb17 Add missing asserts 2016-01-05 12:14:39 +01:00
Johannes Zellner
0319445888 Make sysinfo based on provider 2016-01-05 12:10:25 +01:00
Johannes Zellner
7cba9f50c8 Docker startup is fixed with new service file, no need to wait 2016-01-05 10:07:58 +01:00
Johannes Zellner
9483c3afbc Also support /dev/xvda1 with collectd, needed for ec2 2016-01-04 17:26:02 +01:00
Johannes Zellner
e518976534 Also support /dev/xvda for box-setup.sh which is used in ec2 2016-01-04 15:57:52 +01:00
Johannes Zellner
3626cc2394 Skip some code during tests for now 2016-01-02 16:58:27 +01:00
Johannes Zellner
3a61fc7181 We want all strings in the array as separate strings 2016-01-02 16:52:25 +01:00
Johannes Zellner
4a95fa5e87 Cleanup the installer script 2016-01-02 16:37:28 +01:00
Johannes Zellner
8e3d1422f3 Fix typo, linter could do some work ;-) 2016-01-02 15:39:48 +01:00
Johannes Zellner
640a0b2627 Try to get the latest box release if no sourceTarballUrl is specified in the provisioning data 2016-01-02 15:30:01 +01:00
Johannes Zellner
30e0cb6515 Upload box tarballs with public acl 2016-01-02 15:29:20 +01:00
Johannes Zellner
e3253aacdb Add semver 2016-01-02 15:28:49 +01:00
Johannes Zellner
32f49d2122 Use 10GB for system, since it now includes docker images 2016-01-01 16:29:16 +01:00
Johannes Zellner
b4bef44135 Do not put docker images into te btrfs volume 2016-01-01 16:10:16 +01:00
Johannes Zellner
ce38742caf Fix cloudron tests 2015-12-31 11:55:01 +01:00
Johannes Zellner
a7d39cc8d4 Merge branch 'selfhost' 2015-12-31 11:41:46 +01:00
Johannes Zellner
cb7eb660b9 Do not fail to list apps if we are in developer mode and do not have an appstore token 2015-12-31 10:42:02 +01:00
Johannes Zellner
bad28c60ae Do not rely on the VPS name but just get the memory from the system 2015-12-31 10:30:42 +01:00
Johannes Zellner
ab4c04085c Use image init script from within the installer tar ball 2015-12-31 10:01:08 +01:00
Johannes Zellner
761002f39d Include the image scripts in the installer tar 2015-12-31 09:37:55 +01:00
Johannes Zellner
996f9c7f5d Set letsencrypt as tls config 2015-12-31 09:31:50 +01:00
Johannes Zellner
a6eca44a0d Do not attempt to purchase an app if we dont have an appstore token 2015-12-31 09:15:27 +01:00
Girish Ramakrishnan
1820751801 show all fields 2015-12-30 19:48:10 -08:00
Johannes Zellner
030faaa5d1 Remove unused information within backup listing 2015-12-30 20:31:00 +01:00
Johannes Zellner
95e1947352 Merge branch 'selfhost' 2015-12-30 18:54:33 +01:00
Johannes Zellner
7deb11a0a6 Support s3 and route53 configs 2015-12-30 18:45:19 +01:00
Johannes Zellner
41c597801f Sort backups by creationTime 2015-12-30 18:31:44 +01:00
Johannes Zellner
128a138e74 Remove the backup prefix from the key 2015-12-30 18:30:41 +01:00
Johannes Zellner
ca74b5740a Partly implement backup listing for s3 backend 2015-12-30 18:21:38 +01:00
Johannes Zellner
9afcbe1565 Fix the email require detection in setup 2015-12-30 14:10:20 +01:00
Johannes Zellner
9e531a05e1 Do not ask for aws credentials for selfhosting 2015-12-30 13:28:14 +01:00
Johannes Zellner
7c3562cea2 Ensure focus and form validation is correct in the setup wizard 2015-12-29 22:10:10 +01:00
Johannes Zellner
1edddf79d2 token is not yet required for provisioning anymore 2015-12-29 19:47:13 +01:00
Johannes Zellner
114f03e434 'null' does not survive the shell script hopping well 2015-12-29 18:51:49 +01:00
Johannes Zellner
3ee1487985 We might support more than just caas and selfhosted 2015-12-29 17:57:11 +01:00
Johannes Zellner
a9eda2176e Only send heartbeats and fetch cloudron details if we have a token 2015-12-29 17:43:54 +01:00
Johannes Zellner
bb90bafb62 Fix crash when the appstore server does not respond correctly on setup 2015-12-29 16:07:04 +01:00
Johannes Zellner
cea8783fec Skip calling back home on activation in non caas case 2015-12-29 15:56:37 +01:00
Johannes Zellner
789d1fef84 Remove commented dom elements 2015-12-29 15:46:13 +01:00
Johannes Zellner
d83939b165 Fix setup wizard dependency on setupToken 2015-12-29 15:27:46 +01:00
Johannes Zellner
3a4ec5c86a The var is named 2015-12-29 12:25:22 +01:00
Johannes Zellner
013c14530b Provide provider in cloudron.conf 2015-12-29 11:30:03 +01:00
Johannes Zellner
ebf1cfc113 Read provider field from cloudron.conf 2015-12-29 11:29:08 +01:00
Johannes Zellner
ec4d04c338 Skip setupToken auth for non caas cloudrons 2015-12-29 11:24:45 +01:00
Johannes Zellner
584b7790e4 Get the cloudron vps provider from config 2015-12-29 11:24:34 +01:00
Johannes Zellner
ef25f66107 Ask for the user's email if not provided 2015-12-29 11:17:08 +01:00
Johannes Zellner
3060b34bdd customDomain during setup is a special caas case 2015-12-29 11:04:42 +01:00
Johannes Zellner
3fb5c682f8 Remove Client.isServerFirstTime() 2015-12-29 11:00:32 +01:00
Johannes Zellner
bb5dfa13ee Add Client.getStatus() 2015-12-29 10:58:26 +01:00
Johannes Zellner
9e391941c5 Redirect the browser to /setup.html in non caas mode 2015-12-29 10:54:00 +01:00
Johannes Zellner
8b1d3e5fba Send provider field with cloudron config 2015-12-29 10:53:22 +01:00
Girish Ramakrishnan
07e322df96 default targetBoxVersion to the maximum possible version ever
Apps that do not provide a targetBoxVersion are assumed to be
capable of running everywhere.
2015-12-27 16:43:50 -08:00
Girish Ramakrishnan
a8959cbf26 fix path to cpu metrics 2015-12-24 12:46:42 -08:00
Johannes Zellner
d793e5bae5 Remove obsolete generate_certificate.sh 2015-12-24 10:01:00 +01:00
Johannes Zellner
29954fa9e8 Generate certs inline 2015-12-24 10:00:45 +01:00
Girish Ramakrishnan
0384fa9a51 fix debugs 2015-12-23 15:22:36 -08:00
Girish Ramakrishnan
75b19d3883 scheduler: remove scheduler.json
don't bother saving state across restarts. needlessly complicated.
2015-12-23 14:27:26 -08:00
Girish Ramakrishnan
c15f84da08 scheduler: do not bother tracking containerIds 2015-12-23 13:29:00 -08:00
Girish Ramakrishnan
8539d4caf1 scheduler: delete containers by name
scheduler.json gets nuked during updates. When the box code restarts,
the scheduler is unable to remove old container because the state file
scheduler.json is now gone. It proceeds to create new container but that
does not work because of name conflict.

Fixes #531
2015-12-23 13:23:49 -08:00
Johannes Zellner
3eb1fe5e4b Ensure we reload the systemd daemon to pickup the new service files 2015-12-23 13:58:35 +01:00
Johannes Zellner
16b88b697e Ensure docker is running correctly 2015-12-23 13:54:49 +01:00
Johannes Zellner
08ba6ac831 Docker does not have a -d option anymore
This was depricated in 1.8 and is now gone
https://github.com/docker/docker/blob/master/CHANGELOG.md#cli
2015-12-23 13:32:37 +01:00
Johannes Zellner
49710618ff Docker does not have a -d option anymore
This was depricated in 1.8 and is now gone
https://github.com/docker/docker/blob/master/CHANGELOG.md#cli
2015-12-23 13:28:15 +01:00
Johannes Zellner
b4ba001617 Use multipart upload for s3 by reducing the chunk size
This avoids file upload issues for larger files
2015-12-23 13:27:54 +01:00
Johannes Zellner
87e0876cce Only pull infra images if we have an INFRA_VERSION file 2015-12-23 13:27:33 +01:00
Girish Ramakrishnan
87f5e3f102 workaround journalctl logging bug 2015-12-22 13:05:00 -08:00
Johannes Zellner
e921d0db6e do not rely on INFRA_VERSION to be present 2015-12-22 06:25:13 +01:00
Girish Ramakrishnan
b7a85580fa why is the linter not finding this again? 2015-12-21 16:14:30 -08:00
Johannes Zellner
2e93bc2e1d Generate self signed certs for now 2015-12-21 20:44:37 +01:00
Johannes Zellner
5ac15c8c49 Remove trailing slash 2015-12-21 15:06:44 +01:00
Johannes Zellner
98c05f3614 Remove comma 2015-12-21 15:06:44 +01:00
Johannes Zellner
05af56defc Fix typo 2015-12-20 14:58:05 +01:00
Johannes Zellner
ce48a2fc12 Some small fixes for selfhost 2015-12-20 12:01:18 +01:00
Johannes Zellner
01e910af79 Prepare provisioning data for installer 2015-12-20 11:12:59 +01:00
Johannes Zellner
3e2ce9e94c make cloudron.conf file path a 'const' 2015-12-20 10:25:12 +01:00
Johannes Zellner
9db602b274 Decide if we run in caas or selfhost mode and fetch user data accordingly 2015-12-20 10:23:25 +01:00
Johannes Zellner
a2d0ac7ee3 Run installer with selfhost flag 2015-12-20 10:22:55 +01:00
Girish Ramakrishnan
24cbd1a345 if i wrote a linter, these are the bugs it would catch 2015-12-19 13:48:14 -08:00
Girish Ramakrishnan
8b3e6742d5 better debugs 2015-12-19 13:47:48 -08:00
Johannes Zellner
62bd3f6e83 Add installer.sh 2015-12-19 21:56:33 +01:00
Johannes Zellner
20ac2ff6e7 Do not move ssh port in selfhosting case 2015-12-19 21:56:00 +01:00
Johannes Zellner
aa7c9e06a4 Initial commit 2015-12-19 18:47:24 +01:00
Johannes Zellner
0c2fb7c0d9 Use multipart upload for s3 by reducing the chunk size
This avoids file upload issues for larger files
2015-12-19 17:50:54 +01:00
Girish Ramakrishnan
7ec2b1da8c fix function name in debug 2015-12-17 20:30:30 -08:00
Girish Ramakrishnan
190c2b2756 firefox is unhappy with incorrect chain 2015-12-17 19:42:49 -08:00
Girish Ramakrishnan
7c975384cd better error messages 2015-12-17 19:35:52 -08:00
Girish Ramakrishnan
fe042891a3 Add acme.getCertificate 2015-12-17 13:31:28 -08:00
Girish Ramakrishnan
a9b594373d do not pass accountKeyPem everywhere 2015-12-17 13:27:10 -08:00
Girish Ramakrishnan
5edc3cde2a set prod option based on provider 2015-12-17 13:17:46 -08:00
Girish Ramakrishnan
a636731764 allow configuring prod/staging of LE url 2015-12-17 13:12:54 -08:00
Girish Ramakrishnan
b4433af9b5 remove unused require 2015-12-17 12:55:47 -08:00
Girish Ramakrishnan
72cc318607 install docker 1.9.1
We hit this error:
https://github.com/docker/docker/issues/18283
https://github.com/docker/docker/issues/17083
2015-12-15 17:17:28 -08:00
Girish Ramakrishnan
5ae45381e2 fix metrics path
See 03da5cc6b382f6f7aad69395d9f8a9d29d18ec26 in installer

We now use the cgroupfs driver instead of systemd cgroup driver
2015-12-15 15:54:19 -08:00
Girish Ramakrishnan
b533d325a4 Creating containers fails sporadically
HTTP code is 500 which indicates error: server error - Cannot start container redis-9d0ae0eb-a08f-4d0d-a980-ac6fa15d1a3d: [8] System error: write /sys/fs/cgroup/memory/system.slice/docker-fa6d6f3fce88f15844710e6ce4a8ac4d3a42e329437501416991b4c55ea3d078.scope/memory.memsw.limit_in_bytes: invalid argument

https://github.com/docker/docker/issues/16256
https://github.com/docker/docker/pull/17704
https://github.com/docker/docker/issues/17653
2015-12-15 15:02:45 -08:00
Girish Ramakrishnan
9dad7ff563 Fix sed 2015-12-15 14:43:01 -08:00
Girish Ramakrishnan
1ae2e07883 leave note on 429 error code 2015-12-15 14:25:23 -08:00
Girish Ramakrishnan
aa34850d4e fix typo 2015-12-15 12:52:41 -08:00
Girish Ramakrishnan
9f524da642 use admin@cloudron.io for email
registrations are failing because the LE server is doing a MX check.
we don't have a proper email to provide here since the box is not
activated yet. we should "update" the email at some point with
the owner information.
2015-12-15 10:39:03 -08:00
Girish Ramakrishnan
8b707e23ca update shrinkwrap 2015-12-15 10:04:45 -08:00
Girish Ramakrishnan
a4ea693c3c update superagent
the latest superchanged changed the meaning of 'error'. Previously,
error implied a network error. With the latest superagent, error means
a REST api error i.e 4xx, 5xx are flagged as errors.

error && !error.response means network error
2015-12-15 09:53:37 -08:00
Girish Ramakrishnan
aca443a909 update redis 2015-12-15 08:36:47 -08:00
Girish Ramakrishnan
2ae5223da9 update password-generator, validator and nock 2015-12-15 08:34:13 -08:00
Girish Ramakrishnan
b5b67f2e6a define CA_ORIGIN 2015-12-15 00:49:00 -08:00
Girish Ramakrishnan
fe723f5a53 remove trailing slash in url 2015-12-15 00:42:18 -08:00
Girish Ramakrishnan
c55e1ff6b7 debug output the error 2015-12-15 00:23:57 -08:00
Girish Ramakrishnan
4bd88e1220 create acme data dir 2015-12-15 00:21:29 -08:00
Girish Ramakrishnan
f46af93528 do not installAdminCertificate for upgrades 2015-12-14 23:37:52 -08:00
Girish Ramakrishnan
8ead0e662a detect apptask crashes 2015-12-14 19:11:26 -08:00
Girish Ramakrishnan
365ee01f96 show error dialog 2015-12-14 18:56:15 -08:00
Girish Ramakrishnan
fca6de3997 add function to show the error dialog 2015-12-14 17:57:58 -08:00
Girish Ramakrishnan
dceb265742 add error dialog 2015-12-14 17:52:56 -08:00
Girish Ramakrishnan
409096cbff Use production LE 2015-12-14 17:31:41 -08:00
Girish Ramakrishnan
e5a40faf82 simply use fallback certs if LE fails
currently, it fails if we cannot get a cert.

This means that we need to provide some option to simply use fallback
cert. This requires UI changes that I want to avoid :-)
2015-12-14 17:13:54 -08:00
Girish Ramakrishnan
859c78c785 fix fallback cert message 2015-12-14 16:42:34 -08:00
Girish Ramakrishnan
89bff16053 fix crash 2015-12-14 14:08:45 -08:00
Girish Ramakrishnan
a89476c538 fix renewal check 2015-12-14 13:52:54 -08:00
Girish Ramakrishnan
f51b61e407 do not dump the csr 2015-12-14 13:41:30 -08:00
Girish Ramakrishnan
177103bccd update safetydance for readdirSync 2015-12-14 13:10:04 -08:00
Girish Ramakrishnan
f31d63aabd implement cert auto-renewal 2015-12-14 12:40:39 -08:00
Girish Ramakrishnan
fd20246e8b ensureCertificate: check if cert needs renewal 2015-12-14 12:38:19 -08:00
Girish Ramakrishnan
0c1ea39a02 add getApi 2015-12-14 12:28:00 -08:00
Girish Ramakrishnan
a409dd026d use url file to download cert if present 2015-12-14 12:22:57 -08:00
Girish Ramakrishnan
4731f8e5a7 move key creation into the acme flow 2015-12-14 12:21:41 -08:00
Girish Ramakrishnan
7e05259b0e save url for renewal in .url files 2015-12-14 12:17:57 -08:00
Girish Ramakrishnan
14ab85dc4f do not pass outdir 2015-12-14 11:42:59 -08:00
Girish Ramakrishnan
0651bfc4b8 provide cert and key file in callback 2015-12-14 09:29:48 -08:00
Girish Ramakrishnan
21b94b2655 fix debug message 2015-12-14 08:52:43 -08:00
Girish Ramakrishnan
4e40c2341a code now uses backend 2015-12-14 08:50:57 -08:00
Girish Ramakrishnan
d9a83eacd2 explicitly prune out second argument 2015-12-13 20:35:23 -08:00
Girish Ramakrishnan
7b40674c0d add a backend for caas 2015-12-13 19:09:57 -08:00
Girish Ramakrishnan
936c1989f1 refactor code a bit for renewal 2015-12-13 12:26:31 -08:00
Girish Ramakrishnan
cfe336c37c fix path to acme key 2015-12-13 11:54:17 -08:00
Girish Ramakrishnan
d8a1e4aab0 more debug messages 2015-12-12 20:39:24 -08:00
Girish Ramakrishnan
be4d2afff3 fix path to cert 2015-12-12 20:30:50 -08:00
Girish Ramakrishnan
c2a4ef5f93 maybe this gets the certificate 2015-12-12 20:30:50 -08:00
Girish Ramakrishnan
b389d30728 max-time is per retry. it cannot take more than 3 mins to download the tarball 2015-12-12 17:36:34 -08:00
Girish Ramakrishnan
22634b4ceb tlsConfig is part of the database 2015-12-12 15:43:42 -08:00
Girish Ramakrishnan
abc4975b3d add tls configuration to database 2015-12-12 15:40:33 -08:00
Girish Ramakrishnan
36d81ff8d1 do not write tls config in this version 2015-12-12 14:21:50 -08:00
Girish Ramakrishnan
fe94190c2f do not save certs in database 2015-12-12 13:29:10 -08:00
Girish Ramakrishnan
f32027e15b Try alternative configuration for systemd restart rate limit 2015-12-12 13:15:41 -08:00
Girish Ramakrishnan
4b6a92955b configure to get only 1 email every 10 minutes 2015-12-12 11:47:32 -08:00
Girish Ramakrishnan
35a2da744c fix typo 2015-12-11 23:29:07 -08:00
Girish Ramakrishnan
9d91340223 add settings.setTlsConfig 2015-12-11 22:39:13 -08:00
Girish Ramakrishnan
e0a56f75c3 typo 2015-12-11 22:27:00 -08:00
Girish Ramakrishnan
4cfd30f9e8 use tlsConfig to determine acme or not 2015-12-11 22:25:57 -08:00
Girish Ramakrishnan
3fbcbf0e5d store tls config in database 2015-12-11 22:14:56 -08:00
Girish Ramakrishnan
8b7833e8b1 fix debug namespacing 2015-12-11 21:49:24 -08:00
Girish Ramakrishnan
66441f133d fix typo 2015-12-11 20:09:16 -08:00
Girish Ramakrishnan
8a12d6019a assert assert everywhere, hope none fires! 2015-12-11 14:50:30 -08:00
Girish Ramakrishnan
39c626dc75 more moving of nginx code 2015-12-11 14:48:39 -08:00
Girish Ramakrishnan
a7480c3f29 implement installation of admin certificate via acme 2015-12-11 14:37:55 -08:00
Girish Ramakrishnan
8af682acf1 add attempt 2015-12-11 14:20:37 -08:00
Girish Ramakrishnan
95eba1db81 Add certificates.ensureCertificate which gets cert via acme 2015-12-11 14:15:44 -08:00
Girish Ramakrishnan
0b8fde7d8d rename app.setAppCertificate 2015-12-11 14:13:29 -08:00
Girish Ramakrishnan
2f7517152a rename certificates.initialize 2015-12-11 14:02:58 -08:00
Girish Ramakrishnan
3e2ea0e087 refactor certificate settings 2015-12-11 13:58:43 -08:00
Girish Ramakrishnan
723556d6a2 Add CertificatesError 2015-12-11 13:43:33 -08:00
Girish Ramakrishnan
1f53d76cef wait forever by default 2015-12-11 13:41:17 -08:00
Girish Ramakrishnan
d15488431b add waitfordns.js (refactored from appstore) 2015-12-11 13:14:27 -08:00
Girish Ramakrishnan
cf80fd7dc5 rename certificatemanager 2015-12-11 12:24:52 -08:00
Girish Ramakrishnan
73d891b98e move validateCertificate to certificateManager 2015-12-10 20:38:49 -08:00
Girish Ramakrishnan
875ec1028d remove backward compat code now that we have migrated 2015-12-10 16:31:22 -08:00
Girish Ramakrishnan
fd985c2011 configure nginx as the last step
this allow us to wait for certificate (in the case of LE)
2015-12-10 15:26:36 -08:00
Girish Ramakrishnan
47981004c9 split port reserving to separate function
this allows us to move nginx configuration to the bottom of apptask
(required for tls cert download support)
2015-12-10 15:25:15 -08:00
Girish Ramakrishnan
e3f7c8f63d use fqdn to save admin certs as well 2015-12-10 14:29:54 -08:00
Girish Ramakrishnan
853db53f82 rename admin.cert/.key to {admin_fqdn}.cert/.key 2015-12-10 14:05:44 -08:00
Girish Ramakrishnan
5992c0534a remove dead comment 2015-12-10 13:56:00 -08:00
Girish Ramakrishnan
1874c93c5c no need to template main nginx config 2015-12-10 13:54:53 -08:00
Girish Ramakrishnan
3c4adb1aed fix config path 2015-12-10 13:36:44 -08:00
Girish Ramakrishnan
66db918273 add certificate manager stub 2015-12-10 13:35:02 -08:00
Girish Ramakrishnan
69845d5ddd add config.adminFqdn() 2015-12-10 13:14:13 -08:00
Girish Ramakrishnan
42181d597b keep the requires sorted 2015-12-10 13:08:38 -08:00
Girish Ramakrishnan
b56e9ca745 do not log the token 2015-12-10 12:50:54 -08:00
Girish Ramakrishnan
5fc4788269 remove test code 2015-12-10 11:09:37 -08:00
Girish Ramakrishnan
d0f8293b73 treat acme as a cert backend 2015-12-10 11:08:22 -08:00
Girish Ramakrishnan
44582bcd4b download the certificate as binary 2015-12-10 11:07:10 -08:00
Girish Ramakrishnan
5c73aed953 remove unused require 2015-12-10 09:54:21 -08:00
Girish Ramakrishnan
e1ec48530e acme: create cert file with the chain 2015-12-10 09:11:08 -08:00
Girish Ramakrishnan
54c4053728 add LE cross signed
https://letsencrypt.org/certs/lets-encrypt-x1-cross-signed.pem.txt
2015-12-10 09:06:36 -08:00
Girish Ramakrishnan
79ffb0df5c acme: openssl does not play well with buffers. use files instead 2015-12-10 08:57:53 -08:00
Girish Ramakrishnan
c510952c88 s/privateKeyPem/accountKeyPem 2015-12-09 19:23:19 -08:00
Girish Ramakrishnan
6109da531d acme: use safe 2015-12-09 19:22:53 -08:00
Girish Ramakrishnan
56877332db pull in urlBase64Encode 2015-12-09 18:34:27 -08:00
Girish Ramakrishnan
6fc972d160 set default response type to text/plain 2015-12-09 18:34:13 -08:00
Girish Ramakrishnan
5346153d9b add ursa 2015-12-09 18:33:35 -08:00
Girish Ramakrishnan
aaf266d272 convert cert to pem 2015-12-08 20:05:14 -08:00
Girish Ramakrishnan
0750db9aae rename function 2015-12-08 19:54:37 -08:00
Girish Ramakrishnan
316976d295 generate the acme account key on first run 2015-12-08 19:42:33 -08:00
Girish Ramakrishnan
593b5d945b use this fake email as the account owner for now 2015-12-08 19:15:17 -08:00
Girish Ramakrishnan
88f0240757 serve acme directory from nginx 2015-12-08 19:04:48 -08:00
Girish Ramakrishnan
f5c2f8849d Add LE staging url for testing 2015-12-08 18:25:45 -08:00
Girish Ramakrishnan
5c4a8f7803 add acme support
this is not used anywhere since we want to wait for rate limits to be
fixed.

The current limits are :

    Rate limit on registrations per IP is currently 10 per 3 hours
    Rate limit on certificates per Domain is currently 5 per 7 days

The domains are counted based on https://publicsuffix.org/list/ (not TLD). Like appspot.com, herokuapp.com while not a TLD, it a public suffix. This list allows browser authors to limit how cookies can be manipulated by the subdomain of those domains. like app1.appspot.com cannot go and change things of app2.appspot.com.

This means
a) we cannot use LE for cloudron.me, cloudron.us (or we have to get on that list)

b) even for custom domains we get only 5 certs every 7 days. And one of them is taken for my.xx domain.

https://community.letsencrypt.org/t/public-beta-rate-limits/4772/38
2015-12-08 15:52:30 -08:00
Girish Ramakrishnan
5b8fdad5cb Revert "remove targetBoxVersion checks since all apps are now ported"
This reverts commit d104f2a077.

gitlab is not ported :-(
2015-12-05 02:29:06 -08:00
Girish Ramakrishnan
fe819f95ec always return logs regardless of state 2015-12-04 13:13:54 -08:00
Girish Ramakrishnan
be6728f8cb send support an email for app crashes 2015-12-02 16:50:00 -08:00
Girish Ramakrishnan
24d3a81bc8 remove targetBoxVersion checks since all apps are now ported 2015-12-02 15:02:16 -08:00
Girish Ramakrishnan
268c7b5bcf always create an isolated network ns 2015-12-01 13:59:45 -08:00
Girish Ramakrishnan
64716a2de5 cloudron exec: disable links for subcontainers
Dec 01 08:36:53 girish.cloudron.us node[5431]: Error: HTTP code is 409 which indicates error: undefined - Conflicting options: --net=container can't be used with links. This would result in undefined behavior
2015-12-01 00:51:41 -08:00
Girish Ramakrishnan
d2c8457ab1 reset health when app is stopped 2015-11-30 15:41:56 -08:00
Johannes Zellner
667cb84af7 Protect from crash on shutdown 2015-11-27 10:05:57 +01:00
Girish Ramakrishnan
df8653cdd5 Do not set Hostname for subcontainers 2015-11-26 19:26:29 -08:00
Girish Ramakrishnan
32f677ca0d make app subcontainers share network namespace with app
pid namespace sharing is coming in https://github.com/docker/docker/issues/10163
2015-11-26 19:18:31 -08:00
Johannes Zellner
6f5408f0d6 Make all json blobs in db TEXT fields 2015-11-26 12:17:02 +01:00
Johannes Zellner
23c04fb10b Use console.error() to report update errors 2015-11-26 12:04:39 +01:00
Johannes Zellner
0c5d6b1045 Set app backup progress only after we check the error 2015-11-26 12:00:44 +01:00
Johannes Zellner
33f30decd1 Support redirectURIs which already contain query params 2015-11-25 17:50:39 +01:00
Johannes Zellner
9595b63939 Correctly encode the redirectURI in oauth callback 2015-11-25 17:45:18 +01:00
Johannes Zellner
b9695b09cd Fix crash due to wrong AppsError usage 2015-11-25 13:49:20 +01:00
Girish Ramakrishnan
606885b23c fix typo 2015-11-23 13:51:14 -08:00
Girish Ramakrishnan
bc7b8aadc4 vultr: fix waitForSnapshot call 2015-11-23 13:39:02 -08:00
Girish Ramakrishnan
d136b2065f ignore vultr transfer image call 2015-11-23 13:35:36 -08:00
Girish Ramakrishnan
3b2683463d localize transfer logic for DO 2015-11-23 13:35:05 -08:00
Girish Ramakrishnan
989730d402 wait for snapshot 2015-11-23 13:19:23 -08:00
Girish Ramakrishnan
50f7209ba2 print the provider 2015-11-23 12:46:08 -08:00
Girish Ramakrishnan
44b728c660 remove get_image_id api 2015-11-23 12:45:09 -08:00
Girish Ramakrishnan
9abc5bbf96 better error handling 2015-11-23 12:37:30 -08:00
Girish Ramakrishnan
56dd936e9c create systemd log dir if needed 2015-11-23 12:33:45 -08:00
Girish Ramakrishnan
e982281cd4 install acl 2015-11-23 11:32:05 -08:00
Girish Ramakrishnan
a6b7b5fa94 complete vultr backend 2015-11-23 11:30:24 -08:00
Girish Ramakrishnan
ef00114aab rename arg box to name 2015-11-23 11:20:21 -08:00
Girish Ramakrishnan
ba4edc5c0e implement some vultr api 2015-11-23 11:01:52 -08:00
Girish Ramakrishnan
dae2d81764 remove image_region as well 2015-11-23 10:49:09 -08:00
Girish Ramakrishnan
cee9cd14c0 hardcode the box size to smallest 2015-11-23 10:46:16 -08:00
Girish Ramakrishnan
f1ec110673 vultr: getSshKeyId 2015-11-23 10:27:27 -08:00
Girish Ramakrishnan
7104a3b738 use debug to put messages in stderr 2015-11-23 10:14:40 -08:00
Girish Ramakrishnan
114951b18c add get_image_id command 2015-11-23 09:31:42 -08:00
Girish Ramakrishnan
3c85a602a4 add vultr backend 2015-11-23 09:22:43 -08:00
Girish Ramakrishnan
a6415b8689 remove droplet from command names 2015-11-23 09:13:30 -08:00
Girish Ramakrishnan
b37670de84 make DO backend a binary 2015-11-23 08:59:45 -08:00
Girish Ramakrishnan
c9053bb0bc rename image creation schript 2015-11-23 08:39:21 -08:00
Girish Ramakrishnan
5362102be6 add --provider 2015-11-23 08:38:56 -08:00
Girish Ramakrishnan
bf4601470b remove functions not part of vps api 2015-11-23 08:33:57 -08:00
Girish Ramakrishnan
bd6274282b s/droplet/server 2015-11-23 08:32:54 -08:00
Girish Ramakrishnan
5a0f7df377 handle scheduler error 2015-11-22 21:17:17 -08:00
Girish Ramakrishnan
2e54be3df8 Revert "fix crash in scheduler"
This reverts commit 3b5e30f922.
2015-11-22 21:13:05 -08:00
Girish Ramakrishnan
6625610aca fix crash in scheduler 2015-11-22 17:22:06 -08:00
Girish Ramakrishnan
5c9abfe97a debug output the changeIds 2015-11-19 17:49:30 -08:00
Johannes Zellner
e06f3d4180 Docker bridge default ip has changed 2015-11-19 16:32:03 +01:00
Girish Ramakrishnan
331b4d8524 use docker 1.9.0 2015-11-18 18:28:28 -08:00
Girish Ramakrishnan
e3cc12da4f new addon images based on docker 1.9.0 2015-11-18 17:53:58 -08:00
Girish Ramakrishnan
1e19f68cb5 Install docker binaries instead of apt
The apt binaries lxc-* are obsolete and replaced with 'docker-engine'
packages. The new repos however do not allow pinning to a specific version.

so brain dead.

https://docs.docker.com/engine/installation/binaries/#get-the-linux-binary
2015-11-18 13:02:51 -08:00
Girish Ramakrishnan
c286b491d6 Fix debug output 2015-11-16 16:43:53 -08:00
Girish Ramakrishnan
c7acdbf20d add empty certs dir 2015-11-16 15:21:42 -08:00
Girish Ramakrishnan
3cd0cc01c4 Add test certs
This is simply a self-signed cert
2015-11-16 15:20:08 -08:00
Girish Ramakrishnan
87d109727a fix path to secrets 2015-11-16 15:08:26 -08:00
Girish Ramakrishnan
47b6819ec8 scp does not require this option 2015-11-16 14:48:05 -08:00
Girish Ramakrishnan
00ee89a693 fix paths 2015-11-16 14:31:39 -08:00
Girish Ramakrishnan
ac14b08af4 we have to use 4.1.1 2015-11-16 14:31:39 -08:00
Girish Ramakrishnan
db97d7e836 Fix options usage 2015-11-16 13:15:12 -08:00
Girish Ramakrishnan
5a0c80611e better error message 2015-11-16 12:15:44 -08:00
Girish Ramakrishnan
4e872865a3 use different keys for different env 2015-11-16 12:15:15 -08:00
Girish Ramakrishnan
aea39a83b6 change yellowtent key to caas 2015-11-16 12:12:07 -08:00
Johannes Zellner
3d80821203 Give correct feedback if an app cannot be found in the appstore 2015-11-13 10:35:29 +01:00
Johannes Zellner
d9bfcc7c8a Change manifestJson column from VARCHAR to TEXT 2015-11-13 10:21:03 +01:00
Johannes Zellner
8bd9a6c109 Do not serve up the status page for 500 upstream errors 2015-11-13 09:39:33 +01:00
Johannes Zellner
d89db24bfc Fix indentantion 2015-11-13 09:30:33 +01:00
Johannes Zellner
352b5ca736 Update supererror 2015-11-13 09:23:32 +01:00
Girish Ramakrishnan
6bd9173a9d this docker registry keeps going down 2015-11-12 16:22:53 -08:00
Girish Ramakrishnan
0cef3e1090 do not trust the health state blindly 2015-11-12 16:16:05 -08:00
Girish Ramakrishnan
6bd68961d1 typo 2015-11-12 16:13:15 -08:00
Girish Ramakrishnan
7f8ad917d9 filter out non-healthy apps 2015-11-12 16:04:33 -08:00
Girish Ramakrishnan
7cd89accaf better pullImage debug output 2015-11-12 15:58:39 -08:00
Girish Ramakrishnan
ffee084d2b new format of provisioning info 2015-11-12 14:22:43 -08:00
Girish Ramakrishnan
2bb657a733 rename variable for clarity 2015-11-12 12:40:41 -08:00
Girish Ramakrishnan
bc48171626 use fallback cert from backup if it exists 2015-11-12 12:37:43 -08:00
Girish Ramakrishnan
50924b0cd3 use admin.cert and admin.key if present in backup dir 2015-11-12 12:33:52 -08:00
Girish Ramakrishnan
3d86950cc9 fix indentation 2015-11-12 12:28:05 -08:00
Girish Ramakrishnan
db9ddf9969 backup fallback cert 2015-11-12 12:27:25 -08:00
Girish Ramakrishnan
1b507370dc Cannot use >= node 4.1.2
https://github.com/nodejs/node/issues/3803
2015-11-12 12:19:13 -08:00
Girish Ramakrishnan
b9a3c508c9 Fix target path 2015-11-12 06:58:01 -08:00
Girish Ramakrishnan
9ae49e7169 link npm 2015-11-11 22:04:58 -08:00
Girish Ramakrishnan
7a1cdd62a4 install node 4.2.2 2015-11-11 16:02:42 -08:00
Girish Ramakrishnan
a242881101 change engine requirements 2015-11-11 15:56:14 -08:00
Girish Ramakrishnan
3c5e221c39 change engine requirements 2015-11-11 15:55:59 -08:00
Girish Ramakrishnan
9c37f35d5a new shrinkwrap for 4.2.2 2015-11-11 15:55:24 -08:00
Girish Ramakrishnan
44ca59ac70 update shrinkwrap 2015-11-11 15:49:38 -08:00
Girish Ramakrishnan
398dfce698 update packages 2015-11-11 15:48:32 -08:00
Girish Ramakrishnan
0ebe6bde3d remove async and superagent from dev deps 2015-11-11 15:46:15 -08:00
Girish Ramakrishnan
4044070d76 Add -app prefix for all app sources
so that this doesn't conflict with some user.
2015-11-11 13:27:45 -08:00
Girish Ramakrishnan
8f05917d97 delete container on network error 2015-11-10 21:56:17 -08:00
Girish Ramakrishnan
3766d67daa create new container from cloudron exec 2015-11-10 21:36:20 -08:00
Johannes Zellner
b1290c073e log lines should be newline separated 2015-11-10 11:31:07 +01:00
Girish Ramakrishnan
15f686fc69 reboot automatically on panic after 5 seconds 2015-11-10 01:53:09 -08:00
Girish Ramakrishnan
36daf86ea2 send mail even if no related app was found (for addons) 2015-11-10 01:39:02 -08:00
Girish Ramakrishnan
4fb07a6ab3 make crashnotifier send mails again
mailer module waits for dns syncing. crashnotifier has no time for all that.
neither does it initialize the database. it simply wants to send mail.
(the crash itself could have happenned because of some db issue)

maybe it should simply use a shell script at some point.
2015-11-10 00:25:47 -08:00
Girish Ramakrishnan
8f2119272b print all missing images 2015-11-09 23:31:04 -08:00
Girish Ramakrishnan
ee5bd456e0 set bucket and prefix to make migrate test pass 2015-11-09 22:45:07 -08:00
Girish Ramakrishnan
9c549ed4d8 wait for old apptask to finish before starting new one
kill() is async and takes no callback :/
2015-11-09 22:10:10 -08:00
Girish Ramakrishnan
61fc8b7968 better taskmanager debugs 2015-11-09 21:58:34 -08:00
Girish Ramakrishnan
6b30d65e05 add backupConfig in test 2015-11-09 21:08:23 -08:00
Girish Ramakrishnan
10c876ac75 make migrate test work 2015-11-09 20:34:27 -08:00
Girish Ramakrishnan
0966bd0bb1 use debug instead of console.error 2015-11-09 19:10:33 -08:00
Girish Ramakrishnan
294d1bfca4 remove ununsed require 2015-11-09 18:57:57 -08:00
Girish Ramakrishnan
af1d1236ea expose api server origin to determine if the box is dev/staging/prod 2015-11-09 17:50:09 -08:00
Girish Ramakrishnan
eaf9febdfd do not save aws and backupKey
it is now part of backupConfig
2015-11-09 08:39:01 -08:00
Johannes Zellner
8748226ef3 The controller already ensures we don't show the views here 2015-11-09 09:56:59 +01:00
Johannes Zellner
73568777c0 Only show dns and cert pages for custom domain cloudrons 2015-11-09 09:56:08 +01:00
Girish Ramakrishnan
c64697dde7 cannot read propery provider of null 2015-11-09 00:42:25 -08:00
Girish Ramakrishnan
0701e38a04 do not set dnsConfig for caas on activation 2015-11-09 00:20:16 -08:00
Girish Ramakrishnan
2a27d96e08 pass dnsConfig in update 2015-11-08 23:57:42 -08:00
Girish Ramakrishnan
ba42611701 token is not a function 2015-11-08 23:54:47 -08:00
Girish Ramakrishnan
54486138f0 pass dnsConfig to backend api 2015-11-08 23:21:55 -08:00
Girish Ramakrishnan
13d3f506b0 always add dns config in tests 2015-11-08 23:05:55 -08:00
Girish Ramakrishnan
32ca686e1f read dnsConfig from settings to choose api backend 2015-11-08 22:55:31 -08:00
Girish Ramakrishnan
a5ef9ff372 Add getAllPaged to storage api 2015-11-08 22:41:13 -08:00
Girish Ramakrishnan
738bfa7601 remove unused variable 2015-11-08 11:04:39 -08:00
Girish Ramakrishnan
40cdd270b1 ensure correct token is used in tests 2015-11-07 22:19:23 -08:00
Girish Ramakrishnan
53a2a8015e set the backupConfig in backups test 2015-11-07 22:18:39 -08:00
Girish Ramakrishnan
15aaa440a2 Add test for settings.backupConfig 2015-11-07 22:17:08 -08:00
Girish Ramakrishnan
d8a4014eff remove bad reference to config.aws 2015-11-07 22:12:03 -08:00
Girish Ramakrishnan
d25d423ccd remove legacy update params
these are now part of settings
2015-11-07 22:07:25 -08:00
Girish Ramakrishnan
49b0fde18b remove config.aws and config.backupKey 2015-11-07 22:06:56 -08:00
Girish Ramakrishnan
8df7f17186 load backup config from settingsdb 2015-11-07 22:06:09 -08:00
Girish Ramakrishnan
adc395f888 save backupConfig in db 2015-11-07 21:45:38 -08:00
Girish Ramakrishnan
e770664365 Add backup config to settings 2015-11-07 18:02:45 -08:00
Girish Ramakrishnan
05d4ad3b5d read new format of restore keys 2015-11-07 17:53:54 -08:00
Girish Ramakrishnan
cc6f726f71 change backup provider to caas 2015-11-07 09:17:58 -08:00
Girish Ramakrishnan
a4923f894c prepare for new backupConfig 2015-11-07 00:26:12 -08:00
Girish Ramakrishnan
12200f2e0d split aws code into storage backends 2015-11-06 18:22:29 -08:00
Girish Ramakrishnan
a853afc407 backups: add api call 2015-11-06 18:14:59 -08:00
Girish Ramakrishnan
de471b0012 return BackupError and not SubdomainError 2015-11-06 18:08:25 -08:00
Girish Ramakrishnan
b6f1ad75b8 merge SubdomainError into subdomains.js like other error classes 2015-11-06 17:58:01 -08:00
Girish Ramakrishnan
e6840f352d remove spurious debugs 2015-11-05 11:59:11 -08:00
Johannes Zellner
6456874f97 Avoid ui glitch in setup if no custom domain
This moves the wizard flow logic to next() instead of the views individually
2015-11-05 20:32:30 +01:00
Johannes Zellner
66b4a4b02a Remove unused favicon middleware 2015-11-05 19:29:08 +01:00
Girish Ramakrishnan
7e36b3f8e5 mailer: set dnsReady flag 2015-11-05 10:28:41 -08:00
Girish Ramakrishnan
12061cc707 mailDnsRecordIds is never used 2015-11-05 10:22:25 -08:00
Girish Ramakrishnan
afcc62ecf6 mailServer is never used 2015-11-05 10:22:25 -08:00
Johannes Zellner
bec6850c98 Specify icon for oauth views 2015-11-05 19:19:28 +01:00
Girish Ramakrishnan
d253a06bab Revert "Remove unused very old yellowtent favicon"
This reverts commit c15a200d4a.

This is used by the favicon middleware
2015-11-05 10:14:59 -08:00
Johannes Zellner
857c5c69b1 force reload the webadmin on cert upload 2015-11-05 18:38:32 +01:00
Girish Ramakrishnan
766fc49f39 setup dkim records for custom domain 2015-11-05 09:30:23 -08:00
Johannes Zellner
941e09ca9f Update the cloudron icon 2015-11-05 18:05:19 +01:00
Johannes Zellner
2466a97fb8 Remove more unused image assets 2015-11-05 18:04:54 +01:00
Johannes Zellner
81f92f5182 Remover another unused favicon 2015-11-05 17:49:25 +01:00
Johannes Zellner
91e1d442ff Update the avatar 2015-11-05 17:48:18 +01:00
Johannes Zellner
a1d6ae2296 Remove unused very old yellowtent favicon 2015-11-05 17:46:17 +01:00
Girish Ramakrishnan
b529fd3bea we expect the server cert to be first like the rfc says 2015-11-04 19:22:37 -08:00
Girish Ramakrishnan
bf319cf593 more verbose certificate message 2015-11-04 19:15:23 -08:00
Girish Ramakrishnan
15eedd2a84 pass on certificate errors 2015-11-04 19:13:16 -08:00
Girish Ramakrishnan
d0cd3d1c32 Put dns first since it says dns & certs 2015-11-04 18:23:51 -08:00
Girish Ramakrishnan
747786d0c8 fix avatar url for custom domains 2015-11-04 18:21:46 -08:00
Girish Ramakrishnan
b232255170 move dns and certs to own view 2015-11-04 18:21:43 -08:00
Girish Ramakrishnan
bd2982ea69 move backups after about 2015-11-04 16:41:33 -08:00
Girish Ramakrishnan
1c948cc83c route53: Do no use weight and setIdentifier 2015-11-04 15:15:39 -08:00
Girish Ramakrishnan
ccde1e51ad debug any failure 2015-11-04 14:28:26 -08:00
Girish Ramakrishnan
03ec940352 Add space in spf record 2015-11-04 14:22:56 -08:00
Girish Ramakrishnan
bd5b15e279 insert any dns config into settings 2015-11-04 08:45:12 -08:00
Girish Ramakrishnan
b6897a4577 Revert "Add isConfigured fallback for caas cloudrons"
This reverts commit 338f68a0f3.

We will send dns config from appstore instead
2015-11-04 08:28:21 -08:00
Johannes Zellner
f7225523ec Add isConfigured fallback for caas cloudrons 2015-11-04 12:53:16 +01:00
Girish Ramakrishnan
6a0b8e0722 remove unused backup route 2015-11-03 16:52:38 -08:00
Girish Ramakrishnan
9d9509525c listen on timezone key only when configured 2015-11-03 16:11:24 -08:00
Girish Ramakrishnan
b1dbb3570b Add configured event
Cloudron code paths like cron/mailer/taskmanager now wait for configuration
to be complete before doing anything.

This is useful when a cloudron is moved from a non-custom domain to a custom domain.
In that case, we require route53 configs.
2015-11-03 16:06:38 -08:00
Girish Ramakrishnan
c075160e5d Remove event listener 2015-11-03 15:22:02 -08:00
Girish Ramakrishnan
612ceba98a unsubscribe from events 2015-11-03 15:19:06 -08:00
Girish Ramakrishnan
7d5e0040bc debug only if error 2015-11-03 15:15:37 -08:00
Girish Ramakrishnan
d6e19d2000 resume tasks only if cloudron is activated 2015-11-03 15:14:59 -08:00
Girish Ramakrishnan
a01d5db2a0 minor refactor 2015-11-02 17:45:38 -08:00
Girish Ramakrishnan
5de3baffd4 send monotonic timestamp as well 2015-11-02 14:26:15 -08:00
Girish Ramakrishnan
63c10e8f02 fix typo 2015-11-02 14:23:02 -08:00
Girish Ramakrishnan
a99e7c2783 disable logstream testing (since it requires journald) 2015-11-02 14:08:34 -08:00
Girish Ramakrishnan
88b1cc553f Use journalctl to get app logs 2015-11-02 14:08:34 -08:00
Girish Ramakrishnan
316e8dedd3 name is a query parameter 2015-11-02 14:08:34 -08:00
Girish Ramakrishnan
bb040eb5a8 give yellowtent user access to cloudron logs 2015-11-02 13:20:43 -08:00
Johannes Zellner
f106a76cd5 Fix the avatar and brand label links in navbar 2015-11-02 21:27:52 +01:00
Girish Ramakrishnan
95b2bea828 Give containers a name 2015-11-02 09:34:31 -08:00
Girish Ramakrishnan
51a0ad70aa log to journald instead 2015-11-02 08:54:10 -08:00
Girish Ramakrishnan
71faa5f89e Move ssh to port 202 2015-11-01 13:16:32 -08:00
Girish Ramakrishnan
58d6166592 fix indexOf matching in addDnsRecords 2015-10-30 18:12:24 -07:00
Girish Ramakrishnan
d42f66bfed Fix casing of zone id 2015-10-30 18:05:08 -07:00
Girish Ramakrishnan
6cf0554e23 do not resolve to a tag 2015-10-30 17:40:17 -07:00
Girish Ramakrishnan
5bd8579e73 add null check 2015-10-30 16:24:56 -07:00
Girish Ramakrishnan
01cd0b6b87 fix indexOf matching 2015-10-30 16:12:39 -07:00
Girish Ramakrishnan
b4aec552fc txtRecords is a single level array 2015-10-30 16:04:09 -07:00
Girish Ramakrishnan
93ab606d94 dns resolve using the authoritative nameserver 2015-10-30 15:57:15 -07:00
Girish Ramakrishnan
94e94f136d sendHearbeat on init 2015-10-30 14:58:48 -07:00
Girish Ramakrishnan
1b57128ef6 ListResourceRecordSet returns items based on lexical sorting 2015-10-30 14:41:34 -07:00
Girish Ramakrishnan
219a2b0798 rename function 2015-10-30 13:53:12 -07:00
Girish Ramakrishnan
b37d5b0fda enable back spf 2015-10-30 13:48:46 -07:00
Girish Ramakrishnan
0e9aac14eb leave a note on subdomains.update 2015-10-30 13:47:10 -07:00
Girish Ramakrishnan
cf81ab0306 subdomains.update now takes array 2015-10-30 13:45:10 -07:00
Girish Ramakrishnan
00d8148e46 fix get call 2015-10-30 13:45:10 -07:00
Johannes Zellner
0b59281dbb Remove leftover console.log() 2015-10-30 21:30:57 +01:00
Johannes Zellner
e0c845ca16 Fix cloudron tests when run together 2015-10-30 21:30:57 +01:00
Girish Ramakrishnan
d6bff57c7d subdomains.del now takes array values 2015-10-30 13:30:19 -07:00
Girish Ramakrishnan
5c4b4d764e implement subdomains.get for route53 2015-10-30 13:23:43 -07:00
Girish Ramakrishnan
bf13b5b931 subdomains.add takes array values 2015-10-30 13:23:43 -07:00
Johannes Zellner
afade0a5ac Ensure the next buttons are properly disabled when the fields are invalid 2015-10-30 21:10:01 +01:00
Johannes Zellner
40da8736d4 Do not use special wizard page controller for dns view 2015-10-30 21:10:01 +01:00
Johannes Zellner
a55675b440 Fixup the enter focus flow 2015-10-30 21:10:01 +01:00
Girish Ramakrishnan
6ce71c7506 prepare dns backends to accepts array of values 2015-10-30 13:04:43 -07:00
Girish Ramakrishnan
0dda91078d remove listeners on uninitialize 2015-10-30 12:52:33 -07:00
Girish Ramakrishnan
93632f5c76 disable spf for testing 2015-10-30 12:50:47 -07:00
Girish Ramakrishnan
cb4cd10013 settings changed callback provides the changed setting as first argument 2015-10-30 12:50:47 -07:00
Johannes Zellner
62bcf09ab4 Fix error message in set dns config 2015-10-30 20:32:58 +01:00
Girish Ramakrishnan
b466dc1970 remove unused require 2015-10-30 11:43:40 -07:00
Girish Ramakrishnan
0a10eb66cc caas: add subdomains.get 2015-10-29 16:41:04 -07:00
Girish Ramakrishnan
c6322c00aa remove unused subdomains.addMany 2015-10-29 16:39:07 -07:00
Girish Ramakrishnan
b549a4bddf minor rename of variable 2015-10-29 16:38:18 -07:00
Girish Ramakrishnan
3fa50f2a1a handle getSubdomain error 2015-10-29 16:27:01 -07:00
Girish Ramakrishnan
ddded0ebfb emit change event 2015-10-29 16:21:41 -07:00
Girish Ramakrishnan
71c0945607 add updateSubdomain in route53 backend 2015-10-29 15:37:51 -07:00
Girish Ramakrishnan
f0295c5dc5 debug message for update already in progress 2015-10-29 15:34:30 -07:00
Girish Ramakrishnan
4e1286a8cf addDnsRecords on restarts after activation 2015-10-29 15:00:53 -07:00
Girish Ramakrishnan
d69cead362 remove unused variable 2015-10-29 14:57:51 -07:00
Girish Ramakrishnan
7699cffa26 implement dns updates for custom domains 2015-10-29 14:33:34 -07:00
Girish Ramakrishnan
1021fc566f Add subdomains.update 2015-10-29 14:16:09 -07:00
Girish Ramakrishnan
1fb3b2c373 Add get and update subdomain to caas 2015-10-29 14:15:54 -07:00
Girish Ramakrishnan
2428000262 Add square bracket for empty string/no apps 2015-10-29 13:07:52 -07:00
Johannes Zellner
3d5b4f3191 Ensure we only show cert related ui parts for custom domain cloudrons 2015-10-29 21:01:24 +01:00
Girish Ramakrishnan
eb6a217f4a set non-custom domain provider as caas 2015-10-29 12:41:31 -07:00
Girish Ramakrishnan
06aaf98716 dnsConfig provider can be caas 2015-10-29 12:33:10 -07:00
Girish Ramakrishnan
26fc1fd7a6 use debug again 2015-10-29 12:28:50 -07:00
Girish Ramakrishnan
a9aa3c4fd8 use debug instead of console.error 2015-10-29 12:26:58 -07:00
Girish Ramakrishnan
61d4509a8e do not emit fake activation event
cloudron is simply initialized the first thing
2015-10-29 12:18:25 -07:00
Johannes Zellner
8cff4f4ff1 Only print an app alive digest 2015-10-29 19:54:20 +01:00
Girish Ramakrishnan
5dc30e02c4 mailer: do not start until activated 2015-10-29 10:12:44 -07:00
Girish Ramakrishnan
55f070e12c ensure cloudron.js is initialized first 2015-10-29 10:11:05 -07:00
Girish Ramakrishnan
0afb8f51c3 Fix typo 2015-10-29 09:00:31 -07:00
Girish Ramakrishnan
42f2637078 setup dns records on activation
do no wait for records to sync as well. the appstore does all the
waiting now (or the user in selfhosted case)
2015-10-29 08:43:25 -07:00
Girish Ramakrishnan
bbec7c6610 send heartbeat regardless of activation 2015-10-29 08:40:52 -07:00
Johannes Zellner
76fc257661 Add a link to support page 2015-10-29 14:48:02 +01:00
Johannes Zellner
58ce50571a Adjust text 2015-10-29 14:41:59 +01:00
Johannes Zellner
14205d2810 Add missing = 2015-10-29 14:36:25 +01:00
Johannes Zellner
d798fc4b3f Show current dns config in settings ui 2015-10-29 14:34:43 +01:00
Johannes Zellner
d29d07cb2d Add Client.getDnsConfig() 2015-10-29 14:34:25 +01:00
Johannes Zellner
07a0b360f6 Add form logic for dns credentials 2015-10-29 13:53:48 +01:00
Johannes Zellner
8b253a8a61 Rename cert header 2015-10-29 12:56:12 +01:00
Johannes Zellner
fddbf96c9c Add form feedback for cert forms in settings 2015-10-29 12:55:32 +01:00
Johannes Zellner
d1d01ae4b8 Do not double submit the forms 2015-10-29 12:37:59 +01:00
Johannes Zellner
51706afc46 Fix typo in field name 2015-10-29 12:37:30 +01:00
Johannes Zellner
d4ea23b1ac Add client.setAdminCertificate() 2015-10-29 12:29:25 +01:00
Johannes Zellner
0460beccf0 Add route to set the admin certificate
This route is separate until we can treat the webadmin just
like any other app
2015-10-29 12:27:57 +01:00
Johannes Zellner
aa5ed17dfa streamline cert upload forms in settings 2015-10-29 11:52:53 +01:00
Girish Ramakrishnan
32173b19c9 Do not subscribe to activation event if already activated 2015-10-28 17:07:13 -07:00
Girish Ramakrishnan
1a8fd7dd92 remove gInitialized pattern
not sure why we initialize anything more than once
2015-10-28 17:05:28 -07:00
Girish Ramakrishnan
f0047bc1aa console.error -> debug 2015-10-28 17:05:16 -07:00
Girish Ramakrishnan
917832e0ae Change DKIM selector to cloudron 2015-10-28 16:16:15 -07:00
Girish Ramakrishnan
cf8948ac69 console.error to debug 2015-10-28 16:08:12 -07:00
Girish Ramakrishnan
b2df639155 Move dns backends to separate directory 2015-10-28 16:04:49 -07:00
Girish Ramakrishnan
70ace09ff5 remove unused digitalocean.js 2015-10-28 15:25:22 -07:00
Girish Ramakrishnan
35a69f595a remove unused require 2015-10-28 15:22:10 -07:00
Girish Ramakrishnan
f4c4a931d2 Fix debug message 2015-10-28 15:21:47 -07:00
Girish Ramakrishnan
7caced2fe8 Do not send email if SPF record is not setup correctly 2015-10-28 14:45:51 -07:00
Johannes Zellner
846e5deb36 Add cert form error reporting to app install form 2015-10-28 22:21:20 +01:00
Johannes Zellner
eca328b247 Add cert and key to app install route 2015-10-28 22:09:19 +01:00
Johannes Zellner
c0e9091e4b Preselect current user for singleuser apps 2015-10-28 22:07:41 +01:00
Johannes Zellner
6b6e417435 Add user id to profile object 2015-10-28 22:07:30 +01:00
Johannes Zellner
954bb7039c Make cert forms appear as a group 2015-10-28 21:20:59 +01:00
Johannes Zellner
ae01f517c7 Mark cert fields as optional 2015-10-28 20:51:11 +01:00
Johannes Zellner
385bfe07e2 Add cert handling to install form 2015-10-28 20:50:55 +01:00
Johannes Zellner
25aff6a53b Remove unsed scope vars 2015-10-28 20:38:22 +01:00
Johannes Zellner
edcbf79b85 Add form feedback for certs 2015-10-28 20:30:35 +01:00
Girish Ramakrishnan
2591b8e10c minor rewording 2015-10-28 10:17:46 -07:00
Johannes Zellner
9df9d1667f Test certs are now simply embedded 2015-10-28 16:34:09 +01:00
Johannes Zellner
7798111af1 Ensure the test certs match domain and the folder is created 2015-10-28 16:33:45 +01:00
Johannes Zellner
12351113a9 Fixup the tests for wildcard cert 2015-10-28 16:00:51 +01:00
Johannes Zellner
d9256f99af Make sure the dns sync file is removed 2015-10-28 15:32:49 +01:00
Johannes Zellner
cf021066ed Move cert validation to settings and use it for wildcard cert 2015-10-28 14:35:39 +01:00
Johannes Zellner
04eb2a982f Add proper cert validator 2015-10-28 14:20:25 +01:00
Johannes Zellner
22dcc787b5 Add x509 node module 2015-10-28 14:20:03 +01:00
Johannes Zellner
5d4d0c0a86 Add missing fs. 2015-10-28 12:56:09 +01:00
Johannes Zellner
e81db9728a Set the cert and key dynamically when rendering nginx appconfig 2015-10-28 12:42:04 +01:00
Johannes Zellner
db305af8c9 We name certs with .cert extension 2015-10-28 12:33:27 +01:00
Johannes Zellner
4b3aca7773 certs should be stored with app fqdn 2015-10-28 12:28:57 +01:00
Johannes Zellner
5b5abe99e7 Save the uploaded certs to app cert directory 2015-10-28 12:24:59 +01:00
Johannes Zellner
8f670eb755 Add per app cert dir 2015-10-28 12:23:16 +01:00
Johannes Zellner
21a604814c Add tests for app cert upload 2015-10-28 12:13:37 +01:00
Johannes Zellner
7eeb835d96 Adjust the settings view to upload certs as json body 2015-10-28 12:12:54 +01:00
Johannes Zellner
57de915133 Make settings certificate upload route also just using the json body 2015-10-28 12:12:06 +01:00
Johannes Zellner
a892de5c2d Ensure cert and key are strings 2015-10-28 11:50:50 +01:00
Johannes Zellner
69cd01955b No more dns view 2015-10-28 10:36:55 +01:00
Girish Ramakrishnan
f39809c941 EE API is synchronous 2015-10-27 22:18:02 -07:00
Girish Ramakrishnan
09c4bfeb51 Add DNS records for non-custom domains before activation 2015-10-27 21:10:00 -07:00
Girish Ramakrishnan
615789a9ad fix unregisterSubdomain loop 2015-10-27 18:53:06 -07:00
Girish Ramakrishnan
bec5eaf3c9 send heartbeat immediately on startup 2015-10-27 17:05:56 -07:00
Girish Ramakrishnan
4f13ef9cea hearbeat does not rely on dns sync 2015-10-27 16:42:24 -07:00
Girish Ramakrishnan
873de48beb Do not add DNS records for custom domain 2015-10-27 16:23:08 -07:00
Girish Ramakrishnan
87e70b86d3 sendHeartbeat on activation event 2015-10-27 16:20:14 -07:00
Girish Ramakrishnan
140aa85223 Add cloudron.isActivatedSync 2015-10-27 16:12:05 -07:00
Girish Ramakrishnan
3ac3207497 send heartbeats regardless of activation 2015-10-27 16:05:19 -07:00
Girish Ramakrishnan
e36a0b9a30 create cron jobs only on activation 2015-10-27 16:04:29 -07:00
Girish Ramakrishnan
0b1aac7687 add null check for all jobs 2015-10-27 16:02:42 -07:00
Girish Ramakrishnan
e008cde2ff Add dns records on activation 2015-10-27 16:00:31 -07:00
Girish Ramakrishnan
d1e46be8ad Do not set dns config if null 2015-10-27 12:41:13 -07:00
Girish Ramakrishnan
dc18a18248 remove unused variables 2015-10-27 12:39:06 -07:00
Girish Ramakrishnan
b9a0ad73ab $location is not defined 2015-10-27 12:37:01 -07:00
Girish Ramakrishnan
e2c3fb309c Add custom domain setup step 2015-10-27 12:04:27 -07:00
Girish Ramakrishnan
d5255b8cf4 Add Client.setDnsConfig 2015-10-27 12:02:47 -07:00
Girish Ramakrishnan
42e70e870b deploymentConfig is never used 2015-10-27 11:38:05 -07:00
Johannes Zellner
8ffd7b0197 Adjust the webadmin code for cert upload 2015-10-27 18:38:46 +01:00
Johannes Zellner
01ead194d8 Move cert upload route to /settings 2015-10-27 18:38:46 +01:00
Girish Ramakrishnan
80b9d4be50 awscredentials route is not called anymore 2015-10-27 10:24:42 -07:00
Girish Ramakrishnan
ef06836804 make apps-test partially work 2015-10-27 10:14:51 -07:00
Johannes Zellner
916870b546 Send cert and key with configure 2015-10-27 18:11:48 +01:00
Girish Ramakrishnan
2da7216be6 make apptask-test work 2015-10-27 10:02:43 -07:00
Girish Ramakrishnan
54215cff7a Use the aws backend for tests 2015-10-27 10:02:43 -07:00
Girish Ramakrishnan
166257bbdc Allow endpoint to be configured (for the tests) 2015-10-27 10:02:43 -07:00
Girish Ramakrishnan
d502e04cbd use aws backend for custom domains 2015-10-27 10:02:43 -07:00
Girish Ramakrishnan
1fca680a67 read dns config from settings 2015-10-27 10:02:43 -07:00
Johannes Zellner
4ea3238391 Pass certs down to apps.configure 2015-10-27 16:36:09 +01:00
Johannes Zellner
fa12e7bd97 Add cert and key to app configure route 2015-10-27 15:44:47 +01:00
Johannes Zellner
6118535c4a Add test helper script to generate a selfsigned cert 2015-10-27 15:06:53 +01:00
Johannes Zellner
920f04aab3 Use test-app image 10.0.0 2015-10-27 14:20:19 +01:00
Johannes Zellner
ed13f2d6ef Add basic form elements for certificate in app configure 2015-10-27 12:26:55 +01:00
Johannes Zellner
dff27fe7b3 Remove unused dns views 2015-10-27 10:40:05 +01:00
Johannes Zellner
5d589e7330 Move certificate upload form from dns to settings 2015-10-27 10:39:02 +01:00
Johannes Zellner
01ec16f472 Remove useless console.log() 2015-10-27 10:38:11 +01:00
Girish Ramakrishnan
f510d4bc10 add route for setting/getting dns settings 2015-10-26 16:52:59 -07:00
Girish Ramakrishnan
2db2eb13af add settings.get/setDnsConfig 2015-10-26 16:35:50 -07:00
Girish Ramakrishnan
82e1c07722 separate out dns and backup credentials 2015-10-26 16:23:41 -07:00
Girish Ramakrishnan
23ba078a17 Fix redis hostname 2015-10-23 19:24:22 -07:00
Girish Ramakrishnan
b5358e7565 recreate docker containers for hostname change 2015-10-23 16:30:17 -07:00
Girish Ramakrishnan
697699bd5f test the new env vars APP_* 2015-10-23 16:27:40 -07:00
Girish Ramakrishnan
dd2a806ab8 Do not set hostname of app container
Some apps like pasteboard try to curl the public app url from inside
the container. This fails because we set the hostname and the hostname
maps to the internal docker IP.

To fix this, simply export two environment variables providing the
app's domain and origin. The hostname is set to the app location instead
of the FQDN for debugging.

Fixes #521
2015-10-23 16:17:35 -07:00
Girish Ramakrishnan
84d96cebee linter fixes 2015-10-23 16:06:55 -07:00
Johannes Zellner
10658606d7 Bring back 'Cloudron' in the login header 2015-10-23 20:21:31 +02:00
Johannes Zellner
f72d89fa76 Replace the ugly oauth proxy checkbox 2015-10-22 13:18:58 +02:00
Johannes Zellner
f9f4a8e7ad Check memory availability if an app can be installed or not 2015-10-22 11:16:55 +02:00
Johannes Zellner
fd58e83da9 Provide the memory byte count with the cloudron config route 2015-10-22 11:16:55 +02:00
Johannes Zellner
bfcedfdb2a Add node module bytes 2015-10-22 11:16:55 +02:00
Johannes Zellner
d11e030150 Add resource constrait view on app installation attempt 2015-10-22 11:16:55 +02:00
Johannes Zellner
6103640b53 Remove leftover console.log() 2015-10-22 11:16:55 +02:00
Girish Ramakrishnan
259199897b update test image 2015-10-21 09:16:04 -07:00
Johannes Zellner
ee498b9e2b A readable stream does not have .end() 2015-10-21 17:25:14 +02:00
Johannes Zellner
18a464b1d2 Make data/ directory writeable by yellowtent user 2015-10-21 17:18:45 +02:00
Johannes Zellner
d1c8e34540 dns in sync file should be under data/ 2015-10-21 17:18:39 +02:00
Johannes Zellner
a151846f1c Use config.(set)dnsInSync()
Fixes #520
2015-10-21 16:44:03 +02:00
Johannes Zellner
9f19b0bc9e Use a persistent file for dns sync flag 2015-10-21 16:42:17 +02:00
Johannes Zellner
289fe76adc Avoid network request for access token verification in oauth proxy 2015-10-21 16:23:15 +02:00
Johannes Zellner
1eb1c44926 Clear oauthproxy session in case the access token is invalid 2015-10-21 15:57:18 +02:00
Girish Ramakrishnan
bc09e4204b use debug instead of console.error 2015-10-20 19:03:34 -07:00
Girish Ramakrishnan
1a2948df85 VolumesFrom is part of HostConfig 2015-10-20 17:34:47 -07:00
Girish Ramakrishnan
16df15cf55 containerId does not mean it is running 2015-10-20 16:56:57 -07:00
Girish Ramakrishnan
0566bad6d9 bump infra version 2015-10-20 15:07:35 -07:00
Girish Ramakrishnan
edc90ccc00 bump test image 2015-10-20 14:40:27 -07:00
Girish Ramakrishnan
3688602d16 test the scheduler 2015-10-20 14:30:50 -07:00
Girish Ramakrishnan
0deadc5cf2 autodetect image id 2015-10-20 13:07:25 -07:00
Girish Ramakrishnan
10ac435d53 addons is mandatory 2015-10-20 12:57:00 -07:00
Girish Ramakrishnan
16f025181f ensure boolean 2015-10-20 12:49:02 -07:00
Girish Ramakrishnan
3808f60e69 appState can be null 2015-10-20 12:32:50 -07:00
Girish Ramakrishnan
a00615bd4e manifest always has addons 2015-10-20 12:27:23 -07:00
Girish Ramakrishnan
14bc2c7232 rename isSubcontainer -> isAppContainer 2015-10-20 10:55:06 -07:00
Girish Ramakrishnan
76d286703c ignore portBindings and exportPorts for subcontainers 2015-10-20 10:42:35 -07:00
Girish Ramakrishnan
c80a5b59ab do not dump containerOptions 2015-10-20 10:27:53 -07:00
Girish Ramakrishnan
db6882e9f5 do not kill containers on restart 2015-10-20 10:22:42 -07:00
Girish Ramakrishnan
3fd9d9622b schedulerConfig cannot be null 2015-10-20 09:44:46 -07:00
Girish Ramakrishnan
5ae4c891de scheduler: sync more often to catch bugs sooner 2015-10-20 09:36:55 -07:00
Girish Ramakrishnan
fb2e7cb009 scheduler: crash fixes 2015-10-20 09:36:30 -07:00
Johannes Zellner
8124f0ac7f Remove cloudron name from the setup wizard 2015-10-20 13:36:25 +02:00
Johannes Zellner
446f571bec The activate route does not take a cloudron name anymore 2015-10-20 13:12:37 +02:00
Johannes Zellner
142ae76542 Finally remove the cloudron name from the api wrapper and index page 2015-10-20 12:59:57 +02:00
Johannes Zellner
ed1873f47e Remove cloudron name related UI in the settings view 2015-10-20 12:57:51 +02:00
Johannes Zellner
0ee04e6ef3 Remove cloudron name usage in error.html 2015-10-20 12:55:30 +02:00
Johannes Zellner
1e4475b275 Remove cloudron name usage in naked domain page 2015-10-20 12:46:45 +02:00
Johannes Zellner
9dd9743943 Do not use cloudron name in appstatus 2015-10-20 12:41:52 +02:00
Johannes Zellner
5fbcebf80b Stop using the cloudron name in the oauth views 2015-10-20 12:31:16 +02:00
Girish Ramakrishnan
852b016389 scheduler: do not save cronjob object in state
the cronjob object has lots of js stuff and stringify fails
2015-10-20 01:31:11 -07:00
Girish Ramakrishnan
73f28d7653 put back request 2015-10-20 00:20:21 -07:00
Girish Ramakrishnan
1f28678c27 scheduler: make it work 2015-10-20 00:05:19 -07:00
Girish Ramakrishnan
daba68265c stop all containers of an app 2015-10-20 00:05:19 -07:00
Girish Ramakrishnan
6d04481c27 fix debug tag 2015-10-19 23:38:55 -07:00
Girish Ramakrishnan
ed5d6f73bb scheduler: fix require 2015-10-19 22:42:13 -07:00
Girish Ramakrishnan
d0360e9e68 scheduler: load/save state 2015-10-19 22:41:42 -07:00
Girish Ramakrishnan
32ddda404c explicitly specify all to 0 (this is the default) 2015-10-19 22:09:38 -07:00
Girish Ramakrishnan
41de667e3d do not set container name (we use labels instead) 2015-10-19 22:09:38 -07:00
Girish Ramakrishnan
8530e70af6 delete all containers of an app 2015-10-19 22:09:34 -07:00
Girish Ramakrishnan
7a840ad15f scheduler: make stopJobs async 2015-10-19 21:36:55 -07:00
Girish Ramakrishnan
682c2721d2 scheduler: kill existing tasks if they are still running 2015-10-19 21:36:23 -07:00
Girish Ramakrishnan
fb56795cbd merge start options into hostconfig 2015-10-19 21:35:02 -07:00
Girish Ramakrishnan
15aa4ecc5d Add docker.createSubcontainer 2015-10-19 21:33:53 -07:00
Girish Ramakrishnan
351d7d22fb rename tasks to tasksConfig 2015-10-19 16:29:28 -07:00
Girish Ramakrishnan
79999887a9 job -> cronJob 2015-10-19 16:27:03 -07:00
Girish Ramakrishnan
25d74ed649 createContainer takes optional command 2015-10-19 16:22:35 -07:00
Girish Ramakrishnan
9346666b3e add labels to container 2015-10-19 16:01:04 -07:00
Girish Ramakrishnan
13453552b5 createContainer only takes app object 2015-10-19 16:00:40 -07:00
Girish Ramakrishnan
ef38074b55 add asserts 2015-10-19 15:51:02 -07:00
Girish Ramakrishnan
e5e8eea7ac make it work without app object 2015-10-19 15:45:43 -07:00
Girish Ramakrishnan
9be2efc4f2 downloadImage only requires manifest now 2015-10-19 15:37:57 -07:00
Girish Ramakrishnan
990b7a2d20 implement scheduler
- scan for apps every 10 minutes and schedules tasks
- uses docker.exec
    - there is no way to control exec container. docker developers
      feel exec is for debugging purposes primarily
- future version will be based on docker run instead

part of #519
2015-10-19 14:53:34 -07:00
Girish Ramakrishnan
8d6dd62ef4 refactor container code into docker.js 2015-10-19 14:44:01 -07:00
Girish Ramakrishnan
69d09e8133 use docker.connection 2015-10-19 14:09:20 -07:00
Girish Ramakrishnan
6671b211e0 export a connection property from docker.js 2015-10-19 11:24:21 -07:00
Girish Ramakrishnan
307e815e97 remove unused require 2015-10-19 11:18:50 -07:00
Girish Ramakrishnan
d8e2bd6ff5 Refactor docker.js to not have mac stuff 2015-10-19 11:14:11 -07:00
Girish Ramakrishnan
e74c2f686b remove unused require 2015-10-19 11:05:31 -07:00
Girish Ramakrishnan
c7d5115a56 Remove vbox.js
... and all related mac code. It's totally untested at this point and
most likely doesn't work
2015-10-19 10:54:36 -07:00
Girish Ramakrishnan
774ba11a92 Move HostConfig to createContainer
Newer docker has obsoleted HostConfig in start container
2015-10-19 10:38:46 -07:00
Girish Ramakrishnan
322edbdc20 getByAppIdAndType 2015-10-19 08:58:07 -07:00
Johannes Zellner
c1ba551e07 Cleanup some of the html form elements 2015-10-19 10:31:19 +02:00
Johannes Zellner
9917412329 Indicate during app installation and configuration if the app is a single user app 2015-10-19 10:29:51 +02:00
Girish Ramakrishnan
2f4adb4d5f keep addon listing alphabetical 2015-10-18 20:06:26 -07:00
Girish Ramakrishnan
b61b864094 make callback noop 2015-10-17 13:57:19 -07:00
Johannes Zellner
fa193276c9 Require exactly one user in accessRestriction for singleUser app installations 2015-10-16 20:01:45 +02:00
Johannes Zellner
0ca09c384a Hide client secret field for simple auth 2015-10-16 19:41:50 +02:00
Johannes Zellner
a6a39cc4e6 Adapt clients.getAllWithDetailsByUserId() to new client types 2015-10-16 19:36:12 +02:00
Johannes Zellner
c9f84f6259 Show user selection for singleUser apps 2015-10-16 18:06:49 +02:00
Johannes Zellner
07063ca4f0 Adjust the webadmin to the accessRestriction changes 2015-10-16 16:14:23 +02:00
Johannes Zellner
b5cfdcf875 Fixup the unit tests for accessRestriction format change 2015-10-16 16:06:13 +02:00
Johannes Zellner
373db25077 Make accessRestriction a JSON format to prepare for group access control 2015-10-16 15:32:19 +02:00
Johannes Zellner
f8c2ebe61a Taks accessRestriction and oauthProxy into account for an update through the cli 2015-10-16 14:50:00 +02:00
Johannes Zellner
ae23fade1e Show oauthProxy and accessRestriction values at app installation and configuration 2015-10-16 14:50:00 +02:00
Johannes Zellner
5386c05c0d Give developer tokens the correct scopes 2015-10-16 14:50:00 +02:00
Johannes Zellner
aed94c8aaf roleDeveloper is no more 2015-10-16 14:50:00 +02:00
Johannes Zellner
37185fc4d5 Only allow simple auth clients through simple auth 2015-10-16 14:49:51 +02:00
Johannes Zellner
cc64c6c9f7 Test using simple auth credentials in oauth 2015-10-16 11:48:12 +02:00
Johannes Zellner
0c0782ccd7 Fixup oauth to not allow simple auth clients 2015-10-16 11:27:42 +02:00
Johannes Zellner
5bc9f9e995 use clientdb types in authorization endpoint 2015-10-16 11:22:16 +02:00
Johannes Zellner
22402d1741 Remove legacy test auth client type 2015-10-16 10:05:58 +02:00
Johannes Zellner
8f203b07a1 Fix indentation 2015-10-16 09:19:05 +02:00
Girish Ramakrishnan
9c157246b7 add type field to clients table 2015-10-15 17:35:47 -07:00
Girish Ramakrishnan
d0dfe1ef7f remove unused variable 2015-10-15 17:35:47 -07:00
Girish Ramakrishnan
a9ccc7e2aa remove updating clients
clients are immutable
2015-10-15 16:08:17 -07:00
Girish Ramakrishnan
63edbae1be minor rename 2015-10-15 15:51:51 -07:00
Girish Ramakrishnan
8afe537497 fix typo 2015-10-15 15:32:14 -07:00
Girish Ramakrishnan
f33844d8f1 fix debug tag 2015-10-15 15:19:28 -07:00
Girish Ramakrishnan
c750d00355 ignore any tmp cleanup errors 2015-10-15 14:47:43 -07:00
Girish Ramakrishnan
bb9b39e3c0 callback can be null 2015-10-15 14:25:38 -07:00
Girish Ramakrishnan
057b89ab8e Check error code of image removal 2015-10-15 14:06:05 -07:00
Girish Ramakrishnan
23fc4bec36 callback can be null 2015-10-15 12:06:38 -07:00
Girish Ramakrishnan
6b82fb9ddb Remove old addon images on infra update
Fixes #329
2015-10-15 12:01:31 -07:00
Girish Ramakrishnan
a3ca5a36e8 update test image 2015-10-15 11:11:54 -07:00
Girish Ramakrishnan
f57c91847d addons do not write to /var/log anymore 2015-10-15 11:00:51 -07:00
Johannes Zellner
eda4dc83a3 Do not fail in container.sh when trying to remove non-existing directories 2015-10-15 18:06:57 +02:00
Johannes Zellner
5a0bf8071e Handle the various appId types we have by now 2015-10-15 17:57:07 +02:00
Johannes Zellner
09dfc6a34b Get the oauth2 debug()s in shape 2015-10-15 16:55:48 +02:00
Johannes Zellner
3b8ebe9a59 Fixup the oauth tests with accessRestriction support 2015-10-15 16:50:05 +02:00
Johannes Zellner
2ba1092809 Adhere to accessRestriction for oauth authorization endpoint 2015-10-15 16:49:13 +02:00
Johannes Zellner
7c97ab5408 Revert "Since we got fully rid of the decision dialog, no need to serialze the client anymore"
This is now again required, due to the accesRestriction check

This reverts commit 2c9ff1ee3b.
2015-10-15 16:33:05 +02:00
Johannes Zellner
ac1991f8d1 Fix typo in oauth test 2015-10-15 15:37:56 +02:00
Johannes Zellner
2a573f6ac5 Fixup the simpleauth tests 2015-10-15 15:19:01 +02:00
Johannes Zellner
9833d0cce6 Adhere to accessRestriction in simple auth 2015-10-15 15:18:40 +02:00
Johannes Zellner
fbc3ed0213 Add apps.hasAccessTo() 2015-10-15 15:06:34 +02:00
Johannes Zellner
c916a76e6b Prepare simpleauth test for accessRestriction 2015-10-15 13:29:44 +02:00
Johannes Zellner
ae1bfaf0c8 roleUser is gone as well 2015-10-15 12:50:48 +02:00
Johannes Zellner
0aedff4fec roleAdmin is gone 2015-10-15 12:37:42 +02:00
Johannes Zellner
73d88a3491 Rewrite accessRestriction validator 2015-10-15 12:37:42 +02:00
Girish Ramakrishnan
5d389337cd make /var/log readonly
Expect apps to redirect logs of stdout/stderr

Part of #503
2015-10-15 00:46:50 -07:00
Girish Ramakrishnan
a977597217 cleanup tmpdir in janitor 2015-10-14 23:21:03 -07:00
Girish Ramakrishnan
b3b4106b99 Add janitor tests 2015-10-14 22:50:07 -07:00
Girish Ramakrishnan
7f29eed326 fold janitor into main box code cron job
the volume cleaner will now also come into janitor
2015-10-14 22:39:34 -07:00
Girish Ramakrishnan
ec895a4f31 do not use -f to logrotate
Normally, logrotate is run as a daily cron job. It will not modify a log
multiple times in one day unless the criterion for that log is based on
the log's size and logrotate is being run multiple times each day, or
unless the -f or --force option is used.
2015-10-14 15:10:53 -07:00
Girish Ramakrishnan
fe0c1745e1 make it explicit that logrotate is run via cron in base system 2015-10-14 15:08:38 -07:00
Girish Ramakrishnan
3fc0a96bb0 Add docker volumes janitor
This cleans up tmp and logrotates /var/log every 12 hours.

Note that this janitor is separate from the box janitor because they
run as different users.

Fixes #503
2015-10-14 14:18:36 -07:00
Girish Ramakrishnan
c154f342c2 show restore button if we have a lastBackupId
This is the only way to roll back even if you have a functioning app.
Use cases include:
1. You updated and something doesn't work
2. The app is in 'starting...' state (so it's installed) but no data yet
2015-10-14 11:36:12 -07:00
Johannes Zellner
8f1666dcca Consolidate the oauth comments 2015-10-14 16:31:55 +02:00
Johannes Zellner
9aa4750f55 Since we got fully rid of the decision dialog, no need to serialze the client anymore 2015-10-14 16:22:50 +02:00
Johannes Zellner
c52d985d45 Properly skip decision dialog 2015-10-14 16:16:37 +02:00
Johannes Zellner
376d8d9a38 Cleanup the client serialization 2015-10-14 16:15:51 +02:00
Johannes Zellner
08de0a4e79 Add token exchange tests 2015-10-14 16:15:32 +02:00
Johannes Zellner
11d327edcf Remove unused session error route 2015-10-14 15:51:55 +02:00
Johannes Zellner
d2f7b83ea7 Add oauth callback tests 2015-10-14 15:50:00 +02:00
Johannes Zellner
72ca1b39e8 Add oauth session logout test 2015-10-14 15:38:40 +02:00
Johannes Zellner
69bd234abc Test for unkown client_id 2015-10-14 15:30:10 +02:00
Johannes Zellner
94e6978abf Add test for grant type requests 2015-10-14 15:08:04 +02:00
Johannes Zellner
b5272cbf4d roleAdmin is not part of scopes anymore 2015-10-14 14:59:54 +02:00
Johannes Zellner
edb213089c Add oauth2 test when user is already logged in with his session 2015-10-14 14:46:25 +02:00
Johannes Zellner
b772cf3e5a Add tester tag 2015-10-14 14:46:03 +02:00
Johannes Zellner
e86d043794 The oauth callback does not need a header and footer 2015-10-14 14:36:41 +02:00
Johannes Zellner
4727187071 Also test loginForm submit with email 2015-10-14 14:31:10 +02:00
Johannes Zellner
d8b8f5424c add loginForm submit tests 2015-10-14 14:30:53 +02:00
Johannes Zellner
8425c99a4e Also test oauth clients with oauth proxy 2015-10-14 13:38:37 +02:00
Johannes Zellner
c023dbbc1c Do not handle addon-simpleauth in oauth 2015-10-14 13:35:33 +02:00
Johannes Zellner
af516f42b4 Add oauth login form tests 2015-10-14 13:34:20 +02:00
Johannes Zellner
dbd8e6a08d Add more oauth tests for the authorization endpoint 2015-10-14 12:03:04 +02:00
Johannes Zellner
c24bec9bc6 Remove unused contentType middleware 2015-10-14 11:48:57 +02:00
Johannes Zellner
9854598648 Fix typo to repair oauth and simple auth login
Second time breakage, time for a test ;-)
2015-10-13 21:55:02 +02:00
Girish Ramakrishnan
2cdf73adab Use 20m of logs per app 2015-10-13 11:59:28 -07:00
Johannes Zellner
1e7e2e5e97 Remove decision dialog related route 2015-10-13 20:39:08 +02:00
Johannes Zellner
081e496878 Remove unused oauth decision dialog 2015-10-13 20:32:27 +02:00
Johannes Zellner
aaff7f463a Cleanup the authorization endpoint 2015-10-13 18:23:32 +02:00
Girish Ramakrishnan
55f937bf51 SIMPLE_AUTH_URL -> SIMPLE_AUTH_ORIGIN 2015-10-13 08:40:41 -07:00
Johannes Zellner
d5d1d061bb We also allow non admins to use the webadmin 2015-10-13 15:13:36 +02:00
Johannes Zellner
bc6f602891 Remove unused angular filter for accessRestrictionLabel 2015-10-13 15:11:30 +02:00
Johannes Zellner
ca461057e7 Also update the test image id 2015-10-13 14:24:53 +02:00
Johannes Zellner
b1c5c2468a Fix test to support docker api up to 1.19 and v1.20 2015-10-13 14:24:41 +02:00
Johannes Zellner
562ce3192f Print error when apptask.pullImage() failed 2015-10-13 13:25:43 +02:00
Johannes Zellner
3787dd98b4 Do not crash if a boxVersionsUrl is not set
This prevents test failures when the cron job runs
2015-10-13 13:22:23 +02:00
Johannes Zellner
6c667e4325 Remove console.log 2015-10-13 13:06:50 +02:00
Johannes Zellner
0eec693a85 Update TEST_IMAGE_TAG 2015-10-13 12:30:02 +02:00
Johannes Zellner
c3bf672c2a Ensure we deal with booleans 2015-10-13 12:29:40 +02:00
Johannes Zellner
c3a3b6412f Support oauthProxy in webadmin 2015-10-13 11:49:50 +02:00
Johannes Zellner
44291b842a Fix apps-test.js 2015-10-13 10:41:57 +02:00
Johannes Zellner
36cf502b56 Addons take longer to startup 2015-10-13 10:41:57 +02:00
Johannes Zellner
2df77cf280 Fix settings-test.js 2015-10-13 10:41:57 +02:00
Johannes Zellner
a453e49c27 Fix backups-test.js 2015-10-13 10:41:57 +02:00
Johannes Zellner
e34c34de46 Fixup the apptask-test.js 2015-10-13 10:41:57 +02:00
Johannes Zellner
8dc5bf96e3 Fix apps-test.js 2015-10-13 10:41:57 +02:00
Johannes Zellner
d2c3e1d1ae Fix database tests 2015-10-13 10:41:57 +02:00
Johannes Zellner
4eab101b78 use app.oauthProxy instead of app.accessRestriction 2015-10-13 10:41:57 +02:00
Johannes Zellner
e460d6d15b Add apps.oauthProxy 2015-10-13 10:41:57 +02:00
Girish Ramakrishnan
3012f68a56 pullImage: handle stream error 2015-10-12 21:56:34 -07:00
Girish Ramakrishnan
1909050be2 remove redundant log 2015-10-12 21:54:25 -07:00
Girish Ramakrishnan
d4c62c7295 check for 200 instead of 201 2015-10-12 21:54:18 -07:00
Girish Ramakrishnan
4eb3d1b918 login must return 200 2015-10-12 20:21:27 -07:00
Girish Ramakrishnan
fb6bf50e48 signal redis to backup using SAVE 2015-10-12 13:30:58 -07:00
Girish Ramakrishnan
31125dedc8 add deps of image tooling 2015-10-12 11:36:46 -07:00
Girish Ramakrishnan
a2e2d70660 remove deps of the release tool 2015-10-12 11:34:25 -07:00
Johannes Zellner
d8213f99b1 Ensure we only set the visibility in the progress bar on app restore to not break the layout 2015-10-12 20:32:43 +02:00
Girish Ramakrishnan
7fe1b02ccd moved to release/ repo 2015-10-12 11:30:04 -07:00
Johannes Zellner
7d7b759930 Add navbar with avatar and name to oauth views 2015-10-12 19:56:04 +02:00
Girish Ramakrishnan
04e27496bd remove tmpreaper (will use systemd for this) 2015-10-12 10:52:55 -07:00
Johannes Zellner
51e2e5ec9c Use a temporary file for the release tarball copy
The pipe failed for me a couple of times in a row
2015-10-12 19:37:33 +02:00
Johannes Zellner
6f2bc555e0 Make application name and cloudron name more apparent in oauth login 2015-10-12 17:26:02 +02:00
Johannes Zellner
a8c43ddf4a Show app icon instead of cloudron avatar in oauth login 2015-10-12 16:50:49 +02:00
Johannes Zellner
3eabc27877 Make app icon url public to be used in oauth login screen 2015-10-12 16:49:55 +02:00
Johannes Zellner
cd9602e641 changes 0.0.69 2015-10-12 16:47:25 +02:00
Johannes Zellner
ad379bd766 Support the new oauth client id prefix 2015-10-12 15:18:51 +02:00
Johannes Zellner
8303991217 Revert "install specific version of python"
The specific version does not create /usr/bin/python but only
the exact version /usr/bin/python2.7

The meta package python is the only one creating that link and
according to https://wiki.ubuntu.com/Python/3 /usr/bin/python
will not point to version 3 anytime soon at all.

This reverts commit be128bbecfd2afe9ef2bdca603a3b26e8ccced7b.
2015-10-12 15:02:05 +02:00
Johannes Zellner
c1047535d4 Update to new manifestformat 2015-10-12 13:22:56 +02:00
Girish Ramakrishnan
12eae2c002 remove 0.0.69 2015-10-11 16:03:08 -07:00
Girish Ramakrishnan
10142cc00b make a note of appid format 2015-10-11 14:19:38 -07:00
Girish Ramakrishnan
5e1487d12a appId format has changed in clientdb 2015-10-11 14:16:38 -07:00
Girish Ramakrishnan
39e0c13701 apptest: remove mail addon 2015-10-11 13:53:50 -07:00
Girish Ramakrishnan
c80d984ee6 start the mail addon 2015-10-11 13:48:23 -07:00
Girish Ramakrishnan
3e474767d1 print the values otherwise it gets very confusing 2015-10-11 13:45:02 -07:00
Girish Ramakrishnan
e2b954439c ensure redis container is stopped before removing it
this is required for the configure/update case where the redis container
might be holding some data in memory.

sending redis SIGTERM will make it commit to disk.
2015-10-11 12:08:35 -07:00
Girish Ramakrishnan
950d1eb5c3 remove associated volumes
note that this does not remove host mounts
2015-10-11 11:37:23 -07:00
Girish Ramakrishnan
f48a2520c3 remove RSTATE_ERROR
if startContainer failed, it will still returning success because
it running the db update result
2015-10-11 11:18:30 -07:00
Girish Ramakrishnan
f366d41034 regenerate shrinkwrap with --no-optional for ldapjs 2015-10-11 10:39:41 -07:00
Girish Ramakrishnan
b3c40e1ba7 test image is now 5.0.0 2015-10-11 10:03:42 -07:00
Girish Ramakrishnan
b686d6e011 use latest docker images 2015-10-11 10:03:42 -07:00
Girish Ramakrishnan
99489e5e77 install specific version of python 2015-10-11 10:02:44 -07:00
Johannes Zellner
5c9ff468cc Add python to the base image 2015-10-11 18:48:24 +02:00
Johannes Zellner
bf18307168 0.0.69 changes 2015-10-11 18:19:51 +02:00
Johannes Zellner
5663198bfb Do not log with morgan during test runs for simple auth server 2015-10-11 17:52:22 +02:00
Johannes Zellner
bad50fd78b Merge branch 'simpleauth' 2015-10-11 17:40:10 +02:00
Johannes Zellner
9bd43e3f74 Update to new manifestformat version 1.9.0 2015-10-11 17:19:39 +02:00
Johannes Zellner
f0fefab8ad Prefix morgan logs with source 2015-10-11 17:19:39 +02:00
Johannes Zellner
449231c791 Do not show http request logs during tests 2015-10-11 17:19:39 +02:00
Johannes Zellner
bd161ec677 Remove serving up webadmin/ in test case 2015-10-11 17:19:39 +02:00
Johannes Zellner
8040d4ac2d Add simpleauth logging middleware 2015-10-11 17:19:39 +02:00
Johannes Zellner
06d7820566 read ldap port from config.js 2015-10-11 17:19:39 +02:00
Johannes Zellner
4818a3feee Specify addon env vars for simple auth 2015-10-11 17:19:39 +02:00
Johannes Zellner
9fcb2c0733 Fix the check install to keep up with the base image version 2015-10-11 17:19:39 +02:00
Johannes Zellner
6906b4159a Revert "Attach accessTokens to req for further use"
This reverts commit 895812df1e9226415640b74a001c1f8c1affab01.
2015-10-11 17:19:39 +02:00
Johannes Zellner
763b9309f6 Fixup the simple auth unit tests 2015-10-11 17:19:39 +02:00
Johannes Zellner
2bb4d1c22b Remove the simpleauth route handler 2015-10-11 17:19:39 +02:00
Johannes Zellner
23303363ee Move simple auth to separate express server 2015-10-11 17:19:39 +02:00
Johannes Zellner
79c17abad2 Add simpleAuthPort to config.js 2015-10-11 17:19:39 +02:00
Johannes Zellner
3234e0e3f0 Fixup the simple auth logout route and add tests 2015-10-11 17:19:39 +02:00
Johannes Zellner
982cd1e1f3 Attach accessTokens to req for further use
This helps with extracting the token, which can come
from various places like headers, body or query
2015-10-11 17:19:38 +02:00
Johannes Zellner
df39fc86a4 add simple auth login unit tests 2015-10-11 17:19:38 +02:00
Johannes Zellner
870e0c4144 Fixup the simple login routes for unknown clients 2015-10-11 17:19:38 +02:00
Johannes Zellner
57704b706e Handle 404 in case subdomain does not exist on delete attempt 2015-10-11 17:19:38 +02:00
Johannes Zellner
223e0dfd1f Add SIMPLE_AUTH_ORIGIN 2015-10-11 17:19:38 +02:00
Johannes Zellner
51c438b0b6 Return correct error codes 2015-10-11 17:19:38 +02:00
Girish Ramakrishnan
b3fa76f8c5 0.0.68 changes 2015-10-10 14:39:53 -07:00
Girish Ramakrishnan
93d210a754 Bump the graphite image 2015-10-10 09:57:07 -07:00
Girish Ramakrishnan
8a77242072 clear version field in rerelease 2015-10-09 13:11:53 -07:00
Girish Ramakrishnan
c453df55d6 More 0.0.67 changes 2015-10-09 12:05:03 -07:00
Girish Ramakrishnan
265ee15ac7 fix oldConfig madness
There is a crash when:
* App is configured. So, oldConfig now has {loc, access, portb }
* Cloudron is restored. The restore code path accesses the oldConfig.manifest.addons.

oldConfig is basically a messaging passing thing. It's not really a
db field. With that spirit, we simply pass an empty message in setup_infra.sh
2015-10-09 11:59:25 -07:00
Girish Ramakrishnan
d0da47e0b3 fix comment 2015-10-09 11:48:59 -07:00
Girish Ramakrishnan
0e8553d1a7 code path applies to upgraded cloudrons as well 2015-10-09 11:08:54 -07:00
Girish Ramakrishnan
9229dd2fd5 Add oldConfigJson in schema file 2015-10-09 11:08:16 -07:00
Girish Ramakrishnan
75d69050d5 0.0.67 changes 2015-10-09 10:06:11 -07:00
Girish Ramakrishnan
c2a8744240 fix typo 2015-10-09 10:04:50 -07:00
Girish Ramakrishnan
bc7e07f6a6 mail: not required to expose port 25 2015-10-09 09:56:37 -07:00
Girish Ramakrishnan
bfd6f8965e print mail server ip 2015-10-09 09:50:50 -07:00
Girish Ramakrishnan
eb1e4a1aea mail now runs on port 2500 2015-10-09 09:29:17 -07:00
Girish Ramakrishnan
dc3e8a9cb5 mail now runs on port 2500 2015-10-09 09:13:28 -07:00
Johannes Zellner
494bcc1711 prefix oauth client id and app ids 2015-10-09 11:45:53 +02:00
Johannes Zellner
7e071d9f23 add simpleauth addon hooks 2015-10-09 11:44:32 +02:00
Johannes Zellner
6f821222db Add simple auth routes 2015-10-09 11:37:46 +02:00
Johannes Zellner
6e464dbc81 Add simple auth route handlers 2015-10-09 11:37:39 +02:00
Johannes Zellner
be8ef370c6 Add simple auth logic for login/logout 2015-10-09 11:37:17 +02:00
Johannes Zellner
39a05665b0 Update node modules to support v4.1.1
The sqlite3 versions we had used so far did not work with
new node versions
2015-10-09 10:15:10 +02:00
Girish Ramakrishnan
737e22116a Reword upgrade warning 2015-10-08 16:31:44 -07:00
Girish Ramakrishnan
43e1e4829f new test image 3.0.0 2015-10-08 16:07:14 -07:00
Girish Ramakrishnan
390285d9e5 version 0.0.66 changes 2015-10-08 13:16:23 -07:00
Girish Ramakrishnan
c95778178f make rootfs readonly based on targetBoxVersion 2015-10-08 11:48:33 -07:00
Girish Ramakrishnan
04870313b7 Launch apps with readonly rootfs
We explicitly mark /tmp, /run and /var/log as writable volumes.
Docker creates such volumes in it's own volumes directory. Note
that these volumes are separate from host binds (/app/data).

When removing the container the docker created volumes are
removed (but not host binds).

Fixes #196
2015-10-08 11:33:17 -07:00
Girish Ramakrishnan
6ca040149c run addons as readonly 2015-10-08 11:07:28 -07:00
Girish Ramakrishnan
e487b9d46b update mail image 2015-10-08 11:06:29 -07:00
Girish Ramakrishnan
1375e16ad2 mongodb: readonly rootfs 2015-10-08 10:24:15 -07:00
Girish Ramakrishnan
312f1f0085 mysql: readonly rootfs 2015-10-08 09:43:05 -07:00
Girish Ramakrishnan
721900fc47 postgresql: readonly rootfs 2015-10-08 09:20:25 -07:00
Girish Ramakrishnan
2d815a92a3 redis: use readonly rootfs 2015-10-08 09:00:43 -07:00
Girish Ramakrishnan
1c192b7c11 pass options param in setup call 2015-10-08 02:08:27 -07:00
Girish Ramakrishnan
8ef15df7c0 0.0.65 changes 2015-10-07 18:49:53 -07:00
Girish Ramakrishnan
4a887336bc Do not send app down mails for dev mode apps
Fixes #501
2015-10-07 18:46:48 -07:00
Girish Ramakrishnan
8f6521f942 pass addon options to all functions 2015-10-07 16:10:08 -07:00
Girish Ramakrishnan
fbdfaa4dc7 rename setup and teardown functions of oauth addon 2015-10-07 15:55:57 -07:00
Girish Ramakrishnan
bf4290db3e remove token addon, its a relic of the past 2015-10-07 15:44:55 -07:00
Johannes Zellner
94ad633128 Also unset the returnTo after login 2015-10-01 16:26:17 +02:00
Johannes Zellner
c552917991 Reset the target url after oauth login
This is required for the cloudron button to work for users
which are not logged in
2015-10-01 16:16:29 +02:00
Johannes Zellner
a7ee8c853e Keep checkInstall in sync 2015-09-30 16:12:51 +02:00
Girish Ramakrishnan
29e4879451 fix test image version 2015-09-29 20:22:38 -07:00
Girish Ramakrishnan
8b92344808 redirect stderr 2015-09-29 19:23:39 -07:00
Girish Ramakrishnan
0877cec2e6 Fix EE leak warning 2015-09-29 14:40:23 -07:00
Girish Ramakrishnan
b1ca577be7 use newer test image that dies immediately on stop/term 2015-09-29 14:33:07 -07:00
Girish Ramakrishnan
9b484f5ac9 new version of mysql prints error with -p 2015-09-29 14:13:58 -07:00
Girish Ramakrishnan
b6a9fd81da refactor our test docker image details 2015-09-29 13:59:17 -07:00
Girish Ramakrishnan
109f9567ea 0.0.64 changes 2015-09-29 13:21:33 -07:00
Girish Ramakrishnan
f19113f88e rename test iamge under cloudron/ 2015-09-29 12:52:54 -07:00
Girish Ramakrishnan
3837bee51f retry pulling image
fixes #497
2015-09-29 12:47:03 -07:00
Girish Ramakrishnan
748eadd225 stop apps and installer when retiring cloudron
we cannot put this in stop.sh because that is called during update.
2015-09-29 11:58:14 -07:00
Girish Ramakrishnan
b8e115ddf6 move images script 2015-09-28 23:58:00 -07:00
Girish Ramakrishnan
11d4df4f7d fix loop (by actually using nextPage link) 2015-09-28 23:55:31 -07:00
Girish Ramakrishnan
0c285f21c1 rework images script 2015-09-28 23:47:13 -07:00
Girish Ramakrishnan
89c3296632 debug the status code as well 2015-09-28 23:18:50 -07:00
Girish Ramakrishnan
db55f0696e stringify object when appending to string 2015-09-28 23:10:09 -07:00
Girish Ramakrishnan
03d4ae9058 new base image 0.4.0 2015-09-28 19:33:58 -07:00
Girish Ramakrishnan
f8b41b703c Use fqdn to generate domain name of txt records 2015-09-28 17:20:59 -07:00
Girish Ramakrishnan
c9bf017637 0.0.63 changes 2015-09-28 17:00:50 -07:00
Girish Ramakrishnan
2a989e455c Ensure TXT records are added as dotted domains
Fixes #498
2015-09-28 16:35:58 -07:00
Girish Ramakrishnan
cd24decca0 Send dns status requests in series
And abort status checking after the first one fails. Otherwise, this
bombards the appstore unnecessarily. And checks for status of other
things unnecessarily.
2015-09-28 16:23:39 -07:00
Johannes Zellner
0d78150f10 Log forwarding is no more 2015-09-28 14:33:06 +02:00
Johannes Zellner
f36946a8aa Forward the backup trigger status code and error message 2015-09-28 14:20:10 +02:00
Girish Ramakrishnan
5c51619798 Version 0.0.62 changes 2015-09-26 00:04:52 -07:00
Girish Ramakrishnan
f39842a001 ldap: allow non-anonymous searches
Add LDAP_BIND_DN and LDAP_BIND_PASSWORD that allow
apps to bind before a search. There appear to be two kinds of
ldap flows:

1. App simply binds using cn=<username>,$LDAP_USERS_BASE_DN. This
   works swimmingly today.

2. App searches the username under a "bind_dn" using some admin
   credentials. It takes the result and uses the first dn in the
   result as the user dn. It then binds as step 1.

This commit tries to help out the case 2) apps. These apps really
insist on having some credentials for searching.
2015-09-25 21:28:47 -07:00
Girish Ramakrishnan
a022bdb30d set default version to null to override commander built-in 2015-09-22 22:58:27 -07:00
Girish Ramakrishnan
2cfb91d0ce allow version to be specified in various commands 2015-09-22 22:55:55 -07:00
Girish Ramakrishnan
2a39526a4c Remove old app ids from updatechecker state
Fixes #472
2015-09-22 22:46:14 -07:00
Girish Ramakrishnan
ded5d4c98b debug message when notification is skipped 2015-09-22 22:41:42 -07:00
Girish Ramakrishnan
a0ca59c3f2 Fix typo 2015-09-22 20:22:17 -07:00
Girish Ramakrishnan
5885d76b89 Version 0.0.61 changes 2015-09-22 16:15:43 -07:00
Girish Ramakrishnan
53cfc49807 Save version instead of boolean so we get notified when version changes
part of #472
2015-09-22 16:11:15 -07:00
Girish Ramakrishnan
942eb579e4 save/restore notification state of updatechecker
part of #472
2015-09-22 16:11:04 -07:00
Girish Ramakrishnan
5cb1a2d120 0.0.60 changes 2015-09-22 13:04:45 -07:00
Girish Ramakrishnan
5819cfe412 Fix progress message 2015-09-22 13:02:09 -07:00
Johannes Zellner
5cb62ca412 Remove start/stop buttons in webadmin
Fixes #495
2015-09-22 22:00:42 +02:00
Johannes Zellner
df10c245de app.js is no more 2015-09-22 22:00:42 +02:00
Girish Ramakrishnan
4a804dc52b Do a complete backup for updates
The backup cron job ensures backups every 4 hours which simply does
a 'box' backup listing. If we do only a 'box' backup during update,
this means that this cron job skips doing a backup and thus the apps
are not backed up.

This results in the janitor on the CaaS side complaining that the
app backups are too old.

Since we don't stop apps anymore during updates, it makes sense
to simply backup everything for updates as well. This is probably
what the user wants anyway.
2015-09-22 12:51:58 -07:00
Girish Ramakrishnan
53fa339363 0.0.59 changes 2015-09-21 21:56:16 -07:00
Girish Ramakrishnan
5f0bb0c6ce 0.0.58 changes 2015-09-21 16:26:03 -07:00
Girish Ramakrishnan
ed2f25a998 better debugs 2015-09-21 16:02:58 -07:00
Girish Ramakrishnan
7510c9fe29 Fix typo 2015-09-21 15:57:06 -07:00
Girish Ramakrishnan
78a1d53728 copy old backup as failed/errored apps
This ensures that
a) we don't get emails from janitor about bad app backups
b) that the backups are persisted over the s3 lifecycle

Fixes #493
2015-09-21 15:03:10 -07:00
Girish Ramakrishnan
e9b078cd58 add backups.copyLastBackup 2015-09-21 14:14:43 -07:00
Girish Ramakrishnan
dd8b928684 aws: add copyObject 2015-09-21 14:02:00 -07:00
Girish Ramakrishnan
3dec6ac9f1 0.0.57 changes 2015-09-21 11:12:09 -07:00
Girish Ramakrishnan
185b574bdc Add custom apparmor profile for cloudron apps
Docker generates an apparmor profile on the fly under /etc/apparmor.d/docker.
This profile gets overwritten on every docker daemon start.

This profile allows processes to ptrace themselves. This is required by
circus (python process manager) for reasons unknown to me. It floods the logs
with
    audit[7623]: <audit-1400> apparmor="DENIED" operation="ptrace" profile="docker-default" pid=7623 comm="python3.4" requested_mask="trace" denied_mask="trace" peer="docker-default"

This is easily tested using:
    docker run -it cloudron/base:0.3.3 /bin/bash
        a) now do ps
        b) journalctl should show error log as above

    docker run --security-opt=apparmor:docker-cloudron-app -it cloudron/base:0.3.3 /bin/bash
        a) now do ps
        b) no error!

Note that despite this, the process may not have ability to ptrace since it does not
have CAP_PTRACE. Also, security-opt is the profile name (inside the apparmor config file)
and not the filename.

References:
    https://groups.google.com/forum/#!topic/docker-user/xvxpaceTCyw
    https://github.com/docker/docker/issues/7276
    https://bugs.launchpad.net/ubuntu/+source/docker.io/+bug/1320869

This is an infra update because we need to recreate containers to get the right profile.

Fixes #492
2015-09-21 11:01:44 -07:00
Girish Ramakrishnan
a89726a8c6 Add custom debug.formatArgs to remove timestamp prefix in logs
Fixes #490

See also:
https://github.com/visionmedia/debug/issues/216
2015-09-21 09:05:14 -07:00
Girish Ramakrishnan
c80aca27e6 remove unnecessary supererror call 2015-09-21 09:04:16 -07:00
Girish Ramakrishnan
4c9ec582dc 0.0.56 changes 2015-09-18 14:47:57 -07:00
Girish Ramakrishnan
029acab333 use correct timezone in updater
fixes #491
2015-09-18 14:46:44 -07:00
Girish Ramakrishnan
4f9f10e130 timezone detection is based on browser location/ip and not cloudron region intentionally 2015-09-18 13:40:22 -07:00
Girish Ramakrishnan
9ba11d2e14 print body on failure 2015-09-18 12:03:48 -07:00
Girish Ramakrishnan
23a5a1f79f timezone is already determined automatically using activation 2015-09-18 12:02:36 -07:00
Girish Ramakrishnan
e8dc617d40 print tz for debugging 2015-09-18 10:51:52 -07:00
Girish Ramakrishnan
28b000c820 admin tool is now merged into caas tool 2015-09-17 21:25:07 -07:00
Girish Ramakrishnan
d56794e846 clear backup progress when initiating backup
this ensures that tools can do:
1. backup
2. wait_for_backup

without the synchronous clear, we might get the progress state of
an earlier backup.
2015-09-17 21:17:59 -07:00
Girish Ramakrishnan
30320e0ac6 Wait for backup to complete
Fixes #351
2015-09-17 16:44:44 -07:00
Girish Ramakrishnan
88b682a317 take ip as first argument instead of --ip 2015-09-17 16:40:06 -07:00
Girish Ramakrishnan
2663ec7da0 cloudron.backup does not wait for backup to complete 2015-09-17 16:35:59 -07:00
Girish Ramakrishnan
eec4ae98cd add comment for purpose on internal server 2015-09-17 16:27:46 -07:00
Girish Ramakrishnan
6c5a5c0882 remove tail-stream 2015-09-17 16:21:18 -07:00
Girish Ramakrishnan
1d27fffe44 remove ununsed requires 2015-09-17 16:21:03 -07:00
Girish Ramakrishnan
a3383b1f98 remove logs route
most of this stuff doesn't work anyways since we moved to systemd
2015-09-17 15:44:05 -07:00
Girish Ramakrishnan
f9c2b0acd1 remove cloudronLogin
alternative: admin/admin ssh <ip>
2015-09-17 15:34:17 -07:00
Girish Ramakrishnan
a9444ed879 rename login to ssh 2015-09-17 15:34:04 -07:00
Girish Ramakrishnan
8d5a3ecd69 admin: remove the cloudron chooser
This needlessly ties down this tool to digitalocean
2015-09-17 15:29:44 -07:00
Girish Ramakrishnan
c31a0f4e09 Store dates as iso strings in database
ideally, the database schema should be TIMESTAMP
2015-09-17 13:51:55 -07:00
Girish Ramakrishnan
44ff676eef store dates as iso strings 2015-09-17 13:48:20 -07:00
Girish Ramakrishnan
4bb017b740 verify next version exists 2015-09-17 12:11:14 -07:00
Girish Ramakrishnan
0f2435c308 Version 0.0.55 changes 2015-09-16 17:02:22 -07:00
Girish Ramakrishnan
739db23514 Use the default timezone in settings
Fixes #485
2015-09-16 16:36:08 -07:00
Girish Ramakrishnan
8598fb444b store timezone in config.js (part of provision data) 2015-09-16 15:54:56 -07:00
Girish Ramakrishnan
0cd56f4d4c configure cloudron to use UTC
local timezone should be tracked by the webadmin/box code
2015-09-16 13:17:32 -07:00
Girish Ramakrishnan
0b630ff504 Remove debug that is flooding the logs 2015-09-16 10:50:15 -07:00
Girish Ramakrishnan
84169dea3d Do not set process.env.NODE_TLS_REJECT_UNAUTHORIZED
Doing so will affect all https requests which is dangerous.

We have these options to solve this:
1. Use superagent.ca(). Appstore already provides wildcard certs
   for dev, staging signed with appstore_ca. But we then need to
   send across the appstore_ca cert across in the provision call.
   This is a bit of work.

2. Convert superagent into https.request calls and use the
   rejectUnauthorized option.

3. Simply use http. This is what is done in this commit.

Fixes #488
2015-09-16 10:36:03 -07:00
Girish Ramakrishnan
e328ec2382 0.0.54 changes 2015-09-16 10:35:59 -07:00
Girish Ramakrishnan
d83b5de47a reserve the ldap and oauthproxy port 2015-09-16 10:12:59 -07:00
Girish Ramakrishnan
2719c4240f Get oauth proxy port from the configs 2015-09-16 10:06:34 -07:00
Girish Ramakrishnan
4b5ac67993 Revert "Disable rsyslog"
This reverts commit 3c5db59de2b6c2ef8891ecba54496335b5e2d55f.

Don't revert this. Maybe some system services use this.
2015-09-16 09:48:47 -07:00
Johannes Zellner
d749756b53 Do not show the update action button in non mobile view 2015-09-16 09:36:46 +02:00
Johannes Zellner
0401c61c15 Add tooltip text for the app action icons 2015-09-16 09:36:22 +02:00
Johannes Zellner
34f45da2de Show indicator when app update is available
Fixes #489
2015-09-16 09:28:43 +02:00
Girish Ramakrishnan
baecbf783c journalctl seems to barf on this debug 2015-09-15 20:50:22 -07:00
Girish Ramakrishnan
2f141cd6e0 Make the times absurdly high but that is how long in takes 2015-09-15 18:56:25 -07:00
Girish Ramakrishnan
1296299d02 error is undefined 2015-09-15 18:27:09 -07:00
Girish Ramakrishnan
998ac74d32 oldConfig.location can be null
If we had an update, location is not part of oldConfig. if we now do
an infra update, location is undefined.
2015-09-15 18:08:29 -07:00
Girish Ramakrishnan
b4a34e6432 Explicity debug the fields
for some reason, journalctl barfs on this line
2015-09-15 14:55:20 -07:00
Girish Ramakrishnan
cb73218dfe Disable rsyslog 2015-09-15 14:32:47 -07:00
Girish Ramakrishnan
5523c2d34a Disable forwarding to syslog 2015-09-15 14:29:16 -07:00
Girish Ramakrishnan
01889c45a2 Fix typo 2015-09-15 14:04:30 -07:00
Girish Ramakrishnan
e89b4a151e 0.0.52 is folded into 0.0.53 2015-09-15 11:57:50 -07:00
Girish Ramakrishnan
ec235eafe8 fix staging with stripped releases 2015-09-15 11:25:03 -07:00
Girish Ramakrishnan
d99720258a gray out unreachable releases 2015-09-15 11:17:59 -07:00
Girish Ramakrishnan
3f064322e4 strip unreachable releases when processing 2015-09-15 11:11:56 -07:00
Girish Ramakrishnan
11592279e2 fix regexp for non-dev 2015-09-15 10:41:36 -07:00
Girish Ramakrishnan
31b4923eb2 better output 2015-09-15 10:33:36 -07:00
Girish Ramakrishnan
cfdfb9a907 release: add edit command 2015-09-15 09:48:05 -07:00
Girish Ramakrishnan
e7a21c821e 0.0.53 changes 2015-09-14 22:20:13 -07:00
Girish Ramakrishnan
e70c9d55db apptask: retry for external error as well 2015-09-14 21:45:27 -07:00
Girish Ramakrishnan
268aee6265 Return busy code for 420 response 2015-09-14 21:44:44 -07:00
Girish Ramakrishnan
255422d5be 0.0.52 changes 2015-09-14 17:28:31 -07:00
Girish Ramakrishnan
1ba7b0e0fb context is raw text 2015-09-14 17:25:27 -07:00
Girish Ramakrishnan
72788fdb11 add note on how to test the oom 2015-09-14 17:20:30 -07:00
Girish Ramakrishnan
435afec13c Print OOM context 2015-09-14 17:18:11 -07:00
Girish Ramakrishnan
2cb1877669 Do not reconnect for now 2015-09-14 17:10:49 -07:00
Girish Ramakrishnan
edd672cba7 fix typo 2015-09-14 17:07:44 -07:00
Girish Ramakrishnan
991f37fe05 Provide app information if possible 2015-09-14 17:06:04 -07:00
Girish Ramakrishnan
c147d8004b Add appdb.getByContainerId 2015-09-14 17:01:04 -07:00
Girish Ramakrishnan
cdcc4dfda8 Get notification on app oom
currently, oom events arrive a little late :
https://github.com/docker/docker/issues/16074

fixes #489
2015-09-14 16:51:32 -07:00
Girish Ramakrishnan
2eaba686fb apphealthmonitor.js is not executable 2015-09-14 16:51:32 -07:00
Girish Ramakrishnan
236032b4a6 Remove supererror setup in oauthproxy and apphealthmonitor 2015-09-14 16:49:10 -07:00
Girish Ramakrishnan
5fcba59b3e set memory limits for addons
mysql, postgresql, mongodb - 100m each
mail, graphite, redis (each instance) - 75m

For reference, in yellowtent:
mongo - 5m
postgresql - 33m
mysql - 3.5m
mail: 26m
graphite - 26m
redis - 32m
2015-09-14 13:47:45 -07:00
Girish Ramakrishnan
6efd8fddeb fix require paths 2015-09-14 13:00:03 -07:00
Girish Ramakrishnan
02b4990cb1 Version 0.0.51 changes 2015-09-14 12:24:22 -07:00
Girish Ramakrishnan
8aff2b9e74 remove oauthproxy systemd configs 2015-09-14 12:02:38 -07:00
Girish Ramakrishnan
fbae432b98 merge oauthproxy server into box server 2015-09-14 11:58:28 -07:00
Girish Ramakrishnan
9cad7773ff refactor code to prepare for merge into box server 2015-09-14 11:28:49 -07:00
Girish Ramakrishnan
4adf122486 oauthproxy: refactor for readability 2015-09-14 11:22:33 -07:00
Girish Ramakrishnan
ea47c26d3f apphealthmonitor is not a executable anymore 2015-09-14 11:09:58 -07:00
Girish Ramakrishnan
f57aae9545 Fix typo in assert 2015-09-14 11:09:41 -07:00
Girish Ramakrishnan
cdeb830706 Add apphealthmonitor.stop 2015-09-14 11:02:06 -07:00
Girish Ramakrishnan
0c9618f19a Add ldap.stop 2015-09-14 11:01:35 -07:00
Girish Ramakrishnan
1cd9d07d8c Merge apphealthtask into box server
We used to run this as a separate process but no amount of node/v8 tweaking
makes them run as standalone with 50M RSS.

Three solutions were considered for the memory issue:
1. Use systemd timer. apphealthtask needs to run quiet frequently (10 sec)
   for the ui to get the app health update immediately after install.

2. Merge into box server (this commit)

3. Increase memory to 80M. This seems to make apphealthtask run as-is.
2015-09-14 10:52:11 -07:00
Girish Ramakrishnan
f028649582 Rename app.js to box.js 2015-09-14 10:43:47 -07:00
Johannes Zellner
11bf39374c 0.0.50 changes 2015-09-14 12:37:39 +02:00
Johannes Zellner
d57236959a choose aws subdomain backend for test purpose 2015-09-13 22:02:04 +02:00
Johannes Zellner
ebe975f463 Also send data with the domain deletion 2015-09-13 22:02:04 +02:00
Johannes Zellner
a94267fc98 Use caas.js for subdomain business 2015-09-13 22:02:04 +02:00
Johannes Zellner
f186ea7cc3 Add initial caas.js 2015-09-13 22:02:04 +02:00
Girish Ramakrishnan
29e05b1caa make janitor a systemd timer
one process lesser
2015-09-11 18:43:51 -07:00
Girish Ramakrishnan
6945a712df limit node memory usage
node needs to be told how much space it can usage, otherwise it keeps
allocating and we cannot keep it under 50M. keeping old space to 30M,
lets the memory hover around 40M

there are many options to v8 but I haven't explored them all:
--expose_gc - allows scripts to call gc()
--max_old_space_size=30 --max_semi_space_size=2048 (old/new space)
    node first allocates new objects in new space. if these objects are in use
    around for some time, it moves them to old space. the idea here is that it
    runs gc aggressively on new space since new objects die more than old ones.

    the new space is split into two halves of equal size called semi spaces.

--gc_interval=100 --optimize_for_size --max_executable_size=5 --gc_global --stack_size=1024

http://erikcorry.blogspot.com/2012/11/memory-management-flags-in-v8.html
http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection
https://code.google.com/p/chromium/issues/detail?id=280984
http://stackoverflow.com/questions/30252905/nodejs-decrease-v8-garbage-collector-memory-usage
http://www.appfruits.com/2014/08/running-node-js-on-arduino-yun/

note: this is not part of shebang because linux shebang does not support args! so we cannot
pass node args as part of shebang.
2015-09-10 21:24:36 -07:00
Girish Ramakrishnan
04cf382de5 systemctl stop 2015-09-10 20:45:53 -07:00
Girish Ramakrishnan
03048d7d2f set memorylimit for crashnotifier as well 2015-09-10 14:19:44 -07:00
Girish Ramakrishnan
38884bc0e6 0.0.49 changes 2015-09-10 11:40:58 -07:00
Girish Ramakrishnan
a4d0394d1a Revert "Move ssh to port 919"
This reverts commit 4e4890810f3a22e7ec990cc44381a2c243044d99.

This change is not done yet
2015-09-10 11:40:27 -07:00
Girish Ramakrishnan
28b768b146 Fix app autoupdater logic
The main issue was that app.portBindings is never null but { }
2015-09-10 11:39:29 -07:00
Girish Ramakrishnan
8292e78ef2 Move ssh to port 919 2015-09-10 10:32:59 -07:00
Girish Ramakrishnan
c1e4dceb01 ssh is now on port 919 2015-09-10 10:08:40 -07:00
Johannes Zellner
6243404d1d 0.0.48 changes 2015-09-10 14:39:30 +02:00
Johannes Zellner
954d14cd66 Warn the user when he performs an upgrade instead of update
Fixes #481
2015-09-10 14:33:00 +02:00
Johannes Zellner
2f5e9e2e26 We do have global rest error handler which take care of re-login 2015-09-10 14:16:59 +02:00
Johannes Zellner
b3c058593f Force reload page if version has changed
Fixes #480
2015-09-10 13:58:27 +02:00
Johannes Zellner
3e47e11992 Ensure the stylesheets are in correct order
Fixes #484
2015-09-10 13:32:33 +02:00
Girish Ramakrishnan
6fe67c93fe 0.0.47 changes 2015-09-09 17:03:13 -07:00
Girish Ramakrishnan
8c7dfdcef2 Wait upto 3 seconds for the app to quit
Otherwise systemd will kill us and we get crash emails.

Fixes #483
2015-09-09 16:57:43 -07:00
Girish Ramakrishnan
c88591489d make apps test work 2015-09-09 15:51:56 -07:00
Girish Ramakrishnan
719404b6cf lint 2015-09-09 15:03:43 -07:00
Girish Ramakrishnan
f2c27489c8 test: make unregister subdomain test work 2015-09-09 14:36:09 -07:00
Girish Ramakrishnan
d6a0c93f2f test: make register subdomain work 2015-09-09 14:32:05 -07:00
Girish Ramakrishnan
c64d5fd2e3 error is already Error 2015-09-09 14:26:53 -07:00
Girish Ramakrishnan
5b62aeb73a make aws endpoint configurable for tests 2015-09-09 12:03:47 -07:00
Girish Ramakrishnan
7e83f2dd4a intercept delete calls to test image 2015-09-09 11:32:09 -07:00
Girish Ramakrishnan
ed48f84355 give taskmanager couple of seconds to kill all processes 2015-09-09 10:39:38 -07:00
Girish Ramakrishnan
f3d15cd4a5 fix initialization of apps-test 2015-09-09 10:22:17 -07:00
Girish Ramakrishnan
8c270269db remove dead code 2015-09-09 09:28:06 -07:00
Johannes Zellner
bea605310a Use memoryLimit from manifest for graphs if specified 2015-09-09 17:11:54 +02:00
Johannes Zellner
8184894563 Remove upgrade view altogether 2015-09-09 16:47:13 +02:00
Johannes Zellner
47a87cc298 Remove upgrade link in the menu 2015-09-09 16:46:28 +02:00
Johannes Zellner
553a6347e6 Actually hand the backupKey over in an update 2015-09-09 12:37:09 +02:00
Girish Ramakrishnan
422b65d934 0.0.46 changes 2015-09-09 01:00:12 -07:00
Girish Ramakrishnan
a35ebd57f9 call iteratorDone when finished 2015-09-09 00:43:42 -07:00
Girish Ramakrishnan
97174d7af0 make cloudron-test pass 2015-09-08 22:13:50 -07:00
Girish Ramakrishnan
659268c04a provide default backupPrefix for tests 2015-09-08 21:16:50 -07:00
Girish Ramakrishnan
67d06c5efa better debug messages 2015-09-08 21:11:46 -07:00
Girish Ramakrishnan
6e6d8c0bc5 awscredentials is now POST 2015-09-08 21:02:21 -07:00
Girish Ramakrishnan
658af3edcf disable failing subdomains test
This needs aws mock
2015-09-08 20:38:52 -07:00
Girish Ramakrishnan
9753d9dc7e removeUser takes a userId and not username 2015-09-08 16:38:02 -07:00
Girish Ramakrishnan
2fa3a3c47e 0.0.45 changes 2015-09-08 12:58:06 -07:00
Girish Ramakrishnan
4e331cfb35 retry registering and unregistering subdomain 2015-09-08 12:51:25 -07:00
Girish Ramakrishnan
a1fa94707b Remove ununsed error codes 2015-09-08 11:28:29 -07:00
Girish Ramakrishnan
88f1107ed6 Remove unused AWSError 2015-09-08 11:26:35 -07:00
Girish Ramakrishnan
27e4810239 0.0.44 changes 2015-09-08 10:31:02 -07:00
Girish Ramakrishnan
e97b9fcc60 Do not start apptask for apps that are installed and running 2015-09-08 10:24:39 -07:00
Girish Ramakrishnan
71fe643099 Check if we have reached concurrency limit before locking 2015-09-08 10:20:34 -07:00
Johannes Zellner
74874a459d Remove ... for labels while showing the progress bar 2015-09-08 15:49:10 +02:00
Johannes Zellner
7c5fc17500 Cleanup linter issues in updatechecker.js 2015-09-08 10:03:37 +02:00
Girish Ramakrishnan
cbdae3547b 0.0.43 changes 2015-09-07 23:13:14 -07:00
Girish Ramakrishnan
a5d122c0b3 Leave a note on singleshot After= behavior
"Actually oneshot is also a bit special and that is where RemainAfterExit comes in. For oneshot, systemd waits for the process to exit before it starts any follow-up units (and with multiple ExecStarts I assume it waits for all of them). So that automatically leads to the scheme in berbae's last post. However, with RemainAfterExit, the unit remains active even though the process has exited, so this makes it look more like "normal" service with "
2015-09-07 22:37:23 -07:00
Girish Ramakrishnan
26aefadfba systemd: fix crashnotifier 2015-09-07 21:40:01 -07:00
Girish Ramakrishnan
51a28842cf systemd: pass the instance name as argument 2015-09-07 21:16:22 -07:00
Girish Ramakrishnan
210c2f3cc1 Output some logs in crashnotifier 2015-09-07 21:10:00 -07:00
Girish Ramakrishnan
773c326eb7 systemd: just wait for 5 seconds for box to die 2015-09-07 20:58:14 -07:00
Girish Ramakrishnan
cb2fb026c5 systemd: do not restart crashnotifier 2015-09-07 20:54:58 -07:00
Girish Ramakrishnan
47b662be09 Remove unnecessary alias 2015-09-07 20:53:26 -07:00
Girish Ramakrishnan
a4731ad054 200m is a more sane memory limit 2015-09-07 20:48:29 -07:00
Girish Ramakrishnan
aa33938fb5 systemd: fix config files 2015-09-07 20:46:32 -07:00
Girish Ramakrishnan
edfe8f1ad0 disable pager when collecting logs 2015-09-07 20:27:27 -07:00
Girish Ramakrishnan
41399a2593 Make crashnotifier.js executable 2015-09-07 20:15:13 -07:00
Girish Ramakrishnan
2a4c467ab8 systemd: Fix crashnotifier 2015-09-07 20:14:37 -07:00
Girish Ramakrishnan
1ee09825a0 stop systemd target instead of supervisor 2015-09-07 20:11:19 -07:00
Girish Ramakrishnan
6be6092c0e Add memory limits on services 2015-09-07 19:16:34 -07:00
Girish Ramakrishnan
e76584b0da Move from supervisor to systemd
This removes logrotate as well since we use systemd logging
2015-09-07 14:31:25 -07:00
Girish Ramakrishnan
0a679da968 Type belongs to service 2015-09-07 14:10:34 -07:00
Girish Ramakrishnan
59d174004e box code has moved to systemd 2015-09-07 11:19:16 -07:00
Girish Ramakrishnan
d0d0d95475 0.0.42 changes 2015-09-05 09:21:59 -07:00
Girish Ramakrishnan
b3816615db run upto 5 apptasks in parallel
fixes #482
2015-09-05 09:17:46 -07:00
Johannes Zellner
b08a6840f5 changes for 0.0.41 2015-08-31 21:49:37 -07:00
Johannes Zellner
212d0bd55a Revert "Add hack for broken app backup tarballs"
This reverts commit 9723951bfc.
2015-08-31 21:44:24 -07:00
Girish Ramakrishnan
77ada9c151 Copy upgrade flag 2015-08-31 19:23:43 -07:00
Girish Ramakrishnan
712ada940e Add hack for broken app backup tarballs 2015-08-31 18:58:38 -07:00
Girish Ramakrishnan
222e6b6611 0.0.40 changes 2015-08-31 09:32:12 -07:00
Johannes Zellner
ba690c6346 Add missing records argument 2015-08-30 23:00:01 -07:00
Johannes Zellner
e910e19f57 Fix debug tag 2015-08-30 22:54:52 -07:00
Johannes Zellner
0c2532b0b5 Give default value to config.dnsInSync 2015-08-30 22:35:44 -07:00
Johannes Zellner
9c9b17a5f0 Remove cloudron.config prior to every test run 2015-08-30 22:35:44 -07:00
Johannes Zellner
816dea91ec Assert for dns record values 2015-08-30 22:35:44 -07:00
Johannes Zellner
c228f8d4d5 Merge admin dns and mail dns setup
This now also checks if the mail records are in sync
2015-08-30 22:35:43 -07:00
Johannes Zellner
05bb99fad4 give dns record changeIds as a result for addMany() 2015-08-30 22:35:43 -07:00
Johannes Zellner
51b2457b3d Setup webadmin domain on the box side 2015-08-30 22:35:43 -07:00
Girish Ramakrishnan
ed71fca23e Fix css 2015-08-30 22:25:18 -07:00
Girish Ramakrishnan
20e8e72ac2 reserved blocks are used 2015-08-30 22:24:57 -07:00
Girish Ramakrishnan
13fe0eb882 Only display one donut for memory usage 2015-08-30 22:13:01 -07:00
Girish Ramakrishnan
e0476c9030 Reboot is a post route 2015-08-30 21:38:54 -07:00
Girish Ramakrishnan
70c93c7be7 Provision only once 2015-08-30 21:10:57 -07:00
Girish Ramakrishnan
b73fc70ecf limit systemd journal size 2015-08-30 21:04:39 -07:00
Johannes Zellner
eab33150ad 0.0.39 changes 2015-08-30 17:23:47 -07:00
Girish Ramakrishnan
fca82fd775 Display upto 600mb for apps 2015-08-30 17:21:44 -07:00
Johannes Zellner
37c8ba8ddd Reduce logging for aws credentials 2015-08-30 17:03:10 -07:00
Johannes Zellner
f87011b5c2 Also always check for dns propagation 2015-08-30 17:00:23 -07:00
Johannes Zellner
7f149700f8 Remove wrong optimization for subdomain records 2015-08-30 16:54:33 -07:00
Johannes Zellner
78ba9070fc use config.appFqdn() to handle custom domains 2015-08-30 16:29:09 -07:00
Johannes Zellner
e31e5e1f69 Reuse dnsRecordId for record status id 2015-08-30 15:58:54 -07:00
Johannes Zellner
31d9027677 Query dns status with aws statusId 2015-08-30 15:51:33 -07:00
Johannes Zellner
debcd6f353 aws provides uppercase properties 2015-08-30 15:47:08 -07:00
Johannes Zellner
5cb1681922 Fixup the zonename comparison 2015-08-30 15:37:18 -07:00
Johannes Zellner
9074bccea0 Move subdomain management from appstore to box 2015-08-30 15:29:14 -07:00
Girish Ramakrishnan
21c16d2009 0.0.38 changes 2015-08-28 11:21:43 -07:00
Girish Ramakrishnan
56413ecce6 Account for ext4 reserved space
2G - ram sawp
1G - backup swap
2G - reserved

root@yellowtent:/# du -hcs bin
13M bin
13M total
root@yellowtent:/# du -hcs boot
70M boot
70M total
root@yellowtent:/# du -hcs etc
6.3M    etc
6.3M    total
root@yellowtent:/# du -hcs lib
530M    lib
530M    total
root@yellowtent:/# du -hcs var
634M    var
634M    total
root@yellowtent:/# du -hcs root
33G root
33G total

Filesystem      Size  Used Avail Use% Mounted on
udev            990M     0  990M   0% /dev
tmpfs           201M  960K  200M   1% /run
/dev/vda1        40G   38G     0 100% /
tmpfs          1001M     0 1001M   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs          1001M     0 1001M   0% /sys/fs/cgroup
/dev/loop0       33G  7.9G   24G  25% /home/yellowtent/data
tmpfs           201M     0  201M   0% /run/user/0
2015-08-28 10:46:24 -07:00
Girish Ramakrishnan
291798f574 Pass along aws config for updates 2015-08-27 22:45:04 -07:00
Girish Ramakrishnan
b104843ae1 Add missing quotes to cloudron.conf 2015-08-27 20:15:04 -07:00
Girish Ramakrishnan
e94f2a95de Remove ununsed checkData 2015-08-27 18:51:02 -07:00
Girish Ramakrishnan
c2a43b69a9 Just pass the req.body through
The metadata has things like restoreUrl and restoreKey which should not be
passed through anyways
2015-08-27 18:49:54 -07:00
Girish Ramakrishnan
dd062c656f Fix failing test 2015-08-27 11:43:36 -07:00
Girish Ramakrishnan
ae2eb718c6 check if response has credentials object 2015-08-27 11:43:02 -07:00
Girish Ramakrishnan
7ac26bb653 Fix backup response 2015-08-27 11:19:40 -07:00
Girish Ramakrishnan
41a726e8a7 Fix backup test 2015-08-27 11:17:36 -07:00
Girish Ramakrishnan
4b69216548 bash: quote the array expansion 2015-08-27 10:13:05 -07:00
Girish Ramakrishnan
99395ddf5a bash: quoting array expansion because thats how it is 2015-08-27 09:49:44 -07:00
Girish Ramakrishnan
5f9fa5c352 bash: empty array expansion barfs with set -u 2015-08-27 09:33:40 -07:00
Girish Ramakrishnan
9013331917 Fix coding style 2015-08-27 09:30:32 -07:00
Girish Ramakrishnan
3a8f80477b getSignedDownloadUrl must return an object with url and sessionToken 2015-08-27 09:26:19 -07:00
Girish Ramakrishnan
c90e0fd21e do-resize resizes the disk it seems 2015-08-26 23:38:01 -07:00
Girish Ramakrishnan
6744621415 Print the detected ram and disk size 2015-08-26 22:54:23 -07:00
Girish Ramakrishnan
a6680a775f Start with 8gb instead 2015-08-26 16:02:51 -07:00
Girish Ramakrishnan
5a67be2292 Change ownership only of installer/ 2015-08-26 16:01:45 -07:00
Girish Ramakrishnan
c8b2b34138 Always resize the data volume 2015-08-26 15:40:48 -07:00
Girish Ramakrishnan
af8f4676ba systemd does not use /etc/default/docker 2015-08-26 15:32:40 -07:00
Girish Ramakrishnan
b51cb9d84a Remove redundant variable 2015-08-26 15:22:55 -07:00
Girish Ramakrishnan
ec7a61021f create single btrfs partition
apps and data consume space from the same btrfs partition now
2015-08-26 15:19:57 -07:00
Girish Ramakrishnan
8d0d19132e Add missing variable 2015-08-26 13:51:32 -07:00
Girish Ramakrishnan
d2bde5f0b1 Leave 5G for the system 2015-08-26 13:17:42 -07:00
Girish Ramakrishnan
3f9ae5d6bf refactor size calculation 2015-08-26 12:22:32 -07:00
Girish Ramakrishnan
9b97e26b58 Use fallocate everywhere 2015-08-26 12:15:47 -07:00
Girish Ramakrishnan
219032bbbb Dynamically size the home and docker partitions 2015-08-26 11:31:58 -07:00
Johannes Zellner
f0fd4ea45c Fixup tests after removing provisioning routes 2015-08-26 11:04:02 -07:00
Johannes Zellner
23a5a275f8 Reread box config from args.data 2015-08-26 11:04:02 -07:00
Johannes Zellner
813c680ed0 pass full box data to the update 2015-08-26 10:59:17 -07:00
Girish Ramakrishnan
3a1bfa91d1 Create app and data partitions dynamically 2015-08-26 09:59:45 -07:00
Johannes Zellner
a0eccd615f Send new version to update to to the installer 2015-08-26 09:42:48 -07:00
Johannes Zellner
59be539ecd make restoreapp.sh support aws session tokens 2015-08-26 09:14:15 -07:00
Johannes Zellner
a04740114c Generate app restore urls locally 2015-08-26 09:11:28 -07:00
Girish Ramakrishnan
a4f77dfcd0 Create systemd service to allocate swap
This can be used to make the swap creation dynamic based on the
ram in the cloudron
2015-08-26 09:11:05 -07:00
Johannes Zellner
60b5d71c74 appBackupIds are not needed for backup url generation 2015-08-26 09:06:45 -07:00
Girish Ramakrishnan
795ba3e365 Not sure why this is here 2015-08-26 00:07:15 -07:00
Girish Ramakrishnan
83ef4234bc Fix typo 2015-08-25 23:43:42 -07:00
Girish Ramakrishnan
b12b464462 Set RemainAfterExit
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=750683
2015-08-25 23:43:04 -07:00
Girish Ramakrishnan
04726ba697 Restore iptables before docker 2015-08-25 23:27:44 -07:00
Girish Ramakrishnan
4d607ada9d Make installer a systemd service 2015-08-25 23:25:42 -07:00
Johannes Zellner
0a8b4b0c43 Load our style sheet as early as possible 2015-08-25 21:59:01 -07:00
Johannes Zellner
2c7cf9faa1 0.0.37 changes 2015-08-25 20:50:57 -07:00
Johannes Zellner
ec21105c47 use backupKey from userData 2015-08-25 18:44:52 -07:00
Girish Ramakrishnan
444258e7ee backupKey is a function 2015-08-25 18:37:51 -07:00
Johannes Zellner
e6fd05c2bd Support optional aws related userData 2015-08-25 17:52:01 -07:00
Johannes Zellner
9fdcd452d0 Use locally generate signed urls for app backup 2015-08-25 17:52:01 -07:00
Johannes Zellner
f39b9d5618 Support session tokens in backupapp.sh 2015-08-25 17:52:00 -07:00
Johannes Zellner
76e4c4919d Only federated tokens need session token 2015-08-25 17:52:00 -07:00
Johannes Zellner
d1f159cdb4 Also send the restoreKey for the backup done webhook 2015-08-25 17:52:00 -07:00
Johannes Zellner
c63065e460 Also send the sessionToken when using the pre-signed url 2015-08-25 17:52:00 -07:00
Johannes Zellner
124c1d94a4 Translate the federated credentials 2015-08-25 17:52:00 -07:00
Johannes Zellner
e9161b726a AWS credential creation returns 201 2015-08-25 17:52:00 -07:00
Johannes Zellner
fd0d27b192 AWS credentials are now dealt with a level down 2015-08-25 17:52:00 -07:00
Johannes Zellner
50064a40fe Use dev bucket for now as a default 2015-08-25 17:52:00 -07:00
Johannes Zellner
c9bc5fc38e Use signed urls for upload on the box side 2015-08-25 17:52:00 -07:00
Johannes Zellner
58f533fe50 Add config.aws().backupPrefix 2015-08-25 17:52:00 -07:00
Johannes Zellner
efcdffd8ff Add getSignedUploadUrl() to aws.js 2015-08-25 17:52:00 -07:00
Johannes Zellner
22793c3886 move aws-sdk from dev to normal dependencies 2015-08-25 17:52:00 -07:00
Johannes Zellner
797ddbacc0 Return aws credentials from config.js 2015-08-25 17:52:00 -07:00
Johannes Zellner
e011962469 refactor backupBoxWithAppBackupIds() 2015-08-25 17:52:00 -07:00
Johannes Zellner
b376ad9815 Add webhooks.js 2015-08-25 17:51:59 -07:00
Johannes Zellner
77248fe65c Construct backupUrl locally 2015-08-25 17:51:59 -07:00
Johannes Zellner
1dad115203 Add initial aws object to config.js 2015-08-25 17:51:59 -07:00
Johannes Zellner
8812d58031 Add backupKey to config 2015-08-25 17:51:59 -07:00
Johannes Zellner
fff7568f7e Add aws.js 2015-08-25 17:51:59 -07:00
Johannes Zellner
ff6662579d Fix typo in backupapp.sh help output 2015-08-25 17:51:59 -07:00
Girish Ramakrishnan
0cf9fbd909 Merge data into args 2015-08-25 15:55:52 -07:00
Girish Ramakrishnan
7efa2fd072 Make update route work 2015-08-25 15:54:15 -07:00
Girish Ramakrishnan
9adf5167c9 Remove apiServerOrigin 2015-08-25 15:25:03 -07:00
Girish Ramakrishnan
4978984d75 Remove provision, restore, update routes 2015-08-25 15:09:35 -07:00
Girish Ramakrishnan
3b7ef4615a installer does not announce anymore 2015-08-25 15:06:39 -07:00
Girish Ramakrishnan
af8f4b64c0 Use userData from metadata API 2015-08-25 15:05:16 -07:00
Girish Ramakrishnan
848b745fcb Fix boolean logic 2015-08-25 12:24:02 -07:00
Girish Ramakrishnan
93042d862d Fix CHANGES 2015-08-25 11:27:49 -07:00
Girish Ramakrishnan
ba5424c250 0.0.37 changes 2015-08-25 11:09:09 -07:00
Girish Ramakrishnan
afdde9b032 Disable forwarding from containers to metadata IP 2015-08-25 10:46:23 -07:00
Girish Ramakrishnan
9a35c40b24 Add force argument
This fixes crash when auto-updating apps
2015-08-25 10:01:20 -07:00
Girish Ramakrishnan
1f1e6124cd oldConfig can be null during a restore/upgrade 2015-08-25 09:59:44 -07:00
Girish Ramakrishnan
a033480500 More 0.0.36 changes 2015-08-24 23:31:28 -07:00
Girish Ramakrishnan
033df970ad Update manifestformat@1.7.0 2015-08-24 22:56:02 -07:00
Girish Ramakrishnan
dd80a795a0 Read memoryLimit from manifest 2015-08-24 22:44:35 -07:00
Girish Ramakrishnan
1eec6a39c6 Show upto 200mb 2015-08-24 22:39:06 -07:00
Girish Ramakrishnan
14333e2910 Enable memory accounting 2015-08-24 22:34:51 -07:00
Girish Ramakrishnan
dd6b8face9 Set app memory limit to 200MB (includes 100 MB swap) 2015-08-24 21:58:19 -07:00
Girish Ramakrishnan
288de7e03a Add RSTATE_ERROR 2015-08-24 21:58:19 -07:00
Girish Ramakrishnan
a760ef4d22 Rebase addons to use base image 0.3.3 2015-08-24 10:19:18 -07:00
Girish Ramakrishnan
20df96b6ba Add v0.0.36 changes 2015-08-24 09:37:20 -07:00
Johannes Zellner
0dd745bce4 Fix form submit with enter for update form 2015-08-22 17:21:25 -07:00
Johannes Zellner
d4d5d371ac Use POST heartbeat route instead of GET 2015-08-22 16:51:56 -07:00
Johannes Zellner
205bf4ddbd Offset the footer in apps view 2015-08-20 23:50:52 -07:00
Girish Ramakrishnan
4ab84d42c6 Delete image only if it changed
This optimization won't work if we have two dockerImage with same
image id....
2015-08-19 14:24:32 -07:00
Girish Ramakrishnan
ee74badf3a Check for dockerImage in manifest in install/update/restore routes 2015-08-19 11:08:45 -07:00
Girish Ramakrishnan
aa173ff74c restore without a backup is the same as re-install 2015-08-19 11:00:00 -07:00
Girish Ramakrishnan
a7729e1597 Add v0.0.35 changelog 2015-08-18 23:46:25 -07:00
Girish Ramakrishnan
b584fc33f5 CN of admin group is admins 2015-08-18 16:35:52 -07:00
Girish Ramakrishnan
15c9d8682e Base image is now 0.3.3 2015-08-18 15:43:50 -07:00
Girish Ramakrishnan
361be8c26b containerId can be null 2015-08-18 15:43:50 -07:00
Girish Ramakrishnan
4db9a5edd6 Clean up the old image and not the current one 2015-08-18 10:01:15 -07:00
Johannes Zellner
bcc878da43 Hide update input fields and update button if it is blocked by apps 2015-08-18 16:59:36 +02:00
Johannes Zellner
79f179fed4 Add note, why sendError() is required 2015-08-18 16:53:29 +02:00
Johannes Zellner
a924a9a627 Revert "remove obsolete sendError() function"
This reverts commit 5d9b122dd5.
2015-08-18 16:49:53 +02:00
Girish Ramakrishnan
45d444df0e leave a note about force_update 2015-08-17 21:30:56 -07:00
Girish Ramakrishnan
92461a3366 Remove ununsed require 2015-08-17 21:23:32 -07:00
Girish Ramakrishnan
032a430c51 Fix debug message 2015-08-17 21:23:27 -07:00
Girish Ramakrishnan
a6a3855e79 Do not remove icon for non-appstore installs
Fixes #466
2015-08-17 19:37:51 -07:00
Girish Ramakrishnan
29c3233375 0.0.34 changes 2015-08-17 10:12:09 -07:00
Girish Ramakrishnan
2386545814 Add a note why oldConfig can be null 2015-08-17 10:05:07 -07:00
Johannes Zellner
2059152dd3 remove obsolete sendError() function 2015-08-17 14:55:56 +02:00
Johannes Zellner
32d2c260ab Move appstore badges out of the way for the app titles 2015-08-17 11:50:31 +02:00
Johannes Zellner
384c7873aa Correctly mark apps pending for approval
Fixes #339
2015-08-17 11:50:08 +02:00
Girish Ramakrishnan
d0eab70974 0.0.33 changes 2015-08-13 16:06:52 -07:00
Girish Ramakrishnan
f3cbb91527 fetch the latest codes to generate the change log 2015-08-12 21:54:47 -07:00
Girish Ramakrishnan
9772cfe1f2 0.0.32 changes 2015-08-12 20:33:36 -07:00
Girish Ramakrishnan
3557e8a125 Use constants from the box repo 2015-08-12 19:57:01 -07:00
Girish Ramakrishnan
9e0bb6ca34 Use latest images 2015-08-12 19:04:37 -07:00
Johannes Zellner
bb74eef601 more changes for 0.0.31 2015-08-12 17:45:13 +02:00
Girish Ramakrishnan
d1db38ba8e Add 0.0.31 changes 2015-08-11 17:01:11 -07:00
Girish Ramakrishnan
e9af9fb16b 0.0.30 changes 2015-08-10 21:38:22 -07:00
Girish Ramakrishnan
fd74be8848 Update docker to 1.7.0 2015-08-10 19:17:21 -07:00
Girish Ramakrishnan
926fafd7f6 Add 0.0.29 changelog 2015-08-10 18:24:36 -07:00
Girish Ramakrishnan
c51c715cee Use systemctl 2015-08-10 17:57:24 -07:00
Girish Ramakrishnan
aa80210075 Update base image to 15.04 2015-08-10 17:57:24 -07:00
Johannes Zellner
768654ae63 Version 0.0.28 changelog 2015-08-10 15:33:47 +02:00
Girish Ramakrishnan
6efb291449 0.0.27 changes 2015-08-08 19:13:13 -07:00
Girish Ramakrishnan
7a431b9b83 0.0.26 changes 2015-08-06 14:07:07 -07:00
Johannes Zellner
c78d09df66 Do not install optional dependencies for production
This was needed due to the dtrace-provider failures as optional
deps for ldapjs and bunyan
2015-08-05 17:37:09 +02:00
Johannes Zellner
7d30d9e867 Use shrinkwrap instead of package.json for node module cache 2015-08-05 17:36:51 +02:00
Girish Ramakrishnan
d3fb244cef list ldap as 0.0.25 change 2015-08-04 16:29:49 -07:00
277 changed files with 30583 additions and 9788 deletions

7
.gitattributes vendored
View File

@@ -1,6 +1,7 @@
# Skip files when using git archive
# following files are skipped when exporting using git archive
/release export-ignore
/admin export-ignore
test export-ignore
.gitattributes export-ignore
.gitignore export-ignore
/scripts export-ignore
test export-ignore

6
.gitignore vendored
View File

@@ -3,11 +3,9 @@ coverage/
docs/
webadmin/dist/
setup/splash/website/
installer/src/certs/server.key
# vim swam files
# vim swap files
*.swp
# supervisor
supervisord.pid
supervisord.log

537
CHANGES Normal file
View File

@@ -0,0 +1,537 @@
[0.0.1]
- Hot Chocolate
[0.0.2]
- Hotfix appstore ui in webadim
[0.0.3]
- Tall Pike
[0.0.4]
- This will be 0.0.4 changes
[0.0.5]
- App install/configure route fixes
[0.0.6]
- Not sure what happenned here
[0.0.7]
- resetToken is now sent as part of create user
- Same as 0.0.7 which got released by mistake
[0.0.8]
- Manifest changes
[0.0.9]
- Fix app restore
- Fix backup issues
[0.0.10]
- Unknown orchestra
[0.0.11]
- Add ldap addon
[0.0.12]
- Support OAuth2 state
[0.0.13]
- Use docker image from cloudron repository
[0.0.14]
- Improve setup flow
[0.0.15]
- Improved Appstore view
[0.0.16]
- Improved Backup approach
[0.0.17]
- Upgrade testing
- App auto updates
- Usage graphs
[0.0.18]
- Rework backups and updates
[0.0.19]
- Graphite fixes
- Avatar and Cloudron name support
[0.0.20]
- Apptask fixes
- Chrome related fixes
[0.0.21]
- Increase nginx hostname size to 64
[0.0.22]
- Testing the e2e tests
[0.0.23]
- Better error status page
- Fix updater and backup progress reporting
- New avatar set
- Improved setup wizard
[0.0.24]
- Hotfix the ldap support
[0.0.25]
- Add support page
- Really fix ldap issues
[0.0.26]
- Add configurePath support
[0.0.27]
- Improved log collector
[0.0.28]
- Improve app feedback
- Restyle login page
[0.0.29]
- Update to ubuntu 15.04
[0.0.30]
- Move to docker 1.7
[0.0.31]
- WARNING: This update restarts your containers
- System processes are prioritized over apps
- Add ldap group support
[0.0.32]
- MySQL addon update
[0.0.33]
- Fix graphs
- Fix MySQL 5.6 memory usage
[0.0.34]
- Correctly mark apps pending for approval
[0.0.35]
- Fix ldap admin group username
[0.0.36]
- Fix restore without backup
- Optimize image deletion during updates
- Add memory accounting
- Restrict access to metadata from containers
[0.0.37]
- Prepare for Selfhosting 1. part
- Use userData instead of provisioning calls
[0.0.38]
- Account for Ext4 reserved block when partitioning disk
[0.0.39]
- Move subdomain management to the cloudron
[0.0.40]
- Add journal limit
- Fix reprovisioning on reboot
- Fix subdomain management during startup
[0.0.41]
- Finally bring things to a sane state
[0.0.42]
- Parallel apptask
[0.0.43]
- Move to systemd
[0.0.44]
- Fix apptask concurrency bug
[0.0.45]
- Retry subdomain registration
[0.0.46]
- Fix app update email notification
[0.0.47]
- Ensure box code quits within 5 seconds
[0.0.48]
- Styling fixes
- Improved session handling
[0.0.49]
- Fix app autoupdate logic
[0.0.50]
- Use domainmanagement via CaaS
[0.0.51]
- Fix memory management
[0.0.52]
- Restrict addons memory
- Get nofication about container OOMs
[0.0.53]
- Restrict addons memory
- Get notification about container OOMs
- Add retry to subdomain logic
[0.0.54]
- OAuth Proxy now uses internal port forwarding
[0.0.55]
- Setup cloudron timezone based on droplet region
[0.0.56]
- Use correct timezone in updater
[0.0.57]
- Fix systemd logging issues
[0.0.58]
- Ensure backups of failed apps are retained across archival cycles
[0.0.59]
- Installer API fixes
[0.0.60]
- Do full box backup on updates
[0.0.61]
- Track update notifications to inform admin only once
[0.0.62]
- Export bind dn and password from LDAP addon
[0.0.63]
- Fix creation of TXT records
[0.0.64]
- Stop apps in a retired cloudron
- Retry downloading application on failure
[0.0.65]
- Do not send crash mails for apps in development
[0.0.66]
- Readonly application and addon containers
[0.0.67]
- Fix email notifications
- Fix bug when restoring from certain backups
[0.0.68]
- Update graphite image
- Add simpleauth addon support
[0.0.69]
- Support newer manifest format
- Fix app listing rendering in chrome
- Fix redis backup across upgrades
[0.0.70]
- Retry app download on error
[0.0.71]
- Fix oauth and simple auth login
[0.0.72]
- Cleanup application volumes periodically
- New application logging design
[0.0.73]
- Update SSL certificate
[0.0.74]
- Support singleUser apps
[0.0.75]
- scheduler addon
[0.0.76]
- DNS Sync fixes
- Show warning to user when memory limit reached
[0.0.77]
- Do not set hostname in app containers
[0.0.78]
- Support custom domains
[0.0.79]
- Move SSH Port
[0.0.80]
- Use journalctl for container logs
[0.1.0]
- Wait for configuration changes before starting Cloudron
[0.1.1]
- Ensure dns config for all cloudrons
[0.1.2]
- Make email work again
- Add DKIM keys for custom domains
[0.1.3]
- Storage backend
[0.1.4]
- CaaS Backup configuration fix
[0.1.5]
- Use correct tokens for DNS backend
[0.1.6]
- Add hook to determine the api server of the box
- Fix crash notification
[0.2.0]
- New cloudron exec implementation
[0.2.1]
- Update to node 4.1.1
- Fix certification installation with custom domains
[0.2.2]
- Better debug output
- Retry more times if docker registry goes down
[0.3.0]
- Update SSH keys
- Allow bigger manifest files
[0.4.0]
- Update to docker 1.9.0
[0.4.1]
- Fix scheduler crash
- Crucial OAuth fixes
[0.4.2]
- Fix crash when reporting backup error
- Allow larger manifests
[0.4.3]
- Fix cloudron exec
[0.4.4]
- Initial Lets Encrypt integration
[0.4.5]
- Fixup nginx configuration to allow dynamic certificates
[0.4.6]
- LetsEncrypt integration for custom domains
- Rate limit crash emails
[0.5.0]
- Enable staging Lets Encrypt Integration
[0.5.1]
- Display error dialog for app installation errors
- Enable prod Lets Encrypt Integration
- Handle apptask crashes correctly
[0.5.2]
- Fix apphealthtask crash
- Use cgroup fs driver instead of systemd cgroup driver in docker
[0.5.3]
- Changes for e2e testing
[0.5.4]
- Fix bug in LE server selection
[0.5.5]
- Scheduler redesign
- Fix journalctl logging
[0.5.6]
- Prepare for selfhosting option
[0.5.7]
- Move app images off the btrfs subvolume
[0.6.0]
- Consolidate code repositories
[0.6.1]
- Use no-reply as email from address for apps in naked domains
- Update Lets Encrypt account with owner email when available
- Fix email templates to indicate auto update
- Add notification UI
[0.6.2]
- Fix `cloudron exec` container to have same namespaces as app
- Add developmentMode to manifest
[0.6.3]
- Make sending invite for new users optional
[0.6.4]
- Add support for display names
- Send invite links to admins for user setup
- Enforce stronger passwords
[0.6.5]
- Finalize stronger password requirement
[0.7.0]
- Upgrade to 15.10
- Do not remove docker images when in use by another container
- Fix sporadic error when reconfiguring apps
- Handle journald crashes gracefully
[0.7.1]
- Allow admins to edit users
- Fix graphs
- Support more LDAP cases
- Allow appstore deep linking
[0.7.2]
- Fix 5xx errors when password does not meet requirements
- Improved box update management using prereleases
- Less aggressive disk space checks
[0.8.0]
- MySQL addon : multiple database support
[0.8.1]
- Set Host HTTP header when querying healthCheckPath
- Show application Changelog in app update emails
[0.9.0]
- Fix bug in multdb mysql addon backup
- Add initial user group support
- Improved app memory limit handling
[0.9.1]
- Introduce per app group access control
[0.9.2]
- Fix bug where reconfiguring apps would trigger memory limit warning
- Allow more apps to be installed in bigger sized cloudrons
- Allow user to override memory limit warning and install anyway
[0.9.3]
- Admin flag is handled outside of groups
- User interface fixes for groups
- Allow to set access restrictions on app installation
[0.10.0]
- Upgrade to docker 1.10.2
- Fix MySQL addon to handle heavier loads
- Allow listing and download of backups (using the CLI tool)
- Ubuntu security updates till 8th March 2016 (http://www.ubuntu.com/usn)
[0.10.1]
- Fix Let's Encrypt certificate renewal
[0.10.2]
- Apps can now bind with username or email with LDAP
- Disallow updating an app with mismatching manifest id
- Use admin domain instead of naked domain in the SPF record
- Download Lets Encrypt intermediate cert
[0.10.3]
- Store the backup config for each backup. This will allow using multiple buckets/providers for backups simultaneously.
- Fix SPF record check
[0.10.4]
- Fix restore for droplets in EU region
[0.11.0]
- Store backups in the same region as the Cloudron
- Fix PCRE security issue (http://www.ubuntu.com/usn/usn-2943-1/)
[0.11.1]
- Improve the backup logic
[0.11.2]
- Allow users to choose a username on first sign up
- Fix app graphs
[0.12.0]
- Fix upload of large backups
- Postgres addon whitelists pg_trgm and hstore extensions
- Suppress boring update emails from patch releases
- Setup bounce alerts for emails
- Query admin's name in activation wizard
- Admin emails are now delivered as no-reply
- Fix crash when user attempts to set a duplicate email
- Improved mongodb crash recovery
[0.12.1]
- Fix crash when backing up apps
[0.12.2]
- Improved error handling for addons
[0.12.3]
- LDAP: Do not set sn attribute when user has no surname
[0.12.4]
- Install app only after platform is ready
[0.12.5]
- Get alerts for app task failures
- Fix update issue when one or more apps are in failed state
[0.12.6]
- Allow setting an alternate external domain for apps
[0.12.7]
- Fix changing password
[0.13.0]
- Upgrade to ubuntu 16.04
- Add event log
[0.13.1]
- Make activity log viewable to admins
- Fix geoip lookup
[0.13.2]
- Fix crash in app auto updater
- Fix crash with empty timezone
[0.13.3]
- Enable auth in email addon
- Add search for activity log
- Add tutorial for first time users
[0.13.4]
- Fix mail addon restart issue
[0.14.0]
- You have mail :-)
[0.14.1]
- 2-character usernames are now allowed
- Make cloudron CLI push/pull more robust
[0.14.2]
- Update mail addon
[0.15.0]
- [REST API](https://cloudron.io/references/api.html) is now in public beta
- Enable Developer mode by default for new Cloudrons
- Reverse proxy fixes for apps exposing a WebDav server
- Allow admins to optionally set the username and displayName on user creation
- Fix app autoupdate logic to detect if one or more in-use port bindings was removed
[0.15.1]
- Fix mail connectivity from IPv6 clients
- Add API token management UI
- Improved UI to enter email aliases
[0.15.2]
- Allow restoring apps from any previous backup

View File

@@ -1,11 +1,17 @@
The Box
=======
Cloudron a Smart Server
=======================
Development setup
-----------------
* sudo useradd -m yellowtent
** This dummy user is required for supervisor 'box' configs
** Add admin-localhost as 127.0.0.1 in /etc/hosts
** All apps will be installed as hypened-subdomains of localhost. You should add
hyphened-subdomains of your apps into /etc/hosts
Selfhost Instructions
---------------------
The smart server currently relies on an AWS account with access to Route53 and S3 and is tested on DigitalOcean and EC2.
First create a virtual private server with Ubuntu 15.04 and run the following commands in an ssh session to initialize the base image:
```
curl https://s3.amazonaws.com/prod-cloudron-releases/installer.sh -o installer.sh
chmod +x installer.sh
./installer.sh <domain> <aws access key> <aws acccess secret> <backup bucket> <provider> <release sha1>
```

47
app.js
View File

@@ -1,47 +0,0 @@
#!/usr/bin/env node
'use strict';
require('supererror')({ splatchError: true });
var server = require('./src/server.js'),
ldap = require('./src/ldap.js'),
config = require('./src/config.js');
console.log();
console.log('==========================================');
console.log(' Cloudron will use the following settings ');
console.log('==========================================');
console.log();
console.log(' Environment: ', config.CLOUDRON ? 'CLOUDRON' : 'TEST');
console.log(' Version: ', config.version());
console.log(' Admin Origin: ', config.adminOrigin());
console.log(' Appstore token: ', config.token());
console.log(' Appstore API server origin: ', config.apiServerOrigin());
console.log(' Appstore Web server origin: ', config.webServerOrigin());
console.log();
console.log('==========================================');
console.log();
server.start(function (err) {
if (err) {
console.error('Error starting server', err);
process.exit(1);
}
console.log('Server listening on port ' + config.get('port'));
ldap.start(function (error) {
if (error) {
console.error('Error LDAP starting server', err);
process.exit(1);
}
console.log('LDAP server listen on port ' + config.get('ldapPort'));
});
});
var NOOP_CALLBACK = function () { };
process.on('SIGINT', function () { server.stop(NOOP_CALLBACK); });
process.on('SIGTERM', function () { server.stop(NOOP_CALLBACK); });

View File

@@ -1,147 +0,0 @@
#!/usr/bin/env node
'use strict';
require('supererror')({ splatchError: true });
var appdb = require('./src/appdb.js'),
assert = require('assert'),
async = require('async'),
database = require('./src/database.js'),
DatabaseError = require('./src/databaseerror.js'),
debug = require('debug')('box:apphealthtask'),
docker = require('./src/docker.js'),
mailer = require('./src/mailer.js'),
superagent = require('superagent'),
util = require('util');
exports = module.exports = {
run: run
};
var HEALTHCHECK_INTERVAL = 10 * 1000; // every 10 seconds. this needs to be small since the UI makes only healthy apps clickable
var UNHEALTHY_THRESHOLD = 3 * 60 * 1000; // 3 minutes
var gHealthInfo = { }; // { time, emailSent }
function debugApp(app, args) {
assert(!app || typeof app === 'object');
var prefix = app ? app.location : '(no app)';
debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
}
function initialize(callback) {
assert.strictEqual(typeof callback, 'function');
async.series([
database.initialize,
mailer.initialize
], callback);
}
function setHealth(app, health, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof health, 'string');
assert.strictEqual(typeof callback, 'function');
var now = new Date();
if (!(app.id in gHealthInfo)) { // add new apps to list
gHealthInfo[app.id] = { time: now, emailSent: false };
}
if (health === appdb.HEALTH_HEALTHY) {
gHealthInfo[app.id].time = now;
} else if (Math.abs(now - gHealthInfo[app.id].time) > UNHEALTHY_THRESHOLD) {
if (gHealthInfo[app.id].emailSent) return callback(null);
debugApp(app, 'marking as unhealthy since not seen for more than %s minutes', UNHEALTHY_THRESHOLD/(60 * 1000));
mailer.appDied(app);
gHealthInfo[app.id].emailSent = true;
} else {
debugApp(app, 'waiting for sometime to update the app health');
return callback(null);
}
appdb.setHealth(app.id, health, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null); // app uninstalled?
if (error) return callback(error);
app.health = health;
callback(null);
});
}
// callback is called with error for fatal errors and not if health check failed
function checkAppHealth(app, callback) {
if (app.installationState !== appdb.ISTATE_INSTALLED || app.runState !== appdb.RSTATE_RUNNING) {
debugApp(app, 'skipped. istate:%s rstate:%s', app.installationState, app.runState);
return callback(null);
}
var container = docker.getContainer(app.containerId),
manifest = app.manifest;
container.inspect(function (err, data) {
if (err || !data || !data.State) {
debugApp(app, 'Error inspecting container');
return setHealth(app, appdb.HEALTH_ERROR, callback);
}
if (data.State.Running !== true) {
debugApp(app, 'exited');
return setHealth(app, appdb.HEALTH_DEAD, callback);
}
// poll through docker network instead of nginx to bypass any potential oauth proxy
var healthCheckUrl = 'http://127.0.0.1:' + app.httpPort + manifest.healthCheckPath;
superagent
.get(healthCheckUrl)
.redirects(0)
.timeout(HEALTHCHECK_INTERVAL)
.end(function (error, res) {
if (error || res.status >= 400) { // 2xx and 3xx are ok
debugApp(app, 'not alive : %s', error || res.status);
setHealth(app, appdb.HEALTH_UNHEALTHY, callback);
} else {
debugApp(app, 'alive');
setHealth(app, appdb.HEALTH_HEALTHY, callback);
}
});
});
}
function processApps(callback) {
appdb.getAll(function (error, apps) {
if (error) return callback(error);
async.each(apps, checkAppHealth, function (error) {
if (error) console.error(error);
callback(null);
});
});
}
function run() {
processApps(function (error) {
if (error) console.error(error);
setTimeout(run, HEALTHCHECK_INTERVAL);
});
}
if (require.main === module) {
initialize(function (error) {
if (error) {
console.error('apphealth task exiting with error', error);
process.exit(1);
}
run();
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

186
baseimage/createImage Executable file
View File

@@ -0,0 +1,186 @@
#!/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"
provider="digitalocean"
installer_revision=$(git rev-parse HEAD)
box_name=""
server_id=""
server_ip=""
destroy_server="yes"
deploy_env="dev"
# 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 "provider:,revision:,regions:,size:,name:,no-destroy,env:" -n "$0" -- "$@")
eval set -- "${args}"
while true; do
case "$1" in
--env) deploy_env="$2"; shift 2;;
--revision) installer_revision="$2"; shift 2;;
--provider) provider="$2"; shift 2;;
--name) box_name="$2"; destroy_server="no"; shift 2;;
--no-destroy) destroy_server="no"; shift 2;;
--) break;;
*) echo "Unknown option $1"; exit 1;;
esac
done
echo "Creating image using ${provider}"
if [[ "${provider}" == "digitalocean" ]]; then
if [[ "${deploy_env}" == "staging" ]]; then
assertNotEmpty DIGITAL_OCEAN_TOKEN_STAGING
export DIGITAL_OCEAN_TOKEN="${DIGITAL_OCEAN_TOKEN_STAGING}"
elif [[ "${deploy_env}" == "dev" ]]; then
assertNotEmpty DIGITAL_OCEAN_TOKEN_DEV
export DIGITAL_OCEAN_TOKEN="${DIGITAL_OCEAN_TOKEN_DEV}"
elif [[ "${deploy_env}" == "prod" ]]; then
assertNotEmpty DIGITAL_OCEAN_TOKEN_PROD
export DIGITAL_OCEAN_TOKEN="${DIGITAL_OCEAN_TOKEN_PROD}"
else
echo "No such env ${deploy_env}."
exit 1
fi
vps="/bin/bash ${SCRIPT_DIR}/digitalocean.sh"
else
echo "Unknown provider : ${provider}"
exit 1
fi
readonly ssh_keys="${HOME}/.ssh/id_rsa_caas_${deploy_env}"
readonly scp202="scp -P 202 -o ConnectTimeout=10 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ${ssh_keys}"
readonly scp22="scp -o ConnectTimeout=10 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ${ssh_keys}"
readonly ssh202="ssh -p 202 -o IdentitiesOnly=yes -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ${ssh_keys}"
readonly ssh22="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
function get_pretty_revision() {
local git_rev="$1"
local sha1=$(git rev-parse --short "${git_rev}" 2>/dev/null)
echo "${sha1}"
}
now=$(date "+%Y-%m-%d-%H%M%S")
pretty_revision=$(get_pretty_revision "${installer_revision}")
if [[ -z "${box_name}" ]]; then
# if you change this, change the regexp is appstore/janitor.js
box_name="box-${deploy_env}-${pretty_revision}-${now}" # remove slashes
# create a new server if no name given
if ! caas_ssh_key_id=$($vps get_ssh_key_id "caas"); then
echo "Could not query caas ssh key"
exit 1
fi
echo "Detected caas ssh key id: ${caas_ssh_key_id}"
echo "Creating Server with name [${box_name}]"
if ! server_id=$($vps create ${caas_ssh_key_id} ${box_name}); then
echo "Failed to create server"
exit 1
fi
echo "Created server with id: ${server_id}"
# If we run scripts overenthusiastically without the wait, setup script randomly fails
echo -n "Waiting 120 seconds for server creation"
for i in $(seq 1 24); do
echo -n "."
sleep 5
done
echo ""
else
if ! server_id=$($vps get_id "${box_name}"); then
echo "Could not determine id from name"
exit 1
fi
echo "Reusing server with id: ${server_id}"
$vps power_on "${server_id}"
fi
# Query until we get an IP
while true; do
echo "Trying to get the server IP"
if server_ip=$($vps get_ip "${server_id}"); then
echo "Server IP : [${server_ip}]"
break
fi
echo "Timedout, trying again in 10 seconds"
sleep 10
done
while true; do
echo "Trying to copy init script to server"
if $scp22 "${SCRIPT_DIR}/initializeBaseUbuntuImage.sh" root@${server_ip}:.; then
break
fi
echo "Timedout, trying again in 30 seconds"
sleep 30
done
echo "Copying infra_version.js"
$scp22 "${SCRIPT_DIR}/../src/infra_version.js" root@${server_ip}:.
echo "Copying box source"
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}"; 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
# wait 10 secs for actual shutdown
echo "Waiting for 10 seconds for server to shutdown"
sleep 30
echo "Powering off server"
if ! $vps power_off "${server_id}"; then
echo "Could not power off server"
exit 1
fi
snapshot_name="box-${deploy_env}-${pretty_revision}-${now}"
echo "Snapshotting as ${snapshot_name}"
if ! image_id=$($vps snapshot "${server_id}" "${snapshot_name}"); then
echo "Could not snapshot and get image id"
exit 1
fi
if [[ "${destroy_server}" == "yes" ]]; then
echo "Destroying server"
if ! $vps destroy "${server_id}"; then
echo "Could not destroy server"
exit 1
fi
else
echo "Skipping server destroy"
fi
echo "Transferring image ${image_id} to other regions"
$vps transfer_image_to_all_regions "${image_id}"
echo "Done."

260
baseimage/digitalocean.sh Normal file
View File

@@ -0,0 +1,260 @@
#!/bin/bash
if [[ -z "${DIGITAL_OCEAN_TOKEN}" ]]; then
echo "Script requires DIGITAL_OCEAN_TOKEN env to be set"
exit 1
fi
if [[ -z "${JSON}" ]]; then
echo "Script requires JSON env to be set to path of JSON binary"
exit 1
fi
readonly CURL="curl --retry 5 -s -u ${DIGITAL_OCEAN_TOKEN}:"
function debug() {
echo "$@" >&2
}
function get_ssh_key_id() {
id=$($CURL "https://api.digitalocean.com/v2/account/keys" \
| $JSON ssh_keys \
| $JSON -c "this.name === \"$1\"" \
| $JSON 0.id)
[[ -z "$id" ]] && exit 1
echo "$id"
}
function create_droplet() {
local ssh_key_id="$1"
local box_name="$2"
local image_region="sfo1"
local ubuntu_image_slug="ubuntu-16-04-x64"
local box_size="512mb"
local data="{\"name\":\"${box_name}\",\"size\":\"${box_size}\",\"region\":\"${image_region}\",\"image\":\"${ubuntu_image_slug}\",\"ssh_keys\":[ \"${ssh_key_id}\" ],\"backups\":false}"
id=$($CURL -X POST -H 'Content-Type: application/json' -d "${data}" "https://api.digitalocean.com/v2/droplets" | $JSON droplet.id)
[[ -z "$id" ]] && exit 1
echo "$id"
}
function get_droplet_ip() {
local droplet_id="$1"
ip=$($CURL "https://api.digitalocean.com/v2/droplets/${droplet_id}" | $JSON "droplet.networks.v4[0].ip_address")
[[ -z "$ip" ]] && exit 1
echo "$ip"
}
function get_droplet_id() {
local droplet_name="$1"
id=$($CURL "https://api.digitalocean.com/v2/droplets?per_page=100" | $JSON "droplets" | $JSON -c "this.name === '${droplet_name}'" | $JSON "[0].id")
[[ -z "$id" ]] && exit 1
echo "$id"
}
function power_off_droplet() {
local droplet_id="$1"
local data='{"type":"power_off"}'
local response=$($CURL -X POST -H 'Content-Type: application/json' -d "${data}" "https://api.digitalocean.com/v2/droplets/${droplet_id}/actions")
local event_id=`echo "${response}" | $JSON action.id`
if [[ -z "${event_id}" ]]; then
debug "Got no event id, assuming already powered off."
debug "Response: ${response}"
return
fi
debug "Powered off droplet. Event id: ${event_id}"
debug -n "Waiting for droplet to power off"
while true; do
local event_status=`$CURL "https://api.digitalocean.com/v2/droplets/${droplet_id}/actions/${event_id}" | $JSON action.status`
if [[ "${event_status}" == "completed" ]]; then
break
fi
debug -n "."
sleep 10
done
debug ""
}
function power_on_droplet() {
local droplet_id="$1"
local data='{"type":"power_on"}'
local event_id=`$CURL -X POST -H 'Content-Type: application/json' -d "${data}" "https://api.digitalocean.com/v2/droplets/${droplet_id}/actions" | $JSON action.id`
debug "Powered on droplet. Event id: ${event_id}"
if [[ -z "${event_id}" ]]; then
debug "Got no event id, assuming already powered on"
return
fi
debug -n "Waiting for droplet to power on"
while true; do
local event_status=`$CURL "https://api.digitalocean.com/v2/droplets/${droplet_id}/actions/${event_id}" | $JSON action.status`
if [[ "${event_status}" == "completed" ]]; then
break
fi
debug -n "."
sleep 10
done
debug ""
}
function get_image_id() {
local snapshot_name="$1"
local image_id=""
if ! response=$($CURL "https://api.digitalocean.com/v2/images?per_page=100"); then
echo "Failed to get image listing. ${response}"
return 1
fi
if ! image_id=$(echo "$response" \
| $JSON images \
| $JSON -c "this.name === \"${snapshot_name}\"" 0.id); then
echo "Failed to parse curl response: ${response}"
fi
if [[ -z "${image_id}" ]]; then
echo "Failed to get image id of ${snapshot_name}. reponse: ${response}"
return 1
fi
echo "${image_id}"
}
function snapshot_droplet() {
local droplet_id="$1"
local snapshot_name="$2"
local data="{\"type\":\"snapshot\",\"name\":\"${snapshot_name}\"}"
local event_id=`$CURL -X POST -H 'Content-Type: application/json' -d "${data}" "https://api.digitalocean.com/v2/droplets/${droplet_id}/actions" | $JSON action.id`
debug "Droplet snapshotted as ${snapshot_name}. Event id: ${event_id}"
debug -n "Waiting for snapshot to complete"
while true; do
if ! response=$($CURL "https://api.digitalocean.com/v2/droplets/${droplet_id}/actions/${event_id}"); then
echo "Could not get action status. ${response}"
continue
fi
if ! event_status=$(echo "${response}" | $JSON action.status); then
echo "Could not parse action.status from response. ${response}"
continue
fi
if [[ "${event_status}" == "completed" ]]; then
break
fi
debug -n "."
sleep 10
done
debug "! done"
if ! image_id=$(get_image_id "${snapshot_name}"); then
return 1
fi
echo "${image_id}"
}
function destroy_droplet() {
local droplet_id="$1"
# TODO: check for 204 status
$CURL -X DELETE "https://api.digitalocean.com/v2/droplets/${droplet_id}"
debug "Droplet destroyed"
debug ""
}
function transfer_image() {
local image_id="$1"
local region_slug="$2"
local data="{\"type\":\"transfer\",\"region\":\"${region_slug}\"}"
local event_id=`$CURL -X POST -H 'Content-Type: application/json' -d "${data}" "https://api.digitalocean.com/v2/images/${image_id}/actions" | $JSON action.id`
echo "${event_id}"
}
function wait_for_image_event() {
local image_id="$1"
local event_id="$2"
debug -n "Waiting for ${event_id}"
while true; do
local event_status=`$CURL "https://api.digitalocean.com/v2/images/${image_id}/actions/${event_id}" | $JSON action.status`
if [[ "${event_status}" == "completed" ]]; then
break
fi
debug -n "."
sleep 10
done
debug ""
}
function transfer_image_to_all_regions() {
local image_id="$1"
xfer_events=()
image_regions=(ams3) ## sfo1 is where the image is created
for image_region in ${image_regions[@]}; do
xfer_event=$(transfer_image ${image_id} ${image_region})
echo "Image transfer to ${image_region} initiated. Event id: ${xfer_event}"
xfer_events+=("${xfer_event}")
sleep 1
done
echo "Image transfer initiated, but they will take some time to get transferred."
for xfer_event in ${xfer_events[@]}; do
$vps wait_for_image_event "${image_id}" "${xfer_event}"
done
}
if [[ $# -lt 1 ]]; then
debug "<command> <params...>"
exit 1
fi
case $1 in
get_ssh_key_id)
get_ssh_key_id "${@:2}"
;;
create)
create_droplet "${@:2}"
;;
get_id)
get_droplet_id "${@:2}"
;;
get_ip)
get_droplet_ip "${@:2}"
;;
power_on)
power_on_droplet "${@:2}"
;;
power_off)
power_off_droplet "${@:2}"
;;
snapshot)
snapshot_droplet "${@:2}"
;;
destroy)
destroy_droplet "${@:2}"
;;
transfer_image_to_all_regions)
transfer_image_to_all_regions "${@:2}"
;;
*)
echo "Unknown command $1"
exit 1
esac

View File

@@ -0,0 +1,303 @@
#!/bin/bash
set -euv -o pipefail
readonly USER=yellowtent
readonly USER_HOME="/home/${USER}"
readonly INSTALLER_SOURCE_DIR="${USER_HOME}/installer"
readonly INSTALLER_REVISION="$1"
readonly SELFHOSTED=$(( $# > 1 ? 1 : 0 ))
readonly USER_DATA_FILE="/root/user_data.img"
readonly USER_DATA_DIR="/home/yellowtent/data"
readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
function die {
echo $1
exit 1
}
[[ "$(systemd --version 2>&1)" == *"systemd 229"* ]] || die "Expecting systemd to be 229"
if [ ${SELFHOSTED} == 0 ]; then
echo "!! Initializing Ubuntu image for CaaS"
else
echo "!! Initializing Ubuntu image for Selfhosting"
fi
echo "==== Create User ${USER} ===="
if ! id "${USER}"; then
useradd "${USER}" -m
fi
echo "=== Yellowtent base image preparation (installer revision - ${INSTALLER_REVISION}) ==="
echo "=== Prepare installer source ==="
rm -rf "${INSTALLER_SOURCE_DIR}" && mkdir -p "${INSTALLER_SOURCE_DIR}"
rm -rf /tmp/box && mkdir -p /tmp/box
tar xvf /tmp/box.tar.gz -C /tmp/box && rm /tmp/box.tar.gz
cp -rf /tmp/box/installer/* "${INSTALLER_SOURCE_DIR}"
echo "${INSTALLER_REVISION}" > "${INSTALLER_SOURCE_DIR}/REVISION"
export DEBIAN_FRONTEND=noninteractive
echo "=== Upgrade ==="
apt-get update
apt-get dist-upgrade -y
apt-get install -y curl
# 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 DROP
iptables -P FORWARD ACCEPT # TODO: disable icc and make this as reject
iptables -P OUTPUT ACCEPT
# 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
if [ ${SELFHOSTED} == 0 ]; 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.17.0.0/16 -j ACCEPT # required to accept any connections from apps to our IP:<public port>
# loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# 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
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
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
MountFlags=slave
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
[Install]
WantedBy=multi-user.target
EOF
echo "=== Setup btrfs data ==="
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 =="
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
# 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
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 ===="
images=$(node -e "var i = require('${SOURCE_DIR}/infra_version.js'); console.log(i.baseImage, Object.keys(i.images).map(function (x) { return i.images[x].tag; }).join(' '));")
echo "Pulling images: ${images}"
for image in ${images}; do
docker pull "${image}"
done
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 "==== Install build-essential ===="
apt-get -y install build-essential rcconf
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"
echo "==== Install pwgen and swaks awscli ===="
apt-get -y install pwgen swaks awscli
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 "=== Rebuilding npm packages ==="
cd "${INSTALLER_SOURCE_DIR}" && npm install --production
chown "${USER}:${USER}" -R "${INSTALLER_SOURCE_DIR}"
echo "==== Install installer systemd script ===="
provisionEnv="PROVISION=digitalocean"
if [ ${SELFHOSTED} == 1 ]; then
provisionEnv="PROVISION=local"
fi
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
[Service]
Type=idle
ExecStart="${INSTALLER_SOURCE_DIR}/src/server.js"
Environment="DEBUG=installer*,connect-lastmile" ${provisionEnv}
; 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
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
After=do-resize.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
if [ ${SELFHOSTED} == 0 ]; then
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/^#\?Port .*/Port 202/g' \
-e 's/^#\?PermitRootLogin .*/PermitRootLogin without-password/g' \
-e 's/^#\?PermitEmptyPasswords .*/PermitEmptyPasswords no/g' \
-e 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/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

64
box.js Executable file
View File

@@ -0,0 +1,64 @@
#!/usr/bin/env node
'use strict';
require('supererror')({ splatchError: true });
// remove timestamp from debug() based output
require('debug').formatArgs = function formatArgs() {
arguments[0] = this.namespace + ' ' + arguments[0];
return arguments;
};
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');
console.log();
console.log('==========================================');
console.log(' Cloudron will use the following settings ');
console.log('==========================================');
console.log();
console.log(' Environment: ', config.CLOUDRON ? 'CLOUDRON' : 'TEST');
console.log(' Version: ', config.version());
console.log(' Admin Origin: ', config.adminOrigin());
console.log(' Appstore API server origin: ', config.apiServerOrigin());
console.log(' Appstore Web server origin: ', config.webServerOrigin());
console.log();
console.log('==========================================');
console.log();
async.series([
server.start,
ldap.start,
simpleauth.start,
appHealthMonitor.start,
oauthproxy.start
], function (error) {
if (error) {
console.error('Error starting server', error);
process.exit(1);
}
});
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);
});

View File

@@ -1,53 +0,0 @@
#!/usr/bin/env node
'use strict';
// WARNING This is a supervisor eventlistener!
// The communication happens via stdin/stdout
// !! No console.log() allowed
// !! Do not set DEBUG
var assert = require('assert'),
mailer = require('./src/mailer.js'),
safe = require('safetydance'),
supervisor = require('supervisord-eventlistener'),
path = require('path'),
util = require('util');
var gLastNotifyTime = {};
var gCooldownTime = 1000 * 60 * 5; // 5 min
var COLLECT_LOGS_CMD = path.join(__dirname, 'src/scripts/collectlogs.sh');
function collectLogs(program, callback) {
assert.strictEqual(typeof program, 'string');
assert.strictEqual(typeof callback, 'function');
var logs = safe.child_process.execSync('sudo ' + COLLECT_LOGS_CMD + ' ' + program, { encoding: 'utf8' });
callback(null, logs);
}
supervisor.on('PROCESS_STATE_EXITED', function (headers, data) {
if (data.expected === '1') return console.error('Normal app %s exit', data.processname);
console.error('%s exited unexpectedly', data.processname);
collectLogs(data.processname, function (error, result) {
if (error) {
console.error('Failed to collect logs.', error);
result = util.format('Failed to collect logs.', error);
}
if (!gLastNotifyTime[data.processname] || gLastNotifyTime[data.processname] < Date.now() - gCooldownTime) {
console.error('Send mail.');
mailer.sendCrashNotification(data.processname, result);
gLastNotifyTime[data.processname] = Date.now();
} else {
console.error('Do not send mail, already sent one recently.');
}
});
});
mailer.initialize(function () {
supervisor.listen(process.stdin, process.stdout);
console.error('Crashnotifier listening...');
});

16
crashnotifierservice.js Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env node
'use strict';
var sendFailureLogs = require('./src/logcollector').sendFailureLogs;
function main() {
if (process.argv.length !== 3) return console.error('Usage: crashnotifier.js <processName>');
var processName = process.argv[2];
console.log('Started crash notifier for', processName);
sendFailureLogs(processName, { unit: processName });
}
main();

View File

@@ -10,7 +10,7 @@ var ejs = require('gulp-ejs'),
serve = require('gulp-serve'),
sass = require('gulp-sass'),
sourcemaps = require('gulp-sourcemaps'),
minifyCSS = require('gulp-minify-css'),
cssnano = require('gulp-cssnano'),
autoprefixer = require('gulp-autoprefixer'),
argv = require('yargs').argv;
@@ -22,6 +22,7 @@ gulp.task('3rdparty', function () {
'webadmin/src/3rdparty/**/*.otf',
'webadmin/src/3rdparty/**/*.eot',
'webadmin/src/3rdparty/**/*.svg',
'webadmin/src/3rdparty/**/*.gif',
'webadmin/src/3rdparty/**/*.ttf',
'webadmin/src/3rdparty/**/*.woff',
'webadmin/src/3rdparty/**/*.woff2'
@@ -39,7 +40,7 @@ gulp.task('3rdparty', function () {
// JavaScript
// --------------
gulp.task('js', ['js-index', 'js-setup', 'js-update', 'js-error'], function () {});
gulp.task('js', ['js-index', 'js-setup', 'js-update'], function () {});
var oauth = {
clientId: argv.clientId || 'cid-webadmin',
@@ -80,14 +81,6 @@ gulp.task('js-setup', function () {
.pipe(gulp.dest('webadmin/dist/js'));
});
gulp.task('js-error', function () {
gulp.src(['webadmin/src/js/error.js'])
.pipe(sourcemaps.init())
.pipe(uglify())
.pipe(sourcemaps.write())
.pipe(gulp.dest('webadmin/dist/js'));
});
gulp.task('js-update', function () {
gulp.src(['webadmin/src/js/update.js'])
.pipe(sourcemaps.init())
@@ -102,7 +95,7 @@ gulp.task('js-update', function () {
// HTML
// --------------
gulp.task('html', ['html-views', 'html-update'], function () {
gulp.task('html', ['html-views', 'html-update', 'html-templates'], function () {
return gulp.src('webadmin/src/*.html').pipe(gulp.dest('webadmin/dist'));
});
@@ -114,6 +107,10 @@ gulp.task('html-views', function () {
return gulp.src('webadmin/src/views/**/*.html').pipe(gulp.dest('webadmin/dist/views'));
});
gulp.task('html-templates', function () {
return gulp.src('webadmin/src/templates/**/*.html').pipe(gulp.dest('webadmin/dist/templates'));
});
// --------------
// CSS
// --------------
@@ -123,7 +120,7 @@ gulp.task('css', function () {
.pipe(sourcemaps.init())
.pipe(sass({ includePaths: ['node_modules/bootstrap-sass/assets/stylesheets/'] }).on('error', sass.logError))
.pipe(autoprefixer())
.pipe(minifyCSS())
.pipe(cssnano())
.pipe(sourcemaps.write())
.pipe(gulp.dest('webadmin/dist'))
.pipe(gulp.dest('setup/splash/website'));
@@ -143,8 +140,8 @@ gulp.task('watch', ['default'], function () {
gulp.watch(['webadmin/src/img/*'], ['images']);
gulp.watch(['webadmin/src/**/*.html'], ['html']);
gulp.watch(['webadmin/src/views/*.html'], ['html-views']);
gulp.watch(['webadmin/src/templates/*.html'], ['html-templates']);
gulp.watch(['webadmin/src/js/update.js'], ['js-update']);
gulp.watch(['webadmin/src/js/error.js'], ['js-error']);
gulp.watch(['webadmin/src/js/setup.js', 'webadmin/src/js/client.js'], ['js-setup']);
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']);

164
installer.sh.ejs Executable file
View File

@@ -0,0 +1,164 @@
#!/bin/bash
set -eu -o pipefail
echo ""
echo "======== Cloudron Installer ========"
echo ""
if [ $# -lt 4 ]; then
echo "Usage: ./installer.sh <fqdn> <aws key id> <aws key secret> <bucket> <provider> <revision>"
exit 1
fi
# commandline arguments
readonly fqdn="${1}"
readonly aws_access_key_id="${2}"
readonly aws_access_key_secret="${3}"
readonly aws_backup_bucket="${4}"
readonly provider="${5}"
readonly revision="${6}"
# environment specific urls
<% if (env === 'prod') { %>
readonly api_server_origin="https://api.cloudron.io"
readonly web_server_origin="https://cloudron.io"
<% } else { %>
readonly api_server_origin="https://api.<%= env %>.cloudron.io"
readonly web_server_origin="https://<%= env %>.cloudron.io"
<% } %>
readonly release_bucket_url="https://s3.amazonaws.com/<%= env %>-cloudron-releases"
readonly versions_url="https://s3.amazonaws.com/<%= env %>-cloudron-releases/versions.json"
readonly installer_code_url="${release_bucket_url}/box-${revision}.tar.gz"
# runtime consts
readonly installer_code_file="/tmp/box.tar.gz"
readonly installer_tmp_dir="/tmp/box"
readonly cert_folder="/tmp/certificates"
# check for fqdn in /ets/hosts
echo "[INFO] checking for hostname entry"
readonly hostentry_found=$(grep "${fqdn}" /etc/hosts || true)
if [[ -z $hostentry_found ]]; then
echo "[WARNING] No entry for ${fqdn} found in /etc/hosts"
echo "Adding an entry ..."
cat >> /etc/hosts <<EOF
# The following line was added by the Cloudron installer script
127.0.1.1 ${fqdn} ${fqdn}
EOF
else
echo "Valid hostname entry found in /etc/hosts"
fi
echo ""
echo "[INFO] ensure minimal dependencies ..."
apt-get update
apt-get install -y curl
echo ""
echo "[INFO] Generating certificates ..."
rm -rf "${cert_folder}"
mkdir -p "${cert_folder}"
cat > "${cert_folder}/CONFIG" <<EOF
[ req ]
default_bits = 1024
default_keyfile = keyfile.pem
distinguished_name = req_distinguished_name
prompt = no
req_extensions = v3_req
[ req_distinguished_name ]
C = DE
ST = Berlin
L = Berlin
O = Cloudron UG
OU = Cloudron
CN = ${fqdn}
emailAddress = cert@cloudron.io
[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${fqdn}
DNS.2 = *.${fqdn}
EOF
# generate cert files
openssl genrsa 2048 > "${cert_folder}/host.key"
openssl req -new -out "${cert_folder}/host.csr" -key "${cert_folder}/host.key" -config "${cert_folder}/CONFIG"
openssl x509 -req -days 3650 -in "${cert_folder}/host.csr" -signkey "${cert_folder}/host.key" -out "${cert_folder}/host.cert" -extensions v3_req -extfile "${cert_folder}/CONFIG"
# make them json compatible, by collapsing to one line
tls_cert=$(sed ':a;N;$!ba;s/\n/\\n/g' "${cert_folder}/host.cert")
tls_key=$(sed ':a;N;$!ba;s/\n/\\n/g' "${cert_folder}/host.key")
echo ""
echo "[INFO] Fetching installer code ..."
curl "${installer_code_url}" -o "${installer_code_file}"
echo ""
echo "[INFO] Extracting installer code to ${installer_tmp_dir} ..."
rm -rf "${installer_tmp_dir}" && mkdir -p "${installer_tmp_dir}"
tar xvf "${installer_code_file}" -C "${installer_tmp_dir}"
echo ""
echo "Creating initial provisioning config ..."
cat > /root/provision.json <<EOF
{
"sourceTarballUrl": "",
"data": {
"apiServerOrigin": "${api_server_origin}",
"webServerOrigin": "${web_server_origin}",
"fqdn": "${fqdn}",
"token": "",
"isCustomDomain": true,
"boxVersionsUrl": "${versions_url}",
"version": "",
"tlsCert": "${tls_cert}",
"tlsKey": "${tls_key}",
"provider": "${provider}",
"backupConfig": {
"provider": "s3",
"accessKeyId": "${aws_access_key_id}",
"secretAccessKey": "${aws_access_key_secret}",
"bucket": "${aws_backup_bucket}",
"prefix": "backups"
},
"dnsConfig": {
"provider": "route53",
"accessKeyId": "${aws_access_key_id}",
"secretAccessKey": "${aws_access_key_secret}"
},
"tlsConfig": {
"provider": "letsencrypt-<%= env %>"
}
}
}
EOF
echo "[INFO] Running Ubuntu initializing script ..."
/bin/bash "${installer_tmp_dir}/baseimage/initializeBaseUbuntuImage.sh" "${revision}" selfhosting
echo ""
echo "[INFO] Reloading systemd daemon ..."
systemctl daemon-reload
echo ""
echo "[INFO] Restart docker ..."
systemctl restart docker
echo ""
echo "[FINISHED] Now starting Cloudron init jobs ..."
systemctl start box-setup
# TODO this is only for convenience we should probably just let the user do a restart
sleep 5 && sync
systemctl start cloudron-installer
journalctl -u cloudron-installer.service -f

516
installer/npm-shrinkwrap.json generated Normal file
View File

@@ -0,0 +1,516 @@
{
"name": "installer",
"version": "0.0.1",
"dependencies": {
"async": {
"version": "1.5.0",
"from": "async@>=1.5.0 <2.0.0",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz"
},
"body-parser": {
"version": "1.14.1",
"from": "body-parser@>=1.12.0 <2.0.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.1.tgz",
"dependencies": {
"bytes": {
"version": "2.1.0",
"from": "bytes@2.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz"
},
"content-type": {
"version": "1.0.1",
"from": "content-type@>=1.0.1 <1.1.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.1.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.3.1",
"from": "http-errors@>=1.3.1 <1.4.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz",
"dependencies": {
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <2.1.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
},
"statuses": {
"version": "1.2.1",
"from": "statuses@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz"
}
}
},
"iconv-lite": {
"version": "0.4.12",
"from": "iconv-lite@0.4.12",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.12.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": "5.1.0",
"from": "qs@5.1.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz"
},
"raw-body": {
"version": "2.1.4",
"from": "raw-body@>=2.1.4 <2.2.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.4.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.9",
"from": "type-is@>=1.6.9 <1.7.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.9.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.7",
"from": "mime-types@>=2.1.7 <2.2.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.7.tgz",
"dependencies": {
"mime-db": {
"version": "1.19.0",
"from": "mime-db@>=1.19.0 <1.20.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.19.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.13.3",
"from": "express@>=4.11.2 <5.0.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.13.3.tgz",
"dependencies": {
"accepts": {
"version": "1.2.13",
"from": "accepts@>=1.2.12 <1.3.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz",
"dependencies": {
"mime-types": {
"version": "2.1.7",
"from": "mime-types@>=2.1.6 <2.2.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.7.tgz",
"dependencies": {
"mime-db": {
"version": "1.19.0",
"from": "mime-db@>=1.19.0 <1.20.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.19.0.tgz"
}
}
},
"negotiator": {
"version": "0.5.3",
"from": "negotiator@0.5.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.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.0",
"from": "content-disposition@0.5.0",
"resolved": "http://registry.npmjs.org/content-disposition/-/content-disposition-0.5.0.tgz"
},
"content-type": {
"version": "1.0.1",
"from": "content-type@>=1.0.1 <1.1.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.1.tgz"
},
"cookie": {
"version": "0.1.3",
"from": "cookie@0.1.3",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.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.0.1",
"from": "depd@>=1.0.1 <1.1.0",
"resolved": "http://registry.npmjs.org/depd/-/depd-1.0.1.tgz"
},
"escape-html": {
"version": "1.0.2",
"from": "escape-html@1.0.2",
"resolved": "http://registry.npmjs.org/escape-html/-/escape-html-1.0.2.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.4.0",
"from": "finalhandler@0.4.0",
"resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz",
"dependencies": {
"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.0",
"from": "merge-descriptors@1.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.0.tgz"
},
"methods": {
"version": "1.1.1",
"from": "methods@>=1.1.1 <1.2.0",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.1.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.0",
"from": "parseurl@>=1.3.0 <1.4.0",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.0.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.0.8",
"from": "proxy-addr@>=1.0.8 <1.1.0",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.8.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.0.1",
"from": "ipaddr.js@1.0.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.1.tgz"
}
}
},
"qs": {
"version": "4.0.0",
"from": "qs@4.0.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz"
},
"range-parser": {
"version": "1.0.3",
"from": "range-parser@>=1.0.2 <1.1.0",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.3.tgz"
},
"send": {
"version": "0.13.0",
"from": "send@0.13.0",
"resolved": "http://registry.npmjs.org/send/-/send-0.13.0.tgz",
"dependencies": {
"destroy": {
"version": "1.0.3",
"from": "destroy@1.0.3",
"resolved": "http://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz"
},
"http-errors": {
"version": "1.3.1",
"from": "http-errors@>=1.3.1 <1.4.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz",
"dependencies": {
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <2.1.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.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.2.1",
"from": "statuses@>=1.2.1 <1.3.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz"
}
}
},
"serve-static": {
"version": "1.10.0",
"from": "serve-static@>=1.10.0 <1.11.0",
"resolved": "http://registry.npmjs.org/serve-static/-/serve-static-1.10.0.tgz"
},
"type-is": {
"version": "1.6.9",
"from": "type-is@>=1.6.9 <1.7.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.9.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.7",
"from": "mime-types@>=2.1.6 <2.2.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.7.tgz",
"dependencies": {
"mime-db": {
"version": "1.19.0",
"from": "mime-db@>=1.19.0 <1.20.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.19.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.0.1",
"from": "vary@>=1.0.1 <1.1.0",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz"
}
}
},
"json": {
"version": "9.0.3",
"from": "json@>=9.0.3 <10.0.0",
"resolved": "https://registry.npmjs.org/json/-/json-9.0.3.tgz"
},
"morgan": {
"version": "1.6.1",
"from": "morgan@>=1.5.1 <2.0.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz",
"dependencies": {
"basic-auth": {
"version": "1.0.3",
"from": "basic-auth@>=1.0.3 <1.1.0",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.3.tgz"
},
"depd": {
"version": "1.0.1",
"from": "depd@>=1.0.1 <1.1.0",
"resolved": "http://registry.npmjs.org/depd/-/depd-1.0.1.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.0 <1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz"
}
}
},
"proxy-middleware": {
"version": "0.15.0",
"from": "proxy-middleware@>=0.15.0 <0.16.0",
"resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.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.1.0",
"from": "semver@>=5.1.0 <6.0.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.1.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": {
"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"
},
"cookiejar": {
"version": "2.0.1",
"from": "cookiejar@2.0.1",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.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": {
"async": {
"version": "0.9.2",
"from": "async@>=0.9.0 <0.10.0",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
},
"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"
}
}
}
}
},
"formidable": {
"version": "1.0.14",
"from": "formidable@1.0.14",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz"
},
"methods": {
"version": "1.0.1",
"from": "methods@1.0.1",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.0.1.tgz"
},
"mime": {
"version": "1.2.11",
"from": "mime@1.2.11",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
},
"qs": {
"version": "1.2.0",
"from": "qs@1.2.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-1.2.0.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.1",
"from": "core-util-is@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
},
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <2.1.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.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"
}
}
},
"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"
}
}
}
}
}

47
installer/package.json Normal file
View File

@@ -0,0 +1,47 @@
{
"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": {
"async": "^1.5.0",
"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",
"proxy-middleware": "^0.15.0",
"safetydance": "0.0.19",
"semver": "^5.1.0",
"superagent": "^0.21.0"
},
"devDependencies": {
"colors": "^1.1.2",
"commander": "^2.8.1",
"expect.js": "^0.3.1",
"istanbul": "^0.3.5",
"lodash": "^3.2.0",
"mocha": "^2.1.0",
"nock": "^0.59.1",
"sleep": "^3.0.0",
"superagent-sync": "^0.2.0",
"supererror": "^0.7.0",
"yesno": "0.0.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"
}
}

0
installer/src/certs/.gitignore vendored Normal file
View File

112
installer/src/installer.js Normal file
View File

@@ -0,0 +1,112 @@
/* 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 latestVersion = Object.keys(versions).sort(semver.compare).pop();
debug('ensureVersion: Latest version is %s etag:%s', latestVersion, result.header['etag']);
if (!versions[latestVersion]) return callback(new Error('No version available'));
if (!versions[latestVersion].sourceTarballUrl) return callback(new Error('No sourceTarballUrl specified'));
args.sourceTarballUrl = versions[latestVersion].sourceTarballUrl;
args.data.version = latestVersion;
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);
});
}

View File

@@ -0,0 +1,67 @@
#!/bin/bash
set -eu -o pipefail
readonly BOX_SRC_DIR=/home/yellowtent/box
readonly DATA_DIR=/home/yellowtent/data
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=$([[ -d "${BOX_SRC_DIR}" ]] && 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}"

144
installer/src/server.js Executable file
View File

@@ -0,0 +1,144 @@
#!/usr/bin/env node
/* jslint node: true */
'use strict';
var assert = require('assert'),
async = require('async'),
debug = require('debug')('installer:server'),
express = require('express'),
fs = require('fs'),
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'),
superagent = require('superagent');
exports = module.exports = {
start: start,
stop: stop
};
var PROVISION_CONFIG_FILE = '/root/provision.json';
var CLOUDRON_CONFIG_FILE = '/home/yellowtent/configs/cloudron.conf';
var gHttpServer = null; // update server; used for updates
function provisionDigitalOcean(callback) {
if (fs.existsSync(CLOUDRON_CONFIG_FILE)) return callback(null); // already provisioned
superagent.get('http://169.254.169.254/metadata/v1.json').end(function (error, result) {
if (error || result.statusCode !== 200) {
console.error('Error getting metadata', error);
return callback(new Error('Error getting metadata'));
}
var userData = JSON.parse(result.body.user_data);
installer.provision(userData, callback);
});
}
function provisionLocal(callback) {
if (fs.existsSync(CLOUDRON_CONFIG_FILE)) return callback(null); // already provisioned
if (!fs.existsSync(PROVISION_CONFIG_FILE)) {
console.error('No provisioning data found at %s', PROVISION_CONFIG_FILE);
return callback(new Error('No provisioning data found'));
}
var userData = require(PROVISION_CONFIG_FILE);
installer.provision(userData, callback);
}
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 startUpdateServer(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 stopUpdateServer(callback) {
assert.strictEqual(typeof callback, 'function');
debug('Stopping update server');
if (!gHttpServer) return callback(null);
gHttpServer.close(callback);
gHttpServer = null;
}
function start(callback) {
assert.strictEqual(typeof callback, 'function');
var actions;
if (process.env.PROVISION === 'local') {
debug('Starting Installer in selfhost mode');
actions = [
startUpdateServer,
provisionLocal
];
} else { // current fallback, should be 'digitalocean' eventually, see initializeBaseUbuntuImage.sh
debug('Starting Installer in managed mode');
actions = [
startUpdateServer,
provisionDigitalOcean
];
}
async.series(actions, callback);
}
function stop(callback) {
assert.strictEqual(typeof callback, 'function');
async.series([
stopUpdateServer
], callback);
}
if (require.main === module) {
start(function (error) {
if (error) console.error(error);
});
}

View File

@@ -0,0 +1,179 @@
/* 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();
});
});
});
});

54
installer/systemd/box-setup.sh Executable file
View File

@@ -0,0 +1,54 @@
#!/bin/bash
set -eu -o pipefail
readonly USER_HOME="/home/yellowtent"
readonly APPS_SWAP_FILE="/apps.swap"
readonly USER_DATA_FILE="/root/user_data.img"
readonly USER_DATA_DIR="/home/yellowtent/data"
# detect device
if [[ -b "/dev/vda1" ]]; then
disk_device="/dev/vda1"
fi
if [[ -b "/dev/xvda1" ]]; then
disk_device="/dev/xvda1"
fi
# all sizes are in mb
readonly physical_memory=$(free -m | awk '/Mem:/ { print $2 }')
readonly swap_size="${physical_memory}" # 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 '{ print $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 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}"
# Allocate swap for general app usage
if [[ ! -f "${APPS_SWAP_FILE}" ]]; then
echo "Creating Apps swap file of size ${swap_size}M"
fallocate -l "${swap_size}m" "${APPS_SWAP_FILE}"
chmod 600 "${APPS_SWAP_FILE}"
mkswap "${APPS_SWAP_FILE}"
swapon "${APPS_SWAP_FILE}"
echo "${APPS_SWAP_FILE} none swap sw 0 0" >> /etc/fstab
else
echo "Apps Swap file already exists"
fi
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"
umount "${USER_DATA_DIR}" || true
# Do not preallocate (non-sparse). Doing so overallocates for data too much in advance and causes problems when using many apps with smaller data
# fallocate -l "${home_data_size}m" "${USER_DATA_FILE}" # does not overwrite existing data
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}"

View File

@@ -1,70 +0,0 @@
#!/usr/bin/env node
'use strict';
require('supererror')({ splatchError: true });
var assert = require('assert'),
debug = require('debug')('box:janitor'),
async = require('async'),
tokendb = require('./src/tokendb.js'),
authcodedb = require('./src/authcodedb.js'),
database = require('./src/database.js');
var TOKEN_CLEANUP_INTERVAL = 30000;
function initialize(callback) {
assert.strictEqual(typeof callback, 'function');
async.series([
database.initialize
], callback);
}
function cleanupExpiredTokens(callback) {
assert.strictEqual(typeof callback, 'function');
tokendb.delExpired(function (error, result) {
if (error) return callback(error);
debug('Cleaned up %s expired tokens.', result);
callback(null);
});
}
function cleanupExpiredAuthCodes(callback) {
assert.strictEqual(typeof callback, 'function');
authcodedb.delExpired(function (error, result) {
if (error) return callback(error);
debug('Cleaned up %s expired authcodes.', result);
callback(null);
});
}
function run() {
cleanupExpiredTokens(function (error) {
if (error) console.error(error);
cleanupExpiredAuthCodes(function (error) {
if (error) console.error(error);
setTimeout(run, TOKEN_CLEANUP_INTERVAL);
});
});
}
if (require.main === module) {
initialize(function (error) {
if (error) {
console.error('janitor task exiting with error', error);
process.exit(1);
}
run();
});
}

View File

@@ -0,0 +1,17 @@
dbm = dbm || require('db-migrate');
var type = dbm.dataType;
exports.up = function(db, callback) {
db.runSql('ALTER TABLE apps ADD COLUMN oauthProxy BOOLEAN DEFAULT 0', function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE apps DROP COLUMN oauthProxy', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,17 @@
dbm = dbm || require('db-migrate');
var type = dbm.dataType;
var async = require('async');
exports.up = function(db, callback) {
async.series([
db.runSql.bind(db, 'DELETE FROM clients'),
db.runSql.bind(db, 'ALTER TABLE clients ADD COLUMN type VARCHAR(16) NOT NULL'),
], callback);
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE clients DROP COLUMN type', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,17 @@
var dbm = global.dbm || require('db-migrate');
var type = dbm.dataType;
exports.up = function(db, callback) {
db.runSql('ALTER TABLE apps CHANGE accessRestriction accessRestrictionJson VARCHAR(2048)', [], function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE apps CHANGE accessRestrictionJson accessRestriction VARCHAR(2048)', [], function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,16 @@
dbm = dbm || require('db-migrate');
var type = dbm.dataType;
exports.up = function(db, callback) {
db.runSql('ALTER TABLE apps MODIFY manifestJson TEXT', [], function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE apps MODIFY manifestJson VARCHAR(2048)', [], function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,19 @@
dbm = dbm || require('db-migrate');
var type = dbm.dataType;
var async = require('async');
exports.up = function(db, callback) {
async.series([
db.runSql.bind(db, 'ALTER TABLE apps MODIFY accessRestrictionJson TEXT'),
db.runSql.bind(db, 'ALTER TABLE apps MODIFY lastBackupConfigJson TEXT'),
db.runSql.bind(db, 'ALTER TABLE apps MODIFY oldConfigJson TEXT')
], callback);
};
exports.down = function(db, callback) {
async.series([
db.runSql.bind(db, 'ALTER TABLE apps MODIFY accessRestrictionJson VARCHAR(2048)'),
db.runSql.bind(db, 'ALTER TABLE apps MODIFY lastBackupConfigJson VARCHAR(2048)'),
db.runSql.bind(db, 'ALTER TABLE apps MODIFY oldConfigJson VARCHAR(2048)')
], callback);
};

View File

@@ -0,0 +1,15 @@
dbm = dbm || require('db-migrate');
exports.up = function(db, callback) {
db.runSql('ALTER TABLE users ADD COLUMN displayName VARCHAR(512) DEFAULT ""', function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE users DROP COLUMN displayName', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,15 @@
dbm = dbm || require('db-migrate');
exports.up = function(db, callback) {
db.runSql('ALTER TABLE apps ADD COLUMN memoryLimit BIGINT DEFAULT 0', function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE apps DROP COLUMN memoryLimit', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,21 @@
var dbm = global.dbm || require('db-migrate');
var type = dbm.dataType;
exports.up = function(db, callback) {
var cmd = "CREATE TABLE groups(" +
"id VARCHAR(128) NOT NULL UNIQUE," +
"name VARCHAR(128) NOT NULL UNIQUE," +
"PRIMARY KEY(id))";
db.runSql(cmd, function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('DROP TABLE groups', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,22 @@
var dbm = global.dbm || require('db-migrate');
var type = dbm.dataType;
exports.up = function(db, callback) {
var cmd = "CREATE TABLE IF NOT EXISTS groupMembers(" +
"groupId VARCHAR(128) NOT NULL," +
"userId VARCHAR(128) NOT NULL," +
"FOREIGN KEY(groupId) REFERENCES groups(id)," +
"FOREIGN KEY(userId) REFERENCES users(id));";
db.runSql(cmd, function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('DROP TABLE groupMembers', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,30 @@
'use strict';
var dbm = global.dbm || require('db-migrate');
var async = require('async');
var ADMIN_GROUP_ID = 'admin'; // see groups.js
exports.up = function(db, callback) {
async.series([
db.runSql.bind(db, 'START TRANSACTION;'),
db.runSql.bind(db, 'INSERT INTO groups (id, name) VALUES (?, ?)', [ ADMIN_GROUP_ID, 'admin' ]),
function migrateAdminFlag(done) {
db.all('SELECT * FROM users WHERE admin=1', function (error, results) {
if (error) return done(error);
console.dir(results);
async.eachSeries(results, function (r, next) {
db.runSql('INSERT INTO groupMembers (groupId, userId) VALUES (?, ?)', [ ADMIN_GROUP_ID, r.id ], next);
}, done);
});
},
db.runSql.bind(db, 'ALTER TABLE users DROP COLUMN admin'),
db.runSql.bind(db, 'COMMIT')
], callback);
};
exports.down = function(db, callback) {
callback();
};

View File

@@ -0,0 +1,25 @@
var dbm = global.dbm || require('db-migrate');
var type = dbm.dataType;
exports.up = function(db, callback) {
var cmd = "CREATE TABLE backups(" +
"filename VARCHAR(128) NOT NULL," +
"creationTime TIMESTAMP," +
"version VARCHAR(128) NOT NULL," +
"type VARCHAR(16) NOT NULL," +
"dependsOn VARCHAR(4096)," +
"state VARCHAR(16) NOT NULL," +
"PRIMARY KEY (filename))";
db.runSql(cmd, function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('DROP TABLE backups', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,17 @@
var dbm = global.dbm || require('db-migrate');
var type = dbm.dataType;
exports.up = function(db, callback) {
db.runSql('ALTER TABLE backups ADD COLUMN configJson TEXT', function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE backups DROP COLUMN configJson', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,17 @@
var dbm = dbm || require('db-migrate');
var type = dbm.dataType;
exports.up = function(db, callback) {
db.runSql('ALTER TABLE backups DROP COLUMN configJson', function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE backups ADD COLUMN configJson TEXT', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,16 @@
var dbm = global.dbm || require('db-migrate');
var type = dbm.dataType;
exports.up = function(db, callback) {
db.runSql('ALTER TABLE backups CHANGE filename id VARCHAR(128)', [], function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE backups CHANGE id filename VARCHAR(128)', [], function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,15 @@
dbm = dbm || require('db-migrate');
exports.up = function(db, callback) {
db.runSql('ALTER TABLE users MODIFY username VARCHAR(254) UNIQUE', [], function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE users MODIFY username VARCHAR(254) NOT NULL UNIQUE', [], function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,17 @@
'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);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE apps DROP COLUMN altDomain', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,24 @@
var dbm = global.dbm || require('db-migrate');
var type = dbm.dataType;
exports.up = function(db, callback) {
var cmd = "CREATE TABLE eventlog(" +
"id VARCHAR(128) NOT NULL," +
"source JSON," +
"creationTime TIMESTAMP," +
"action VARCHAR(128) NOT NULL," +
"data JSON," +
"PRIMARY KEY (id))";
db.runSql(cmd, function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('DROP TABLE eventlog', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,15 @@
dbm = dbm || require('db-migrate');
exports.up = function(db, callback) {
db.runSql('ALTER TABLE users ADD COLUMN showTutorial BOOLEAN DEFAULT 0', function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE users DROP COLUMN showTutorial', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,24 @@
'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,' +
'aliasTarget VARCHAR(128),' +
'creationTime TIMESTAMP,' +
'PRIMARY KEY (name))';
db.runSql(cmd, function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('DROP TABLE mailboxes', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -0,0 +1,25 @@
var dbm = global.dbm || require('db-migrate');
var type = dbm.dataType;
// imports mailbox entries for existing users
exports.up = function(db, callback) {
async.series([
db.runSql.bind(db, 'START TRANSACTION;'),
function addUserMailboxes(done) {
db.all('SELECT username FROM users', function (error, results) {
if (error) return done(error);
async.eachSeries(results, function (r, next) {
if (!r.username) return next();
db.runSql('INSERT INTO mailboxes (name) VALUES (?)', [ r.username ], next);
}, done);
});
},
db.runSql.bind(db, 'COMMIT')
], callback);
};
exports.down = function(db, callback) {
callback();
};

View File

@@ -0,0 +1,16 @@
dbm = dbm || require('db-migrate');
var type = dbm.dataType;
exports.up = function(db, callback) {
db.runSql('ALTER TABLE apps DROP COLUMN lastBackupConfigJson', function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE apps ADD COLUMN lastBackupConfigJson TEXT', function (error) {
if (error) console.error(error);
callback(error);
});
};

View File

@@ -11,26 +11,40 @@
CREATE TABLE IF NOT EXISTS users(
id VARCHAR(128) NOT NULL UNIQUE,
username VARCHAR(254) NOT NULL UNIQUE,
username VARCHAR(254) UNIQUE,
email VARCHAR(254) NOT NULL UNIQUE,
password VARCHAR(1024) NOT NULL,
salt VARCHAR(512) NOT NULL,
createdAt VARCHAR(512) NOT NULL,
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(
id VARCHAR(128) NOT NULL UNIQUE,
username VARCHAR(254) NOT NULL UNIQUE,
PRIMARY KEY(id));
CREATE TABLE IF NOT EXISTS groupMembers(
groupId VARCHAR(128) NOT NULL,
userId VARCHAR(128) NOT NULL,
FOREIGN KEY(groupId) REFERENCES groups(id),
FOREIGN KEY(userId) REFERENCES users(id));
CREATE TABLE IF NOT EXISTS tokens(
accessToken VARCHAR(128) NOT NULL UNIQUE,
identifier VARCHAR(128) NOT NULL,
clientId VARCHAR(128),
scope VARCHAR(512) NOT NULL,
expires BIGINT NOT NULL,
expires BIGINT NOT NULL, // FIXME: make this a timestamp
PRIMARY KEY(accessToken));
CREATE TABLE IF NOT EXISTS clients(
id VARCHAR(128) NOT NULL UNIQUE,
id VARCHAR(128) NOT NULL UNIQUE, // prefixed with cid- to identify token easily in auth routes
appId VARCHAR(128) NOT NULL,
type VARCHAR(16) NOT NULL,
clientSecret VARCHAR(512) NOT NULL,
redirectURI VARCHAR(512) NOT NULL,
scope VARCHAR(512) NOT NULL,
@@ -44,15 +58,20 @@ CREATE TABLE IF NOT EXISTS apps(
runState VARCHAR(512),
health VARCHAR(128),
containerId VARCHAR(128),
manifestJson VARCHAR(2048),
manifestJson TEXT,
httpPort INTEGER, // this is the nginx proxy port and not manifest.httpPort
location VARCHAR(128) NOT NULL UNIQUE,
dnsRecordId VARCHAR(512),
accessRestriction VARCHAR(512),
accessRestrictionJson TEXT, // { users: [ ], groups: [ ] }
oauthProxy BOOLEAN DEFAULT 0,
createdAt TIMESTAMP(2) NOT NULL DEFAULT CURRENT_TIMESTAMP,
memoryLimit BIGINT DEFAULT 0,
altDomain VARCHAR(256),
lastBackupId VARCHAR(128), // tracks last valid backup, can be removed
oldConfigJson TEXT, // used to pass old config for apptask, can be removed when we use a queue
lastBackupId VARCHAR(128),
lastBackupConfigJson VARCHAR(2048), // used for appstore and non-appstore installs. it's here so it's easy to do REST validation
PRIMARY KEY(id));
CREATE TABLE IF NOT EXISTS appPortBindings(
@@ -66,7 +85,7 @@ CREATE TABLE IF NOT EXISTS authcodes(
authCode VARCHAR(128) NOT NULL UNIQUE,
userId VARCHAR(128) NOT NULL,
clientId VARCHAR(128) NOT NULL,
expiresAt BIGINT NOT NULL,
expiresAt BIGINT NOT NULL, // ## FIXME: make this a timestamp
PRIMARY KEY(authCode));
CREATE TABLE IF NOT EXISTS settings(
@@ -80,3 +99,32 @@ CREATE TABLE IF NOT EXISTS appAddonConfigs(
value VARCHAR(512) NOT NULL,
FOREIGN KEY(appId) REFERENCES apps(id));
CREATE TABLE IF NOT EXISTS backups(
filename VARCHAR(128) NOT NULL,
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 */
state VARCHAR(16) NOT NULL,
PRIMARY KEY (filename));
CREATE TABLE IF NOT EXISTS eventlog(
id VARCHAR(128) NOT NULL,
action VARCHAR(128) NOT NULL,
source JSON, /* { userId, username, ip }. userId can be null for cron,sysadmin */
data JSON, /* free flowing json based on action */
creationTime TIMESTAMP, /* FIXME: precision must be TIMESTAMP(2) */
PRIMARY KEY (id));
/* Future fields:
* accessRestriction - to determine who can access it. So this has foreign keys
* quota - per mailbox quota
*/
CREATE TABLE IF NOT EXISTS mailboxes(
name VARCHAR(128) NOT NULL,
aliasTarget VARCHAR(128), /* the target name type is an alias */
creationTime TIMESTAMP,
PRIMARY KEY (id));

6530
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,185 +0,0 @@
#!/usr/bin/env node
'use strict';
require('supererror')({ splatchError: true });
var express = require('express'),
url = require('url'),
uuid = require('node-uuid'),
async = require('async'),
superagent = require('superagent'),
assert = require('assert'),
debug = require('debug')('box:proxy'),
proxy = require('proxy-middleware'),
session = require('cookie-session'),
database = require('./src/database.js'),
appdb = require('./src/appdb.js'),
clientdb = require('./src/clientdb.js'),
config = require('./src/config.js'),
http = require('http');
// Allow self signed certs!
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
var gSessions = {};
var gProxyMiddlewareCache = {};
var gApp = express();
var gHttpServer = http.createServer(gApp);
var CALLBACK_URI = '/callback';
var PORT = 4000;
function startServer(callback) {
assert.strictEqual(typeof callback, 'function');
gHttpServer.on('error', console.error);
gApp.use(session({
keys: ['blue', 'cheese', 'is', 'something']
}));
// ensure we have a in memory store for the session to cache client information
gApp.use(function (req, res, next) {
assert.strictEqual(typeof req.session, 'object');
if (!req.session.id || !gSessions[req.session.id]) {
req.session.id = uuid.v4();
gSessions[req.session.id] = {};
}
// attach the session data to the requeset
req.sessionData = gSessions[req.session.id];
next();
});
gApp.use(function verifySession(req, res, next) {
assert.strictEqual(typeof req.sessionData, 'object');
if (!req.sessionData.accessToken) {
req.authenticated = false;
return next();
}
superagent.get(config.adminOrigin() + '/api/v1/profile').query({ access_token: req.sessionData.accessToken}).end(function (error, result) {
if (error) {
console.error(error);
req.authenticated = false;
} else if (result.statusCode !== 200) {
req.sessionData.accessToken = null;
req.authenticated = false;
} else {
req.authenticated = true;
}
next();
});
});
gApp.use(function (req, res, next) {
// proceed if we are authenticated
if (req.authenticated) return next();
if (req.path === CALLBACK_URI && req.sessionData.returnTo) {
// exchange auth code for an access token
var query = {
response_type: 'token',
client_id: req.sessionData.clientId
};
var data = {
grant_type: 'authorization_code',
code: req.query.code,
redirect_uri: req.sessionData.returnTo,
client_id: req.sessionData.clientId,
client_secret: req.sessionData.clientSecret
};
superagent.post(config.adminOrigin() + '/api/v1/oauth/token').query(query).send(data).end(function (error, result) {
if (error) {
console.error(error);
return res.send(500, 'Unable to contact the oauth server.');
}
if (result.statusCode !== 200) {
console.error('Failed to exchange auth code for a token.', result.statusCode, result.body);
return res.send(500, 'Failed to exchange auth code for a token.');
}
req.sessionData.accessToken = result.body.access_token;
debug('user verified.');
// now redirect to the actual initially requested URL
res.redirect(req.sessionData.returnTo);
});
} else {
var port = parseInt(req.headers['x-cloudron-proxy-port'], 10);
if (!Number.isFinite(port)) {
console.error('Failed to parse nginx proxy header to get app port.');
return res.send(500, 'Routing error. No forwarded port.');
}
debug('begin verifying user for app on port %s.', port);
appdb.getByHttpPort(port, function (error, result) {
if (error) {
console.error('Unknown app.', error);
return res.send(500, 'Unknown app.');
}
clientdb.getByAppId('proxy-' + result.id, function (error, result) {
if (error) {
console.error('Unkonwn OAuth client.', error);
return res.send(500, 'Unknown OAuth client.');
}
req.sessionData.port = port;
req.sessionData.returnTo = result.redirectURI + req.path;
req.sessionData.clientId = result.id;
req.sessionData.clientSecret = result.clientSecret;
var callbackUrl = result.redirectURI + CALLBACK_URI;
var scope = 'profile,roleUser';
var oauthLogin = config.adminOrigin() + '/api/v1/oauth/dialog/authorize?response_type=code&client_id=' + result.id + '&redirect_uri=' + callbackUrl + '&scope=' + scope;
debug('begin OAuth flow for client %s.', result.name);
// begin the OAuth flow
res.redirect(oauthLogin);
});
});
}
});
gApp.use(function (req, res, next) {
var port = req.sessionData.port;
debug('proxy request for port %s with path %s.', port, req.path);
var proxyMiddleware = gProxyMiddlewareCache[port];
if (!proxyMiddleware) {
console.log('Adding proxy middleware for port %d', port);
proxyMiddleware = proxy(url.parse('http://127.0.0.1:' + port));
gProxyMiddlewareCache[port] = proxyMiddleware;
}
proxyMiddleware(req, res, next);
});
gHttpServer.listen(PORT, callback);
}
async.series([
database.initialize,
startServer
], function (error) {
if (error) {
console.error('Failed to start proxy server.', error);
process.exit(1);
}
console.log('Proxy server listening...');
});

View File

@@ -10,17 +10,15 @@
"type": "git"
},
"engines": [
"node >= 0.12.0"
"node >=4.0.0 <=4.1.1"
],
"bin": {
"cloudron": "./app.js"
},
"dependencies": {
"async": "^1.2.1",
"aws-sdk": "^2.1.46",
"body-parser": "^1.13.1",
"cloudron-manifestformat": "^1.6.0",
"cloudron-manifestformat": "^2.4.0",
"connect-ensure-login": "^0.1.1",
"connect-lastmile": "0.0.13",
"connect-lastmile": "^0.1.0",
"connect-timeout": "^1.5.0",
"cookie-parser": "^1.3.5",
"cookie-session": "^1.1.0",
@@ -28,65 +26,69 @@
"csurf": "^1.6.6",
"db-migrate": "^0.9.2",
"debug": "^2.2.0",
"dockerode": "^2.2.2",
"dockerode": "^2.2.10",
"ejs": "^2.2.4",
"ejs-cli": "^1.0.1",
"ejs-cli": "^1.2.0",
"express": "^4.12.4",
"express-session": "^1.11.3",
"hat": "0.0.3",
"ini": "^1.3.4",
"json": "^9.0.3",
"ldapjs": "^0.7.1",
"memorystream": "^0.3.0",
"mime": "^1.3.4",
"morgan": "^1.6.0",
"morgan": "^1.7.0",
"multiparty": "^4.1.2",
"mysql": "^2.7.0",
"native-dns": "^0.7.0",
"node-df": "^0.1.1",
"node-uuid": "^1.4.3",
"nodemailer": "^1.3.0",
"nodemailer-smtp-transport": "^1.0.3",
"oauth2orize": "^1.0.1",
"once": "^1.3.2",
"parse-links": "^0.1.0",
"passport": "^0.2.2",
"passport-http": "^0.2.2",
"passport-http-bearer": "^1.0.1",
"passport-local": "^1.0.0",
"passport-oauth2-client-password": "^0.1.2",
"password-generator": "^1.0.0",
"password-generator": "^2.0.2",
"proxy-middleware": "^0.13.0",
"safetydance": "0.0.19",
"safetydance": "^0.1.1",
"semver": "^4.3.6",
"serve-favicon": "^2.2.0",
"split": "^1.0.0",
"superagent": "~0.21.0",
"supererror": "^0.7.0",
"supervisord-eventlistener": "^0.1.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": "^3.30.0"
"validator": "^4.9.0",
"x509": "^0.2.4"
},
"devDependencies": {
"apidoc": "*",
"aws-sdk": "^2.1.10",
"bootstrap-sass": "^3.3.3",
"deep-extend": "^0.4.1",
"del": "^1.1.1",
"expect.js": "*",
"gulp": "^3.8.11",
"gulp-autoprefixer": "^2.3.0",
"gulp-concat": "^2.4.3",
"gulp-cssnano": "^2.1.0",
"gulp-ejs": "^1.0.0",
"gulp-minify-css": "^1.1.3",
"gulp-sass": "^2.0.1",
"gulp-serve": "^1.0.0",
"gulp-sourcemaps": "^1.5.2",
"gulp-uglify": "^1.1.0",
"hock": "~1.2.0",
"istanbul": "*",
"js2xmlparser": "^1.0.0",
"mocha": "*",
"nock": "^2.6.0",
"nock": "^3.4.0",
"node-sass": "^3.0.0-alpha.0",
"redis": "^0.12.1",
"request": "^2.65.0",
"sinon": "^1.12.2",
"yargs": "^3.15.0"
},

123
scripts/createReleaseTarball Executable file
View File

@@ -0,0 +1,123 @@
#!/bin/bash
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" -- "$@")
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;;
--) 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"
exit 1
fi
version=$(cd "${SOURCE_DIR}" && git rev-parse "${commitish}")
bundle_dir=$(mktemp -d -t box 2>/dev/null || mktemp -d box-XXXXXXXXXX --tmpdir=$TMPDIR)
[[ -z "$bundle_file" ]] && bundle_file="${TMPDIR}/box-${version}.tar.gz"
chmod "o+rx,g+rx" "${bundle_dir}" # otherwise extracted tarball director won't be readable by others/group
echo "Checking out code [${version}] into ${bundle_dir}"
(cd "${SOURCE_DIR}" && git archive --format=tar ${version} | (cd "${bundle_dir}" && tar xf -))
if diff "${TMPDIR}/boxtarball.cache/npm-shrinkwrap.json.all" "${bundle_dir}/npm-shrinkwrap.json" >/dev/null 2>&1; then
echo "Reusing dev modules from cache"
cp -r "${TMPDIR}/boxtarball.cache/node_modules-all/." "${bundle_dir}/node_modules"
else
echo "Installing modules with dev dependencies"
(cd "${bundle_dir}" && npm install)
echo "Caching dev dependencies"
mkdir -p "${TMPDIR}/boxtarball.cache/node_modules-all"
rsync -a --delete "${bundle_dir}/node_modules/" "${TMPDIR}/boxtarball.cache/node_modules-all/"
cp "${bundle_dir}/npm-shrinkwrap.json" "${TMPDIR}/boxtarball.cache/npm-shrinkwrap.json.all"
fi
echo "Building webadmin assets"
(cd "${bundle_dir}" && gulp)
echo "Remove intermediate files required at build-time only"
rm -rf "${bundle_dir}/node_modules/"
rm -rf "${bundle_dir}/webadmin/src"
rm -rf "${bundle_dir}/gulpfile.js"
if diff "${TMPDIR}/boxtarball.cache/npm-shrinkwrap.json.prod" "${bundle_dir}/npm-shrinkwrap.json" >/dev/null 2>&1; then
echo "Reusing prod modules from cache"
cp -r "${TMPDIR}/boxtarball.cache/node_modules-prod/." "${bundle_dir}/node_modules"
else
echo "Installing modules for production"
(cd "${bundle_dir}" && npm install --production --no-optional)
echo "Caching prod dependencies"
mkdir -p "${TMPDIR}/boxtarball.cache/node_modules-prod"
rsync -a --delete "${bundle_dir}/node_modules/" "${TMPDIR}/boxtarball.cache/node_modules-prod/"
cp "${bundle_dir}/npm-shrinkwrap.json" "${TMPDIR}/boxtarball.cache/npm-shrinkwrap.json.prod"
fi
echo "Create final tarball"
(cd "${bundle_dir}" && tar czf "${bundle_file}" .)
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

View File

@@ -16,7 +16,7 @@ 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 supervisor or nginx configs might be around.
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.
@@ -40,7 +40,7 @@ version (see below) or the mysql/postgresql data etc.
* It then setups up the cloud infra (setup_infra.sh) and creates cloudron.conf.
* supervisor is then started
* box services are then started
setup_infra.sh
This setups containers like graphite, mail and the addons containers.

View File

@@ -1,17 +0,0 @@
#!/bin/bash
# If you change the infra version, be sure to put a warning
# in the change log
INFRA_VERSION=8
# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
# These constants are used in the installer script as well
BASE_IMAGE=cloudron/base:0.3.1
MYSQL_IMAGE=cloudron/mysql:0.3.2
POSTGRESQL_IMAGE=cloudron/postgresql:0.3.1
MONGODB_IMAGE=cloudron/mongodb:0.3.1
REDIS_IMAGE=cloudron/redis:0.3.1 # if you change this, fix src/addons.js as well
MAIL_IMAGE=cloudron/mail:0.3.1
GRAPHITE_IMAGE=cloudron/graphite:0.3.3

View File

@@ -11,11 +11,17 @@ arg_is_custom_domain="false"
arg_restore_key=""
arg_restore_url=""
arg_retire="false"
arg_tls_config=""
arg_tls_cert=""
arg_tls_key=""
arg_token=""
arg_version=""
arg_web_server_origin=""
arg_backup_config=""
arg_dns_config=""
arg_update_config=""
arg_provider=""
arg_app_bundle=""
args=$(getopt -o "" -l "data:,retire" -n "$0" -- "$@")
eval set -- "${args}"
@@ -28,19 +34,36 @@ while true; do
;;
--data)
# only read mandatory non-empty parameters here
read -r arg_api_server_origin arg_web_server_origin arg_fqdn arg_token arg_is_custom_domain arg_box_versions_url arg_version <<EOF
$(echo "$2" | $json apiServerOrigin webServerOrigin fqdn token isCustomDomain boxVersionsUrl version | tr '\n' ' ')
read -r arg_api_server_origin arg_web_server_origin arg_fqdn arg_is_custom_domain arg_box_versions_url arg_version <<EOF
$(echo "$2" | $json apiServerOrigin webServerOrigin fqdn isCustomDomain boxVersionsUrl version | tr '\n' ' ')
EOF
# read possibly empty parameters here
arg_app_bundle=$(echo "$2" | $json appBundle)
[[ "${arg_app_bundle}" == "" ]] && arg_app_bundle="[]"
arg_tls_cert=$(echo "$2" | $json tlsCert)
arg_tls_key=$(echo "$2" | $json tlsKey)
arg_token=$(echo "$2" | $json token)
arg_provider=$(echo "$2" | $json provider)
arg_restore_url=$(echo "$2" | $json restoreUrl)
arg_tls_config=$(echo "$2" | $json tlsConfig)
[[ "${arg_tls_config}" == "null" ]] && arg_tls_config=""
arg_restore_url=$(echo "$2" | $json restore.url)
[[ "${arg_restore_url}" == "null" ]] && arg_restore_url=""
arg_restore_key=$(echo "$2" | $json restoreKey)
arg_restore_key=$(echo "$2" | $json restore.key)
[[ "${arg_restore_key}" == "null" ]] && arg_restore_key=""
arg_backup_config=$(echo "$2" | $json backupConfig)
[[ "${arg_backup_config}" == "null" ]] && arg_backup_config=""
arg_dns_config=$(echo "$2" | $json dnsConfig)
[[ "${arg_dns_config}" == "null" ]] && arg_dns_config=""
arg_update_config=$(echo "$2" | $json updateConfig)
[[ "${arg_update_config}" == "null" ]] && arg_update_config=""
shift 2
;;
--) break;;
@@ -58,5 +81,7 @@ echo "restore url: ${arg_restore_url}"
echo "tls cert: ${arg_tls_cert}"
echo "tls key: ${arg_tls_key}"
echo "token: ${arg_token}"
echo "tlsConfig: ${arg_tls_config}"
echo "version: ${arg_version}"
echo "web server: ${arg_web_server_origin}"
echo "provider: ${arg_provider}"

View File

@@ -13,22 +13,24 @@ readonly DATA_DIR="/home/yellowtent/data"
rm -rf "${CONFIG_DIR}"
sudo -u yellowtent mkdir "${CONFIG_DIR}"
########## logrotate (default ubuntu runs this daily)
rm -rf /etc/logrotate.d/*
cp -r "${container_files}/logrotate/." /etc/logrotate.d/
########## supervisor
rm -rf /etc/supervisor/*
cp -r "${container_files}/supervisor/." /etc/supervisor/
########## systemd
rm -f /etc/systemd/system/janitor.*
cp -r "${container_files}/systemd/." /etc/systemd/system/
systemctl daemon-reload
systemctl enable cloudron.target
########## sudoers
rm /etc/sudoers.d/*
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

View File

@@ -0,0 +1,32 @@
#include <tunables/global>
profile docker-cloudron-app flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
ptrace peer=@{profile_name},
network,
capability,
file,
umount,
deny @{PROC}/sys/fs/** wklx,
deny @{PROC}/sysrq-trigger rwklx,
deny @{PROC}/mem rwklx,
deny @{PROC}/kmem rwklx,
deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx,
deny @{PROC}/sys/kernel/*/** wklx,
deny mount,
deny /sys/[^f]*/** wklx,
deny /sys/f[^s]*/** wklx,
deny /sys/fs/[^c]*/** wklx,
deny /sys/fs/c[^g]*/** wklx,
deny /sys/fs/cg[^r]*/** wklx,
deny /sys/firmware/efi/efivars/** rwklx,
deny /sys/kernel/security/** rwklx,
}

View File

@@ -1,6 +0,0 @@
/var/log/cloudron/*log {
missingok
notifempty
size 100k
nocompress
}

View File

@@ -1,7 +0,0 @@
/var/log/supervisor/*log {
missingok
copytruncate
notifempty
size 100k
nocompress
}

View File

@@ -4,4 +4,4 @@
# http://bugs.mysql.com/bug.php?id=68514
[mysqld]
performance_schema=OFF
max_connection=50
max_connections=50

View File

@@ -1,3 +1,6 @@
# sudo logging breaks journalctl output with very long urls (systemd bug)
Defaults !syslog
Defaults!/home/yellowtent/box/src/scripts/createappdir.sh env_keep="HOME BOX_ENV"
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/createappdir.sh
@@ -22,8 +25,12 @@ yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/reboot.sh
Defaults!/home/yellowtent/box/src/scripts/reloadcollectd.sh env_keep="HOME BOX_ENV"
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/reloadcollectd.sh
Defaults!/home/yellowtent/box/src/scripts/backupswap.sh env_keep="HOME BOX_ENV"
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/backupswap.sh
Defaults!/home/yellowtent/box/src/scripts/collectlogs.sh env_keep="HOME BOX_ENV"
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/collectlogs.sh
Defaults!/home/yellowtent/box/src/scripts/retire.sh env_keep="HOME BOX_ENV"
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/retire.sh
Defaults!/home/yellowtent/box/src/scripts/setup_infra.sh env_keep="HOME BOX_ENV"
yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/setup_infra.sh

View File

@@ -1,10 +0,0 @@
[program:apphealthtask]
command=/usr/bin/node "/home/yellowtent/box/apphealthtask.js"
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/apphealthtask.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=2
user=yellowtent
environment=HOME="/home/yellowtent",USER="yellowtent",DEBUG="box*",BOX_ENV="cloudron",NODE_ENV="production"

View File

@@ -1,10 +0,0 @@
[program:box]
command=/usr/bin/node "/home/yellowtent/box/app.js"
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/box.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=2
user=yellowtent
environment=HOME="/home/yellowtent",USER="yellowtent",DEBUG="box*,connect-lastmile",BOX_ENV="cloudron",NODE_ENV="production"

View File

@@ -1,11 +0,0 @@
[eventlistener:crashnotifier]
command=/usr/bin/node "/home/yellowtent/box/crashnotifier.js"
events=PROCESS_STATE
autostart=true
autorestart=true
redirect_stderr=false
stderr_logfile=/var/log/supervisor/crashnotifier.log
stderr_logfile_maxbytes=50MB
stderr_logfile_backups=2
user=yellowtent
environment=HOME="/home/yellowtent",USER="yellowtent",BOX_ENV="cloudron",NODE_ENV="production"

View File

@@ -1,10 +0,0 @@
[program:janitor]
command=/usr/bin/node "/home/yellowtent/box/janitor.js"
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/janitor.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=2
user=yellowtent
environment=HOME="/home/yellowtent",USER="yellowtent",DEBUG="box*",BOX_ENV="cloudron",NODE_ENV="production"

View File

@@ -1,10 +0,0 @@
[program:oauthproxy]
command=/usr/bin/node "/home/yellowtent/box/oauthproxy.js"
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/oauthproxy.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=2
user=yellowtent
environment=HOME="/home/yellowtent",USER="yellowtent",DEBUG="box*",BOX_ENV="cloudron",NODE_ENV="production"

View File

@@ -1,33 +0,0 @@
; supervisor config file
; http://coffeeonthekeyboard.com/using-supervisorctl-with-linux-permissions-but-without-root-or-sudo-977/
[inet_http_server]
port = 127.0.0.1:9001
[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
logfile_maxbytes = 50MB
logfile_backups=10
loglevel = info
nodaemon = false
childlogdir = /var/log/supervisor/
; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=http://127.0.0.1:9001
; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.
[include]
files = conf.d/*.conf

View File

@@ -0,0 +1,22 @@
[Unit]
Description=Cloudron Admin
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
[Service]
Type=idle
WorkingDirectory=/home/yellowtent/box
Restart=always
ExecStart=/usr/bin/node --max_old_space_size=150 /home/yellowtent/box/box.js
Environment="HOME=/home/yellowtent" "USER=yellowtent" "DEBUG=box*,connect-lastmile" "BOX_ENV=cloudron" "NODE_ENV=production"
; kill apptask processes as well
KillMode=control-group
User=yellowtent
Group=yellowtent
MemoryLimit=200M
TimeoutStopSec=5s
StartLimitInterval=1
StartLimitBurst=60

View File

@@ -0,0 +1,10 @@
[Unit]
Description=Cloudron Smartserver
Documentation=https://cloudron.io/documentation.html
StopWhenUnneeded=true
Requires=box.service
After=box.service
# AllowIsolate=yes
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,15 @@
# http://northernlightlabs.se/systemd.status.mail.on.unit.failure
[Unit]
Description=Cloudron Crash Notifier for %i
# otherwise, systemd will kill this unit immediately as nobody requires it
StopWhenUnneeded=false
[Service]
Type=idle
WorkingDirectory=/home/yellowtent/box
ExecStart="/home/yellowtent/box/crashnotifierservice.js" %I
Environment="HOME=/home/yellowtent" "USER=yellowtent" "DEBUG=box*,connect-lastmile" "BOX_ENV=cloudron" "NODE_ENV=production"
KillMode=process
User=yellowtent
Group=yellowtent
MemoryLimit=50M

View File

@@ -9,8 +9,6 @@ readonly BOX_SRC_DIR="/home/yellowtent/box"
readonly DATA_DIR="/home/yellowtent/data"
readonly ADMIN_LOCATION="my" # keep this in sync with constants.js
source "${script_dir}/INFRA_VERSION" # this injects INFRA_VERSION
echo "Setting up nginx update page"
source "${script_dir}/argparser.sh" "$@" # this injects the arg_* variables used below
@@ -24,15 +22,18 @@ rm -rf "${SETUP_WEBSITE_DIR}" && mkdir -p "${SETUP_WEBSITE_DIR}"
cp -r "${script_dir}/splash/website/"* "${SETUP_WEBSITE_DIR}"
# create nginx config
infra_version="none"
[[ -f "${DATA_DIR}/INFRA_VERSION" ]] && infra_version=$(cat "${DATA_DIR}/INFRA_VERSION")
if [[ "${arg_retire}" == "true" || "${infra_version}" != "${INFRA_VERSION}" ]]; then
readonly current_infra=$(node -e "console.log(require('${script_dir}/../src/infra_version.js').version);")
existing_infra="none"
[[ -f "${DATA_DIR}/INFRA_VERSION" ]] && existing_infra=$(node -e "console.log(JSON.parse(require('fs').readFileSync('${DATA_DIR}/INFRA_VERSION', 'utf8')).version);")
if [[ "${arg_retire}" == "true" || "${existing_infra}" != "${current_infra}" ]]; then
echo "Showing progress bar on all subdomains in retired mode or infra update. retire: ${arg_retire} 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" \
-O "{ \"vhost\": \"~^(.+)\$\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"splash\", \"sourceDir\": \"${SETUP_WEBSITE_DIR}\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
-O "{ \"vhost\": \"~^(.+)\$\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"splash\", \"sourceDir\": \"${SETUP_WEBSITE_DIR}\", \"certFilePath\": \"cert/host.cert\", \"keyFilePath\": \"cert/host.key\" }" > "${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" \
-O "{ \"vhost\": \"${admin_fqdn}\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"splash\", \"sourceDir\": \"${SETUP_WEBSITE_DIR}\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
-O "{ \"vhost\": \"${admin_fqdn}\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"splash\", \"sourceDir\": \"${SETUP_WEBSITE_DIR}\", \"certFilePath\": \"cert/host.cert\", \"keyFilePath\": \"cert/host.key\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
fi
echo '{ "update": { "percent": "10", "message": "Updating cloudron software" }, "backup": null }' > "${SETUP_WEBSITE_DIR}/progress.json"

View File

@@ -38,7 +38,9 @@ set_progress "10" "Ensuring directories"
# 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/mail"
mkdir -p "${DATA_DIR}/box/certs"
mkdir -p "${DATA_DIR}/box/mail/dkim/${arg_fqdn}"
mkdir -p "${DATA_DIR}/box/acme" # acme keys
mkdir -p "${DATA_DIR}/graphite"
mkdir -p "${DATA_DIR}/mysql"
@@ -47,6 +49,7 @@ mkdir -p "${DATA_DIR}/mongodb"
mkdir -p "${DATA_DIR}/snapshots"
mkdir -p "${DATA_DIR}/addons"
mkdir -p "${DATA_DIR}/collectd/collectd.conf.d"
mkdir -p "${DATA_DIR}/acme" # acme challenges
# bookkeep the version as part of data
echo "{ \"version\": \"${arg_version}\", \"boxVersionsUrl\": \"${arg_box_versions_url}\" }" > "${DATA_DIR}/box/version"
@@ -89,34 +92,36 @@ EOF
set_progress "28" "Setup collectd"
cp "${script_dir}/start/collectd.conf" "${DATA_DIR}/collectd/collectd.conf"
# collectd 5.4.1 has some bug where we simply cannot get it to create df-vda1
mkdir -p "${DATA_DIR}/graphite/whisper/collectd/localhost/"
vda1_id=$(blkid -s UUID -o value /dev/vda1)
ln -sfF "df-disk_by-uuid_${vda1_id}" "${DATA_DIR}/graphite/whisper/collectd/localhost/df-vda1"
service collectd restart
set_progress "30" "Setup nginx"
# setup naked domain to use admin by default. app restoration will overwrite this config
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 the main nginx config file
${BOX_SRC_DIR}/node_modules/.bin/ejs-cli -f "${script_dir}/start/nginx/nginx.ejs" \
-O "{ \"sourceDir\": \"${BOX_SRC_DIR}\" }" > "${DATA_DIR}/nginx/nginx.conf"
# 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}\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
-O "{ \"vhost\": \"${admin_fqdn}\", \"adminOrigin\": \"${admin_origin}\", \"endpoint\": \"admin\", \"sourceDir\": \"${BOX_SRC_DIR}\", \"certFilePath\": \"${admin_cert_file}\", \"keyFilePath\": \"${admin_key_file}\" }" > "${DATA_DIR}/nginx/applications/admin.conf"
mkdir -p "${DATA_DIR}/nginx/cert"
echo "${arg_tls_cert}" > ${DATA_DIR}/nginx/cert/host.cert
echo "${arg_tls_key}" > ${DATA_DIR}/nginx/cert/host.key
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
echo "${arg_tls_cert}" > "${DATA_DIR}/nginx/cert/host.cert"
echo "${arg_tls_key}" > "${DATA_DIR}/nginx/cert/host.key"
fi
set_progress "33" "Changing ownership"
chown "${USER}:${USER}" -R "${DATA_DIR}/box" "${DATA_DIR}/nginx" "${DATA_DIR}/collectd" "${DATA_DIR}/addons"
set_progress "40" "Setting up infra"
${script_dir}/start/setup_infra.sh "${arg_fqdn}"
chown "${USER}:${USER}" -R "${DATA_DIR}/box" "${DATA_DIR}/nginx" "${DATA_DIR}/collectd" "${DATA_DIR}/addons" "${DATA_DIR}/acme"
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
@@ -131,14 +136,15 @@ cat > "${CONFIG_DIR}/cloudron.conf" <<CONF_END
"fqdn": "${arg_fqdn}",
"isCustomDomain": ${arg_is_custom_domain},
"boxVersionsUrl": "${arg_box_versions_url}",
"adminEmail": "admin@${arg_fqdn}",
"provider": "${arg_provider}",
"database": {
"hostname": "localhost",
"username": "root",
"password": "${mysql_root_password}",
"port": 3306,
"name": "box"
}
},
"appBundle": ${arg_app_bundle}
}
CONF_END
@@ -150,35 +156,57 @@ cat > "${BOX_SRC_DIR}/webadmin/dist/config.json" <<CONF_END
CONF_END
EOF
# Add webadmin oauth client
# Add Backup Configuration
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 oauth cient"
ADMIN_SCOPES="root,developer,profile,users,apps,settings,roleUser"
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, clientSecret, redirectURI, scope) VALUES (\"cid-webadmin\", \"webadmin\", \"secret-webadmin\", \"${admin_origin}\", \"${ADMIN_SCOPES}\")" box
-e "REPLACE INTO clients (id, appId, type, clientSecret, redirectURI, scope) VALUES (\"cid-webadmin\", \"Settings\", \"built-in\", \"secret-webadmin\", \"${admin_origin}\", \"${ADMIN_SCOPES}\")" box
echo "Add localhost test oauth cient"
ADMIN_SCOPES="root,developer,profile,users,apps,settings,roleUser"
echo "Add SDK api client"
mysql -u root -p${mysql_root_password} \
-e "REPLACE INTO clients (id, appId, clientSecret, redirectURI, scope) VALUES (\"cid-test\", \"test\", \"secret-test\", \"http://127.0.0.1:5000\", \"${ADMIN_SCOPES}\")" box
-e "REPLACE INTO clients (id, appId, type, clientSecret, redirectURI, scope) VALUES (\"cid-sdk\", \"SDK\", \"built-in\", \"secret-sdk\", \"${admin_origin}\", \"*,roleSdk\")" box
set_progress "80" "Reloading supervisor"
# looks like restarting supervisor completely is the only way to reload it
service supervisor stop || true
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
echo -n "Waiting for supervisord to stop"
while test -e "/var/run/supervisord.pid" && kill -0 `cat /var/run/supervisord.pid`; do
echo -n "."
sleep 1
done
echo ""
set_progress "80" "Starting Cloudron"
systemctl start cloudron.target
echo "Starting supervisor"
service supervisor start
sleep 2 # give supervisor sometime to start the processes
sleep 2 # give systemd sometime to start the processes
set_progress "85" "Reloading nginx"
nginx -s reload

View File

@@ -133,10 +133,10 @@ LoadPlugin nginx
# Globals true
#</LoadPlugin>
#LoadPlugin pinba
LoadPlugin ping
#LoadPlugin ping
#LoadPlugin postgresql
#LoadPlugin powerdns
LoadPlugin processes
#LoadPlugin processes
#LoadPlugin protocols
#<LoadPlugin python>
# Globals true
@@ -161,7 +161,7 @@ LoadPlugin tail
#LoadPlugin users
#LoadPlugin uuid
#LoadPlugin varnish
LoadPlugin vmem
#LoadPlugin vmem
#LoadPlugin vserver
#LoadPlugin wireless
LoadPlugin write_graphite
@@ -193,11 +193,11 @@ LoadPlugin write_graphite
</Plugin>
<Plugin df>
FSType "tmpfs"
MountPoint "/dev"
FSType "ext4"
FSType "btrfs"
ReportByDevice true
IgnoreSelected true
IgnoreSelected false
ValuesAbsolute true
ValuesPercentage true
@@ -212,17 +212,6 @@ LoadPlugin write_graphite
URL "http://127.0.0.1/nginx_status"
</Plugin>
<Plugin ping>
Host "google.com"
Interval 1.0
Timeout 0.9
TTL 255
</Plugin>
<Plugin processes>
ProcessMatch "app" "node app.js"
</Plugin>
<Plugin swap>
ReportByDevice false
ReportBytes true
@@ -255,10 +244,6 @@ LoadPlugin write_graphite
</File>
</Plugin>
<Plugin vmem>
Verbose false
</Plugin>
<Plugin write_graphite>
<Node "graphing">
Host "localhost"

View File

@@ -10,17 +10,18 @@ server {
ssl on;
# paths are relative to prefix and not to this file
ssl_certificate cert/host.cert;
ssl_certificate_key cert/host.key;
ssl_certificate <%= certFilePath %>;
ssl_certificate_key <%= keyFilePath %>;
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;
# https://bettercrypto.org/static/applied-crypto-hardening.pdf
# https://mozilla.github.io/server-side-tls/ssl-config-generator/
# https://cipherli.st/
# 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 'AES128+EECDH:AES128+EDH';
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains";
proxy_http_version 1.1;
@@ -37,11 +38,24 @@ server {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
error_page 500 502 503 504 @appstatus;
# 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;
}
<% if ( endpoint === 'app' ) { %>
# For some reason putting this webdav block inside location does not work
# http://serverfault.com/questions/121766/webdav-rename-fails-on-an-apache-mod-dav-install-behind-nginx
if ($request_method ~ ^(COPY|MOVE)$) {
set $destination $http_destination;
}
if ($destination ~* ^https(.+)$) {
set $destination http$1;
}
proxy_set_header Destination $destination;
<% } %>
location / {
# increase the proxy buffer sizes to not run into buffer issues (http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffers)
proxy_buffer_size 128k;
@@ -57,19 +71,25 @@ server {
client_max_body_size 1m;
}
# graphite paths
location ~ ^/(graphite|content|metrics|dashboard|render|browser|composer)/ {
proxy_pass http://127.0.0.1:8000;
client_max_body_size 1m;
# the read timeout is between successive reads and not the whole connection
location ~ ^/api/v1/apps/.*/exec$ {
proxy_pass http://127.0.0.1:3000;
proxy_read_timeout 30m;
}
# graphite paths
# location ~ ^/(graphite|content|metrics|dashboard|render|browser|composer)/ {
# proxy_pass http://127.0.0.1:8000;
# client_max_body_size 1m;
# }
location / {
root <%= sourceDir %>/webadmin/dist;
index index.html index.htm;
}
<% } else if ( endpoint === 'oauthproxy' ) { %>
proxy_pass http://127.0.0.1:4000;
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 %>;

View File

@@ -24,7 +24,14 @@ http {
sendfile on;
keepalive_timeout 65;
# timeout for client to finish sending headers
client_header_timeout 30s;
# timeout for reading client request body (successive read timeout and not whole body!)
client_body_timeout 60s;
# keep-alive connections timeout in 65s. this is because many browsers timeout in 60 seconds
keepalive_timeout 65s;
# HTTP server
server {
@@ -38,14 +45,21 @@ http {
deny all;
}
# acme challenges
location /.well-known/acme-challenge/ {
default_type text/plain;
alias /home/yellowtent/data/acme/;
}
location / {
# redirect everything to HTTPS
return 301 https://$host$request_uri;
}
}
# We have to enable https for nginx to read in the vhost in http request
# and send a 404. This is a side-effect of using wildcard DNS
# 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;
@@ -55,11 +69,21 @@ http {
error_page 404 = @fallback;
location @fallback {
internal;
root <%= sourceDir %>/webadmin/dist;
root /home/yellowtent/box/webadmin/dist;
rewrite ^/$ /nakeddomain.html break;
}
return 404;
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;

View File

@@ -1,95 +0,0 @@
#!/bin/bash
set -eu -o pipefail
readonly DATA_DIR="/home/yellowtent/data"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${script_dir}/../INFRA_VERSION" # this injects INFRA_VERSION
arg_fqdn="$1"
# removing containers ensures containers are launched with latest config updates
# restore code in appatask does not delete old containers
infra_version="none"
[[ -f "${DATA_DIR}/INFRA_VERSION" ]] && infra_version=$(cat "${DATA_DIR}/INFRA_VERSION")
if [[ "${infra_version}" == "${INFRA_VERSION}" ]]; then
echo "Infrastructure is upto date"
exit 0
fi
echo "Upgrading infrastructure from ${infra_version} to ${INFRA_VERSION}"
existing_containers=$(docker ps -qa)
echo "Remove containers: ${existing_containers}"
if [[ -n "${existing_containers}" ]]; then
echo "${existing_containers}" | xargs docker rm -f
fi
# graphite
graphite_container_id=$(docker run --restart=always -d --name="graphite" \
-p 127.0.0.1:2003:2003 \
-p 127.0.0.1:2004:2004 \
-p 127.0.0.1:8000:8000 \
-v "${DATA_DIR}/graphite:/app/data" \
"${GRAPHITE_IMAGE}")
echo "Graphite container id: ${graphite_container_id}"
# mail
mail_container_id=$(docker run --restart=always -d --name="mail" \
-p 127.0.0.1:25:25 \
-h "${arg_fqdn}" \
-e "DOMAIN_NAME=${arg_fqdn}" \
-v "${DATA_DIR}/box/mail:/app/data" \
"${MAIL_IMAGE}")
echo "Mail container id: ${mail_container_id}"
# mysql
mysql_addon_root_password=$(pwgen -1 -s)
docker0_ip=$(/sbin/ifconfig docker0 | grep "inet addr" | awk -F: '{print $2}' | awk '{print $1}')
cat > "${DATA_DIR}/addons/mysql_vars.sh" <<EOF
readonly MYSQL_ROOT_PASSWORD='${mysql_addon_root_password}'
readonly MYSQL_ROOT_HOST='${docker0_ip}'
EOF
mysql_container_id=$(docker run --restart=always -d --name="mysql" \
-h "${arg_fqdn}" \
-v "${DATA_DIR}/mysql:/var/lib/mysql" \
-v "${DATA_DIR}/addons/mysql_vars.sh:/etc/mysql/mysql_vars.sh:ro" \
"${MYSQL_IMAGE}")
echo "MySQL container id: ${mysql_container_id}"
# postgresql
postgresql_addon_root_password=$(pwgen -1 -s)
cat > "${DATA_DIR}/addons/postgresql_vars.sh" <<EOF
readonly POSTGRESQL_ROOT_PASSWORD='${postgresql_addon_root_password}'
EOF
postgresql_container_id=$(docker run --restart=always -d --name="postgresql" \
-h "${arg_fqdn}" \
-v "${DATA_DIR}/postgresql:/var/lib/postgresql" \
-v "${DATA_DIR}/addons/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh:ro" \
"${POSTGRESQL_IMAGE}")
echo "PostgreSQL container id: ${postgresql_container_id}"
# mongodb
mongodb_addon_root_password=$(pwgen -1 -s)
cat > "${DATA_DIR}/addons/mongodb_vars.sh" <<EOF
readonly MONGODB_ROOT_PASSWORD='${mongodb_addon_root_password}'
EOF
mongodb_container_id=$(docker run --restart=always -d --name="mongodb" \
-h "${arg_fqdn}" \
-v "${DATA_DIR}/mongodb:/var/lib/mongodb" \
-v "${DATA_DIR}/addons/mongodb_vars.sh:/etc/mongodb_vars.sh:ro" \
"${MONGODB_IMAGE}")
echo "Mongodb container id: ${mongodb_container_id}"
if [[ "${infra_version}" == "none" ]]; then
# if no existing infra was found (for new and restoring cloudons), download app backups
echo "Marking installed apps for restore"
mysql -u root -ppassword -e 'UPDATE apps SET installationState = "pending_restore" WHERE installationState = "installed"' box
else
# if existing infra was found, just mark apps for reconfiguration
mysql -u root -ppassword -e 'UPDATE apps SET installationState = "pending_configure" WHERE installationState = "installed"' box
fi
echo -n "${INFRA_VERSION}" > "${DATA_DIR}/INFRA_VERSION"

View File

@@ -2,14 +2,6 @@
set -eu -o pipefail
echo "Stopping box code"
service supervisor stop || true
echo -n "Waiting for supervisord to stop"
while test -e "/var/run/supervisord.pid" && kill -0 `cat /var/run/supervisord.pid`; do
echo -n "."
sleep 1
done
echo ""
echo "Stopping cloudron"
systemctl stop cloudron.target

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,8 @@
exports = module.exports = {
get: get,
getBySubdomain: getBySubdomain,
getByHttpPort: getByHttpPort,
getByContainerId: getByContainerId,
add: add,
exists: exists,
del: del,
@@ -35,12 +35,12 @@ exports = module.exports = {
ISTATE_ERROR: 'error', // error executing last pending_* command
ISTATE_INSTALLED: 'installed', // app is installed
// run codes (keep in sync in UI)
RSTATE_RUNNING: 'running',
RSTATE_PENDING_START: 'pending_start',
RSTATE_PENDING_STOP: 'pending_stop',
RSTATE_STOPPED: 'stopped', // app stopped by use
// run codes (keep in sync in UI)
HEALTH_HEALTHY: 'healthy',
HEALTH_UNHEALTHY: 'unhealthy',
HEALTH_ERROR: 'error',
@@ -56,13 +56,9 @@ var assert = require('assert'),
safe = require('safetydance'),
util = require('util');
var APPS_FIELDS = [ 'id', 'appStoreId', 'installationState', 'installationProgress', 'runState',
'health', 'containerId', 'manifestJson', 'httpPort', 'location', 'dnsRecordId',
'accessRestriction', 'lastBackupId', 'lastBackupConfigJson', 'oldConfigJson' ].join(',');
var APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationState', 'apps.installationProgress', 'apps.runState',
'apps.health', 'apps.containerId', 'apps.manifestJson', 'apps.httpPort', 'apps.location', 'apps.dnsRecordId',
'apps.accessRestriction', 'apps.lastBackupId', 'apps.lastBackupConfigJson', 'apps.oldConfigJson' ].join(',');
'apps.accessRestrictionJson', 'apps.lastBackupId', 'apps.oldConfigJson', 'apps.memoryLimit', 'apps.altDomain' ].join(',');
var PORT_BINDINGS_FIELDS = [ 'hostPort', 'environmentVariable', 'appId' ].join(',');
@@ -73,10 +69,6 @@ function postProcess(result) {
result.manifest = safe.JSON.parse(result.manifestJson);
delete result.manifestJson;
assert(result.lastBackupConfigJson === null || typeof result.lastBackupConfigJson === 'string');
result.lastBackupConfig = safe.JSON.parse(result.lastBackupConfigJson);
delete result.lastBackupConfigJson;
assert(result.oldConfigJson === null || typeof result.oldConfigJson === 'string');
result.oldConfig = safe.JSON.parse(result.oldConfigJson);
delete result.oldConfigJson;
@@ -94,6 +86,11 @@ function postProcess(result) {
for (var i = 0; i < environmentVariables.length; i++) {
result.portBindings[environmentVariables[i]] = parseInt(hostPorts[i], 10);
}
assert(result.accessRestrictionJson === null || typeof result.accessRestrictionJson === 'string');
result.accessRestriction = safe.JSON.parse(result.accessRestrictionJson);
if (result.accessRestriction && !result.accessRestriction.users) result.accessRestriction.users = [];
delete result.accessRestrictionJson;
}
function get(id, callback) {
@@ -112,22 +109,6 @@ function get(id, callback) {
});
}
function getBySubdomain(subdomain, callback) {
assert.strictEqual(typeof subdomain, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + APPS_FIELDS_PREFIXED + ','
+ 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables'
+ ' FROM apps LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId WHERE location = ? GROUP BY apps.id', [ subdomain ], function (error, result) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
if (result.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND));
postProcess(result[0]);
callback(null, result[0]);
});
}
function getByHttpPort(httpPort, callback) {
assert.strictEqual(typeof httpPort, 'number');
assert.strictEqual(typeof callback, 'function');
@@ -144,6 +125,22 @@ function getByHttpPort(httpPort, callback) {
});
}
function getByContainerId(containerId, callback) {
assert.strictEqual(typeof containerId, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + APPS_FIELDS_PREFIXED + ','
+ 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables'
+ ' FROM apps LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId WHERE containerId = ? GROUP BY apps.id', [ containerId ], function (error, result) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
if (result.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND));
postProcess(result[0]);
callback(null, result[0]);
});
}
function getAll(callback) {
assert.strictEqual(typeof callback, 'function');
@@ -159,24 +156,27 @@ function getAll(callback) {
});
}
function add(id, appStoreId, manifest, location, portBindings, accessRestriction, callback) {
function add(id, appStoreId, manifest, location, portBindings, accessRestriction, memoryLimit, altDomain, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof appStoreId, 'string');
assert(manifest && typeof manifest === 'object');
assert.strictEqual(typeof manifest.version, 'string');
assert.strictEqual(typeof location, 'string');
assert.strictEqual(typeof portBindings, 'object');
assert.strictEqual(typeof accessRestriction, 'string');
assert.strictEqual(typeof accessRestriction, 'object');
assert.strictEqual(typeof memoryLimit, 'number');
assert(altDomain === null || typeof altDomain === 'string');
assert.strictEqual(typeof callback, 'function');
portBindings = portBindings || { };
var manifestJson = JSON.stringify(manifest);
var accessRestrictionJson = JSON.stringify(accessRestriction);
var queries = [ ];
queries.push({
query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, location, accessRestriction) VALUES (?, ?, ?, ?, ?, ?)',
args: [ id, appStoreId, manifestJson, exports.ISTATE_PENDING_INSTALL, location, accessRestriction ]
query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, location, accessRestrictionJson, memoryLimit, altDomain) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
args: [ id, appStoreId, manifestJson, exports.ISTATE_PENDING_INSTALL, location, accessRestrictionJson, memoryLimit, altDomain ]
});
Object.keys(portBindings).forEach(function (env) {
@@ -261,6 +261,7 @@ function updateWithConstraints(id, app, constraints, callback) {
assert.strictEqual(typeof constraints, 'string');
assert.strictEqual(typeof callback, 'function');
assert(!('portBindings' in app) || typeof app.portBindings === 'object');
assert(!('accessRestriction' in app) || typeof app.accessRestriction === 'object' || app.accessRestriction === '');
var queries = [ ];
@@ -279,12 +280,12 @@ function updateWithConstraints(id, app, constraints, callback) {
if (p === 'manifest') {
fields.push('manifestJson = ?');
values.push(JSON.stringify(app[p]));
} else if (p === 'lastBackupConfig') {
fields.push('lastBackupConfigJson = ?');
values.push(JSON.stringify(app[p]));
} else if (p === 'oldConfig') {
fields.push('oldConfigJson = ?');
values.push(JSON.stringify(app[p]));
} else if (p === 'accessRestriction') {
fields.push('accessRestrictionJson = ?');
values.push(JSON.stringify(app[p]));
} else if (p !== 'portBindings') {
fields.push(p + ' = ?');
values.push(app[p]);
@@ -335,6 +336,7 @@ function setInstallationCommand(appId, installationState, values, callback) {
// Rules are:
// uninstall is allowed in any state
// force update is allowed in any state including pending_uninstall! (for better or worse)
// restore is allowed from installed or error state
// update and configure are allowed only in installed state
@@ -342,7 +344,7 @@ function setInstallationCommand(appId, installationState, values, callback) {
updateWithConstraints(appId, values, '', callback);
} else if (installationState === exports.ISTATE_PENDING_RESTORE) {
updateWithConstraints(appId, values, 'AND (installationState = "installed" OR installationState = "error")', callback);
} else if (installationState === exports.ISTATE_PENDING_UPDATE || exports.ISTATE_PENDING_CONFIGURE || installationState == exports.ISTATE_PENDING_BACKUP) {
} else if (installationState === exports.ISTATE_PENDING_UPDATE || installationState === exports.ISTATE_PENDING_CONFIGURE || installationState === exports.ISTATE_PENDING_BACKUP) {
updateWithConstraints(appId, values, 'AND installationState = "installed"', callback);
} else {
callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, 'invalid installationState'));

200
src/apphealthmonitor.js Normal file
View File

@@ -0,0 +1,200 @@
'use strict';
var appdb = require('./appdb.js'),
assert = require('assert'),
async = require('async'),
config = require('./config.js'),
DatabaseError = require('./databaseerror.js'),
debug = require('debug')('box:apphealthmonitor'),
docker = require('./docker.js').connection,
mailer = require('./mailer.js'),
superagent = require('superagent'),
util = require('util');
exports = module.exports = {
start: start,
stop: stop
};
var HEALTHCHECK_INTERVAL = 10 * 1000; // every 10 seconds. this needs to be small since the UI makes only healthy apps clickable
var UNHEALTHY_THRESHOLD = 10 * 60 * 1000; // 10 minutes
var gHealthInfo = { }; // { time, emailSent }
var gRunTimeout = null;
var gDockerEventStream = null;
function debugApp(app) {
assert(!app || typeof app === 'object');
var prefix = app ? (app.location || 'naked_domain') : '(no app)';
var manifestAppId = app ? app.manifest.id : '';
var id = app ? app.id : '';
debug(prefix + ' ' + manifestAppId + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)) + ' - ' + id);
}
function setHealth(app, health, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof health, 'string');
assert.strictEqual(typeof callback, 'function');
var now = new Date();
if (!(app.id in gHealthInfo)) { // add new apps to list
gHealthInfo[app.id] = { time: now, emailSent: false };
}
if (health === appdb.HEALTH_HEALTHY) {
gHealthInfo[app.id].time = now;
} else if (Math.abs(now - gHealthInfo[app.id].time) > UNHEALTHY_THRESHOLD) {
if (gHealthInfo[app.id].emailSent) return callback(null);
debugApp(app, 'marking as unhealthy since not seen for more than %s minutes', UNHEALTHY_THRESHOLD/(60 * 1000));
if (app.appStoreId !== '') mailer.appDied(app); // do not send mails for dev apps
gHealthInfo[app.id].emailSent = true;
} else {
debugApp(app, 'waiting for sometime to update the app health');
return callback(null);
}
appdb.setHealth(app.id, health, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null); // app uninstalled?
if (error) return callback(error);
app.health = health;
callback(null);
});
}
// callback is called with error for fatal errors and not if health check failed
function checkAppHealth(app, callback) {
if (app.installationState !== appdb.ISTATE_INSTALLED || app.runState !== appdb.RSTATE_RUNNING) {
debugApp(app, 'skipped. istate:%s rstate:%s', app.installationState, app.runState);
return callback(null);
}
var container = docker.getContainer(app.containerId),
manifest = app.manifest;
container.inspect(function (err, data) {
if (err || !data || !data.State) {
debugApp(app, 'Error inspecting container');
return setHealth(app, appdb.HEALTH_ERROR, callback);
}
if (data.State.Running !== true) {
debugApp(app, 'exited');
return setHealth(app, appdb.HEALTH_DEAD, callback);
}
// poll through docker network instead of nginx to bypass any potential oauth proxy
var healthCheckUrl = 'http://127.0.0.1:' + app.httpPort + manifest.healthCheckPath;
superagent
.get(healthCheckUrl)
.set('Host', config.appFqdn(app.location)) // required for some apache configs with rewrite rules
.redirects(0)
.timeout(HEALTHCHECK_INTERVAL)
.end(function (error, res) {
if (error && !error.response) {
debugApp(app, 'not alive (network error): %s', error.message);
setHealth(app, appdb.HEALTH_UNHEALTHY, callback);
} else if (res.statusCode >= 400) { // 2xx and 3xx are ok
debugApp(app, 'not alive : %s', error || res.status);
setHealth(app, appdb.HEALTH_UNHEALTHY, callback);
} else {
setHealth(app, appdb.HEALTH_HEALTHY, callback);
}
});
});
}
function processApps(callback) {
appdb.getAll(function (error, apps) {
if (error) return callback(error);
async.each(apps, checkAppHealth, function (error) {
if (error) console.error(error);
var alive = apps
.filter(function (a) { return a.installationState === appdb.ISTATE_INSTALLED && a.runState === appdb.RSTATE_RUNNING && a.health === appdb.HEALTH_HEALTHY; })
.map(function (a) { return (a.location || 'naked_domain') + '|' + a.manifest.id; }).join(', ');
debug('apps alive: [%s]', alive);
callback(null);
});
});
}
function run() {
processApps(function (error) {
if (error) console.error(error);
gRunTimeout = setTimeout(run, HEALTHCHECK_INTERVAL);
});
}
/*
OOM can be tested using stress tool like so:
docker run -ti -m 100M cloudron/base:0.3.3 /bin/bash
apt-get update && apt-get install stress
stress --vm 1 --vm-bytes 200M --vm-hang 0
*/
function processDockerEvents() {
// note that for some reason, the callback is called only on the first event
debug('Listening for docker events');
docker.getEvents({ filters: JSON.stringify({ event: [ 'oom' ] }) }, function (error, stream) {
if (error) return console.error(error);
gDockerEventStream = stream;
stream.setEncoding('utf8');
stream.on('data', function (data) {
var ev = JSON.parse(data);
debug('Container ' + ev.id + ' went OOM');
appdb.getByContainerId(ev.id, function (error, app) {
var program = error || !app.appStoreId ? ev.id : app.appStoreId;
var context = JSON.stringify(ev);
if (app) context = context + '\n\n' + JSON.stringify(app, null, 4) + '\n';
debug('OOM Context: %s', context);
// do not send mails for dev apps
if (error || app.appStoreId !== '') mailer.unexpectedExit(program, context); // app can be null if it's an addon crash
});
});
stream.on('error', function (error) {
console.error('Error reading docker events', error);
gDockerEventStream = null; // TODO: reconnect?
});
stream.on('end', function () {
console.error('Docker event stream ended');
gDockerEventStream = null; // TODO: reconnect?
});
});
}
function start(callback) {
assert.strictEqual(typeof callback, 'function');
debug('Starting apphealthmonitor');
processDockerEvents();
run();
callback();
}
function stop(callback) {
assert.strictEqual(typeof callback, 'function');
clearTimeout(gRunTimeout);
gDockerEventStream.end();
callback();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -10,8 +10,9 @@ exports = module.exports = {
var assert = require('assert'),
BasicStrategy = require('passport-http').BasicStrategy,
BearerStrategy = require('passport-http-bearer').Strategy,
clientdb = require('./clientdb'),
clients = require('./clients'),
ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy,
ClientsError = clients.ClientsError,
DatabaseError = require('./databaseerror'),
debug = require('debug')('box:auth'),
LocalStrategy = require('passport-local').Strategy,
@@ -19,7 +20,6 @@ var assert = require('assert'),
passport = require('passport'),
tokendb = require('./tokendb'),
user = require('./user'),
userdb = require('./userdb'),
UserError = user.UserError,
_ = require('underscore');
@@ -27,11 +27,11 @@ function initialize(callback) {
assert.strictEqual(typeof callback, 'function');
passport.serializeUser(function (user, callback) {
callback(null, user.username);
callback(null, user.id);
});
passport.deserializeUser(function(username, callback) {
userdb.get(username, function (error, result) {
passport.deserializeUser(function(userId, callback) {
user.get(userId, function (error, result) {
if (error) return callback(error);
var md5 = crypto.createHash('md5').update(result.email.toLowerCase()).digest('hex');
@@ -43,7 +43,7 @@ function initialize(callback) {
passport.use(new LocalStrategy(function (username, password, callback) {
if (username.indexOf('@') === -1) {
user.verify(username, password, function (error, result) {
user.verifyWithUsername(username, password, function (error, result) {
if (error && error.reason === UserError.NOT_FOUND) return callback(null, false);
if (error && error.reason === UserError.WRONG_PASSWORD) return callback(null, false);
if (error) return callback(error);
@@ -66,14 +66,14 @@ function initialize(callback) {
debug('BasicStrategy: detected client id %s instead of username:password', username);
// username is actually client id here
// password is client secret
clientdb.get(username, function (error, client) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, false);
clients.get(username, function (error, client) {
if (error && error.reason === ClientsError.NOT_FOUND) return callback(null, false);
if (error) return callback(error);
if (client.clientSecret != password) return callback(null, false);
return callback(null, client);
});
} else {
user.verify(username, password, function (error, result) {
user.verifyWithUsername(username, password, function (error, result) {
if (error && error.reason === UserError.NOT_FOUND) return callback(null, false);
if (error && error.reason === UserError.WRONG_PASSWORD) return callback(null, false);
if (error) return callback(error);
@@ -84,8 +84,8 @@ function initialize(callback) {
}));
passport.use(new ClientPasswordStrategy(function (clientId, clientSecret, callback) {
clientdb.get(clientId, function(error, client) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, false);
clients.get(clientId, function(error, client) {
if (error && error.reason === ClientsError.NOT_FOUND) return callback(null, false);
if (error) { return callback(error); }
if (client.clientSecret != clientSecret) { return callback(null, false); }
return callback(null, client);
@@ -100,29 +100,11 @@ function initialize(callback) {
// scopes here can define what capabilities that token carries
// passport put the 'info' object into req.authInfo, where we can further validate the scopes
var info = { scope: token.scope };
var tokenType;
if (token.identifier.indexOf(tokendb.PREFIX_DEV) === 0) {
token.identifier = token.identifier.slice(tokendb.PREFIX_DEV.length);
tokenType = tokendb.TYPE_DEV;
} else if (token.identifier.indexOf(tokendb.PREFIX_APP) === 0) {
tokenType = tokendb.TYPE_APP;
return callback(null, { id: token.identifier.slice(tokendb.PREFIX_APP.length), tokenType: tokenType }, info);
} else if (token.identifier.indexOf(tokendb.PREFIX_USER) === 0) {
tokenType = tokendb.TYPE_USER;
token.identifier = token.identifier.slice(tokendb.PREFIX_USER.length);
} else {
// legacy tokens assuming a user access token
tokenType = tokendb.TYPE_USER;
}
userdb.get(token.identifier, function (error, user) {
user.get(token.identifier, function (error, user) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, false);
if (error) return callback(error);
// amend the tokenType of the token owner
user.tokenType = tokenType;
callback(null, user, info);
});
});

114
src/backupdb.js Normal file
View File

@@ -0,0 +1,114 @@
'use strict';
var assert = require('assert'),
database = require('./database.js'),
DatabaseError = require('./databaseerror.js'),
util = require('util');
var BACKUPS_FIELDS = [ 'id', 'creationTime', 'version', 'type', 'dependsOn', 'state', ];
exports = module.exports = {
add: add,
getPaged: getPaged,
get: get,
del: del,
getByAppIdPaged: getByAppIdPaged,
_clear: clear,
BACKUP_TYPE_APP: 'app',
BACKUP_TYPE_BOX: 'box',
BACKUP_STATE_NORMAL: 'normal', // should rename to created to avoid listing in UI?
};
function postProcess(result) {
assert.strictEqual(typeof result, 'object');
result.dependsOn = result.dependsOn ? result.dependsOn.split(',') : [ ];
}
function getPaged(page, perPage, callback) {
assert(typeof page === 'number' && page > 0);
assert(typeof perPage === 'number' && perPage > 0);
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + BACKUPS_FIELDS + ' FROM backups WHERE type = ? AND state = ? ORDER BY creationTime DESC LIMIT ?,?',
[ exports.BACKUP_TYPE_BOX, exports.BACKUP_STATE_NORMAL, (page-1)*perPage, perPage ], function (error, results) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
results.forEach(function (result) { postProcess(result); });
callback(null, results);
});
}
function getByAppIdPaged(page, perPage, appId, callback) {
assert(typeof page === 'number' && page > 0);
assert(typeof perPage === 'number' && perPage > 0);
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + BACKUPS_FIELDS + ' FROM backups WHERE type = ? AND state = ? AND id LIKE ? ORDER BY creationTime DESC LIMIT ?,?',
[ exports.BACKUP_TYPE_APP, exports.BACKUP_STATE_NORMAL, 'appbackup\\_' + appId + '\\_%', (page-1)*perPage, perPage ], function (error, results) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
results.forEach(function (result) { postProcess(result); });
callback(null, results);
});
}
function get(id, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + BACKUPS_FIELDS + ' FROM backups WHERE id = ? ORDER BY creationTime DESC',
[ id ], function (error, result) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
if (result.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND));
postProcess(result[0]);
callback(null, result[0]);
});
}
function add(backup, callback) {
assert(backup && typeof backup === 'object');
assert.strictEqual(typeof backup.id, 'string');
assert.strictEqual(typeof backup.version, 'string');
assert(backup.type === exports.BACKUP_TYPE_APP || backup.type === exports.BACKUP_TYPE_BOX);
assert(util.isArray(backup.dependsOn));
assert.strictEqual(typeof callback, 'function');
var creationTime = backup.creationTime || new Date(); // allow tests to set the time
database.query('INSERT INTO backups (id, version, type, creationTime, state, dependsOn) VALUES (?, ?, ?, ?, ?, ?)',
[ backup.id, backup.version, backup.type, creationTime, exports.BACKUP_STATE_NORMAL, backup.dependsOn.join(',') ],
function (error) {
if (error && error.code === 'ER_DUP_ENTRY') return callback(new DatabaseError(DatabaseError.ALREADY_EXISTS));
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
callback(null);
});
}
function clear(callback) {
assert.strictEqual(typeof callback, 'function');
database.query('TRUNCATE TABLE backups', [], function (error) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
callback(null);
});
}
function del(id, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('DELETE FROM backups WHERE id=?', [ id ], function (error) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
callback(null);
});
}

View File

@@ -3,17 +3,56 @@
exports = module.exports = {
BackupsError: BackupsError,
getAllPaged: getAllPaged,
getPaged: getPaged,
getByAppIdPaged: getByAppIdPaged,
getBackupUrl: getBackupUrl,
getRestoreUrl: getRestoreUrl
getRestoreUrl: getRestoreUrl,
getRestoreConfig: getRestoreConfig,
ensureBackup: ensureBackup,
backup: backup,
backupApp: backupApp,
restoreApp: restoreApp,
backupBoxAndApps: backupBoxAndApps
};
var assert = require('assert'),
var addons = require('./addons.js'),
appdb = require('./appdb.js'),
apps = require('./apps.js'),
async = require('async'),
assert = require('assert'),
backupdb = require('./backupdb.js'),
caas = require('./storage/caas.js'),
config = require('./config.js'),
DatabaseError = require('./databaseerror.js'),
debug = require('debug')('box:backups'),
eventlog = require('./eventlog.js'),
locker = require('./locker.js'),
path = require('path'),
paths = require('./paths.js'),
progress = require('./progress.js'),
s3 = require('./storage/s3.js'),
safe = require('safetydance'),
shell = require('./shell.js'),
settings = require('./settings.js'),
superagent = require('superagent'),
util = require('util');
util = require('util'),
webhooks = require('./webhooks.js');
var BACKUP_BOX_CMD = path.join(__dirname, 'scripts/backupbox.sh'),
BACKUP_APP_CMD = path.join(__dirname, 'scripts/backupapp.sh'),
RESTORE_APP_CMD = path.join(__dirname, 'scripts/restoreapp.sh');
var NOOP_CALLBACK = function (error) { if (error) debug(error); };
function debugApp(app, args) {
assert(!app || typeof app === 'object');
var prefix = app ? app.location : '(no app)';
debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
}
function BackupsError(reason, errorOrMessage) {
assert.strictEqual(typeof reason, 'string');
@@ -36,60 +75,422 @@ function BackupsError(reason, errorOrMessage) {
util.inherits(BackupsError, Error);
BackupsError.EXTERNAL_ERROR = 'external error';
BackupsError.INTERNAL_ERROR = 'internal error';
BackupsError.BAD_STATE = 'bad state';
BackupsError.MISSING_CREDENTIALS = 'missing credentials';
function getAllPaged(page, perPage, callback) {
assert.strictEqual(typeof page, 'number');
assert.strictEqual(typeof perPage, 'number');
// choose which storage backend we use for test purpose we use s3
function api(provider) {
switch (provider) {
case 'caas': return caas;
case 's3': return s3;
default: return null;
}
}
function getPaged(page, perPage, callback) {
assert(typeof page === 'number' && page > 0);
assert(typeof perPage === 'number' && perPage > 0);
assert.strictEqual(typeof callback, 'function');
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/backups';
backupdb.getPaged(page, perPage, function (error, results) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
superagent.get(url).query({ token: config.token() }).end(function (error, result) {
if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error));
if (result.statusCode !== 200) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, result.text));
if (!result.body || !util.isArray(result.body.backups)) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, 'Unexpected response'));
// [ { creationTime, boxVersion, restoreKey, dependsOn: [ ] } ] sorted by time (latest first)
return callback(null, result.body.backups);
callback(null, results);
});
}
function getBackupUrl(app, appBackupIds, callback) {
assert(!app || typeof app === 'object');
assert(!appBackupIds || util.isArray(appBackupIds));
function getByAppIdPaged(page, perPage, appId, callback) {
assert(typeof page === 'number' && page > 0);
assert(typeof perPage === 'number' && perPage > 0);
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof callback, 'function');
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/backupurl';
backupdb.getByAppIdPaged(page, perPage, appId, function (error, results) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
var data = {
boxVersion: config.version(),
appId: app ? app.id : null,
appVersion: app ? app.manifest.version : null,
appBackupIds: appBackupIds
};
superagent.put(url).query({ token: config.token() }).send(data).end(function (error, result) {
if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error));
if (result.statusCode !== 201) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, result.text));
if (!result.body || !result.body.url) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, 'Unexpected response'));
return callback(null, result.body);
callback(null, results);
});
}
function getBoxBackupCredentials(appBackupIds, callback) {
assert(util.isArray(appBackupIds));
assert.strictEqual(typeof callback, 'function');
var now = new Date();
var filebase = util.format('backup_%s-v%s', now.toISOString(), config.version());
var filename = filebase + '.tar.gz';
settings.getBackupConfig(function (error, backupConfig) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
api(backupConfig.provider).getBackupCredentials(backupConfig, function (error, result) {
if (error) return callback(error);
result.id = filename;
result.s3Url = 's3://' + backupConfig.bucket + '/' + backupConfig.prefix + '/' + filename;
result.backupKey = backupConfig.key;
debug('getBoxBackupCredentials: %j', result);
callback(null, result);
});
});
}
function getAppBackupCredentials(app, manifest, callback) {
assert.strictEqual(typeof app, 'object');
assert(manifest && typeof manifest === 'object');
assert.strictEqual(typeof callback, 'function');
var now = new Date();
var filebase = util.format('appbackup_%s_%s-v%s', app.id, now.toISOString(), manifest.version);
var configFilename = filebase + '.json', dataFilename = filebase + '.tar.gz';
settings.getBackupConfig(function (error, backupConfig) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
api(backupConfig.provider).getBackupCredentials(backupConfig, function (error, result) {
if (error) return callback(error);
result.id = dataFilename;
result.s3ConfigUrl = 's3://' + backupConfig.bucket + '/' + backupConfig.prefix + '/' + configFilename;
result.s3DataUrl = 's3://' + backupConfig.bucket + '/' + backupConfig.prefix + '/' + dataFilename;
result.backupKey = backupConfig.key;
debug('getAppBackupCredentials: %j', result);
callback(null, result);
});
});
}
// backupId is the s3 filename. appbackup_%s_%s-v%s.tar.gz
function getRestoreConfig(backupId, callback) {
assert.strictEqual(typeof backupId, 'string');
assert.strictEqual(typeof callback, 'function');
var configFile = backupId.replace(/\.tar\.gz$/, '.json');
settings.getBackupConfig(function (error, backupConfig) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
api(backupConfig.provider).getRestoreUrl(backupConfig, configFile, function (error, result) {
if (error) return callback(error);
superagent.get(result.url).buffer(true).end(function (error, response) {
if (error && !error.response) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error.message));
if (response.statusCode !== 200) return callback(new Error('Invalid response code when getting config.json : ' + response.statusCode));
var config = safe.JSON.parse(response.text);
if (!config) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, 'Error in config:' + safe.error.message));
return callback(null, config);
});
});
});
}
// backupId is the s3 filename. appbackup_%s_%s-v%s.tar.gz
function getRestoreUrl(backupId, callback) {
assert.strictEqual(typeof backupId, 'string');
assert.strictEqual(typeof callback, 'function');
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/restoreurl';
settings.getBackupConfig(function (error, backupConfig) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
superagent.put(url).query({ token: config.token(), backupId: backupId }).end(function (error, result) {
if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error));
if (result.statusCode !== 201) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, result.text));
if (!result.body || !result.body.url) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, 'Unexpected response'));
api(backupConfig.provider).getRestoreUrl(backupConfig, backupId, function (error, result) {
if (error) return callback(error);
return callback(null, result.body);
var obj = {
id: backupId,
url: result.url,
backupKey: backupConfig.key
};
debug('getRestoreUrl: id:%s url:%s backupKey:%s', obj.id, obj.url, obj.backupKey);
callback(null, obj);
});
});
}
function copyLastBackup(app, manifest, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof app.lastBackupId, 'string');
assert(manifest && typeof manifeset === 'object');
assert.strictEqual(typeof callback, 'function');
var now = new Date();
var toFilenameArchive = util.format('appbackup_%s_%s-v%s.tar.gz', app.id, now.toISOString(), manifest.version);
var toFilenameConfig = util.format('appbackup_%s_%s-v%s.json', app.id, now.toISOString(), manifest.version);
settings.getBackupConfig(function (error, backupConfig) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
debug('copyLastBackup: copying archive %s to %s', app.lastBackupId, toFilenameArchive);
api(backupConfig.provider).copyObject(backupConfig, app.lastBackupId, toFilenameArchive, function (error) {
if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error));
// TODO change that logic by adjusting app.lastBackupId to not contain the file type
var configFileId = app.lastBackupId.slice(0, -'.tar.gz'.length) + '.json';
debug('copyLastBackup: copying config %s to %s', configFileId, toFilenameConfig);
api(backupConfig.provider).copyObject(backupConfig, configFileId, toFilenameConfig, function (error) {
if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error));
return callback(null, toFilenameArchive);
});
});
});
}
function backupBoxWithAppBackupIds(appBackupIds, callback) {
assert(util.isArray(appBackupIds));
getBoxBackupCredentials(appBackupIds, function (error, result) {
if (error && error.reason === BackupsError.EXTERNAL_ERROR) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error.message));
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
debug('backupBoxWithAppBackupIds: %j', result);
var args = [ result.s3Url, result.accessKeyId, result.secretAccessKey, result.sessionToken, result.region, result.backupKey ];
shell.sudo('backupBox', [ BACKUP_BOX_CMD ].concat(args), function (error) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
debug('backupBoxWithAppBackupIds: success');
backupdb.add({ id: result.id, version: config.version(), type: backupdb.BACKUP_TYPE_BOX, dependsOn: appBackupIds }, function (error) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
webhooks.backupDone(result.id, null /* app */, appBackupIds, function (error) {
if (error) return callback(error);
callback(null, result.id);
});
});
});
});
}
// this function expects you to have a lock
// function backupBox(callback) {
// apps.getAll(function (error, allApps) {
// if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
//
// var appBackupIds = allApps.map(function (app) { return app.lastBackupId; });
// appBackupIds = appBackupIds.filter(function (id) { return id !== null; }); // remove apps that were never backed up
//
// backupBoxWithAppBackupIds(appBackupIds, callback);
// });
// }
function canBackupApp(app) {
// only backup apps that are installed or pending configure or called from apptask. Rest of them are in some
// state not good for consistent backup (i.e addons may not have been setup completely)
return (app.installationState === appdb.ISTATE_INSTALLED && app.health === appdb.HEALTH_HEALTHY) ||
app.installationState === appdb.ISTATE_PENDING_CONFIGURE ||
app.installationState === appdb.ISTATE_PENDING_BACKUP || // called from apptask
app.installationState === appdb.ISTATE_PENDING_UPDATE; // called from apptask
}
// set the 'creation' date of lastBackup so that the backup persists across time based archival rules
// s3 does not allow changing creation time, so copying the last backup is easy way out for now
function reuseOldAppBackup(app, manifest, callback) {
assert.strictEqual(typeof app.lastBackupId, 'string');
assert(manifest && typeof manifest === 'object');
assert.strictEqual(typeof callback, 'function');
copyLastBackup(app, manifest, function (error, newBackupId) {
if (error) return callback(error);
debugApp(app, 'reuseOldAppBackup: reused old backup %s as %s', app.lastBackupId, newBackupId);
callback(null, newBackupId);
});
}
function createNewAppBackup(app, manifest, callback) {
assert.strictEqual(typeof app, 'object');
assert(manifest && typeof manifest === 'object');
assert.strictEqual(typeof callback, 'function');
getAppBackupCredentials(app, manifest, function (error, result) {
if (error) return callback(error);
debugApp(app, 'createNewAppBackup: backup url:%s backup config url:%s', result.s3DataUrl, result.s3ConfigUrl);
var args = [ app.id, result.s3ConfigUrl, result.s3DataUrl, result.accessKeyId, result.secretAccessKey,
result.sessionToken, result.region, result.backupKey ];
async.series([
addons.backupAddons.bind(null, app, manifest.addons),
shell.sudo.bind(null, 'backupApp', [ BACKUP_APP_CMD ].concat(args))
], function (error) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
debugApp(app, 'createNewAppBackup: %s done', result.id);
backupdb.add({ id: result.id, version: manifest.version, type: backupdb.BACKUP_TYPE_APP, dependsOn: [ ] }, function (error) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
callback(null, result.id);
});
});
});
}
function setRestorePoint(appId, lastBackupId, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof lastBackupId, 'string');
assert.strictEqual(typeof callback, 'function');
appdb.update(appId, { lastBackupId: lastBackupId }, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new BackupsError(BackupsError.NOT_FOUND, 'No such app'));
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
return callback(null);
});
}
function backupApp(app, manifest, callback) {
assert.strictEqual(typeof app, 'object');
assert(manifest && typeof manifest === 'object');
assert.strictEqual(typeof callback, 'function');
var backupFunction;
if (!canBackupApp(app)) {
if (!app.lastBackupId) {
debugApp(app, 'backupApp: cannot backup app');
return callback(new BackupsError(BackupsError.BAD_STATE, 'App not healthy and never backed up previously'));
}
backupFunction = reuseOldAppBackup.bind(null, app, manifest);
} else {
var appConfig = apps.getAppConfig(app);
appConfig.manifest = manifest;
backupFunction = createNewAppBackup.bind(null, app, manifest);
if (!safe.fs.writeFileSync(path.join(paths.DATA_DIR, app.id + '/config.json'), JSON.stringify(appConfig), 'utf8')) {
return callback(safe.error);
}
}
backupFunction(function (error, backupId) {
if (error) return callback(error);
debugApp(app, 'backupApp: successful id:%s', backupId);
setRestorePoint(app.id, backupId, function (error) {
if (error) return callback(error);
return callback(null, backupId);
});
});
}
// this function expects you to have a lock
function backupBoxAndApps(auditSource, callback) {
assert.strictEqual(typeof auditSource, 'object');
callback = callback || NOOP_CALLBACK;
eventlog.add(eventlog.ACTION_BACKUP_START, auditSource, { });
apps.getAll(function (error, allApps) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
var processed = 0;
var step = 100/(allApps.length+1);
progress.set(progress.BACKUP, processed, '');
async.mapSeries(allApps, function iterator(app, iteratorCallback) {
++processed;
backupApp(app, app.manifest, function (error, backupId) {
if (error && error.reason !== BackupsError.BAD_STATE) {
debugApp(app, 'Unable to backup', error);
return iteratorCallback(error);
}
progress.set(progress.BACKUP, step * processed, 'Backed up app at ' + app.location);
iteratorCallback(null, backupId || null); // clear backupId if is in BAD_STATE and never backed up
});
}, function appsBackedUp(error, backupIds) {
if (error) {
progress.set(progress.BACKUP, 100, error.message);
return callback(error);
}
backupIds = backupIds.filter(function (id) { return id !== null; }); // remove apps in bad state that were never backed up
backupBoxWithAppBackupIds(backupIds, function (error, filename) {
progress.set(progress.BACKUP, 100, error ? error.message : '');
eventlog.add(eventlog.ACTION_BACKUP_FINISH, auditSource, { errorMessage: error ? error.message : null, filename: filename });
callback(error, filename);
});
});
});
}
function backup(auditSource, callback) {
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
var error = locker.lock(locker.OP_FULL_BACKUP);
if (error) return callback(new BackupsError(BackupsError.BAD_STATE, error.message));
progress.set(progress.BACKUP, 0, 'Starting'); // ensure tools can 'wait' on progress
backupBoxAndApps(auditSource, function (error) { // start the backup operation in the background
if (error) console.error('backup failed.', error);
locker.unlock(locker.OP_FULL_BACKUP);
});
callback(null);
}
function ensureBackup(auditSource, callback) {
assert.strictEqual(typeof auditSource, 'object');
getPaged(1, 1, function (error, backups) {
if (error) {
debug('Unable to list backups', error);
return callback(error); // no point trying to backup if appstore is down
}
if (backups.length !== 0 && (new Date() - new Date(backups[0].creationTime) < 23 * 60 * 60 * 1000)) { // ~1 day ago
debug('Previous backup was %j, no need to backup now', backups[0]);
return callback(null);
}
backup(auditSource, callback);
});
}
function restoreApp(app, addonsToRestore, backupId, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof addonsToRestore, 'object');
assert.strictEqual(typeof backupId, 'string');
assert.strictEqual(typeof callback, 'function');
assert(app.lastBackupId);
getRestoreUrl(backupId, function (error, result) {
if (error) return callback(error);
debugApp(app, 'restoreApp: restoreUrl:%s', result.url);
shell.sudo('restoreApp', [ RESTORE_APP_CMD, app.id, result.url, result.backupKey, result.sessionToken ], function (error) {
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
addons.restoreAddons(app, addonsToRestore, callback);
});
});
}

467
src/cert/acme.js Normal file
View File

@@ -0,0 +1,467 @@
'use strict';
var assert = require('assert'),
async = require('async'),
crypto = require('crypto'),
debug = require('debug')('box:cert/acme'),
fs = require('fs'),
parseLinks = require('parse-links'),
path = require('path'),
paths = require('../paths.js'),
safe = require('safetydance'),
superagent = require('superagent'),
ursa = require('ursa'),
util = require('util'),
_ = require('underscore');
var CA_PROD = 'https://acme-v01.api.letsencrypt.org',
CA_STAGING = 'https://acme-staging.api.letsencrypt.org',
LE_AGREEMENT = 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf';
exports = module.exports = {
getCertificate: getCertificate
};
function AcmeError(reason, errorOrMessage) {
assert.strictEqual(typeof reason, 'string');
assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined');
Error.call(this);
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.reason = reason;
if (typeof errorOrMessage === 'undefined') {
this.message = reason;
} else if (typeof errorOrMessage === 'string') {
this.message = errorOrMessage;
} else {
this.message = 'Internal error';
this.nestedError = errorOrMessage;
}
}
util.inherits(AcmeError, Error);
AcmeError.INTERNAL_ERROR = 'Internal Error';
AcmeError.EXTERNAL_ERROR = 'External Error';
AcmeError.ALREADY_EXISTS = 'Already Exists';
AcmeError.NOT_COMPLETED = 'Not Completed';
AcmeError.FORBIDDEN = 'Forbidden';
// http://jose.readthedocs.org/en/latest/
// https://www.ietf.org/proceedings/92/slides/slides-92-acme-1.pdf
// https://community.letsencrypt.org/t/list-of-client-implementations/2103
function Acme(options) {
assert.strictEqual(typeof options, 'object');
this.caOrigin = options.prod ? CA_PROD : CA_STAGING;
this.accountKeyPem = null; // Buffer
this.email = options.email;
}
Acme.prototype.getNonce = function (callback) {
superagent.get(this.caOrigin + '/directory', function (error, response) {
if (error) return callback(error);
if (response.statusCode !== 200) return callback(new Error('Invalid response code when fetching nonce : ' + response.statusCode));
return callback(null, response.headers['Replay-Nonce'.toLowerCase()]);
});
};
// urlsafe base64 encoding (jose)
function urlBase64Encode(string) {
return string.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
function b64(str) {
var buf = util.isBuffer(str) ? str : new Buffer(str);
return urlBase64Encode(buf.toString('base64'));
}
Acme.prototype.sendSignedRequest = function (url, payload, callback) {
assert.strictEqual(typeof url, 'string');
assert.strictEqual(typeof payload, 'string');
assert.strictEqual(typeof callback, 'function');
assert(util.isBuffer(this.accountKeyPem));
var privateKey = ursa.createPrivateKey(this.accountKeyPem);
var header = {
alg: 'RS256',
jwk: {
e: b64(privateKey.getExponent()),
kty: 'RSA',
n: b64(privateKey.getModulus())
}
};
var payload64 = b64(payload);
this.getNonce(function (error, nonce) {
if (error) return callback(error);
debug('sendSignedRequest: using nonce %s for url %s', nonce, url);
var protected64 = b64(JSON.stringify(_.extend({ }, header, { nonce: nonce })));
var signer = ursa.createSigner('sha256');
signer.update(protected64 + '.' + payload64, 'utf8');
var signature64 = urlBase64Encode(signer.sign(privateKey, 'base64'));
var data = {
header: header,
protected: protected64,
payload: payload64,
signature: signature64
};
superagent.post(url).set('Content-Type', 'application/x-www-form-urlencoded').send(JSON.stringify(data)).end(function (error, res) {
if (error && !error.response) return callback(error); // network errors
callback(null, res);
});
});
};
Acme.prototype.updateContact = function (registrationUri, callback) {
assert.strictEqual(typeof registrationUri, 'string');
assert.strictEqual(typeof callback, 'function');
debug('updateContact: %s %s', registrationUri, this.email);
// https://github.com/ietf-wg-acme/acme/issues/30
var payload = {
resource: 'reg',
contact: [ 'mailto:' + this.email ],
agreement: LE_AGREEMENT
};
var that = this;
this.sendSignedRequest(registrationUri, JSON.stringify(payload), function (error, result) {
if (error) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when registering user: ' + error.message));
if (result.statusCode !== 202) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to update contact. Expecting 202, got %s %s', result.statusCode, result.text)));
debug('updateContact: contact of user updated to %s', that.email);
callback();
});
};
Acme.prototype.registerUser = function (callback) {
assert.strictEqual(typeof callback, 'function');
var payload = {
resource: 'new-reg',
contact: [ 'mailto:' + this.email ],
agreement: LE_AGREEMENT
};
debug('registerUser: %s', this.email);
var that = this;
this.sendSignedRequest(this.caOrigin + '/acme/new-reg', JSON.stringify(payload), function (error, result) {
if (error) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when registering user: ' + error.message));
if (result.statusCode === 409) return that.updateContact(result.headers.location, callback); // already exists
if (result.statusCode !== 201) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to register user. Expecting 201, got %s %s', result.statusCode, result.text)));
debug('registerUser: registered user %s', that.email);
callback(null);
});
};
Acme.prototype.registerDomain = function (domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
var payload = {
resource: 'new-authz',
identifier: {
type: 'dns',
value: domain
}
};
debug('registerDomain: %s', domain);
this.sendSignedRequest(this.caOrigin + '/acme/new-authz', JSON.stringify(payload), function (error, result) {
if (error) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when registering domain: ' + error.message));
if (result.statusCode === 403) return callback(new AcmeError(AcmeError.FORBIDDEN, result.body.detail));
if (result.statusCode !== 201) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to register user. Expecting 201, got %s %s', result.statusCode, result.text)));
debug('registerDomain: registered %s', domain);
callback(null, result.body);
});
};
Acme.prototype.prepareHttpChallenge = function (challenge, callback) {
assert.strictEqual(typeof challenge, 'object');
assert.strictEqual(typeof callback, 'function');
debug('prepareHttpChallenge: preparing for challenge %j', challenge);
var token = challenge.token;
assert(util.isBuffer(this.accountKeyPem));
var privateKey = ursa.createPrivateKey(this.accountKeyPem);
var jwk = {
e: b64(privateKey.getExponent()),
kty: 'RSA',
n: b64(privateKey.getModulus())
};
var shasum = crypto.createHash('sha256');
shasum.update(JSON.stringify(jwk));
var thumbprint = urlBase64Encode(shasum.digest('base64'));
var keyAuthorization = token + '.' + thumbprint;
debug('prepareHttpChallenge: writing %s to %s', keyAuthorization, path.join(paths.ACME_CHALLENGES_DIR, token));
fs.writeFile(path.join(paths.ACME_CHALLENGES_DIR, token), token + '.' + thumbprint, function (error) {
if (error) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, error));
callback();
});
};
Acme.prototype.notifyChallengeReady = function (challenge, callback) {
assert.strictEqual(typeof challenge, 'object');
assert.strictEqual(typeof callback, 'function');
debug('notifyChallengeReady: %s was met', challenge.uri);
var keyAuthorization = fs.readFileSync(path.join(paths.ACME_CHALLENGES_DIR, challenge.token), 'utf8');
var payload = {
resource: 'challenge',
keyAuthorization: keyAuthorization
};
this.sendSignedRequest(challenge.uri, JSON.stringify(payload), function (error, result) {
if (error) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when notifying challenge: ' + error.message));
if (result.statusCode !== 202) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to notify challenge. Expecting 202, got %s %s', result.statusCode, result.text)));
callback();
});
};
Acme.prototype.waitForChallenge = function (challenge, callback) {
assert.strictEqual(typeof challenge, 'object');
assert.strictEqual(typeof callback, 'function');
debug('waitingForChallenge: %j', challenge);
async.retry({ times: 10, interval: 5000 }, function (retryCallback) {
debug('waitingForChallenge: getting status');
superagent.get(challenge.uri).end(function (error, result) {
if (error && !error.response) {
debug('waitForChallenge: network error getting uri %s', challenge.uri);
return retryCallback(new AcmeError(AcmeError.EXTERNAL_ERROR, error.message)); // network error
}
if (result.statusCode !== 202) {
debug('waitForChallenge: invalid response code getting uri %s', result.statusCode);
return retryCallback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Bad response code:' + result.statusCode));
}
debug('waitForChallenge: status is "%s"', result.body.status);
if (result.body.status === 'pending') return retryCallback(new AcmeError(AcmeError.NOT_COMPLETED));
else if (result.body.status === 'valid') return retryCallback();
else return retryCallback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Unexpected status: ' + result.body.status));
});
}, function retryFinished(error) {
// async.retry will pass 'undefined' as second arg making it unusable with async.waterfall()
callback(error);
});
};
// https://community.letsencrypt.org/t/public-beta-rate-limits/4772 for rate limits
Acme.prototype.signCertificate = function (domain, csrDer, callback) {
assert.strictEqual(typeof domain, 'string');
assert(util.isBuffer(csrDer));
assert.strictEqual(typeof callback, 'function');
var outdir = paths.APP_CERTS_DIR;
var payload = {
resource: 'new-cert',
csr: b64(csrDer)
};
debug('signCertificate: sending new-cert request');
this.sendSignedRequest(this.caOrigin + '/acme/new-cert', JSON.stringify(payload), function (error, result) {
if (error) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when signing certificate: ' + error.message));
// 429 means we reached the cert limit for this domain
if (result.statusCode !== 201) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to sign certificate. Expecting 201, got %s %s', result.statusCode, result.text)));
var certUrl = result.headers.location;
if (!certUrl) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Missing location in downloadCertificate'));
safe.fs.writeFileSync(path.join(outdir, domain + '.url'), certUrl, 'utf8'); // maybe use for renewal
return callback(null, result.headers.location);
});
};
Acme.prototype.createKeyAndCsr = function (domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
var outdir = paths.APP_CERTS_DIR;
var csrFile = path.join(outdir, domain + '.csr');
var privateKeyFile = path.join(outdir, domain + '.key');
var execSync = safe.child_process.execSync;
if (safe.fs.existsSync(privateKeyFile)) {
// in some old releases, csr file was corrupt. so always regenerate it
debug('createKeyAndCsr: reuse the key for renewal at %s', privateKeyFile);
} else {
var key = execSync('openssl genrsa 4096');
if (!key) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
if (!safe.fs.writeFileSync(privateKeyFile, key)) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
debug('createKeyAndCsr: key file saved at %s', privateKeyFile);
}
var csrDer = execSync(util.format('openssl req -new -key %s -outform DER -subj /CN=%s', privateKeyFile, domain));
if (!csrDer) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
if (!safe.fs.writeFileSync(csrFile, csrDer)) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error)); // bookkeeping
debug('createKeyAndCsr: csr file (DER) saved at %s', csrFile);
callback(null, csrDer);
};
// TODO: download the chain in a loop following 'up' header
Acme.prototype.downloadChain = function (linkHeader, callback) {
if (!linkHeader) return new AcmeError(AcmeError.EXTERNAL_ERROR, 'Empty link header when downloading certificate chain');
var linkInfo = parseLinks(linkHeader);
if (!linkInfo || !linkInfo.up) return new AcmeError(AcmeError.EXTERNAL_ERROR, 'Failed to parse link header when downloading certificate chain');
debug('downloadChain: downloading from %s', this.caOrigin + linkInfo.up);
superagent.get(this.caOrigin + linkInfo.up).buffer().parse(function (res, done) {
var data = [ ];
res.on('data', function(chunk) { data.push(chunk); });
res.on('end', function () { res.text = Buffer.concat(data); done(); });
}).end(function (error, result) {
if (error && !error.response) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when downloading certificate'));
if (result.statusCode !== 200) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to get cert. Expecting 200, got %s %s', result.statusCode, result.text)));
var chainDer = result.text;
var execSync = safe.child_process.execSync;
var chainPem = execSync('openssl x509 -inform DER -outform PEM', { input: chainDer }); // this is really just base64 encoding with header
if (!chainPem) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
callback(null, chainPem);
});
};
Acme.prototype.downloadCertificate = function (domain, certUrl, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof certUrl, 'string');
assert.strictEqual(typeof callback, 'function');
var outdir = paths.APP_CERTS_DIR;
var that = this;
superagent.get(certUrl).buffer().parse(function (res, done) {
var data = [ ];
res.on('data', function(chunk) { data.push(chunk); });
res.on('end', function () { res.text = Buffer.concat(data); done(); });
}).end(function (error, result) {
if (error && !error.response) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when downloading certificate'));
if (result.statusCode === 202) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, 'Retry not implemented yet'));
if (result.statusCode !== 200) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to get cert. Expecting 200, got %s %s', result.statusCode, result.text)));
var certificateDer = result.text;
var execSync = safe.child_process.execSync;
safe.fs.writeFileSync(path.join(outdir, domain + '.der'), certificateDer);
debug('downloadCertificate: cert der file for %s saved', domain);
var certificatePem = execSync('openssl x509 -inform DER -outform PEM', { input: certificateDer }); // this is really just base64 encoding with header
if (!certificatePem) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
that.downloadChain(result.header['link'], function (error, chainPem) {
if (error) return callback(error);
var certificateFile = path.join(outdir, domain + '.cert');
var fullChainPem = Buffer.concat([certificatePem, chainPem]);
if (!safe.fs.writeFileSync(certificateFile, fullChainPem)) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
debug('downloadCertificate: cert file for %s saved at %s', domain, certificateFile);
callback();
});
});
};
Acme.prototype.acmeFlow = function (domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
if (!fs.existsSync(paths.ACME_ACCOUNT_KEY_FILE)) {
debug('getCertificate: generating acme account key on first run');
this.accountKeyPem = safe.child_process.execSync('openssl genrsa 4096');
if (!this.accountKeyPem) return callback(new AcmeError(AcmeError.INTERNAL_ERROR, safe.error));
safe.fs.writeFileSync(paths.ACME_ACCOUNT_KEY_FILE, this.accountKeyPem);
} else {
debug('getCertificate: using existing acme account key');
this.accountKeyPem = fs.readFileSync(paths.ACME_ACCOUNT_KEY_FILE);
}
var that = this;
this.registerUser(function (error) {
if (error) return callback(error);
that.registerDomain(domain, function (error, result) {
if (error) return callback(error);
debug('acmeFlow: challenges: %j', result);
var httpChallenges = result.challenges.filter(function(x) { return x.type === 'http-01'; });
if (httpChallenges.length === 0) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'no http challenges'));
var challenge = httpChallenges[0];
async.waterfall([
that.prepareHttpChallenge.bind(that, challenge),
that.notifyChallengeReady.bind(that, challenge),
that.waitForChallenge.bind(that, challenge),
that.createKeyAndCsr.bind(that, domain),
that.signCertificate.bind(that, domain),
that.downloadCertificate.bind(that, domain)
], callback);
});
});
};
Acme.prototype.getCertificate = function (domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
debug('getCertificate: start acme flow for %s from %s', domain, this.caOrigin);
this.acmeFlow(domain, function (error) {
if (error) return callback(error);
var outdir = paths.APP_CERTS_DIR;
callback(null, path.join(outdir, domain + '.cert'), path.join(outdir, domain + '.key'));
});
};
function getCertificate(domain, options, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
var acme = new Acme(options || { });
acme.getCertificate(domain, callback);
}

18
src/cert/caas.js Normal file
View File

@@ -0,0 +1,18 @@
'use strict';
exports = module.exports = {
getCertificate: getCertificate
};
var assert = require('assert'),
debug = require('debug')('box:cert/caas.js');
function getCertificate(domain, options, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
debug('getCertificate: using fallback certificate', domain);
return callback(null, 'cert/host.cert', 'cert/host.key');
}

336
src/certificates.js Normal file
View File

@@ -0,0 +1,336 @@
'use strict';
exports = module.exports = {
installAdminCertificate: installAdminCertificate,
renewAll: renewAll,
setFallbackCertificate: setFallbackCertificate,
setAdminCertificate: setAdminCertificate,
CertificatesError: CertificatesError,
validateCertificate: validateCertificate,
ensureCertificate: ensureCertificate,
getAdminCertificatePath: getAdminCertificatePath
};
var acme = require('./cert/acme.js'),
apps = require('./apps.js'),
assert = require('assert'),
async = require('async'),
caas = require('./cert/caas.js'),
cloudron = require('./cloudron.js'),
config = require('./config.js'),
constants = require('./constants.js'),
debug = require('debug')('box:src/certificates'),
eventlog = require('./eventlog.js'),
fs = require('fs'),
mailer = require('./mailer.js'),
nginx = require('./nginx.js'),
path = require('path'),
paths = require('./paths.js'),
safe = require('safetydance'),
settings = require('./settings.js'),
sysinfo = require('./sysinfo.js'),
user = require('./user.js'),
util = require('util'),
waitForDns = require('./waitfordns.js'),
x509 = require('x509');
var NOOP_CALLBACK = function (error) { if (error) debug(error); };
function CertificatesError(reason, errorOrMessage) {
assert.strictEqual(typeof reason, 'string');
assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined');
Error.call(this);
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.reason = reason;
if (typeof errorOrMessage === 'undefined') {
this.message = reason;
} else if (typeof errorOrMessage === 'string') {
this.message = errorOrMessage;
} else {
this.message = 'Internal error';
this.nestedError = errorOrMessage;
}
}
util.inherits(CertificatesError, Error);
CertificatesError.INTERNAL_ERROR = 'Internal Error';
CertificatesError.INVALID_CERT = 'Invalid certificate';
CertificatesError.NOT_FOUND = 'Not Found';
function getApi(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
settings.getTlsConfig(function (error, tlsConfig) {
if (error) return callback(error);
var api = !app.altDomain && tlsConfig.provider === 'caas' ? caas : acme;
var options = { };
// used by acme backend to determine the LE origin.
options.prod = (api === caas) ? !config.isDev() : tlsConfig.provider.match(/.*-prod/) !== null;
// registering user with an email requires A or MX record (https://github.com/letsencrypt/boulder/issues/1197)
// we cannot use admin@fqdn because the user might not have set it up.
// we simply update the account with the latest email we have each time when getting letsencrypt certs
// https://github.com/ietf-wg-acme/acme/issues/30
user.getOwner(function (error, owner) {
options.email = error ? 'admin@cloudron.io' : owner.email; // can error if not activated yet
callback(null, api, options);
});
});
}
function installAdminCertificate(callback) {
if (cloudron.isConfiguredSync()) return callback();
settings.getTlsConfig(function (error, tlsConfig) {
if (error) return callback(error);
if (tlsConfig.provider === 'caas') return callback();
sysinfo.getIp(function (error, ip) {
if (error) return callback(error);
waitForDns(config.adminFqdn(), ip, 'A', function (error) {
if (error) return callback(error); // this cannot happen because we retry forever
ensureCertificate({ location: constants.ADMIN_LOCATION }, function (error, certFilePath, keyFilePath) {
if (error) { // currently, this can never happen
debug('Error obtaining certificate. Proceed anyway', error);
return callback();
}
nginx.configureAdmin(certFilePath, keyFilePath, callback);
});
});
});
});
}
function isExpiringSync(certFilePath, hours) {
assert.strictEqual(typeof certFilePath, 'string');
assert.strictEqual(typeof hours, 'number');
if (!fs.existsSync(certFilePath)) return 2; // not found
var result = safe.child_process.spawnSync('/usr/bin/openssl', [ 'x509', '-checkend', String(60 * 60 * hours), '-in', certFilePath ]);
debug('isExpiringSync: %s %s %s', certFilePath, result.stdout.toString('utf8').trim(), result.status);
return result.status === 1; // 1 - expired 0 - not expired
}
function renewAll(auditSource, callback) {
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
debug('renewAll: Checking certificates for renewal');
apps.getAll(function (error, allApps) {
if (error) return callback(error);
allApps.push({ location: constants.ADMIN_LOCATION }); // inject fake webadmin app
var expiringApps = [ ];
for (var i = 0; i < allApps.length; i++) {
var appDomain = allApps[i].altDomain || config.appFqdn(allApps[i].location);
var certFilePath = path.join(paths.APP_CERTS_DIR, appDomain + '.cert');
var keyFilePath = path.join(paths.APP_CERTS_DIR, appDomain + '.key');
if (!safe.fs.existsSync(keyFilePath)) {
debug('renewAll: no existing key file for %s. skipping', appDomain);
continue;
}
if (isExpiringSync(certFilePath, 24 * 30)) { // expired or not found
expiringApps.push(allApps[i]);
}
}
debug('renewAll: %j needs to be renewed', expiringApps.map(function (a) { return a.altDomain || config.appFqdn(a.location); }));
async.eachSeries(expiringApps, function iterator(app, iteratorCallback) {
var domain = app.altDomain || config.appFqdn(app.location);
getApi(app, function (error, api, apiOptions) {
if (error) return callback(error);
debug('renewAll: renewing cert for %s with options %j', domain, apiOptions);
api.getCertificate(domain, apiOptions, function (error) {
var certFilePath = path.join(paths.APP_CERTS_DIR, domain + '.cert');
var keyFilePath = path.join(paths.APP_CERTS_DIR, domain + '.key');
var errorMessage = error ? error.message : '';
eventlog.add(eventlog.ACTION_CERTIFICATE_RENEWAL, auditSource, { domain: domain, errorMessage: errorMessage });
mailer.certificateRenewed(domain, errorMessage);
if (error) {
debug('renewAll: could not renew cert for %s because %s', domain, error);
// check if we should fallback if we expire in the coming day
if (!isExpiringSync(certFilePath, 24 * 1)) return iteratorCallback();
debug('renewAll: using fallback certs for %s since it expires soon', domain, error);
certFilePath = 'cert/host.cert';
keyFilePath = 'cert/host.key';
} else {
debug('renewAll: certificate for %s renewed', domain);
}
// reconfigure and reload nginx. this is required for the case where we got a renewed cert after fallback
var configureFunc = app.location === constants.ADMIN_LOCATION ?
nginx.configureAdmin.bind(null, certFilePath, keyFilePath)
: nginx.configureApp.bind(null, app, certFilePath, keyFilePath);
configureFunc(function (ignoredError) {
if (ignoredError) debug('fallbackExpiredCertificates: error reconfiguring app', ignoredError);
iteratorCallback(); // move to next app
});
});
});
});
});
}
// note: https://tools.ietf.org/html/rfc4346#section-7.4.2 (certificate_list) requires that the
// servers certificate appears first (and not the intermediate cert)
function validateCertificate(cert, key, fqdn) {
assert(cert === null || typeof cert === 'string');
assert(key === null || typeof key === 'string');
assert.strictEqual(typeof fqdn, 'string');
if (cert === null && key === null) return null;
if (!cert && key) return new Error('missing cert');
if (cert && !key) return new Error('missing key');
var content;
try {
content = x509.parseCert(cert);
} catch (e) {
return new Error('invalid cert: ' + e.message);
}
// check expiration
if (content.notAfter < new Date()) return new Error('cert expired');
function matchesDomain(domain) {
if (domain === fqdn) return true;
if (domain.indexOf('*') === 0 && domain.slice(2) === fqdn.slice(fqdn.indexOf('.') + 1)) return true;
return false;
}
// check domain
var domains = content.altNames.concat(content.subject.commonName);
if (!domains.some(matchesDomain)) return new Error(util.format('cert is not valid for this domain. Expecting %s in %j', fqdn, domains));
// http://httpd.apache.org/docs/2.0/ssl/ssl_faq.html#verify
var certModulus = safe.child_process.execSync('openssl x509 -noout -modulus', { encoding: 'utf8', input: cert });
var keyModulus = safe.child_process.execSync('openssl rsa -noout -modulus', { encoding: 'utf8', input: key });
if (certModulus !== keyModulus) return new Error('key does not match the cert');
return null;
}
function setFallbackCertificate(cert, key, callback) {
assert.strictEqual(typeof cert, 'string');
assert.strictEqual(typeof key, 'string');
assert.strictEqual(typeof callback, 'function');
var error = validateCertificate(cert, key, '*.' + config.fqdn());
if (error) return callback(new CertificatesError(CertificatesError.INVALID_CERT, error.message));
// backup the cert
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, 'host.cert'), cert)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message));
if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, 'host.key'), key)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message));
// copy over fallback cert
if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, 'host.cert'), cert)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message));
if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), key)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message));
nginx.reload(function (error) {
if (error) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, error));
return callback(null);
});
}
function getFallbackCertificatePath(callback) {
assert.strictEqual(typeof callback, 'function');
// any user fallback cert is always copied over to nginx cert dir
callback(null, path.join(paths.NGINX_CERT_DIR, 'host.cert'), path.join(paths.NGINX_CERT_DIR, 'host.key'));
}
// FIXME: setting admin cert needs to restart the mail container because it uses admin cert
function setAdminCertificate(cert, key, callback) {
assert.strictEqual(typeof cert, 'string');
assert.strictEqual(typeof key, 'string');
assert.strictEqual(typeof callback, 'function');
var vhost = config.adminFqdn();
var certFilePath = path.join(paths.APP_CERTS_DIR, vhost + '.cert');
var keyFilePath = path.join(paths.APP_CERTS_DIR, vhost + '.key');
var error = validateCertificate(cert, key, vhost);
if (error) return callback(new CertificatesError(CertificatesError.INVALID_CERT, error.message));
// backup the cert
if (!safe.fs.writeFileSync(certFilePath, cert)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message));
if (!safe.fs.writeFileSync(keyFilePath, key)) return callback(new CertificatesError(CertificatesError.INTERNAL_ERROR, safe.error.message));
nginx.configureAdmin(certFilePath, keyFilePath, callback);
}
function getAdminCertificatePath(callback) {
assert.strictEqual(typeof callback, 'function');
var vhost = config.adminFqdn();
var certFilePath = path.join(paths.APP_CERTS_DIR, vhost + '.cert');
var keyFilePath = path.join(paths.APP_CERTS_DIR, vhost + '.key');
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, certFilePath, keyFilePath);
getFallbackCertificatePath(callback);
}
function ensureCertificate(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
var domain = app.altDomain || config.appFqdn(app.location);
// check if user uploaded a specific cert. ideally, we should not mix user certs and automatic certs as we do here...
var userCertFilePath = path.join(paths.APP_CERTS_DIR, domain + '.cert');
var userKeyFilePath = path.join(paths.APP_CERTS_DIR, domain + '.key');
if (fs.existsSync(userCertFilePath) && fs.existsSync(userKeyFilePath)) {
debug('ensureCertificate: %s. certificate already exists at %s', domain, userKeyFilePath);
if (!isExpiringSync(userCertFilePath, 24 * 1)) return callback(null, userCertFilePath, userKeyFilePath);
}
debug('ensureCertificate: %s cert require renewal', domain);
getApi(app, function (error, api, apiOptions) {
if (error) return callback(error);
debug('ensureCertificate: getting certificate for %s with options %j', domain, apiOptions);
api.getCertificate(domain, apiOptions, function (error, certFilePath, keyFilePath) {
if (error) {
debug('ensureCertificate: could not get certificate. using fallback certs', error);
return callback(null, 'cert/host.cert', 'cert/host.key'); // use fallback certs
}
callback(null, certFilePath, keyFilePath);
});
});
}

View File

@@ -5,12 +5,15 @@
exports = module.exports = {
get: get,
getAll: getAll,
getAllWithTokenCount: getAllWithTokenCount,
getAllWithTokenCountByIdentifier: getAllWithTokenCountByIdentifier,
add: add,
del: del,
update: update,
getByAppId: getByAppId,
getByAppIdAndType: getByAppIdAndType,
delByAppId: delByAppId,
delByAppIdAndType: delByAppIdAndType,
_clear: clear
};
@@ -19,8 +22,8 @@ var assert = require('assert'),
database = require('./database.js'),
DatabaseError = require('./databaseerror.js');
var CLIENTS_FIELDS = [ 'id', 'appId', 'clientSecret', 'redirectURI', 'scope' ].join(',');
var CLIENTS_FIELDS_PREFIXED = [ 'clients.id', 'clients.appId', 'clients.clientSecret', 'clients.redirectURI', 'clients.scope' ].join(',');
var CLIENTS_FIELDS = [ 'id', 'appId', 'type', 'clientSecret', 'redirectURI', 'scope' ].join(',');
var CLIENTS_FIELDS_PREFIXED = [ 'clients.id', 'clients.appId', 'clients.type', 'clients.clientSecret', 'clients.redirectURI', 'clients.scope' ].join(',');
function get(id, callback) {
assert.strictEqual(typeof id, 'string');
@@ -44,14 +47,24 @@ function getAll(callback) {
});
}
function getAllWithTokenCount(callback) {
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + CLIENTS_FIELDS_PREFIXED + ',COUNT(tokens.clientId) AS tokenCount FROM clients LEFT OUTER JOIN tokens ON clients.id=tokens.clientId GROUP BY clients.id', [], function (error, results) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
callback(null, results);
});
}
function getAllWithTokenCountByIdentifier(identifier, callback) {
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + CLIENTS_FIELDS_PREFIXED + ',COUNT(tokens.clientId) AS tokenCount FROM clients LEFT OUTER JOIN tokens ON clients.id=tokens.clientId WHERE tokens.identifier=? GROUP BY clients.id', [ identifier ], function (error, results) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
callback(null, results);
callback(null, results);
});
}
@@ -63,21 +76,35 @@ function getByAppId(appId, callback) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
if (result.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND));
return callback(null, result[0]);
callback(null, result[0]);
});
}
function add(id, appId, clientSecret, redirectURI, scope, callback) {
function getByAppIdAndType(appId, type, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + CLIENTS_FIELDS + ' FROM clients WHERE appId = ? AND type = ? LIMIT 1', [ appId, type ], function (error, result) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
if (result.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND));
callback(null, result[0]);
});
}
function add(id, appId, type, clientSecret, redirectURI, scope, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof clientSecret, 'string');
assert.strictEqual(typeof redirectURI, 'string');
assert.strictEqual(typeof scope, 'string');
assert.strictEqual(typeof callback, 'function');
var data = [ id, appId, clientSecret, redirectURI, scope ];
var data = [ id, appId, type, clientSecret, redirectURI, scope ];
database.query('INSERT INTO clients (id, appId, clientSecret, redirectURI, scope) VALUES (?, ?, ?, ?, ?)', data, function (error, result) {
database.query('INSERT INTO clients (id, appId, type, clientSecret, redirectURI, scope) VALUES (?, ?, ?, ?, ?, ?)', data, function (error, result) {
if (error && error.code === 'ER_DUP_ENTRY') return callback(new DatabaseError(DatabaseError.ALREADY_EXISTS));
if (error || result.affectedRows === 0) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
@@ -85,24 +112,6 @@ function add(id, appId, clientSecret, redirectURI, scope, callback) {
});
}
function update(id, appId, clientSecret, redirectURI, scope, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof clientSecret, 'string');
assert.strictEqual(typeof redirectURI, 'string');
assert.strictEqual(typeof scope, 'string');
assert.strictEqual(typeof callback, 'function');
var data = [ appId, clientSecret, redirectURI, scope, id ];
database.query('UPDATE clients SET appId = ?, clientSecret = ?, redirectURI = ?, scope = ? WHERE id = ?', data, function (error, result) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
if (result.affectedRows !== 1) return callback(new DatabaseError(DatabaseError.NOT_FOUND));
callback(null);
});
}
function del(id, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof callback, 'function');
@@ -123,17 +132,30 @@ function delByAppId(appId, callback) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
if (result.affectedRows !== 1) return callback(new DatabaseError(DatabaseError.NOT_FOUND));
return callback(null);
callback(null);
});
}
function delByAppIdAndType(appId, type, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('DELETE FROM clients WHERE appId=? AND type=?', [ appId, type ], function (error, result) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
if (result.affectedRows !== 1) return callback(new DatabaseError(DatabaseError.NOT_FOUND));
callback(null);
});
}
function clear(callback) {
assert.strictEqual(typeof callback, 'function');
database.query('DELETE FROM clients WHERE appId!="webadmin"', function (error) {
database.query('DELETE FROM clients WHERE id!="cid-webadmin" AND id!="cid-sdk" AND id!="cid-cli"', function (error) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
return callback(null);
callback(null);
});
}

View File

@@ -5,11 +5,33 @@ exports = module.exports = {
add: add,
get: get,
update: update,
del: del,
getAllWithDetailsByUserId: getAllWithDetailsByUserId,
getAll: getAll,
getByAppIdAndType: getByAppIdAndType,
getClientTokensByUserId: getClientTokensByUserId,
delClientTokensByUserId: delClientTokensByUserId
delClientTokensByUserId: delClientTokensByUserId,
delByAppIdAndType: delByAppIdAndType,
addClientTokenByUserId: addClientTokenByUserId,
delToken: delToken,
// keep this in sync with start.sh ADMIN_SCOPES that generates the cid-webadmin
SCOPE_APPS: 'apps',
SCOPE_DEVELOPER: 'developer',
SCOPE_PROFILE: 'profile',
SCOPE_CLOUDRON: 'cloudron',
SCOPE_SETTINGS: 'settings',
SCOPE_USERS: 'users',
// roles are handled just like the above scopes, they are parallel to scopes
// scopes enclose API groups, roles specify the usage role
SCOPE_ROLE_SDK: 'roleSdk',
// client type enums
TYPE_EXTERNAL: 'external',
TYPE_BUILT_IN: 'built-in',
TYPE_OAUTH: 'addon-oauth',
TYPE_SIMPLE_AUTH: 'addon-simpleauth',
TYPE_PROXY: 'addon-proxy'
};
var assert = require('assert'),
@@ -17,7 +39,6 @@ var assert = require('assert'),
hat = require('hat'),
appdb = require('./appdb.js'),
tokendb = require('./tokendb.js'),
constants = require('./constants.js'),
async = require('async'),
clientdb = require('./clientdb.js'),
DatabaseError = require('./databaseerror.js'),
@@ -43,36 +64,57 @@ function ClientsError(reason, errorOrMessage) {
}
util.inherits(ClientsError, Error);
ClientsError.INVALID_SCOPE = 'Invalid scope';
ClientsError.INVALID_CLIENT = 'Invalid client';
ClientsError.INVALID_TOKEN = 'Invalid token';
ClientsError.NOT_FOUND = 'Not found';
ClientsError.INTERNAL_ERROR = 'Internal Error';
ClientsError.NOT_ALLOWED = 'Not allowed to remove this client';
function validateScope(scope) {
assert.strictEqual(typeof scope, 'string');
if (scope === '') return new ClientsError(ClientsError.INVALID_SCOPE);
if (scope === '*') return null;
var VALID_SCOPES = [
exports.SCOPE_APPS,
exports.SCOPE_DEVELOPER,
exports.SCOPE_PROFILE,
exports.SCOPE_CLOUDRON,
exports.SCOPE_SETTINGS,
exports.SCOPE_USERS,
'*', // includes all scopes, but not roles
exports.SCOPE_ROLE_SDK
];
// TODO maybe validate all individual scopes if they exist
if (scope === '') return new ClientsError(ClientsError.INVALID_SCOPE, 'Empty scope not allowed');
var allValid = scope.split(',').every(function (s) { return VALID_SCOPES.indexOf(s) !== -1; });
if (!allValid) return new ClientsError(ClientsError.INVALID_SCOPE, 'Invalid scope. Available scopes are ' + VALID_SCOPES.join(', '));
return null;
}
function add(appIdentifier, redirectURI, scope, callback) {
assert.strictEqual(typeof appIdentifier, 'string');
function add(appId, type, redirectURI, scope, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof redirectURI, 'string');
assert.strictEqual(typeof scope, 'string');
assert.strictEqual(typeof callback, 'function');
// allow whitespace
scope = scope.split(',').map(function (s) { return s.trim(); }).join(',');
var error = validateScope(scope);
if (error) return callback(error);
var id = 'cid-' + uuid.v4();
var clientSecret = hat(256);
clientdb.add(id, appIdentifier, clientSecret, redirectURI, scope, function (error) {
clientdb.add(id, appId, type, clientSecret, redirectURI, scope, function (error) {
if (error) return callback(error);
var client = {
id: id,
appId: appIdentifier,
appId: appId,
type: type,
clientSecret: clientSecret,
redirectURI: redirectURI,
scope: scope
@@ -87,94 +129,52 @@ function get(id, callback) {
assert.strictEqual(typeof callback, 'function');
clientdb.get(id, function (error, result) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new ClientsError(ClientsError.NOT_FOUND, 'No such client'));
if (error) return callback(error);
callback(null, result);
});
}
// we only allow appIdentifier and redirectURI to be updated
function update(id, appIdentifier, redirectURI, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof appIdentifier, 'string');
assert.strictEqual(typeof redirectURI, 'string');
assert.strictEqual(typeof callback, 'function');
clientdb.get(id, function (error, result) {
if (error) return callback(error);
clientdb.update(id, appIdentifier, result.clientSecret, redirectURI, result.scope, function (error, result) {
if (error) return callback(error);
callback(null, result);
});
});
}
function del(id, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof callback, 'function');
clientdb.del(id, function (error, result) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new ClientsError(ClientsError.NOT_FOUND, 'No such client'));
if (error) return callback(error);
callback(null, result);
});
}
function getAllWithDetailsByUserId(userId, callback) {
assert.strictEqual(typeof userId, 'string');
function getAll(callback) {
assert.strictEqual(typeof callback, 'function');
clientdb.getAllWithTokenCountByIdentifier(tokendb.PREFIX_USER + userId, function (error, results) {
clientdb.getAll(function (error, results) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, []);
if (error) return callback(error);
// We have several types of records here
// 1) webadmin has an app id of 'webadmin'
// 2) oauth proxy records are always the app id prefixed with 'proxy-'
// 3) addon oauth records for apps prefixed with 'addon-'
// 4) external app records prefixed with 'external-'
// 5) normal apps on the cloudron without a prefix
var tmp = [];
async.each(results, function (record, callback) {
if (record.appId === constants.ADMIN_CLIENT_ID) {
record.name = constants.ADMIN_NAME;
record.location = constants.ADMIN_LOCATION;
record.type = 'webadmin';
tmp.push(record);
return callback(null);
} else if (record.appId === constants.TEST_CLIENT_ID) {
record.name = constants.TEST_NAME;
record.location = constants.TEST_LOCATION;
record.type = 'test';
if (record.type === exports.TYPE_EXTERNAL || record.type === exports.TYPE_BUILT_IN) {
// the appId in this case holds the name
record.name = record.appId;
tmp.push(record);
return callback(null);
}
var appId = record.appId;
var type = 'app';
// Handle our different types of oauth clients
if (record.appId.indexOf('addon-') === 0) {
appId = record.appId.slice('addon-'.length);
type = 'addon';
} else if (record.appId.indexOf('proxy-') === 0) {
appId = record.appId.slice('proxy-'.length);
type = 'proxy';
}
appdb.get(appId, function (error, result) {
appdb.get(record.appId, function (error, result) {
if (error) {
console.error('Failed to get app details for oauth client', result, error);
console.error('Failed to get app details for oauth client', record.appId, error);
return callback(null); // ignore error so we continue listing clients
}
record.name = result.manifest.title + (record.appId.indexOf('proxy-') === 0 ? 'OAuth Proxy' : '');
if (record.type === exports.TYPE_PROXY) record.name = result.manifest.title + ' Website Proxy';
if (record.type === exports.TYPE_OAUTH) record.name = result.manifest.title + ' OAuth';
if (record.type === exports.TYPE_SIMPLE_AUTH) record.name = result.manifest.title + ' Simple Auth';
record.location = result.location;
record.type = type;
tmp.push(record);
@@ -187,15 +187,27 @@ function getAllWithDetailsByUserId(userId, callback) {
});
}
function getByAppIdAndType(appId, type, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof callback, 'function');
clientdb.getByAppIdAndType(appId, type, function (error, result) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new ClientsError(ClientsError.NOT_FOUND, 'No such client'));
if (error) return callback(error);
callback(null, result);
});
}
function getClientTokensByUserId(clientId, userId, callback) {
assert.strictEqual(typeof clientId, 'string');
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof callback, 'function');
tokendb.getByIdentifierAndClientId(tokendb.PREFIX_USER + userId, clientId, function (error, result) {
tokendb.getByIdentifierAndClientId(userId, clientId, function (error, result) {
if (error && error.reason === DatabaseError.NOT_FOUND) {
// this can mean either that there are no tokens or the clientId is actually unknown
clientdb.get(clientId, function (error/*, result*/) {
get(clientId, function (error/*, result*/) {
if (error) return callback(error);
callback(null, []);
});
@@ -211,10 +223,10 @@ function delClientTokensByUserId(clientId, userId, callback) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof callback, 'function');
tokendb.delByIdentifierAndClientId(tokendb.PREFIX_USER + userId, clientId, function (error) {
tokendb.delByIdentifierAndClientId(userId, clientId, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) {
// this can mean either that there are no tokens or the clientId is actually unknown
clientdb.get(clientId, function (error/*, result*/) {
get(clientId, function (error/*, result*/) {
if (error) return callback(error);
callback(null);
});
@@ -224,3 +236,57 @@ function delClientTokensByUserId(clientId, userId, callback) {
callback(null);
});
}
function delByAppIdAndType(appId, type, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof callback, 'function');
clientdb.delByAppIdAndType(appId, type, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new ClientsError(ClientsError.NOT_FOUND, 'No such client'));
if (error) return callback(error);
callback(null);
});
}
function addClientTokenByUserId(clientId, userId, expiresAt, callback) {
assert.strictEqual(typeof clientId, 'string');
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof expiresAt, 'number');
assert.strictEqual(typeof callback, 'function');
get(clientId, function (error, result) {
if (error) return callback(error);
var token = tokendb.generateToken();
tokendb.add(token, userId, result.id, expiresAt, result.scope, function (error) {
if (error) return callback(new ClientsError(ClientsError.INTERNAL_ERROR, error));
callback(null, {
accessToken: token,
identifier: userId,
clientId: result.id,
scope: result.id,
expires: expiresAt
});
});
});
}
function delToken(clientId, tokenId, callback) {
assert.strictEqual(typeof clientId, 'string');
assert.strictEqual(typeof tokenId, 'string');
assert.strictEqual(typeof callback, 'function');
get(clientId, function (error, result) {
if (error) return callback(error);
tokendb.del(tokenId, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new ClientsError(ClientsError.INVALID_TOKEN, 'Invalid token'));
if (error) return callback(new ClientsError(ClientsError.INTERNAL_ERROR, error));
callback(null);
});
});
}

View File

@@ -1,5 +1,3 @@
/* jslint node: true */
'use strict';
exports = module.exports = {
@@ -11,68 +9,63 @@ exports = module.exports = {
getConfig: getConfig,
getStatus: getStatus,
setCertificate: setCertificate,
sendHeartbeat: sendHeartbeat,
update: update,
updateToLatest: updateToLatest,
reboot: reboot,
migrate: migrate,
backup: backup,
ensureBackup: ensureBackup};
retire: retire,
isConfiguredSync: isConfiguredSync,
checkDiskSpace: checkDiskSpace,
events: new (require('events').EventEmitter)(),
EVENT_ACTIVATED: 'activated',
EVENT_CONFIGURED: 'configured',
EVENT_FIRST_RUN: 'firstrun'
};
var apps = require('./apps.js'),
AppsError = require('./apps.js').AppsError,
assert = require('assert'),
async = require('async'),
backups = require('./backups.js'),
BackupsError = require('./backups.js').BackupsError,
clientdb = require('./clientdb.js'),
clients = require('./clients.js'),
config = require('./config.js'),
constants = require('./constants.js'),
debug = require('debug')('box:cloudron'),
df = require('node-df'),
eventlog = require('./eventlog.js'),
fs = require('fs'),
locker = require('./locker.js'),
mailer = require('./mailer.js'),
os = require('os'),
path = require('path'),
paths = require('./paths.js'),
progress = require('./progress.js'),
safe = require('safetydance'),
settings = require('./settings.js'),
SettingsError = settings.SettingsError,
shell = require('./shell.js'),
subdomains = require('./subdomains.js'),
superagent = require('superagent'),
sysinfo = require('./sysinfo.js'),
tokendb = require('./tokendb.js'),
updateChecker = require('./updatechecker.js'),
user = require('./user.js'),
UserError = user.UserError,
userdb = require('./userdb.js'),
user = require('./user.js'),
util = require('util');
var RELOAD_NGINX_CMD = path.join(__dirname, 'scripts/reloadnginx.sh'),
REBOOT_CMD = path.join(__dirname, 'scripts/reboot.sh'),
BACKUP_BOX_CMD = path.join(__dirname, 'scripts/backupbox.sh'),
BACKUP_SWAP_CMD = path.join(__dirname, 'scripts/backupswap.sh'),
INSTALLER_UPDATE_URL = 'http://127.0.0.1:2020/api/v1/installer/update';
var REBOOT_CMD = path.join(__dirname, 'scripts/reboot.sh'),
INSTALLER_UPDATE_URL = 'http://127.0.0.1:2020/api/v1/installer/update',
RETIRE_CMD = path.join(__dirname, 'scripts/retire.sh');
var gAddMailDnsRecordsTimerId = null,
gCloudronDetails = null; // cached cloudron details like region,size...
function debugApp(app, args) {
assert(!app || typeof app === 'object');
var prefix = app ? app.location : '(no app)';
debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
}
function ignoreError(func) {
return function (callback) {
func(function (error) {
if (error) console.error('Ignored error:', error);
callback();
});
};
}
var NOOP_CALLBACK = function (error) { if (error) debug(error); };
var gUpdatingDns = false, // flag for dns update reentrancy
gCloudronDetails = null, // cached cloudron details like region,size...
gAppstoreUserDetails = {},
gIsConfigured = null; // cached configured state so that return value is synchronous. null means we are not initialized yet
function CloudronError(reason, errorOrMessage) {
assert.strictEqual(typeof reason, 'string');
@@ -97,95 +90,161 @@ CloudronError.BAD_FIELD = 'Field error';
CloudronError.INTERNAL_ERROR = 'Internal Error';
CloudronError.EXTERNAL_ERROR = 'External Error';
CloudronError.ALREADY_PROVISIONED = 'Already Provisioned';
CloudronError.BAD_USERNAME = 'Bad username';
CloudronError.BAD_EMAIL = 'Bad email';
CloudronError.BAD_PASSWORD = 'Bad password';
CloudronError.BAD_NAME = 'Bad name';
CloudronError.BAD_STATE = 'Bad state';
CloudronError.ALREADY_UPTODATE = 'No Update Available';
CloudronError.NOT_FOUND = 'Not found';
function initialize(callback) {
assert.strictEqual(typeof callback, 'function');
if (process.env.BOX_ENV !== 'test') {
addMailDnsRecords();
}
ensureDkimKeySync();
// Send heartbeat once we are up and running, this speeds up the Cloudron creation, as otherwise we are bound to the cron.js settings
sendHeartbeat();
exports.events.on(exports.EVENT_CONFIGURED, addDnsRecords);
exports.events.on(exports.EVENT_FIRST_RUN, installAppBundle);
callback(null);
// check activation state for existing cloudrons that do not have first run file
// can be removed once cloudrons have been updated
isActivated(function (error, activated) {
if (error) return callback(error);
debug('initialize: cloudron %s activated', activated ? '' : 'not');
if (activated) fs.writeFileSync(paths.FIRST_RUN_FILE, 'been there, done that', 'utf8');
if (!fs.existsSync(paths.FIRST_RUN_FILE)) {
// EE API is sync. do not keep the server waiting
debug('initialize: emitting first run event');
process.nextTick(function () { exports.events.emit(exports.EVENT_FIRST_RUN); });
fs.writeFileSync(paths.FIRST_RUN_FILE, 'been there, done that', 'utf8');
}
syncConfigState(callback);
});
}
function uninitialize(callback) {
assert.strictEqual(typeof callback, 'function');
clearTimeout(gAddMailDnsRecordsTimerId);
gAddMailDnsRecordsTimerId = null;
exports.events.removeListener(exports.EVENT_CONFIGURED, addDnsRecords);
exports.events.removeListener(exports.EVENT_FIRST_RUN, installAppBundle);
callback(null);
}
function isConfiguredSync() {
return gIsConfigured === true;
}
function isActivated(callback) {
user.getOwner(function (error) {
if (error && error.reason === UserError.NOT_FOUND) return callback(null, false);
if (error) return callback(error);
callback(null, true);
});
}
function isConfigured(callback) {
// set of rules to see if we have the configs required for cloudron to function
// note this checks for missing configs and not invalid configs
settings.getDnsConfig(function (error, dnsConfig) {
if (error) return callback(error);
if (!dnsConfig) return callback(null, false);
var isConfigured = (config.isCustomDomain() && dnsConfig.provider === 'route53') ||
(!config.isCustomDomain() && dnsConfig.provider === 'caas');
callback(null, isConfigured);
});
}
function syncConfigState(callback) {
assert(!gIsConfigured);
callback = callback || NOOP_CALLBACK;
isConfigured(function (error, configured) {
if (error) return callback(error);
debug('syncConfigState: configured = %s', configured);
if (configured) {
exports.events.emit(exports.EVENT_CONFIGURED);
} else {
settings.events.once(settings.DNS_CONFIG_KEY, function () { syncConfigState(); }); // check again later
}
gIsConfigured = configured;
callback();
});
}
function setTimeZone(ip, callback) {
assert.strictEqual(typeof ip, 'string');
assert.strictEqual(typeof callback, 'function');
debug('setTimeZone ip:%s', ip);
superagent.get('http://www.telize.com/geoip/' + ip).end(function (error, result) {
if (error || result.statusCode !== 200) {
debug('Failed to get geo location', error);
// https://github.com/bluesmoon/node-geoip
// https://github.com/runk/node-maxmind
// { url: 'http://freegeoip.net/json/%s', jpath: 'time_zone' },
// { url: 'http://ip-api.com/json/%s', jpath: 'timezone' },
// { url: 'http://geoip.nekudo.com/api/%s', jpath: 'time_zone }
superagent.get('http://freegeoip.net/json/' + ip).end(function (error, result) {
if ((error && !error.response) || result.statusCode !== 200) {
debug('Failed to get geo location: %s', error.message);
return callback(null);
}
if (!result.body.timezone) {
debug('No timezone in geoip response');
if (!result.body.time_zone || typeof result.body.time_zone !== 'string') {
debug('No timezone in geoip response : %j', result.body);
return callback(null);
}
debug('Setting timezone to ', result.body.timezone);
debug('Setting timezone to ', result.body.time_zone);
settings.setTimeZone(result.body.timezone, callback);
settings.setTimeZone(result.body.time_zone, callback);
});
}
function activate(username, password, email, name, ip, callback) {
function activate(username, password, email, displayName, ip, auditSource, callback) {
assert.strictEqual(typeof username, 'string');
assert.strictEqual(typeof password, 'string');
assert.strictEqual(typeof email, 'string');
assert.strictEqual(typeof displayName, 'string');
assert.strictEqual(typeof ip, 'string');
assert(!name || typeof name, 'string');
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
debug('activating user:%s email:%s', username, email);
setTimeZone(ip, function () { });
setTimeZone(ip, function () { }); // TODO: get this from user. note that timezone is detected based on the browser location and not the cloudron region
if (!name) name = settings.getDefaultSync(settings.CLOUDRON_NAME_KEY);
settings.setCloudronName(name, function (error) {
if (error && error.reason === SettingsError.BAD_FIELD) return callback(new CloudronError(CloudronError.BAD_NAME));
user.createOwner(username, password, email, displayName, auditSource, function (error, userObject) {
if (error && error.reason === UserError.ALREADY_EXISTS) return callback(new CloudronError(CloudronError.ALREADY_PROVISIONED));
if (error && error.reason === UserError.BAD_FIELD) return callback(new CloudronError(CloudronError.BAD_FIELD, error.message));
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
user.createOwner(username, password, email, function (error, userObject) {
if (error && error.reason === UserError.ALREADY_EXISTS) return callback(new CloudronError(CloudronError.ALREADY_PROVISIONED));
if (error && error.reason === UserError.BAD_USERNAME) return callback(new CloudronError(CloudronError.BAD_USERNAME));
if (error && error.reason === UserError.BAD_PASSWORD) return callback(new CloudronError(CloudronError.BAD_PASSWORD));
if (error && error.reason === UserError.BAD_EMAIL) return callback(new CloudronError(CloudronError.BAD_EMAIL));
clients.get('cid-webadmin', function (error, result) {
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
clientdb.getByAppId('webadmin', function (error, result) {
// Also generate a token so the admin creation can also act as a login
var token = tokendb.generateToken();
var expires = Date.now() + 24 * 60 * 60 * 1000; // 1 day
tokendb.add(token, userObject.id, result.id, expires, '*', function (error) {
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
// Also generate a token so the admin creation can also act as a login
var token = tokendb.generateToken();
var expires = Date.now() + 24 * 60 * 60 * 1000; // 1 day
// EE API is sync. do not keep the REST API reponse waiting
process.nextTick(function () { exports.events.emit(exports.EVENT_ACTIVATED); });
tokendb.add(token, tokendb.PREFIX_USER + userObject.id, result.id, expires, '*', function (error) {
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
eventlog.add(eventlog.ACTION_ACTIVATE, auditSource, { });
callback(null, { token: token, expires: expires });
});
callback(null, { token: token, expires: expires });
});
});
});
@@ -194,7 +253,7 @@ function activate(username, password, email, name, ip, callback) {
function getStatus(callback) {
assert.strictEqual(typeof callback, 'function');
userdb.count(function (error, count) {
user.count(function (error, count) {
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
settings.getCloudronName(function (error, cloudronName) {
@@ -203,6 +262,9 @@ function getStatus(callback) {
callback(null, {
activated: count !== 0,
version: config.version(),
boxVersionsUrl: config.get('boxVersionsUrl'),
apiServerOrigin: config.apiServerOrigin(), // used by CaaS tool
provider: config.provider(),
cloudronName: cloudronName
});
});
@@ -214,14 +276,24 @@ function getCloudronDetails(callback) {
if (gCloudronDetails) return callback(null, gCloudronDetails);
if (!config.token()) {
gCloudronDetails = {
region: null,
size: null
};
return callback(null, gCloudronDetails);
}
superagent
.get(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn())
.query({ token: config.token() })
.end(function (error, result) {
if (error) return callback(error);
if (result.status !== 200) return callback(new CloudronError(CloudronError.EXTERNAL_ERROR, util.format('%s %j', result.status, result.body)));
if (error && !error.response) return callback(error);
if (result.statusCode !== 200) return callback(new CloudronError(CloudronError.EXTERNAL_ERROR, util.format('%s %j', result.status, result.body)));
gCloudronDetails = result.body.box;
gAppstoreUserDetails = result.body.user;
return callback(null, gCloudronDetails);
});
@@ -230,10 +302,9 @@ function getCloudronDetails(callback) {
function getConfig(callback) {
assert.strictEqual(typeof callback, 'function');
// TODO avoid pyramid of awesomeness with async
getCloudronDetails(function (error, result) {
if (error) {
console.error('Failed to fetch cloudron details.', error);
debug('Failed to fetch cloudron details.', error);
// set fallback values to avoid dependency on appstore
result = {
@@ -248,20 +319,27 @@ function getConfig(callback) {
settings.getDeveloperMode(function (error, developerMode) {
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
callback(null, {
apiServerOrigin: config.apiServerOrigin(),
webServerOrigin: config.webServerOrigin(),
isDev: config.isDev(),
fqdn: config.fqdn(),
ip: sysinfo.getIp(),
version: config.version(),
update: updateChecker.getUpdateInfo(),
progress: progress.get(),
isCustomDomain: config.isCustomDomain(),
developerMode: developerMode,
region: result.region,
size: result.size,
cloudronName: cloudronName
sysinfo.getIp(function (error, ip) {
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
callback(null, {
apiServerOrigin: config.apiServerOrigin(),
webServerOrigin: config.webServerOrigin(),
isDev: config.isDev(),
fqdn: config.fqdn(),
ip: ip,
version: config.version(),
update: updateChecker.getUpdateInfo(),
progress: progress.get(),
isCustomDomain: config.isCustomDomain(),
developerMode: developerMode,
region: result.region,
size: result.size,
billing: !!gAppstoreUserDetails.billing,
memory: os.totalmem(),
provider: config.provider(),
cloudronName: cloudronName
});
});
});
});
@@ -269,94 +347,146 @@ function getConfig(callback) {
}
function sendHeartbeat() {
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/heartbeat';
if (!config.token()) return;
// TODO: this must be a POST
superagent.get(url).query({ token: config.token(), version: config.version() }).timeout(10000).end(function (error, result) {
if (error) debug('Error sending heartbeat.', error);
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/heartbeat';
superagent.post(url).query({ token: config.token(), version: config.version() }).timeout(10000).end(function (error, result) {
if (error && !error.response) debug('Network error sending heartbeat.', error);
else if (result.statusCode !== 200) debug('Server responded to heartbeat with %s %s', result.statusCode, result.text);
else debug('Heartbeat sent to %s', url);
});
}
function sendMailDnsRecordsRequest(callback) {
assert.strictEqual(typeof callback, 'function');
function ensureDkimKeySync() {
var dkimPrivateKeyFile = path.join(paths.MAIL_DATA_DIR, 'dkim/' + config.fqdn() + '/private');
var dkimPublicKeyFile = path.join(paths.MAIL_DATA_DIR, 'dkim/' + config.fqdn() + '/public');
var DKIM_SELECTOR = 'mail';
var DMARC_REPORT_EMAIL = 'dmarc-report@cloudron.io';
if (fs.existsSync(dkimPrivateKeyFile) && fs.existsSync(dkimPublicKeyFile)) {
debug('DKIM keys already present');
return;
}
debug('Generating new DKIM keys');
safe.child_process.execSync('openssl genrsa -out ' + dkimPrivateKeyFile + ' 1024');
safe.child_process.execSync('openssl rsa -in ' + dkimPrivateKeyFile + ' -out ' + dkimPublicKeyFile + ' -pubout -outform PEM');
}
function readDkimPublicKeySync() {
var dkimPublicKeyFile = path.join(paths.MAIL_DATA_DIR, 'dkim/' + config.fqdn() + '/public');
var publicKey = safe.fs.readFileSync(dkimPublicKeyFile, 'utf8');
if (publicKey === null) return callback(new Error('Error reading dkim public key'));
if (publicKey === null) {
debug('Error reading dkim public key.', safe.error);
return null;
}
// remove header, footer and new lines
publicKey = publicKey.split('\n').slice(1, -2).join('');
// note that dmarc requires special DNS records for external RUF and RUA
var records = [
// softfail all mails not from our IP. Note that this uses IP instead of 'a' should we use a load balancer in the future
{ subdomain: '', type: 'TXT', value: '"v=spf1 ip4:' + sysinfo.getIp() + ' ~all"' },
// t=s limits the domainkey to this domain and not it's subdomains
{ subdomain: DKIM_SELECTOR + '._domainkey', type: 'TXT', value: '"v=DKIM1; t=s; p=' + publicKey + '"' },
// DMARC requires special setup if report email id is in different domain
{ subdomain: '_dmarc', type: 'TXT', value: '"v=DMARC1; p=none; pct=100; rua=mailto:' + DMARC_REPORT_EMAIL + '; ruf=' + DMARC_REPORT_EMAIL + '"' }
];
debug('sendMailDnsRecords request:%s', JSON.stringify(records));
superagent
.post(config.apiServerOrigin() + '/api/v1/subdomains')
.set('Accept', 'application/json')
.query({ token: config.token() })
.send({ records: records })
.end(function (error, res) {
if (error) return callback(error);
debug('sendMailDnsRecords status: %s', res.status);
if (res.status === 409) return callback(null); // already registered
if (res.status !== 201) return callback(new Error(util.format('Failed to add Mail DNS records: %s %j', res.status, res.body)));
return callback(null, res.body.ids);
});
return publicKey;
}
function addMailDnsRecords() {
if (config.get('mailDnsRecordIds').length !== 0) return; // already registered
// NOTE: if you change the SPF record here, be sure the wait check in mailer.js
function txtRecordsWithSpf(callback) {
assert.strictEqual(typeof callback, 'function');
sendMailDnsRecordsRequest(function (error, ids) {
if (error) {
console.error('Mail DNS record addition failed', error);
gAddMailDnsRecordsTimerId = setTimeout(addMailDnsRecords, 30000);
return;
subdomains.get('', 'TXT', function (error, txtRecords) {
if (error) return callback(error);
debug('txtRecordsWithSpf: current txt records - %j', txtRecords);
var i, validSpf;
for (i = 0; i < txtRecords.length; i++) {
if (txtRecords[i].indexOf('"v=spf1 ') !== 0) continue; // not SPF
validSpf = txtRecords[i].indexOf(' a:' + config.adminFqdn() + ' ') !== -1;
break;
}
debug('Added Mail DNS records successfully');
config.set('mailDnsRecordIds', ids);
if (validSpf) return callback(null, null);
if (i == txtRecords.length) {
txtRecords[i] = '"v=spf1 a:' + config.adminFqdn() + ' ~all"';
} else {
txtRecords[i] = '"v=spf1 a:' + config.adminFqdn() + ' ' + txtRecords[i].slice('"v=spf1 '.length);
}
return callback(null, txtRecords);
});
}
function setCertificate(certificate, key, callback) {
assert.strictEqual(typeof certificate, 'string');
assert.strictEqual(typeof key, 'string');
assert.strictEqual(typeof callback, 'function');
function addDnsRecords() {
var callback = NOOP_CALLBACK;
debug('Updating certificates');
if (process.env.BOX_ENV === 'test') return callback();
if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, 'host.cert'), certificate)) {
return callback(new CloudronError(CloudronError.INTERNAL_ERROR, safe.error.message));
if (gUpdatingDns) {
debug('addDnsRecords: dns update already in progress');
return callback();
}
gUpdatingDns = true;
if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), key)) {
return callback(new CloudronError(CloudronError.INTERNAL_ERROR, safe.error.message));
}
var DKIM_SELECTOR = 'cloudron';
var DMARC_REPORT_EMAIL = 'dmarc-report@cloudron.io';
shell.sudo('setCertificate', [ RELOAD_NGINX_CMD ], function (error) {
var dkimKey = readDkimPublicKeySync();
if (!dkimKey) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, new Error('Failed to read dkim public key')));
sysinfo.getIp(function (error, ip) {
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
return callback(null);
var webadminRecord = { subdomain: constants.ADMIN_LOCATION, type: 'A', values: [ ip ] };
// t=s limits the domainkey to this domain and not it's subdomains
var dkimRecord = { subdomain: DKIM_SELECTOR + '._domainkey', type: 'TXT', values: [ '"v=DKIM1; t=s; p=' + dkimKey + '"' ] };
// DMARC requires special setup if report email id is in different domain
var dmarcRecord = { subdomain: '_dmarc', type: 'TXT', values: [ '"v=DMARC1; p=none; pct=100; rua=mailto:' + DMARC_REPORT_EMAIL + '; ruf=' + DMARC_REPORT_EMAIL + '"' ] };
var mxRecord = { subdomain: '', type: 'MX', values: [ '10 ' + config.mailFqdn() + '.' ] };
var records = [ ];
if (config.isCustomDomain()) {
records.push(webadminRecord);
records.push(dkimRecord);
records.push(mxRecord);
} else {
// for custom domains, we show a nakeddomain.html page
var nakedDomainRecord = { subdomain: '', type: 'A', values: [ ip ] };
records.push(nakedDomainRecord);
records.push(webadminRecord);
records.push(dkimRecord);
records.push(dmarcRecord);
records.push(mxRecord);
}
debug('addDnsRecords: %j', records);
async.retry({ times: 10, interval: 20000 }, function (retryCallback) {
txtRecordsWithSpf(function (error, txtRecords) {
if (error) return retryCallback(error);
if (txtRecords) records.push({ subdomain: '', type: 'TXT', values: txtRecords });
debug('addDnsRecords: will update %j', records);
async.mapSeries(records, function (record, iteratorCallback) {
subdomains.update(record.subdomain, record.type, record.values, iteratorCallback);
}, function (error, changeIds) {
if (error) debug('addDnsRecords: failed to update : %s. will retry', error);
else debug('addDnsRecords: records %j added with changeIds %j', records, changeIds);
retryCallback(error);
});
});
}, function (error) {
gUpdatingDns = false;
debug('addDnsRecords: done updating records with error:', error);
callback(error);
});
});
}
@@ -364,51 +494,9 @@ function reboot(callback) {
shell.sudo('reboot', [ REBOOT_CMD ], callback);
}
function migrate(size, region, callback) {
assert.strictEqual(typeof size, 'string');
assert.strictEqual(typeof region, 'string');
assert.strictEqual(typeof callback, 'function');
var error = locker.lock(locker.OP_MIGRATE);
if (error) return callback(new CloudronError(CloudronError.BAD_STATE, error.message));
function unlock(error) {
if (error) {
debug('Failed to migrate', error);
locker.unlock(locker.OP_MIGRATE);
} else {
debug('Migration initiated successfully');
// do not unlock; cloudron is migrating
}
return;
}
// initiate the migration in the background
backupBoxAndApps(function (error, restoreKey) {
if (error) return unlock(error);
debug('migrate: size %s region %s restoreKey %s', size, region, restoreKey);
superagent
.post(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/migrate')
.query({ token: config.token() })
.send({ size: size, region: region, restoreKey: restoreKey })
.end(function (error, result) {
if (error) return unlock(error);
if (result.status === 409) return unlock(new CloudronError(CloudronError.BAD_STATE));
if (result.status === 404) return unlock(new CloudronError(CloudronError.NOT_FOUND));
if (result.status !== 202) return unlock(new CloudronError(CloudronError.EXTERNAL_ERROR, util.format('%s %j', result.status, result.body)));
return unlock(null);
});
});
callback(null);
}
function update(boxUpdateInfo, callback) {
function update(boxUpdateInfo, auditSource, callback) {
assert.strictEqual(typeof boxUpdateInfo, 'object');
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
if (!boxUpdateInfo) return callback(null);
@@ -416,12 +504,22 @@ function update(boxUpdateInfo, callback) {
var error = locker.lock(locker.OP_BOX_UPDATE);
if (error) return callback(new CloudronError(CloudronError.BAD_STATE, error.message));
eventlog.add(eventlog.ACTION_UPDATE, auditSource, { boxUpdateInfo: boxUpdateInfo });
// ensure tools can 'wait' on progress
progress.set(progress.UPDATE, 0, 'Starting');
// initiate the update/upgrade but do not wait for it
if (boxUpdateInfo.upgrade) {
if (config.version().match(/[-+]/) !== null && config.version().replace(/[-+].*/, '') === boxUpdateInfo.version) {
doShortCircuitUpdate(boxUpdateInfo, function (error) {
if (error) debug('Short-circuit update failed', error);
locker.unlock(locker.OP_BOX_UPDATE);
});
} else if (boxUpdateInfo.upgrade) {
debug('Starting upgrade');
doUpgrade(boxUpdateInfo, function (error) {
if (error) {
debug('Upgrade failed with error: %s', error);
console.error('Upgrade failed with error:', error);
locker.unlock(locker.OP_BOX_UPDATE);
}
});
@@ -429,7 +527,7 @@ function update(boxUpdateInfo, callback) {
debug('Starting update');
doUpdate(boxUpdateInfo, function (error) {
if (error) {
debug('Update failed with error: %s', error);
console.error('Update failed with error:', error);
locker.unlock(locker.OP_BOX_UPDATE);
}
});
@@ -438,6 +536,27 @@ function update(boxUpdateInfo, callback) {
callback(null);
}
function updateToLatest(auditSource, callback) {
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
var boxUpdateInfo = updateChecker.getUpdateInfo().box;
if (!boxUpdateInfo) return callback(new CloudronError(CloudronError.ALREADY_UPTODATE, 'No update available'));
update(boxUpdateInfo, auditSource, callback);
}
function doShortCircuitUpdate(boxUpdateInfo, callback) {
assert(boxUpdateInfo !== null && typeof boxUpdateInfo === 'object');
debug('Starting short-circuit from prerelease version %s to release version %s', config.version(), boxUpdateInfo.version);
config.setVersion(boxUpdateInfo.version);
progress.clear(progress.UPDATE);
updateChecker.resetUpdateInfo();
callback();
}
function doUpgrade(boxUpdateInfo, callback) {
assert(boxUpdateInfo !== null && typeof boxUpdateInfo === 'object');
@@ -446,23 +565,23 @@ function doUpgrade(boxUpdateInfo, callback) {
callback(e);
}
progress.set(progress.UPDATE, 5, 'Create app and box backup for upgrade');
progress.set(progress.UPDATE, 5, 'Backing up for upgrade');
backupBoxAndApps(function (error) {
backups.backupBoxAndApps({ userId: null, username: 'upgrader' }, function (error) {
if (error) return upgradeError(error);
superagent.post(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/upgrade')
.query({ token: config.token() })
.send({ version: boxUpdateInfo.version })
.end(function (error, result) {
if (error) return upgradeError(new Error('Error making upgrade request: ' + error));
if (result.status !== 202) return upgradeError(new Error('Server not ready to upgrade: ' + result.body));
if (error && !error.response) return upgradeError(new Error('Network error making upgrade request: ' + error));
if (result.statusCode !== 202) return upgradeError(new Error(util.format('Server not ready to upgrade. statusCode: %s body: %j', result.status, result.body)));
progress.set(progress.UPDATE, 10, 'Updating base system');
// no need to unlock since this is the last thing we ever do on this box
callback(null);
callback();
retire();
});
});
}
@@ -475,161 +594,115 @@ function doUpdate(boxUpdateInfo, callback) {
callback(e);
}
progress.set(progress.UPDATE, 5, 'Create box backup for update');
progress.set(progress.UPDATE, 5, 'Backing up for update');
backupBox(function (error) {
backups.backupBoxAndApps({ userId: null, username: 'updater' }, function (error) {
if (error) return updateError(error);
// fetch a signed sourceTarballUrl
superagent.get(config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/sourcetarballurl')
.query({ token: config.token(), boxVersion: boxUpdateInfo.version })
.end(function (error, result) {
if (error) return updateError(new Error('Error fetching sourceTarballUrl: ' + error));
if (result.status !== 200) return updateError(new Error('Error fetching sourceTarballUrl status: ' + result.status));
if (!safe.query(result, 'body.url')) return updateError(new Error('Error fetching sourceTarballUrl response: ' + result.body));
// NOTE: the args here are tied to the installer revision, box code and appstore provisioning logic
var args = {
sourceTarballUrl: boxUpdateInfo.sourceTarballUrl,
// NOTE: the args here are tied to the installer revision, box code and appstore provisioning logic
var args = {
sourceTarballUrl: result.body.url,
// this data is opaque to the installer
data: {
token: config.token(),
apiServerOrigin: config.apiServerOrigin(),
webServerOrigin: config.webServerOrigin(),
fqdn: config.fqdn(),
tlsCert: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.cert'), 'utf8'),
tlsKey: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), 'utf8'),
isCustomDomain: config.isCustomDomain(),
// IMPORTANT: if you change this, fix up argparser.sh as well. keep these sorted for readability
data: {
apiServerOrigin: config.apiServerOrigin(),
boxVersionsUrl: config.get('boxVersionsUrl'),
fqdn: config.fqdn(),
isCustomDomain: config.isCustomDomain(),
restoreKey: null,
restoreUrl: null,
tlsKey: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), 'utf8'),
tlsCert: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.cert'), 'utf8'),
appstore: {
token: config.token(),
version: boxUpdateInfo.version,
apiServerOrigin: config.apiServerOrigin()
},
caas: {
token: config.token(),
apiServerOrigin: config.apiServerOrigin(),
webServerOrigin: config.webServerOrigin()
}
};
},
debug('updating box %j', args);
version: boxUpdateInfo.version,
boxVersionsUrl: config.get('boxVersionsUrl')
}
};
superagent.post(INSTALLER_UPDATE_URL).send(args).end(function (error, result) {
if (error) return updateError(error);
if (result.status !== 202) return updateError(new Error('Error initiating update: ' + result.body));
debug('updating box %j', args);
progress.set(progress.UPDATE, 10, 'Updating cloudron software');
superagent.post(INSTALLER_UPDATE_URL).send(args).end(function (error, result) {
if (error && !error.response) return updateError(error);
if (result.statusCode !== 202) return updateError(new Error('Error initiating update: ' + JSON.stringify(result.body)));
callback(null);
});
progress.set(progress.UPDATE, 10, 'Updating cloudron software');
callback(null);
});
// Do not add any code here. The installer script will stop the box code any instant
});
}
function backup(callback) {
assert.strictEqual(typeof callback, 'function');
function installAppBundle(callback) {
callback = callback || NOOP_CALLBACK;
var error = locker.lock(locker.OP_FULL_BACKUP);
if (error) return callback(new CloudronError(CloudronError.BAD_STATE, error.message));
var bundle = config.get('appBundle');
// start the backup operation in the background
backupBoxAndApps(function (error) {
if (error) console.error('backup failed.', error);
if (!bundle || bundle.length === 0) {
debug('installAppBundle: no bundle set');
return callback();
}
locker.unlock(locker.OP_FULL_BACKUP);
async.eachSeries(bundle, function (appInfo, iteratorCallback) {
debug('autoInstall: installing %s at %s', appInfo.appstoreId, appInfo.location);
var data = {
appStoreId: appInfo.appstoreId,
location: appInfo.location,
portBindings: appInfo.portBindings || null,
accessRestriction: appInfo.accessRestriction || null,
};
apps.install(data, { userId: null, username: 'autoinstaller' }, iteratorCallback);
}, function (error) {
if (error) debug('autoInstallApps: ', error);
callback();
});
callback(null);
}
function ensureBackup(callback) {
callback = callback || function () { };
function checkDiskSpace(callback) {
callback = callback || NOOP_CALLBACK;
backups.getAllPaged(1, 1, function (error, backups) {
debug('Checking disk space');
df(function (error, entries) {
if (error) {
debug('Unable to list backups', error);
return callback(error); // no point trying to backup if appstore is down
debug('df error %s', error.message);
mailer.outOfDiskSpace(error.message);
return callback();
}
if (backups.length !== 0 && (new Date() - new Date(backups[0].creationTime) < 23 * 60 * 60 * 1000)) { // ~1 day ago
debug('Previous backup was %j, no need to backup now', backups[0]);
return callback(null);
}
backup(callback);
});
}
function backupBoxWithAppBackupIds(appBackupIds, callback) {
assert(util.isArray(appBackupIds));
backups.getBackupUrl(null /* app */, appBackupIds, function (error, result) {
if (error && error.reason === BackupsError.EXTERNAL_ERROR) return callback(new CloudronError(CloudronError.EXTERNAL_ERROR, error.message));
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
debug('backup: url %s', result.url);
async.series([
ignoreError(shell.sudo.bind(null, 'mountSwap', [ BACKUP_SWAP_CMD, '--on' ])),
shell.sudo.bind(null, 'backupBox', [ BACKUP_BOX_CMD, result.url, result.backupKey ]),
ignoreError(shell.sudo.bind(null, 'unmountSwap', [ BACKUP_SWAP_CMD, '--off' ])),
], function (error) {
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
debug('backup: successful');
callback(null, result.id);
var oos = entries.some(function (entry) {
return (entry.mount === paths.DATA_DIR && entry.capacity >= 0.90) ||
(entry.mount === '/' && entry.used <= (1.25 * 1024 * 1024)); // 1.5G
});
debug('Disk space checked. ok: %s', !oos);
if (oos) mailer.outOfDiskSpace(JSON.stringify(entries, null, 4));
callback();
});
}
// this function expects you to have a lock
function backupBox(callback) {
apps.getAll(function (error, allApps) {
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
function retire(callback) {
callback = callback || NOOP_CALLBACK;
var appBackupIds = allApps.map(function (app) { return app.lastBackupId; });
appBackupIds = appBackupIds.filter(function (id) { return id !== null; }); // remove apps that were never backed up
backupBoxWithAppBackupIds(appBackupIds, callback);
});
var data = {
isCustomDomain: config.isCustomDomain(),
fqdn: config.fqdn()
};
shell.sudo('retire', [ RETIRE_CMD, JSON.stringify(data) ], callback);
}
// this function expects you to have a lock
function backupBoxAndApps(callback) {
callback = callback || function () { }; // callback can be empty for timer triggered backup
apps.getAll(function (error, allApps) {
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
var processed = 0;
var step = 100/(allApps.length+1);
progress.set(progress.BACKUP, processed, '');
async.mapSeries(allApps, function iterator(app, iteratorCallback) {
++processed;
apps.backupApp(app, app.manifest.addons, function (error, backupId) {
progress.set(progress.BACKUP, step * processed, 'Backing up app at ' + app.location);
if (error && error.reason === AppsError.BAD_STATE) {
debugApp(app, 'Skipping backup (istate:%s health:%s). using lastBackupId:%s', app.installationState, app.health, app.lastBackupId);
backupId = app.lastBackupId;
}
return iteratorCallback(null, backupId);
});
}, function appsBackedUp(error, backupIds) {
if (error) {
progress.set(progress.BACKUP, 100, error.message);
return callback(error);
}
backupIds = backupIds.filter(function (id) { return id !== null; }); // remove apps that were never backed up
backupBoxWithAppBackupIds(backupIds, function (error, restoreKey) {
progress.set(progress.BACKUP, 100, error ? error.message : '');
callback(error, restoreKey);
});
});
});
}

View File

@@ -1,6 +1,6 @@
LoadPlugin "table"
<Plugin table>
<Table "/sys/fs/cgroup/memory/system.slice/docker-<%= containerId %>.scope/memory.stat">
<Table "/sys/fs/cgroup/memory/docker/<%= containerId %>/memory.stat">
Instance "<%= appId %>-memory"
Separator " \\n"
<Result>
@@ -10,7 +10,7 @@ LoadPlugin "table"
</Result>
</Table>
<Table "/sys/fs/cgroup/memory/system.slice/docker-<%= containerId %>.scope/memory.max_usage_in_bytes">
<Table "/sys/fs/cgroup/memory/docker/<%= containerId %>/memory.max_usage_in_bytes">
Instance "<%= appId %>-memory"
Separator "\\n"
<Result>
@@ -20,7 +20,7 @@ LoadPlugin "table"
</Result>
</Table>
<Table "/sys/fs/cgroup/cpuacct/system.slice/docker-<%= containerId %>.scope/cpuacct.stat">
<Table "/sys/fs/cgroup/cpuacct/docker/<%= containerId %>/cpuacct.stat">
Instance "<%= appId %>-cpu"
Separator " \\n"
<Result>

View File

@@ -1,5 +1,3 @@
/* jslint node: true */
'use strict';
exports = module.exports = {
@@ -15,23 +13,29 @@ exports = module.exports = {
TEST: process.env.BOX_ENV === 'test',
// convenience getters
provider: provider,
apiServerOrigin: apiServerOrigin,
webServerOrigin: webServerOrigin,
fqdn: fqdn,
token: token,
version: version,
setVersion: setVersion,
isCustomDomain: isCustomDomain,
database: database,
// these values are derived
adminOrigin: adminOrigin,
internalAdminOrigin: internalAdminOrigin,
sysadminOrigin: sysadminOrigin, // caas routes
adminFqdn: adminFqdn,
mailFqdn: mailFqdn,
appFqdn: appFqdn,
zoneName: zoneName,
isDev: isDev,
// for testing resets to defaults
_reset: initConfig
_reset: _reset
};
var assert = require('assert'),
@@ -56,20 +60,30 @@ function saveSync() {
fs.writeFileSync(cloudronConfigFileName, JSON.stringify(data, null, 4)); // functions are ignored by JSON.stringify
}
function _reset (callback) {
safe.fs.unlinkSync(cloudronConfigFileName);
initConfig();
if (callback) callback();
}
function initConfig() {
// setup defaults
data.fqdn = 'localhost';
data.token = null;
data.mailServer = null;
data.adminEmail = null;
data.mailDnsRecordIds = [ ];
data.boxVersionsUrl = null;
data.version = null;
data.isCustomDomain = false;
data.webServerOrigin = null;
data.internalPort = 3001;
data.smtpPort = 2525; // // this value comes from mail container
data.sysadminPort = 3001;
data.ldapPort = 3002;
data.oauthProxyPort = 3003;
data.simpleAuthPort = 3004;
data.provider = 'caas';
data.appBundle = [ ];
if (exports.CLOUDRON) {
data.port = 3000;
@@ -99,6 +113,9 @@ function initConfig() {
saveSync();
}
// cleanup any old config file we have for tests
if (exports.TEST) safe.fs.unlinkSync(cloudronConfigFileName);
initConfig();
// set(obj) or set(key, value)
@@ -141,10 +158,26 @@ function appFqdn(location) {
return isCustomDomain() ? location + '.' + fqdn() : location + '-' + fqdn();
}
function adminFqdn() {
return appFqdn(constants.ADMIN_LOCATION);
}
function mailFqdn() {
return appFqdn(constants.MAIL_LOCATION);
}
function adminOrigin() {
return 'https://' + appFqdn(constants.ADMIN_LOCATION);
}
function internalAdminOrigin() {
return 'http://127.0.0.1:' + get('port');
}
function sysadminOrigin() {
return 'http://127.0.0.1:' + get('sysadminPort');
}
function token() {
return get('token');
}
@@ -153,6 +186,10 @@ function version() {
return get('version');
}
function setVersion(version) {
set('version', version);
}
function isCustomDomain() {
return get('isCustomDomain');
}
@@ -172,3 +209,6 @@ function isDev() {
return /dev/i.test(get('boxVersionsUrl'));
}
function provider() {
return get('provider');
}

View File

@@ -4,13 +4,16 @@
exports = module.exports = {
ADMIN_LOCATION: 'my',
API_LOCATION: 'api', // this is unused but reserved for future use (#403)
SMTP_LOCATION: 'smtp',
IMAP_LOCATION: 'imap',
MAIL_LOCATION: 'my', // not a typo! should be same as admin location until we figure out certificates
POSTMAN_LOCATION: 'postman', // used in dovecot bounces
ADMIN_NAME: 'Settings',
ADMIN_CLIENT_ID: 'webadmin', // oauth client id
ADMIN_APPID: 'admin', // admin appid (settingsdb)
TEST_NAME: 'Test',
TEST_LOCATION: '',
TEST_CLIENT_ID: 'test'
DEFAULT_MEMORY_LIMIT: (256 * 1024 * 1024) // see also client.js
};

View File

@@ -7,9 +7,14 @@ exports = module.exports = {
var apps = require('./apps.js'),
assert = require('assert'),
backups = require('./backups.js'),
certificates = require('./certificates.js'),
cloudron = require('./cloudron.js'),
config = require('./config.js'),
CronJob = require('cron').CronJob,
debug = require('debug')('box:cron'),
janitor = require('./janitor.js'),
scheduler = require('./scheduler.js'),
settings = require('./settings.js'),
updateChecker = require('./updatechecker.js');
@@ -17,11 +22,15 @@ var gAutoupdaterJob = null,
gBoxUpdateCheckerJob = null,
gAppUpdateCheckerJob = null,
gHeartbeatJob = null,
gBackupJob = null;
var gInitialized = false;
gBackupJob = null,
gCleanupTokensJob = null,
gDockerVolumeCleanerJob = null,
gSchedulerSyncJob = null,
gCertificateRenewJob = null,
gCheckDiskSpaceJob = null;
var NOOP_CALLBACK = function (error) { if (error) console.error(error); };
var AUDIT_SOURCE = { userId: null, username: 'cron' };
// cron format
// Seconds: 0-59
@@ -34,32 +43,39 @@ var NOOP_CALLBACK = function (error) { if (error) console.error(error); };
function initialize(callback) {
assert.strictEqual(typeof callback, 'function');
if (gInitialized) return callback();
gHeartbeatJob = new CronJob({
cronTime: '00 */1 * * * *', // every minute
onTick: cloudron.sendHeartbeat,
start: true
});
cloudron.sendHeartbeat(); // latest unpublished version of CronJob has runOnInit
settings.events.on(settings.TIME_ZONE_KEY, recreateJobs);
settings.events.on(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged);
gInitialized = true;
recreateJobs(callback);
if (cloudron.isConfiguredSync()) {
recreateJobs(callback);
} else {
cloudron.events.on(cloudron.EVENT_ACTIVATED, recreateJobs);
callback();
}
}
function recreateJobs(unusedTimeZone, callback) {
if (typeof unusedTimeZone === 'function') callback = unusedTimeZone;
settings.getAll(function (error, allSettings) {
if (gHeartbeatJob) gHeartbeatJob.stop();
gHeartbeatJob = new CronJob({
cronTime: '00 */1 * * * *', // every minute
onTick: cloudron.sendHeartbeat,
start: true,
timeZone: allSettings[settings.TIME_ZONE_KEY]
});
debug('Creating jobs with timezone %s', allSettings[settings.TIME_ZONE_KEY]);
if (gBackupJob) gBackupJob.stop();
gBackupJob = new CronJob({
cronTime: '00 00 */4 * * *', // every 4 hours
onTick: cloudron.ensureBackup,
onTick: backups.ensureBackup.bind(null, AUDIT_SOURCE, NOOP_CALLBACK),
start: true,
timeZone: allSettings[settings.TIME_ZONE_KEY]
});
if (gCheckDiskSpaceJob) gCheckDiskSpaceJob.stop();
gCheckDiskSpaceJob = new CronJob({
cronTime: '00 30 */4 * * *', // every 4 hours
onTick: cloudron.checkDiskSpace,
start: true,
timeZone: allSettings[settings.TIME_ZONE_KEY]
});
@@ -80,14 +96,52 @@ function recreateJobs(unusedTimeZone, callback) {
timeZone: allSettings[settings.TIME_ZONE_KEY]
});
if (gCleanupTokensJob) gCleanupTokensJob.stop();
gCleanupTokensJob = new CronJob({
cronTime: '00 */30 * * * *', // every 30 minutes
onTick: janitor.cleanupTokens,
start: true,
timeZone: allSettings[settings.TIME_ZONE_KEY]
});
if (gDockerVolumeCleanerJob) gDockerVolumeCleanerJob.stop();
gDockerVolumeCleanerJob = new CronJob({
cronTime: '00 00 */12 * * *', // every 12 hours
onTick: janitor.cleanupDockerVolumes,
start: true,
timeZone: allSettings[settings.TIME_ZONE_KEY]
});
if (gSchedulerSyncJob) gSchedulerSyncJob.stop();
gSchedulerSyncJob = new CronJob({
cronTime: config.TEST ? '*/10 * * * * *' : '00 */1 * * * *', // every minute
onTick: scheduler.sync,
start: true,
timeZone: allSettings[settings.TIME_ZONE_KEY]
});
if (gCertificateRenewJob) gCertificateRenewJob.stop();
gCertificateRenewJob = new CronJob({
cronTime: '00 00 */12 * * *', // every 12 hours
onTick: certificates.renewAll.bind(null, AUDIT_SOURCE, NOOP_CALLBACK),
start: true,
timeZone: allSettings[settings.TIME_ZONE_KEY]
});
settings.events.removeListener(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged);
settings.events.on(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged);
autoupdatePatternChanged(allSettings[settings.AUTOUPDATE_PATTERN_KEY]);
settings.events.removeListener(settings.TIME_ZONE_KEY, recreateJobs);
settings.events.on(settings.TIME_ZONE_KEY, recreateJobs);
if (callback) callback();
});
}
function autoupdatePatternChanged(pattern) {
assert.strictEqual(typeof pattern, 'string');
assert(gBoxUpdateCheckerJob);
debug('Auto update pattern changed to %s', pattern);
@@ -101,41 +155,53 @@ function autoupdatePatternChanged(pattern) {
var updateInfo = updateChecker.getUpdateInfo();
if (updateInfo.box) {
debug('Starting autoupdate to %j', updateInfo.box);
cloudron.update(updateInfo.box, NOOP_CALLBACK);
cloudron.updateToLatest(AUDIT_SOURCE, NOOP_CALLBACK);
} else if (updateInfo.apps) {
debug('Starting app update to %j', updateInfo.apps);
apps.autoupdateApps(updateInfo.apps, NOOP_CALLBACK);
apps.updateApps(updateInfo.apps, AUDIT_SOURCE, NOOP_CALLBACK);
} else {
debug('No auto updates available');
}
},
start: true,
timeZone: gBoxUpdateCheckerJob.cronTime.timeZone // hack
timeZone: gBoxUpdateCheckerJob.cronTime.zone // hack
});
}
function uninitialize(callback) {
assert.strictEqual(typeof callback, 'function');
if (!gInitialized) return callback();
cloudron.events.removeListener(cloudron.EVENT_ACTIVATED, recreateJobs);
settings.events.removeListener(settings.TIME_ZONE_KEY, recreateJobs);
settings.events.removeListener(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged);
if (gAutoupdaterJob) gAutoupdaterJob.stop();
gAutoupdaterJob = null;
gBoxUpdateCheckerJob.stop();
if (gBoxUpdateCheckerJob) gBoxUpdateCheckerJob.stop();
gBoxUpdateCheckerJob = null;
gAppUpdateCheckerJob.stop();
if (gAppUpdateCheckerJob) gAppUpdateCheckerJob.stop();
gAppUpdateCheckerJob = null;
gHeartbeatJob.stop();
if (gHeartbeatJob) gHeartbeatJob.stop();
gHeartbeatJob = null;
gBackupJob.stop();
if (gBackupJob) gBackupJob.stop();
gBackupJob = null;
gInitialized = false;
if (gCleanupTokensJob) gCleanupTokensJob.stop();
gCleanupTokensJob = null;
if (gDockerVolumeCleanerJob) gDockerVolumeCleanerJob.stop();
gDockerVolumeCleanerJob = null;
if (gSchedulerSyncJob) gSchedulerSyncJob.stop();
gSchedulerSyncJob = null;
if (gCertificateRenewJob) gCertificateRenewJob.stop();
gCertificateRenewJob = null;
callback();
}

View File

@@ -1,5 +1,3 @@
/* jslint node: true */
'use strict';
exports = module.exports = {
@@ -116,16 +114,22 @@ function clear(callback) {
async.series([
require('./appdb.js')._clear,
require('./authcodedb.js')._clear,
require('./backupdb.js')._clear,
require('./clientdb.js')._clear,
require('./tokendb.js')._clear,
require('./groupdb.js')._clear,
require('./userdb.js')._clear,
require('./settingsdb.js')._clear
require('./settingsdb.js')._clear,
require('./eventlogdb.js')._clear,
require('./mailboxdb.js')._clear
], callback);
}
function beginTransaction(callback) {
assert.strictEqual(typeof callback, 'function');
if (gConnectionPool === null) return callback(new Error('No database connection pool.'));
gConnectionPool.getConnection(function (error, connection) {
if (error) return callback(error);

View File

@@ -30,3 +30,4 @@ DatabaseError.INTERNAL_ERROR = 'Internal error';
DatabaseError.ALREADY_EXISTS = 'Entry already exist';
DatabaseError.NOT_FOUND = 'Record not found';
DatabaseError.BAD_FIELD = 'Invalid field';
DatabaseError.IN_USE = 'In Use';

View File

@@ -5,7 +5,7 @@
exports = module.exports = {
DeveloperError: DeveloperError,
enabled: enabled,
isEnabled: isEnabled,
setEnabled: setEnabled,
issueDeveloperToken: issueDeveloperToken,
getNonApprovedApps: getNonApprovedApps
@@ -13,6 +13,9 @@ exports = module.exports = {
var assert = require('assert'),
config = require('./config.js'),
clients = require('./clients.js'),
debug = require('debug')('box:developer'),
eventlog = require('./eventlog.js'),
tokendb = require('./tokendb.js'),
settings = require('./settings.js'),
superagent = require('superagent'),
@@ -38,8 +41,9 @@ function DeveloperError(reason, errorOrMessage) {
}
util.inherits(DeveloperError, Error);
DeveloperError.INTERNAL_ERROR = 'Internal Error';
DeveloperError.EXTERNAL_ERROR = 'External Error';
function enabled(callback) {
function isEnabled(callback) {
assert.strictEqual(typeof callback, 'function');
settings.getDeveloperMode(function (error, enabled) {
@@ -48,27 +52,35 @@ function enabled(callback) {
});
}
function setEnabled(enabled, callback) {
function setEnabled(enabled, auditSource, callback) {
assert.strictEqual(typeof enabled, 'boolean');
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
settings.setDeveloperMode(enabled, function (error) {
if (error) return callback(new DeveloperError(DeveloperError.INTERNAL_ERROR, error));
eventlog.add(eventlog.ACTION_CLI_MODE, auditSource, { enabled: enabled });
callback(null);
});
}
function issueDeveloperToken(user, callback) {
function issueDeveloperToken(user, auditSource, callback) {
assert.strictEqual(typeof user, 'object');
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
var token = tokendb.generateToken();
var expiresAt = Date.now() + 24 * 60 * 60 * 1000; // 1 day
var scopes = '*,' + clients.SCOPE_ROLE_SDK;
tokendb.add(token, tokendb.PREFIX_DEV + user.id, '', expiresAt, 'apps,settings,roleDeveloper', function (error) {
tokendb.add(token, user.id, 'cid-cli', expiresAt, scopes, function (error) {
if (error) return callback(new DeveloperError(DeveloperError.INTERNAL_ERROR, error));
callback(null, { token: token, expiresAt: expiresAt });
eventlog.add(eventlog.ACTION_USER_LOGIN, auditSource, { authType: 'cli', userId: user.id, username: user.username });
callback(null, { token: token, expiresAt: new Date(expiresAt).toISOString() });
});
}
@@ -77,8 +89,12 @@ function getNonApprovedApps(callback) {
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/apps';
superagent.get(url).query({ token: config.token(), boxVersion: config.version() }).end(function (error, result) {
if (error) return callback(new DeveloperError(DeveloperError.INTERNAL_ERROR, error));
if (result.status !== 200) return callback(new DeveloperError(DeveloperError.INTERNAL_ERROR, util.format('App listing failed. %s %j', result.status, result.body)));
if (error && !error.response) return callback(new DeveloperError(DeveloperError.EXTERNAL_ERROR, error));
if (result.statusCode === 401) {
debug('Failed to list apps in development. Appstore token invalid or missing. Returning empty list.', result.body);
return callback(null, []);
}
if (result.statusCode !== 200) return callback(new DeveloperError(DeveloperError.EXTERNAL_ERROR, util.format('App listing failed. %s %j', result.status, result.body)));
callback(null, result.body.apps || []);
});

View File

@@ -1,46 +0,0 @@
/* jslint node:true */
'use strict';
exports = module.exports = {
checkPtrRecord: checkPtrRecord
};
var assert = require('assert'),
debug = require('debug')('box:digitalocean'),
dns = require('native-dns');
function checkPtrRecord(ip, fqdn, callback) {
assert(ip === null || typeof ip === 'string');
assert.strictEqual(typeof fqdn, 'string');
assert.strictEqual(typeof callback, 'function');
debug('checkPtrRecord: ' + ip);
if (!ip) return callback(new Error('Network down'));
dns.resolve4('ns1.digitalocean.com', function (error, rdnsIps) {
if (error || rdnsIps.length === 0) return callback(new Error('Failed to query DO DNS'));
var reversedIp = ip.split('.').reverse().join('.');
var req = dns.Request({
question: dns.Question({ name: reversedIp + '.in-addr.arpa', type: 'PTR' }),
server: { address: rdnsIps[0] },
timeout: 5000
});
req.on('timeout', function () { return callback(new Error('Timedout')); });
req.on('message', function (error, message) {
if (error || !message.answer || message.answer.length === 0) return callback(new Error('Failed to query PTR'));
debug('checkPtrRecord: Actual:%s Expecting:%s', message.answer[0].data, fqdn);
callback(null, message.answer[0].data === fqdn);
});
req.send();
});
}

136
src/dns/caas.js Normal file
View File

@@ -0,0 +1,136 @@
/* jslint node:true */
'use strict';
exports = module.exports = {
add: add,
del: del,
update: update,
getChangeStatus: getChangeStatus,
get: get
};
var assert = require('assert'),
config = require('../config.js'),
debug = require('debug')('box:dns/caas'),
SubdomainError = require('../subdomains.js').SubdomainError,
superagent = require('superagent'),
util = require('util'),
_ = require('underscore');
function add(dnsConfig, zoneName, subdomain, type, values, callback) {
assert.strictEqual(typeof dnsConfig, 'object');
assert.strictEqual(typeof zoneName, 'string');
assert.strictEqual(typeof subdomain, 'string');
assert.strictEqual(typeof type, 'string');
assert(util.isArray(values));
assert.strictEqual(typeof callback, 'function');
var fqdn = subdomain !== '' && type === 'TXT' ? subdomain + '.' + config.fqdn() : config.appFqdn(subdomain);
debug('add: %s for zone %s of type %s with values %j', subdomain, zoneName, type, values);
var data = {
type: type,
values: values
};
superagent
.post(config.apiServerOrigin() + '/api/v1/domains/' + fqdn)
.query({ token: dnsConfig.token })
.send(data)
.end(function (error, result) {
if (error && !error.response) return callback(error);
if (result.statusCode === 420) return callback(new SubdomainError(SubdomainError.STILL_BUSY));
if (result.statusCode !== 201) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, util.format('%s %j', result.statusCode, result.body)));
return callback(null, result.body.changeId);
});
}
function get(dnsConfig, zoneName, subdomain, type, callback) {
assert.strictEqual(typeof dnsConfig, 'object');
assert.strictEqual(typeof zoneName, 'string');
assert.strictEqual(typeof subdomain, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof callback, 'function');
var fqdn = subdomain !== '' && type === 'TXT' ? subdomain + '.' + config.fqdn() : config.appFqdn(subdomain);
debug('get: zoneName: %s subdomain: %s type: %s fqdn: %s', zoneName, subdomain, type, fqdn);
superagent
.get(config.apiServerOrigin() + '/api/v1/domains/' + fqdn)
.query({ token: dnsConfig.token, type: type })
.end(function (error, result) {
if (error && !error.response) return callback(error);
if (result.statusCode !== 200) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, util.format('%s %j', result.statusCode, result.body)));
return callback(null, result.body.values);
});
}
function update(dnsConfig, zoneName, subdomain, type, values, callback) {
assert.strictEqual(typeof dnsConfig, 'object');
assert.strictEqual(typeof zoneName, 'string');
assert.strictEqual(typeof subdomain, 'string');
assert.strictEqual(typeof type, 'string');
assert(util.isArray(values));
assert.strictEqual(typeof callback, 'function');
get(dnsConfig, zoneName, subdomain, type, function (error, result) {
if (error) return callback(error);
if (_.isEqual(values, result)) return callback();
add(dnsConfig, zoneName, subdomain, type, values, callback);
});
}
function del(dnsConfig, zoneName, subdomain, type, values, callback) {
assert.strictEqual(typeof dnsConfig, 'object');
assert.strictEqual(typeof zoneName, 'string');
assert.strictEqual(typeof subdomain, 'string');
assert.strictEqual(typeof type, 'string');
assert(util.isArray(values));
assert.strictEqual(typeof callback, 'function');
debug('add: %s for zone %s of type %s with values %j', subdomain, zoneName, type, values);
var data = {
type: type,
values: values
};
superagent
.del(config.apiServerOrigin() + '/api/v1/domains/' + config.appFqdn(subdomain))
.query({ token: dnsConfig.token })
.send(data)
.end(function (error, result) {
if (error && !error.response) return callback(error);
if (result.statusCode === 420) return callback(new SubdomainError(SubdomainError.STILL_BUSY));
if (result.statusCode === 404) return callback(new SubdomainError(SubdomainError.NOT_FOUND));
if (result.statusCode !== 204) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, util.format('%s %j', result.statusCode, result.body)));
return callback(null);
});
}
function getChangeStatus(dnsConfig, changeId, callback) {
assert.strictEqual(typeof dnsConfig, 'object');
assert.strictEqual(typeof changeId, 'string');
assert.strictEqual(typeof callback, 'function');
if (changeId === '') return callback(null, 'INSYNC');
superagent
.get(config.apiServerOrigin() + '/api/v1/domains/' + config.fqdn() + '/status/' + changeId)
.query({ token: dnsConfig.token })
.end(function (error, result) {
if (error && !error.response) return callback(error);
if (result.statusCode !== 200) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, util.format('%s %j', result.statusCode, result.body)));
return callback(null, result.body.status);
});
}

216
src/dns/route53.js Normal file
View File

@@ -0,0 +1,216 @@
/* jslint node:true */
'use strict';
exports = module.exports = {
add: add,
get: get,
del: del,
update: update,
getChangeStatus: getChangeStatus
};
var assert = require('assert'),
AWS = require('aws-sdk'),
config = require('../config.js'),
debug = require('debug')('box:dns/route53'),
SubdomainError = require('../subdomains.js').SubdomainError,
util = require('util'),
_ = require('underscore');
function getDnsCredentials(dnsConfig) {
assert.strictEqual(typeof dnsConfig, 'object');
var credentials = {
accessKeyId: dnsConfig.accessKeyId,
secretAccessKey: dnsConfig.secretAccessKey,
region: dnsConfig.region
};
if (dnsConfig.endpoint) credentials.endpoint = new AWS.Endpoint(dnsConfig.endpoint);
return credentials;
}
function getZoneByName(dnsConfig, zoneName, callback) {
assert.strictEqual(typeof dnsConfig, 'object');
assert.strictEqual(typeof zoneName, 'string');
assert.strictEqual(typeof callback, 'function');
var route53 = new AWS.Route53(getDnsCredentials(dnsConfig));
route53.listHostedZones({}, function (error, result) {
if (error && error.code === 'AccessDenied') return callback(new SubdomainError(SubdomainError.ACCESS_DENIED, error.message));
if (error) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, error.message));
var zone = result.HostedZones.filter(function (zone) {
return zone.Name.slice(0, -1) === zoneName; // aws zone name contains a '.' at the end
})[0];
if (!zone) return callback(new SubdomainError(SubdomainError.NOT_FOUND, 'no such zone'));
callback(null, zone);
});
}
function add(dnsConfig, zoneName, subdomain, type, values, callback) {
assert.strictEqual(typeof dnsConfig, 'object');
assert.strictEqual(typeof zoneName, 'string');
assert.strictEqual(typeof subdomain, 'string');
assert.strictEqual(typeof type, 'string');
assert(util.isArray(values));
assert.strictEqual(typeof callback, 'function');
debug('add: %s for zone %s of type %s with values %j', subdomain, zoneName, type, values);
getZoneByName(dnsConfig, zoneName, function (error, zone) {
if (error) return callback(error);
var fqdn = config.appFqdn(subdomain);
var records = values.map(function (v) { return { Value: v }; });
var params = {
ChangeBatch: {
Changes: [{
Action: 'UPSERT',
ResourceRecordSet: {
Type: type,
Name: fqdn,
ResourceRecords: records,
TTL: 1
}
}]
},
HostedZoneId: zone.Id
};
var route53 = new AWS.Route53(getDnsCredentials(dnsConfig));
route53.changeResourceRecordSets(params, function(error, result) {
if (error && error.code === 'AccessDenied') return callback(new SubdomainError(SubdomainError.ACCESS_DENIED, error.message));
if (error && error.code === 'PriorRequestNotComplete') return callback(new SubdomainError(SubdomainError.STILL_BUSY, error.message));
if (error) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, error.message));
callback(null, result.ChangeInfo.Id);
});
});
}
function update(dnsConfig, zoneName, subdomain, type, values, callback) {
assert.strictEqual(typeof dnsConfig, 'object');
assert.strictEqual(typeof zoneName, 'string');
assert.strictEqual(typeof subdomain, 'string');
assert.strictEqual(typeof type, 'string');
assert(util.isArray(values));
assert.strictEqual(typeof callback, 'function');
get(dnsConfig, zoneName, subdomain, type, function (error, result) {
if (error) return callback(error);
if (_.isEqual(values, result)) return callback();
add(dnsConfig, zoneName, subdomain, type, values, callback);
});
}
function get(dnsConfig, zoneName, subdomain, type, callback) {
assert.strictEqual(typeof dnsConfig, 'object');
assert.strictEqual(typeof zoneName, 'string');
assert.strictEqual(typeof subdomain, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof callback, 'function');
getZoneByName(dnsConfig, zoneName, function (error, zone) {
if (error) return callback(error);
var params = {
HostedZoneId: zone.Id,
MaxItems: '1',
StartRecordName: (subdomain ? subdomain + '.' : '') + zoneName + '.',
StartRecordType: type
};
var route53 = new AWS.Route53(getDnsCredentials(dnsConfig));
route53.listResourceRecordSets(params, function (error, result) {
if (error && error.code === 'AccessDenied') return callback(new SubdomainError(SubdomainError.ACCESS_DENIED, error.message));
if (error) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, error.message));
if (result.ResourceRecordSets.length === 0) return callback(null, [ ]);
if (result.ResourceRecordSets[0].Name !== params.StartRecordName && result.ResourceRecordSets[0].Type !== params.StartRecordType) return callback(null, [ ]);
var values = result.ResourceRecordSets[0].ResourceRecords.map(function (record) { return record.Value; });
callback(null, values);
});
});
}
function del(dnsConfig, zoneName, subdomain, type, values, callback) {
assert.strictEqual(typeof dnsConfig, 'object');
assert.strictEqual(typeof zoneName, 'string');
assert.strictEqual(typeof subdomain, 'string');
assert.strictEqual(typeof type, 'string');
assert(util.isArray(values));
assert.strictEqual(typeof callback, 'function');
getZoneByName(dnsConfig, zoneName, function (error, zone) {
if (error) return callback(error);
var fqdn = config.appFqdn(subdomain);
var records = values.map(function (v) { return { Value: v }; });
var resourceRecordSet = {
Name: fqdn,
Type: type,
ResourceRecords: records,
TTL: 1
};
var params = {
ChangeBatch: {
Changes: [{
Action: 'DELETE',
ResourceRecordSet: resourceRecordSet
}]
},
HostedZoneId: zone.Id
};
var route53 = new AWS.Route53(getDnsCredentials(dnsConfig));
route53.changeResourceRecordSets(params, function(error, result) {
if (error && error.code === 'AccessDenied') return callback(new SubdomainError(SubdomainError.ACCESS_DENIED, error.message));
if (error && error.message && error.message.indexOf('it was not found') !== -1) {
debug('del: resource record set not found.', error);
return callback(new SubdomainError(SubdomainError.NOT_FOUND, error.message));
} else if (error && error.code === 'NoSuchHostedZone') {
debug('del: hosted zone not found.', error);
return callback(new SubdomainError(SubdomainError.NOT_FOUND, error.message));
} else if (error && error.code === 'PriorRequestNotComplete') {
debug('del: resource is still busy', error);
return callback(new SubdomainError(SubdomainError.STILL_BUSY, error.message));
} else if (error && error.code === 'InvalidChangeBatch') {
debug('del: invalid change batch. No such record to be deleted.');
return callback(new SubdomainError(SubdomainError.NOT_FOUND, error.message));
} else if (error) {
debug('del: error', error);
return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, error.message));
}
callback(null);
});
});
}
function getChangeStatus(dnsConfig, changeId, callback) {
assert.strictEqual(typeof dnsConfig, 'object');
assert.strictEqual(typeof changeId, 'string');
assert.strictEqual(typeof callback, 'function');
if (changeId === '') return callback(null, 'INSYNC');
var route53 = new AWS.Route53(getDnsCredentials(dnsConfig));
route53.getChange({ Id: changeId }, function (error, result) {
if (error && error.code === 'AccessDenied') return callback(new SubdomainError(SubdomainError.ACCESS_DENIED, error.message));
if (error) return callback(error);
callback(null, result.ChangeInfo.Status);
});
}

Some files were not shown because too many files have changed in this diff Show More