Compare commits

..

59 Commits

Author SHA1 Message Date
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
19 changed files with 438 additions and 444 deletions
+3
View File
@@ -510,3 +510,6 @@
[0.13.4]
- Fix mail addon restart issue
[0.14.0]
- You have mail :-)
+30 -10
View File
@@ -10,7 +10,7 @@ if [[ -z "${JSON}" ]]; then
exit 1
fi
readonly CURL="curl -s -u ${DIGITAL_OCEAN_TOKEN}:"
readonly CURL="curl --retry 5 -s -u ${DIGITAL_OCEAN_TOKEN}:"
function debug() {
echo "$@" >&2
@@ -109,13 +109,23 @@ function get_image_id() {
local snapshot_name="$1"
local image_id=""
image_id=$($CURL "https://api.digitalocean.com/v2/images?per_page=100" \
| $JSON images \
| $JSON -c "this.name === \"${snapshot_name}\"" 0.id)
if [[ -n "${image_id}" ]]; then
echo "${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() {
@@ -128,16 +138,26 @@ function snapshot_droplet() {
debug -n "Waiting for snapshot to complete"
while true; do
local event_status=`$CURL "https://api.digitalocean.com/v2/droplets/${droplet_id}/actions/${event_id}" | $JSON action.status`
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 ""
debug "! done"
get_image_id "${snapshot_name}"
if ! image_id=$(get_image_id "${snapshot_name}"); then
return 1
fi
echo "${image_id}"
}
function destroy_droplet() {
+3 -3
View File
@@ -67,9 +67,9 @@ iptables -P OUTPUT ACCEPT
# 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 80,202,443,886 -j ACCEPT
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 80,22,443,886 -j ACCEPT
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
@@ -177,7 +177,7 @@ else
echo "=== Pulling graphite docker images ==="
docker pull "${GRAPHITE_IMAGE}"
echo "=== Pulling mail relay ==="
echo "=== Pulling mail ==="
docker pull "${MAIL_IMAGE}"
fi
+50 -157
View File
@@ -8,9 +8,9 @@
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz"
},
"aws-sdk": {
"version": "2.3.5",
"version": "2.3.10",
"from": "aws-sdk@>=2.1.46 <3.0.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.3.5.tgz",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.3.10.tgz",
"dependencies": {
"sax": {
"version": "1.1.5",
@@ -42,19 +42,14 @@
}
},
"body-parser": {
"version": "1.15.0",
"version": "1.15.1",
"from": "body-parser@>=1.13.1 <2.0.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.15.0.tgz",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.15.1.tgz",
"dependencies": {
"bytes": {
"version": "2.2.0",
"from": "bytes@2.2.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz"
},
"content-type": {
"version": "1.0.1",
"version": "1.0.2",
"from": "content-type@>=1.0.1 <1.1.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.1.tgz"
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz"
},
"depd": {
"version": "1.1.0",
@@ -102,14 +97,9 @@
},
"raw-body": {
"version": "2.1.6",
"from": "raw-body@>=2.1.5 <2.2.0",
"from": "raw-body@>=2.1.6 <2.2.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.6.tgz",
"dependencies": {
"bytes": {
"version": "2.3.0",
"from": "bytes@2.3.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.3.0.tgz"
},
"unpipe": {
"version": "1.0.0",
"from": "unpipe@1.0.0",
@@ -119,7 +109,7 @@
},
"type-is": {
"version": "1.6.12",
"from": "type-is@>=1.6.11 <1.7.0",
"from": "type-is@>=1.6.12 <1.7.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.12.tgz",
"dependencies": {
"media-typer": {
@@ -128,14 +118,14 @@
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
},
"mime-types": {
"version": "2.1.10",
"version": "2.1.11",
"from": "mime-types@>=2.1.10 <2.2.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz",
"dependencies": {
"mime-db": {
"version": "1.22.0",
"from": "mime-db@>=1.22.0 <1.23.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz"
"version": "1.23.0",
"from": "mime-db@>=1.23.0 <1.24.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz"
}
}
}
@@ -149,9 +139,9 @@
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.3.0.tgz"
},
"cloudron-manifestformat": {
"version": "2.3.0",
"from": "cloudron-manifestformat@>=2.3.0 <3.0.0",
"resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-2.3.0.tgz",
"version": "2.4.0",
"from": "cloudron-manifestformat@2.4.0",
"resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-2.4.0.tgz",
"dependencies": {
"java-packagename-regex": {
"version": "1.0.0",
@@ -240,7 +230,7 @@
},
"on-headers": {
"version": "1.0.1",
"from": "on-headers@>=1.0.1 <1.1.0",
"from": "on-headers@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz"
}
}
@@ -281,7 +271,7 @@
},
"on-headers": {
"version": "1.0.1",
"from": "on-headers@>=1.0.1 <1.1.0",
"from": "on-headers@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz"
}
}
@@ -452,9 +442,9 @@
}
},
"readable-stream": {
"version": "2.1.0",
"version": "2.1.2",
"from": "readable-stream@latest",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.0.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.2.tgz",
"dependencies": {
"core-util-is": {
"version": "1.0.2",
@@ -466,118 +456,21 @@
"from": "inherits@>=2.0.1 <2.1.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
},
"inline-process-browser": {
"version": "2.0.1",
"from": "inline-process-browser@>=2.0.1 <2.1.0",
"resolved": "https://registry.npmjs.org/inline-process-browser/-/inline-process-browser-2.0.1.tgz",
"dependencies": {
"falafel": {
"version": "1.2.0",
"from": "falafel@>=1.0.1 <2.0.0",
"resolved": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz",
"dependencies": {
"acorn": {
"version": "1.2.2",
"from": "acorn@>=1.0.3 <2.0.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz"
},
"foreach": {
"version": "2.0.5",
"from": "foreach@>=2.0.5 <3.0.0",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz"
},
"isarray": {
"version": "0.0.1",
"from": "isarray@0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
},
"object-keys": {
"version": "1.0.9",
"from": "object-keys@>=1.0.6 <2.0.0",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.9.tgz"
}
}
},
"through2": {
"version": "0.6.5",
"from": "through2@>=0.6.5 <0.7.0",
"resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
"dependencies": {
"readable-stream": {
"version": "1.0.34",
"from": "readable-stream@>=1.0.33-1 <1.1.0-0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"dependencies": {
"isarray": {
"version": "0.0.1",
"from": "isarray@0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
}
}
},
"xtend": {
"version": "4.0.1",
"from": "xtend@>=4.0.0 <4.1.0-0",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
}
}
}
}
},
"isarray": {
"version": "1.0.0",
"from": "isarray@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
},
"process-nextick-args": {
"version": "1.0.6",
"version": "1.0.7",
"from": "process-nextick-args@>=1.0.6 <1.1.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz"
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.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"
},
"unreachable-branch-transform": {
"version": "0.5.1",
"from": "unreachable-branch-transform@>=0.5.0 <0.6.0",
"resolved": "https://registry.npmjs.org/unreachable-branch-transform/-/unreachable-branch-transform-0.5.1.tgz",
"dependencies": {
"esmangle-evaluator": {
"version": "1.0.0",
"from": "esmangle-evaluator@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/esmangle-evaluator/-/esmangle-evaluator-1.0.0.tgz"
},
"recast": {
"version": "0.11.5",
"from": "recast@>=0.11.4 <0.12.0",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.11.5.tgz",
"dependencies": {
"ast-types": {
"version": "0.8.16",
"from": "ast-types@0.8.16",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.16.tgz"
},
"esprima": {
"version": "2.7.2",
"from": "esprima@>=2.7.1 <2.8.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.2.tgz"
},
"private": {
"version": "0.1.6",
"from": "private@>=0.1.5 <0.2.0",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.6.tgz"
},
"source-map": {
"version": "0.5.4",
"from": "source-map@>=0.5.0 <0.6.0",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.4.tgz"
}
}
}
}
},
"util-deprecate": {
"version": "1.0.2",
"from": "util-deprecate@>=1.0.1 <1.1.0",
@@ -1652,7 +1545,7 @@
"dependencies": {
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <2.1.0",
"from": "inherits@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
},
"minimatch": {
@@ -1687,14 +1580,14 @@
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz",
"dependencies": {
"mime-types": {
"version": "2.1.10",
"version": "2.1.11",
"from": "mime-types@>=2.1.6 <2.2.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz",
"dependencies": {
"mime-db": {
"version": "1.22.0",
"from": "mime-db@>=1.22.0 <1.23.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz"
"version": "1.23.0",
"from": "mime-db@>=1.23.0 <1.24.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz"
}
}
},
@@ -1716,9 +1609,9 @@
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.1.tgz"
},
"content-type": {
"version": "1.0.1",
"version": "1.0.2",
"from": "content-type@>=1.0.1 <1.1.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.1.tgz"
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz"
},
"cookie": {
"version": "0.1.5",
@@ -1732,7 +1625,7 @@
},
"depd": {
"version": "1.1.0",
"from": "depd@1.1.0",
"from": "depd@>=1.1.0 <1.2.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz"
},
"escape-html": {
@@ -1871,14 +1764,14 @@
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
},
"mime-types": {
"version": "2.1.10",
"version": "2.1.11",
"from": "mime-types@>=2.1.6 <2.2.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz",
"dependencies": {
"mime-db": {
"version": "1.22.0",
"from": "mime-db@>=1.22.0 <1.23.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz"
"version": "1.23.0",
"from": "mime-db@>=1.23.0 <1.24.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz"
}
}
}
@@ -1923,7 +1816,7 @@
},
"on-headers": {
"version": "1.0.1",
"from": "on-headers@>=1.0.1 <1.1.0",
"from": "on-headers@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz"
},
"parseurl": {
@@ -2070,9 +1963,9 @@
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.7.0.tgz",
"dependencies": {
"basic-auth": {
"version": "1.0.3",
"version": "1.0.4",
"from": "basic-auth@>=1.0.3 <1.1.0",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.3.tgz"
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz"
},
"depd": {
"version": "1.1.0",
@@ -2093,7 +1986,7 @@
},
"on-headers": {
"version": "1.0.1",
"from": "on-headers@>=1.0.1 <1.1.0",
"from": "on-headers@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz"
}
}
@@ -2584,14 +2477,14 @@
}
},
"mime-types": {
"version": "2.1.10",
"version": "2.1.11",
"from": "mime-types@>=2.1.3 <3.0.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz",
"dependencies": {
"mime-db": {
"version": "1.22.0",
"from": "mime-db@>=1.22.0 <1.23.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz"
"version": "1.23.0",
"from": "mime-db@>=1.23.0 <1.24.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz"
}
}
}
@@ -2666,9 +2559,9 @@
"resolved": "http://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz"
},
"ursa": {
"version": "0.9.3",
"version": "0.9.4",
"from": "ursa@>=0.9.3 <0.10.0",
"resolved": "https://registry.npmjs.org/ursa/-/ursa-0.9.3.tgz",
"resolved": "https://registry.npmjs.org/ursa/-/ursa-0.9.4.tgz",
"dependencies": {
"bindings": {
"version": "1.2.1",
@@ -2676,9 +2569,9 @@
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
},
"nan": {
"version": "2.2.1",
"from": "nan@>=2.0.9 <3.0.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.2.1.tgz"
"version": "2.3.3",
"from": "nan@>=2.3.3 <3.0.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.3.3.tgz"
}
}
},
+1 -1
View File
@@ -17,7 +17,7 @@
"aws-sdk": "^2.1.46",
"body-parser": "^1.13.1",
"bytes": "^2.3.0",
"cloudron-manifestformat": "^2.3.0",
"cloudron-manifestformat": "^2.4.0",
"connect-ensure-login": "^0.1.1",
"connect-lastmile": "0.0.13",
"connect-timeout": "^1.5.0",
+4 -4
View File
@@ -3,16 +3,16 @@
# If you change the infra version, be sure to put a warning
# in the change log
INFRA_VERSION=30
INFRA_VERSION=32
# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
# These constants are used in the installer script as well
BASE_IMAGE=cloudron/base:0.8.0
BASE_IMAGE=cloudron/base:0.8.1
MYSQL_IMAGE=cloudron/mysql:0.11.0
POSTGRESQL_IMAGE=cloudron/postgresql:0.9.0
POSTGRESQL_IMAGE=cloudron/postgresql:0.10.0
MONGODB_IMAGE=cloudron/mongodb:0.9.0
REDIS_IMAGE=cloudron/redis:0.8.0 # if you change this, fix src/addons.js as well
MAIL_IMAGE=cloudron/mail:0.12.0
MAIL_IMAGE=cloudron/mail:0.13.0
GRAPHITE_IMAGE=cloudron/graphite:0.8.0
MYSQL_REPO=cloudron/mysql
+111 -13
View File
@@ -44,6 +44,12 @@ var NOOP = function (app, options, callback) { return callback(); };
// setup can be called multiple times for the same app (configure crash restart) and existing data must not be lost
// teardown is destructive. app data stored with the addon is lost
var KNOWN_ADDONS = {
email: {
setup: setupEmail,
teardown: teardownEmail,
backup: NOOP,
restore: setupEmail
},
ldap: {
setup: setupLdap,
teardown: teardownLdap,
@@ -80,6 +86,12 @@ var KNOWN_ADDONS = {
backup: backupPostgreSql,
restore: restorePostgreSql
},
recvmail: {
setup: setupRecvMail,
teardown: teardownRecvMail,
backup: NOOP,
restore: setupRecvMail
},
redis: {
setup: setupRedis,
teardown: teardownRedis,
@@ -129,7 +141,7 @@ function initialize(callback) {
certificates.getAdminCertificatePath(function (error, certFilePath, keyFilePath) {
if (error) return callback(error);
shell.sudo('seutp_infra', [ SETUP_INFRA_CMD, config.fqdn(), config.adminFqdn(), certFilePath, keyFilePath ], callback);
shell.sudo('seutp_infra', [ SETUP_INFRA_CMD, paths.DATA_DIR, config.fqdn(), config.adminFqdn(), certFilePath, keyFilePath, config.database().name, config.database().password ], callback);
});
}
@@ -220,17 +232,23 @@ function getLinksSync(app, addons) {
if (!addons) return links;
var addMail = false;
for (var addon in addons) {
switch (addon) {
case 'mysql': links.push('mysql:mysql'); break;
case 'postgresql': links.push('postgresql:postgresql'); break;
case 'sendmail': links.push('mail:mail'); break;
case 'sendmail': addMail = true; break;
case 'recvmail': addMail = true; break;
case 'email': addMail = true; break;
case 'redis': links.push('redis-' + app.id + ':redis-' + app.id); break;
case 'mongodb': links.push('mongodb:mongodb'); break;
default: break;
}
}
if (addMail) links.push('mail:mail');
return links;
}
@@ -366,6 +384,37 @@ function teardownSimpleAuth(app, options, callback) {
});
}
function setupEmail(app, options, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
// note that "external" access info can be derived from MAIL_DOMAIN (since it's part of user documentation)
var env = [
'MAIL_SMTP_SERVER=mail',
'MAIL_SMTP_PORT=2525',
'MAIL_IMAP_SERVER=mail',
'MAIL_IMAP_PORT=9993',
'MAIL_SIEVE_SERVER=mail',
'MAIL_SIEVE_PORT=4190',
'MAIL_DOMAIN=' + config.fqdn()
];
debugApp(app, 'Setting up Email');
appdb.setAddonConfig(app.id, 'email', env, callback);
}
function teardownEmail(app, options, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
debugApp(app, 'Tearing down Email');
appdb.unsetAddonConfig(app.id, 'email', callback);
}
function setupLdap(app, options, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
@@ -401,20 +450,18 @@ function setupSendMail(app, options, callback) {
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
// FIXME: to can conflict with a real user!
var from = (app.location ? app.location : app.manifest.title.replace(/[^a-zA-Z0-9]/, '')) + '-app';
var env = [
'MAIL_SMTP_SERVER=mail',
'MAIL_SMTP_PORT=2500', // if you change this, change the mail container
'MAIL_SMTP_USERNAME=' + from, // change this to app.id after apps have moved
'MAIL_SMTP_PASSWORD=' + 'app-' + app.id, // this is ignored
'MAIL_FROM=' + from + '@' + config.fqdn(),
'MAIL_DOMAIN=' + config.fqdn()
];
var cmd = [ '/addons/mail/service.sh', 'add-send', from ];
debugApp(app, 'Setting up sendmail');
docker.execContainer('mail', cmd, { bufferStdout: true }, function (error, stdout) {
if (error) return callback(error);
appdb.setAddonConfig(app.id, 'sendmail', env, callback);
var env = stdout.toString('utf8').split('\n').slice(0, -1); // remove trailing newline
debugApp(app, 'Setting sendmail addon config to %j', env);
appdb.setAddonConfig(app.id, 'sendmail', env, callback);
});
}
function teardownSendMail(app, options, callback) {
@@ -424,7 +471,58 @@ function teardownSendMail(app, options, callback) {
debugApp(app, 'Tearing down sendmail');
appdb.unsetAddonConfig(app.id, 'sendmail', callback);
// FIXME: to can conflict with a real user!
var from = (app.location ? app.location : app.manifest.title.replace(/[^a-zA-Z0-9]/, '')) + '-app';
var cmd = [ '/addons/mail/service.sh', 'remove-send', from ];
debugApp(app, 'Tearing down sendmail');
docker.execContainer('mail', cmd, { }, function (error) {
if (error) return callback(error);
appdb.unsetAddonConfig(app.id, 'sendmail', callback);
});
}
function setupRecvMail(app, options, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
debugApp(app, 'Setting up recvmail');
// FIXME: to can conflict with a real user!
var to = (app.location ? app.location : app.manifest.title.replace(/[^a-zA-Z0-9]/, '')) + '-app';
var cmd = [ '/addons/mail/service.sh', 'add-recv', to ];
docker.execContainer('mail', cmd, { bufferStdout: true }, function (error, stdout) {
if (error) return callback(error);
var env = stdout.toString('utf8').split('\n').slice(0, -1); // remove trailing newline
debugApp(app, 'Setting recvmail addon config to %j', env);
appdb.setAddonConfig(app.id, 'recvmail', env, callback);
});
}
function teardownRecvMail(app, options, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
// FIXME: to can conflict with a real user!
var to = (app.location ? app.location : app.manifest.title.replace(/[^a-zA-Z0-9]/, '')) + '-app';
var cmd = [ '/addons/mail/service.sh', 'remove-recv', to ];
debugApp(app, 'Tearing down recvmail');
docker.execContainer('mail', cmd, { }, function (error) {
if (error) return callback(error);
appdb.unsetAddonConfig(app.id, 'recvmail', callback);
});
}
function setupMySql(app, options, callback) {
+2 -1
View File
@@ -103,7 +103,7 @@ AppsError.BAD_CERTIFICATE = 'Invalid certificate';
// https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names
// We are validating the validity of the location-fqdn as host name
function validateHostname(location, fqdn) {
var RESERVED_LOCATIONS = [ constants.ADMIN_LOCATION, constants.API_LOCATION, constants.SMTP_LOCATION, constants.IMAP_LOCATION ];
var RESERVED_LOCATIONS = [ constants.ADMIN_LOCATION, constants.API_LOCATION, constants.SMTP_LOCATION, constants.IMAP_LOCATION, constants.MAIL_LOCATION, constants.POSTMAN_LOCATION ];
if (RESERVED_LOCATIONS.indexOf(location) !== -1) return new Error(location + ' is reserved');
@@ -141,6 +141,7 @@ function validatePortBindings(portBindings, tcpPorts) {
config.get('oauthProxyPort'), /* oauth proxy server (lo) */
config.get('simpleAuthPort'), /* simple auth server (lo) */
3306, /* mysql (lo) */
4190, /* managesieve */
8000 /* graphite (lo) */
];
+6 -1
View File
@@ -33,6 +33,7 @@ var apps = require('./apps.js'),
backups = require('./backups.js'),
clientdb = require('./clientdb.js'),
config = require('./config.js'),
constants = require('./constants.js'),
debug = require('debug')('box:cloudron'),
df = require('node-df'),
eventlog = require('./eventlog.js'),
@@ -444,16 +445,19 @@ function addDnsRecords() {
sysinfo.getIp(function (error, ip) {
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
var webadminRecord = { subdomain: 'my', type: 'A', values: [ ip ] };
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 ] };
@@ -462,6 +466,7 @@ function addDnsRecords() {
records.push(webadminRecord);
records.push(dkimRecord);
records.push(dmarcRecord);
records.push(mxRecord);
}
debug('addDnsRecords: %j', records);
+5
View File
@@ -28,6 +28,7 @@ exports = module.exports = {
internalAdminOrigin: internalAdminOrigin,
sysadminOrigin: sysadminOrigin, // caas routes
adminFqdn: adminFqdn,
mailFqdn: mailFqdn,
appFqdn: appFqdn,
zoneName: zoneName,
adminEmail: adminEmail,
@@ -167,6 +168,10 @@ function adminFqdn() {
return appFqdn(constants.ADMIN_LOCATION);
}
function mailFqdn() {
return appFqdn(constants.MAIL_LOCATION);
}
function adminOrigin() {
return 'https://' + appFqdn(constants.ADMIN_LOCATION);
}
+2
View File
@@ -6,6 +6,8 @@ exports = module.exports = {
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',
-2
View File
@@ -1,5 +1,3 @@
/* jslint node: true */
'use strict';
exports = module.exports = {
+150 -138
View File
@@ -40,159 +40,171 @@ function getAppByRequest(req, callback) {
});
}
function userSearch(req, res, next) {
debug('user search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
user.list(function (error, result) {
if (error) return next(new ldap.OperationsError(error.toString()));
// send user objects
result.forEach(function (entry) {
var dn = ldap.parseDN('cn=' + entry.id + ',ou=users,dc=cloudron');
var groups = [ GROUP_USERS_DN ];
if (entry.admin) groups.push(GROUP_ADMINS_DN);
var displayName = entry.displayName || entry.username;
var nameParts = displayName.split(' ');
var firstName = nameParts[0];
var lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : ''; // choose last part, if it exists
var obj = {
dn: dn.toString(),
attributes: {
objectclass: ['user'],
objectcategory: 'person',
cn: entry.id,
uid: entry.id,
mail: entry.email,
mailAlternateAddress: entry.username + '@' + config.fqdn(), // only valid when incoming mail enabled
displayname: displayName,
givenName: firstName,
username: entry.username,
samaccountname: entry.username, // to support ActiveDirectory clients
memberof: groups
}
};
// http://www.zytrax.com/books/ldap/ape/core-schema.html#sn has 'name' as SUP which is a DirectoryString
// which is required to have atleast one character if present
if (lastName.length !== 0) obj.attributes.sn = lastName;
// ensure all filter values are also lowercase
var lowerCaseFilter = ldap.parseFilter(req.filter.toString().toLowerCase());
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
res.send(obj);
}
});
res.end();
});
}
function groupSearch(req, res, next) {
debug('group search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
user.list(function (error, result){
if (error) return next(new ldap.OperationsError(error.toString()));
var groups = [{
name: 'users',
admin: false
}, {
name: 'admins',
admin: true
}];
groups.forEach(function (group) {
var dn = ldap.parseDN('cn=' + group.name + ',ou=groups,dc=cloudron');
var members = group.admin ? result.filter(function (entry) { return entry.admin; }) : result;
var obj = {
dn: dn.toString(),
attributes: {
objectclass: ['group'],
cn: group.name,
memberuid: members.map(function(entry) { return entry.id; })
}
};
// ensure all filter values are also lowercase
var lowerCaseFilter = ldap.parseFilter(req.filter.toString().toLowerCase());
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
res.send(obj);
}
});
res.end();
});
}
function userBind(req, res, next) {
debug('user bind: %s (from %s)', req.dn.toString(), req.connection.ldap.id);
// extract the common name which might have different attribute names
var attributeName = Object.keys(req.dn.rdns[0])[0];
var commonName = req.dn.rdns[0][attributeName];
if (!commonName) return next(new ldap.NoSuchObjectError(req.dn.toString()));
var api;
if (attributeName === 'mail') {
api = user.verifyWithEmail;
} else if (commonName.indexOf('@') !== -1) { // if mail is specified, enforce mail check
var parts = commonName.split('@');
if (parts[1] === config.fqdn()) { // internal email, verify with username
commonName = parts[0];
api = user.verify;
} else { // external email
api = user.verifyWithEmail;
}
} else if (commonName.indexOf('uid-') === 0) {
api = user.verify;
} else {
api = user.verifyWithUsername;
}
// TODO this should be done after we verified the app has access to avoid leakage of user existence
api(commonName, req.credentials || '', function (error, userObject) {
if (error && error.reason === UserError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (error && error.reason === UserError.WRONG_PASSWORD) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
if (error) return next(new ldap.OperationsError(error));
getAppByRequest(req, function (error, app) {
if (error) return next(error);
if (!app) {
debug('no app found for this container, allow access');
return res.end();
}
apps.hasAccessTo(app, userObject, function (error, result) {
if (error) return next(new ldap.OperationsError(error.toString()));
// we return no such object, to avoid leakage of a users existence
if (!result) return next(new ldap.NoSuchObjectError(req.dn.toString()));
eventlog.add(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', appId: app.id }, { userId: userObject.id });
res.end();
});
});
});
}
function start(callback) {
assert.strictEqual(typeof callback, 'function');
gServer = ldap.createServer({ log: gLogger });
gServer.search('ou=users,dc=cloudron', function (req, res, next) {
debug('user search: dn %s, scope %s, filter %s', req.dn.toString(), req.scope, req.filter.toString());
gServer.search('ou=users,dc=cloudron', userSearch);
gServer.search('ou=groups,dc=cloudron', groupSearch);
gServer.bind('ou=users,dc=cloudron', userBind);
user.list(function (error, result) {
if (error) return next(new ldap.OperationsError(error.toString()));
// send user objects
result.forEach(function (entry) {
var dn = ldap.parseDN('cn=' + entry.id + ',ou=users,dc=cloudron');
var groups = [ GROUP_USERS_DN ];
if (entry.admin) groups.push(GROUP_ADMINS_DN);
var displayName = entry.displayName || entry.username;
var nameParts = displayName.split(' ');
var firstName = nameParts[0];
var lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : ''; // choose last part, if it exists
var obj = {
dn: dn.toString(),
attributes: {
objectclass: ['user'],
objectcategory: 'person',
cn: entry.id,
uid: entry.id,
mail: entry.email,
displayname: displayName,
givenName: firstName,
username: entry.username,
samaccountname: entry.username, // to support ActiveDirectory clients
memberof: groups
}
};
// http://www.zytrax.com/books/ldap/ape/core-schema.html#sn has 'name' as SUP which is a DirectoryString
// which is required to have atleast one character if present
if (lastName.length !== 0) obj.attributes.sn = lastName;
// ensure all filter values are also lowercase
var lowerCaseFilter = ldap.parseFilter(req.filter.toString().toLowerCase());
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
res.send(obj);
}
});
res.end();
});
});
gServer.search('ou=groups,dc=cloudron', function (req, res, next) {
debug('group search: dn %s, scope %s, filter %s', req.dn.toString(), req.scope, req.filter.toString());
user.list(function (error, result){
if (error) return next(new ldap.OperationsError(error.toString()));
var groups = [{
name: 'users',
admin: false
}, {
name: 'admins',
admin: true
}];
groups.forEach(function (group) {
var dn = ldap.parseDN('cn=' + group.name + ',ou=groups,dc=cloudron');
var members = group.admin ? result.filter(function (entry) { return entry.admin; }) : result;
var obj = {
dn: dn.toString(),
attributes: {
objectclass: ['group'],
cn: group.name,
memberuid: members.map(function(entry) { return entry.id; })
}
};
// ensure all filter values are also lowercase
var lowerCaseFilter = ldap.parseFilter(req.filter.toString().toLowerCase());
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
res.send(obj);
}
});
res.end();
});
});
// this is the bind for the mail addon to authorize apps
gServer.bind('ou=sendmail,dc=cloudron', function(req, res, next) {
// TODO: validate password
debug('sendmail bind: %s', req.dn.toString()); // note: cn can be email or id
// this is the bind for addons (after bind, they might search and authenticate)
gServer.bind('ou=addons,dc=cloudron', function(req, res, next) {
debug('addons bind: %s', req.dn.toString()); // note: cn can be email or id
res.end();
});
// this is the bind for apps (after bind, they might search and authenticate user)
gServer.bind('ou=apps,dc=cloudron', function(req, res, next) {
// TODO: validate password
debug('application bind: %s', req.dn.toString());
res.end();
});
gServer.bind('ou=users,dc=cloudron', function(req, res, next) {
debug('user bind: %s', req.dn.toString());
// extract the common name which might have different attribute names
var attributeName = Object.keys(req.dn.rdns[0])[0];
var commonName = req.dn.rdns[0][attributeName];
if (!commonName) return next(new ldap.NoSuchObjectError(req.dn.toString()));
var api;
// if mail is specified, enforce mail check
if (commonName.indexOf('@') !== -1 || attributeName === 'mail') {
api = user.verifyWithEmail;
} else if (commonName.indexOf('uid-') === 0) {
api = user.verify;
} else {
api = user.verifyWithUsername;
}
// TODO this should be done after we verified the app has access to avoid leakage of user existence
api(commonName, req.credentials || '', function (error, userObject) {
if (error && error.reason === UserError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (error && error.reason === UserError.WRONG_PASSWORD) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
if (error) return next(new ldap.OperationsError(error));
getAppByRequest(req, function (error, app) {
if (error) return next(error);
if (!app) {
debug('no app found for this container, allow access');
return res.end();
}
apps.hasAccessTo(app, userObject, function (error, result) {
if (error) return next(new ldap.OperationsError(error.toString()));
// we return no such object, to avoid leakage of a users existence
if (!result) return next(new ldap.NoSuchObjectError(req.dn.toString()));
eventlog.add(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', appId: app.id }, { userId: userObject.id });
res.end();
});
});
});
});
gServer.listen(config.get('ldapPort'), callback);
}
+2 -2
View File
@@ -159,14 +159,14 @@ function sendMails(queue) {
var transport = nodemailer.createTransport(smtpTransport({
host: mailServerIp,
port: 2500, // this value comes from mail container
port: 2525, // this value comes from mail container
auth: {
user: 'no-reply', // derive from adminEmail
pass: 'supersecret'
}
}));
debug('Processing mail queue of size %d (through %s:2500)', queue.length, mailServerIp);
debug('Processing mail queue of size %d (through %s:2525)', queue.length, mailServerIp);
async.mapSeries(queue, function iterator(mailOptions, callback) {
transport.sendMail(mailOptions, function (error) {
+33 -15
View File
@@ -114,24 +114,42 @@ describe('Apps', function () {
before(function (done) {
console.log('Starting addons, this can take 10 seconds');
dockerProxy = startDockerProxy(function interceptor(req, res) {
if (req.method === 'POST' && req.url === '/images/create?fromImage=' + encodeURIComponent(TEST_IMAGE_REPO) + '&tag=' + TEST_IMAGE_TAG) {
imageCreated = true;
res.writeHead(200);
res.end();
return true;
} else if (req.method === 'DELETE' && req.url === '/images/' + TEST_IMAGE + '?force=false&noprune=false') {
imageDeleted = true;
res.writeHead(200);
res.end();
return true;
}
return false;
}, done);
safe.fs.unlinkSync(paths.DATA_DIR + '/INFRA_VERSION');
safe.fs.writeFileSync(paths.DATA_DIR + '/cert', 'utf8');
safe.fs.writeFileSync(paths.DATA_DIR + '/key', 'utf8');
var args = [
path.resolve(__dirname + '/../../scripts/setup_infra.sh'),
paths.DATA_DIR,
config.fqdn(),
config.adminFqdn(),
paths.DATA_DIR + '/cert',
paths.DATA_DIR + '/key',
config.database().name,
'"' + config.database().password + '"' // can be empty...
];
child_process.exec('sudo ' + args.join(' '), { stdio: 'pipe' }, function (error) {
if (error) return done(error);
dockerProxy = startDockerProxy(function interceptor(req, res) {
if (req.method === 'POST' && req.url === '/images/create?fromImage=' + encodeURIComponent(TEST_IMAGE_REPO) + '&tag=' + TEST_IMAGE_TAG) {
imageCreated = true;
res.writeHead(200);
res.end();
return true;
} else if (req.method === 'DELETE' && req.url === '/images/' + TEST_IMAGE + '?force=false&noprune=false') {
imageDeleted = true;
res.writeHead(200);
res.end();
return true;
}
return false;
}, done);
});
});
after(function (done) {
// child_process.exec('docker rm -f mysql; docker rm -f postgresql; docker rm -f mongodb; docker rm -f mail', function (error) {
child_process.exec('docker ps | awk \'{print $1}\' | xargs docker rm -f', function () {
dockerProxy.close(done);
});
-72
View File
@@ -1,72 +0,0 @@
#!/bin/bash
set -eu -o pipefail
readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
source ${SOURCE_DIR}/src/INFRA_VERSION
readonly mysqldatadir="/tmp/mysqldata-$(date +%s)"
readonly postgresqldatadir="/tmp/postgresqldata-$(date +%s)"
readonly mongodbdatadir="/tmp/mongodbdata-$(date +%s)"
root_password=secret
start_postgresql() {
postgresql_vars="POSTGRESQL_ROOT_PASSWORD=${root_password}; POSTGRESQL_ROOT_HOST=172.17.0.0/255.255.0.0"
rm -rf /tmp/postgresql_vars.sh
echo "${postgresql_vars}" > /tmp/postgresql_vars.sh
docker rm -f postgresql 2>/dev/null 1>&2 || true
docker run -dtP --name=postgresql -v "${postgresqldatadir}:/var/lib/postgresql" \
--read-only -v /tmp -v /run \
-v /tmp/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh "${POSTGRESQL_IMAGE}" >/dev/null
}
start_mysql() {
local mysql_vars="MYSQL_ROOT_PASSWORD=${root_password}; MYSQL_ROOT_HOST=172.17.0.0/255.255.0.0"
rm -rf /tmp/mysql_vars.sh
echo "${mysql_vars}" > /tmp/mysql_vars.sh
docker rm -f mysql 2>/dev/null 1>&2 || true
docker run -dP --name=mysql -v "${mysqldatadir}:/var/lib/mysql" \
--read-only -v /tmp -v /run \
-v /tmp/mysql_vars.sh:/etc/mysql/mysql_vars.sh "${MYSQL_IMAGE}" >/dev/null
}
start_mongodb() {
local mongodb_vars="MONGODB_ROOT_PASSWORD=${root_password}"
rm -rf /tmp/mongodb_vars.sh
echo "${mongodb_vars}" > /tmp/mongodb_vars.sh
docker rm -f mongodb 2>/dev/null 1>&2 || true
docker run -dP --name=mongodb -v "${mongodbdatadir}:/var/lib/mongodb" \
--read-only -v /tmp -v /run \
-v /tmp/mongodb_vars.sh:/etc/mongodb_vars.sh "${MONGODB_IMAGE}" >/dev/null
}
start_mail() {
docker rm -f mail 2>/dev/null 1>&2 || true
docker run -dP --name=mail -e MAIL_SERVER_NAME="server.local" -e MAIL_DOMAIN="server.local" \
--read-only -v /tmp -v /run \
-v /tmp/maildata:/app/data "${MAIL_IMAGE}" >/dev/null
}
start_mysql
start_postgresql
start_mongodb
start_mail
echo -n "Waiting for addons to start"
for i in {1..10}; do
echo -n "."
sleep 1
done
echo ""
+33 -22
View File
@@ -12,20 +12,21 @@ if [[ $# == 1 && "$1" == "--check" ]]; then
exit 0
fi
readonly DATA_DIR="/home/yellowtent/data"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${script_dir}/../INFRA_VERSION" # this injects INFRA_VERSION
readonly fqdn="$1"
readonly mail_fqdn="$2"
readonly mail_tls_cert="$3"
readonly mail_tls_key="$4"
readonly data_dir="$1"
readonly fqdn="$2"
readonly mail_fqdn="$3"
readonly mail_tls_cert="$4"
readonly mail_tls_key="$5"
readonly db_name="$6"
readonly db_password="$7"
# 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")
[[ -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
@@ -40,6 +41,12 @@ if [[ -n "${existing_containers}" ]]; then
echo "${existing_containers}" | xargs docker rm -f
fi
# a hack to 'refresh' images when testing with hotfix --recreate-infra
if [[ -z "${infra_version}" ]]; then
echo "Removing existing images"
docker rmi "${BASE_IMAGE}" "${MYSQL_IMAGE}" "${POSTGRESQL_IMAGE}" "${MONGODB_IMAGE}" "${REDIS_IMAGE}" "${MAIL_IMAGE}" "${GRAPHITE_IMAGE}" || true
fi
# graphite
graphite_container_id=$(docker run --restart=always -d --name="graphite" \
-m 75m \
@@ -47,7 +54,7 @@ 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" \
-v "${data_dir}/graphite:/app/data" \
--read-only -v /tmp -v /run \
"${GRAPHITE_IMAGE}")
echo "Graphite container id: ${graphite_container_id}"
@@ -55,7 +62,7 @@ if docker images "${GRAPHITE_REPO}" | tail -n +2 | awk '{ print $1 ":" $2 }' | g
echo "Removed old graphite images"
fi
# mail (MAIL_SMTP_PORT is 2500 in addons.js. used in mailer.js as well)
# mail (note: 2525 is hardcoded in mail container and app use this port)
# MAIL_SERVER_NAME is the hostname of the mailserver i.e server uses these certs
# MAIL_DOMAIN is the domain for which this server is relaying mails
mail_container_id=$(docker run --restart=always -d --name="mail" \
@@ -64,9 +71,13 @@ mail_container_id=$(docker run --restart=always -d --name="mail" \
-h "${fqdn}" \
-e "MAIL_DOMAIN=${fqdn}" \
-e "MAIL_SERVER_NAME=${mail_fqdn}" \
-v "${DATA_DIR}/box/mail:/app/data" \
-v "${data_dir}/box/mail:/app/data" \
-v "${mail_tls_key}:/etc/tls_key.pem:ro" \
-v "${mail_tls_cert}:/etc/tls_cert.pem:ro" \
-p 587:2525 \
-p 993:9993 \
-p 4190:4190 \
-p 25:2525 \
--read-only -v /tmp -v /run \
"${MAIL_IMAGE}")
echo "Mail container id: ${mail_container_id}"
@@ -77,7 +88,7 @@ fi
# 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
cat > "${data_dir}/addons/mysql_vars.sh" <<EOF
readonly MYSQL_ROOT_PASSWORD='${mysql_addon_root_password}'
readonly MYSQL_ROOT_HOST='${docker0_ip}'
EOF
@@ -85,8 +96,8 @@ mysql_container_id=$(docker run --restart=always -d --name="mysql" \
-m 256m \
--memory-swap 512m \
-h "${fqdn}" \
-v "${DATA_DIR}/mysql:/var/lib/mysql" \
-v "${DATA_DIR}/addons/mysql_vars.sh:/etc/mysql/mysql_vars.sh:ro" \
-v "${data_dir}/mysql:/var/lib/mysql" \
-v "${data_dir}/addons/mysql_vars.sh:/etc/mysql/mysql_vars.sh:ro" \
--read-only -v /tmp -v /run \
"${MYSQL_IMAGE}")
echo "MySQL container id: ${mysql_container_id}"
@@ -96,15 +107,15 @@ fi
# postgresql
postgresql_addon_root_password=$(pwgen -1 -s)
cat > "${DATA_DIR}/addons/postgresql_vars.sh" <<EOF
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" \
-m 100m \
--memory-swap 200m \
-h "${fqdn}" \
-v "${DATA_DIR}/postgresql:/var/lib/postgresql" \
-v "${DATA_DIR}/addons/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh:ro" \
-v "${data_dir}/postgresql:/var/lib/postgresql" \
-v "${data_dir}/addons/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh:ro" \
--read-only -v /tmp -v /run \
"${POSTGRESQL_IMAGE}")
echo "PostgreSQL container id: ${postgresql_container_id}"
@@ -114,15 +125,15 @@ fi
# mongodb
mongodb_addon_root_password=$(pwgen -1 -s)
cat > "${DATA_DIR}/addons/mongodb_vars.sh" <<EOF
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" \
-m 100m \
--memory-swap 200m \
-h "${fqdn}" \
-v "${DATA_DIR}/mongodb:/var/lib/mongodb" \
-v "${DATA_DIR}/addons/mongodb_vars.sh:/etc/mongodb_vars.sh:ro" \
-v "${data_dir}/mongodb:/var/lib/mongodb" \
-v "${data_dir}/addons/mongodb_vars.sh:/etc/mongodb_vars.sh:ro" \
--read-only -v /tmp -v /run \
"${MONGODB_IMAGE}")
echo "Mongodb container id: ${mongodb_container_id}"
@@ -139,10 +150,10 @@ fi
if [[ "${infra_version}" == "none" ]]; then
# if no existing infra was found (for new, upgraded and restored cloudons), download app backups
echo "Marking installed apps for restore"
mysql -u root -ppassword -e 'UPDATE apps SET installationState = "pending_restore", oldConfigJson = NULL WHERE installationState = "installed"' box
mysql -u root --password="${db_password}" -e 'UPDATE apps SET installationState = "pending_restore", oldConfigJson = NULL WHERE installationState = "installed"' ${db_name}
else
# if existing infra was found, just mark apps for reconfiguration
mysql -u root -ppassword -e 'UPDATE apps SET installationState = "pending_configure", oldConfigJson = NULL WHERE installationState = "installed"' box
mysql -u root --password="${db_password}" -e 'UPDATE apps SET installationState = "pending_configure", oldConfigJson = NULL WHERE installationState = "installed"' ${db_name}
fi
echo -n "${INFRA_VERSION}" > "${DATA_DIR}/INFRA_VERSION"
echo -n "${INFRA_VERSION}" > "${data_dir}/INFRA_VERSION"
+2 -2
View File
@@ -8,10 +8,10 @@ readonly source_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)"
! "${source_dir}/src/test/checkInstall" && exit 1
# create dir structure
rm -rf $HOME/.cloudron_test
rm -rf $HOME/.cloudron_test 2>/dev/null || true # some of those docker container data requires sudo to be removed
mkdir -p $HOME/.cloudron_test
cd $HOME/.cloudron_test
mkdir -p data/appdata data/box/appicons data/mail data/nginx/cert data/nginx/applications data/collectd/collectd.conf.d data/addons configs data/box/certs data/box/mail/dkim/localhost
mkdir -p data/appdata data/box/appicons data/mail data/nginx/cert data/nginx/applications data/collectd/collectd.conf.d data/addons configs data/box/certs data/box/mail/dkim/localhost data/box/mail/dkim/foobar.com
webadmin_scopes="root,profile,users,apps,settings"
webadmin_origin="https://${ADMIN_LOCATION}-localhost"
+1 -1
View File
@@ -78,7 +78,7 @@ function validateUsername(username) {
assert.strictEqual(typeof username, 'string');
// https://github.com/gogits/gogs/blob/52c8f691630548fe091d30bcfe8164545a05d3d5/models/repo.go#L393
// admin@fqdn is also reservd for sending emails
var RESERVED_USERNAMES = [ 'admin', 'no-reply' ]; // apps like wordpress, gogs don't like these
var RESERVED_USERNAMES = [ 'admin', 'no-reply', 'postmaster', 'mailer-daemon' ]; // apps like wordpress, gogs don't like these
// allow empty usernames
if (username === '') return null;