diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..5c389c0560 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,99 @@ +# Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +# See License.txt for license information. +FROM ubuntu:14.04 + +# Install Dependancies +RUN apt-get update && apt-get install -y build-essential +RUN apt-get install -y curl +RUN curl -sL https://deb.nodesource.com/setup | bash - +RUN apt-get install -y nodejs +RUN apt-get install -y ruby-full +RUN gem install compass + +# Postfix +RUN apt-get install -y postfix + +# +# Install GO +# + +RUN apt-get update && apt-get install -y \ + gcc libc6-dev make git mercurial \ + --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* + +ENV GOLANG_VERSION 1.4.2 + +RUN curl -sSL https://golang.org/dl/go$GOLANG_VERSION.src.tar.gz \ + | tar -v -C /usr/src -xz + +RUN cd /usr/src/go/src && ./make.bash --no-clean 2>&1 + +ENV PATH /usr/src/go/bin:$PATH + +RUN mkdir -p /go/src /go/bin && chmod -R 777 /go +ENV GOPATH /go +ENV PATH /go/bin:$PATH +WORKDIR /go + +# --------------------------------------------------------------------------------------------------------------------- + +# +# Install SQL +# + +ENV MYSQL_ROOT_PASSWORD=mostest +ENV MYSQL_USER=mmuser +ENV MYSQL_PASSWORD=mostest +ENV MYSQL_DATABASE=mattermost_test + +RUN groupadd -r mysql && useradd -r -g mysql mysql + +RUN apt-get update && apt-get install -y perl --no-install-recommends && rm -rf /var/lib/apt/lists/* + +RUN apt-key adv --keyserver pool.sks-keyservers.net --recv-keys A4A9406876FCBD3C456770C88C718D3B5072E1F5 + +ENV MYSQL_MAJOR 5.6 +ENV MYSQL_VERSION 5.6.25 + +RUN echo "deb http://repo.mysql.com/apt/debian/ wheezy mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list + +RUN apt-get update \ + && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install mysql-server \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql + +RUN sed -Ei 's/^(bind-address|log)/#&/' /etc/mysql/my.cnf + +VOLUME /var/lib/mysql +# --------------------------------------------------------------------------------------------------------------------- + +# +# Install Redis +# + +RUN apt-get update && apt-get install -y wget +RUN wget http://download.redis.io/redis-stable.tar.gz; \ + tar xvzf redis-stable.tar.gz; \ + cd redis-stable; \ + make install + +# --------------------------------------------------------------------------------------------------------------------- + +# Copy over files +ADD . /go/src/github.com/mattermost/platform + +# Insert postfix config +ADD ./docker/main.cf /etc/postfix/ + +RUN go get github.com/tools/godep +RUN cd /go/src/github.com/mattermost/platform; godep restore +RUN go install github.com/mattermost/platform +RUN cd /go/src/github.com/mattermost/platform/web/react; npm install + +RUN chmod +x /go/src/github.com/mattermost/platform/docker/docker-entry.sh +ENTRYPOINT /go/src/github.com/mattermost/platform/docker/docker-entry.sh + +# Ports +EXPOSE 80 diff --git a/NOTICE.txt b/NOTICE.txt index 07c6a8bf4f..fd5fc74b51 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,4 +1,11 @@ Mattermost Platform Preview -Copyright 2015 SpinPunch, Inc. +© 2015 Spinpunch, Inc. All Rights Reserved. See LICENSE.txt for license information. -Regular expression support is provided by the PCRE library package, which is open source software, written by Philip Hazel, and copyright by the University of Cambridge, England. The original software is available from ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/ +NOTICES: + +This product contains a modified portion of 'draw2d', a Go library to draw 2d geometrical forms on images by Laurent Le Goff + +* LICENSE: + * http://opensource.org/licenses/BSD-3-Clause (BSD 3-Clause) +* HOMEPAGE: + * https://code.google.com/p/draw2d/ diff --git a/config/config.json b/config/config.json index a6c79efac6..1b844c259f 100644 --- a/config/config.json +++ b/config/config.json @@ -8,7 +8,7 @@ "FileLocation": "" }, "ServiceSettings": { - "SiteName": "XXXXXXMustBeFilledIn", + "SiteName": "Mattermost Preview", "Domain": "xxxxxxmustbefilledin.com", "Mode" : "dev", "AllowTesting" : false, @@ -57,6 +57,7 @@ "SMTPUsername": "", "SMTPPassword": "", "SMTPServer": "", + "UseTLS": false, "FeedbackEmail": "feedback@xxxxxxmustbefilledin.com", "FeedbackName": "", "ApplePushServer": "", diff --git a/config/config_docker.json b/config/config_docker.json new file mode 100644 index 0000000000..f2e56bef81 --- /dev/null +++ b/config/config_docker.json @@ -0,0 +1,85 @@ +{ + "LogSettings": { + "ConsoleEnable": false, + "ConsoleLevel": "DEBUG", + "FileEnable": true, + "FileLevel": "INFO", + "FileFormat": "", + "FileLocation": "" + }, + "ServiceSettings": { + "SiteName": "Mattermost Preview", + "Domain": "", + "Mode" : "prod", + "AllowTesting" : false, + "UseSSL": false, + "Port": "80", + "Version": "developer", + "Shards": { + }, + "InviteSalt": "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6", + "PublicLinkSalt": "TO3pTyXIZzwHiwyZgGql7lM7DG3zeId4", + "ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t", + "AnalyticsUrl": "" + }, + "SqlSettings": { + "DriverName": "mysql", + "DataSource": "mmuser:mostest@tcp(localhost:3306)/mattermost_test", + "DataSourceReplicas": ["mmuser:mostest@tcp(localhost:3306)/mattermost_test"], + "MaxIdleConns": 10, + "MaxOpenConns": 10, + "Trace": false, + "AtRestEncryptKey": "Ya0xMrybACJ3sZZVWQC7e31h5nSDWZFS" + }, + "RedisSettings": { + "DataSource": "localhost:6379", + "MaxOpenConns": 1000 + }, + "AWSSettings": { + "S3AccessKeyId": "", + "S3SecretAccessKey": "", + "S3Bucket": "", + "S3Region": "", + "Route53AccessKeyId": "", + "Route53SecretAccessKey": "", + "Route53ZoneId": "", + "Route53Region": "" + }, + "ImageSettings": { + "ThumbnailWidth": 200, + "ThumbnailHeight": 0, + "PreviewWidth": 1024, + "PreviewHeight": 0, + "ProfileWidth": 128, + "ProfileHeight": 128 + }, + "EmailSettings": { + "SMTPUsername": "", + "SMTPPassword": "", + "SMTPServer": "localhost:25", + "UseTLS": false, + "FeedbackEmail": "feedback@xxxxxxmustbefilledin.com", + "FeedbackName": "", + "ApplePushServer": "", + "ApplePushCertPublic": "", + "ApplePushCertPrivate": "" + }, + "PrivacySettings": { + "ShowEmailAddress": true, + "ShowPhoneNumber": true, + "ShowSkypeId": true, + "ShowFullName": true + }, + "TeamSettings": { + "MaxUsersPerTeam": 150, + "AllowPublicLink": true, + "AllowValet": false, + "TermsLink": "/static/help/configure_links.html", + "PrivacyLink": "/static/help/configure_links.html", + "AboutLink": "/static/help/configure_links.html", + "HelpLink": "/static/help/configure_links.html", + "ReportProblemLink": "/static/help/configure_links.html", + "TourLink": "/static/help/configure_links.html", + "DefaultThemeColor": "#2389D7" + } +} diff --git a/docker/docker-entry.sh b/docker/docker-entry.sh new file mode 100755 index 0000000000..cfa589041f --- /dev/null +++ b/docker/docker-entry.sh @@ -0,0 +1,122 @@ +#!/bin/bash +# Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +# See License.txt for license information. + +mkdir -p web/static/js + +echo "127.0.0.1 dockerhost" >> /etc/hosts +/etc/init.d/networking restart + +echo configuring mysql + +# SQL!!! +set -e + +get_option () { + local section=$1 + local option=$2 + local default=$3 + ret=$(my_print_defaults $section | grep '^--'${option}'=' | cut -d= -f2-) + [ -z $ret ] && ret=$default + echo $ret +} + + +# Get config +DATADIR="$("mysqld" --verbose --help 2>/dev/null | awk '$1 == "datadir" { print $2; exit }')" +SOCKET=$(get_option mysqld socket "$DATADIR/mysql.sock") +PIDFILE=$(get_option mysqld pid-file "/var/run/mysqld/mysqld.pid") + +if [ ! -d "$DATADIR/mysql" ]; then + if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" ]; then + echo >&2 'error: database is uninitialized and MYSQL_ROOT_PASSWORD not set' + echo >&2 ' Did you forget to add -e MYSQL_ROOT_PASSWORD=... ?' + exit 1 + fi + + mkdir -p "$DATADIR" + chown -R mysql:mysql "$DATADIR" + + echo 'Running mysql_install_db' + mysql_install_db --user=mysql --datadir="$DATADIR" --rpm --keep-my-cnf + echo 'Finished mysql_install_db' + + mysqld --user=mysql --datadir="$DATADIR" --skip-networking & + for i in $(seq 30 -1 0); do + [ -S "$SOCKET" ] && break + echo 'MySQL init process in progress...' + sleep 1 + done + if [ $i = 0 ]; then + echo >&2 'MySQL init process failed.' + exit 1 + fi + + # These statements _must_ be on individual lines, and _must_ end with + # semicolons (no line breaks or comments are permitted). + # TODO proper SQL escaping on ALL the things D: + + tempSqlFile=$(mktemp /tmp/mysql-first-time.XXXXXX.sql) + cat > "$tempSqlFile" <<-EOSQL + -- What's done in this file shouldn't be replicated + -- or products like mysql-fabric won't work + SET @@SESSION.SQL_LOG_BIN=0; + + DELETE FROM mysql.user ; + CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ; + GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ; + DROP DATABASE IF EXISTS test ; + EOSQL + + if [ "$MYSQL_DATABASE" ]; then + echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" >> "$tempSqlFile" + fi + + if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then + echo "CREATE USER '"$MYSQL_USER"'@'%' IDENTIFIED BY '"$MYSQL_PASSWORD"' ;" >> "$tempSqlFile" + + if [ "$MYSQL_DATABASE" ]; then + echo "GRANT ALL ON \`"$MYSQL_DATABASE"\`.* TO '"$MYSQL_USER"'@'%' ;" >> "$tempSqlFile" + fi + fi + + echo 'FLUSH PRIVILEGES ;' >> "$tempSqlFile" + + mysql -uroot < "$tempSqlFile" + + rm -f "$tempSqlFile" + kill $(cat $PIDFILE) + for i in $(seq 30 -1 0); do + [ -f "$PIDFILE" ] || break + echo 'MySQL init process in progress...' + sleep 1 + done + if [ $i = 0 ]; then + echo >&2 'MySQL hangs during init process.' + exit 1 + fi + echo 'MySQL init process done. Ready for start up.' +fi + +chown -R mysql:mysql "$DATADIR" + +mysqld & + +sleep 5 + +# ------------------------ + +echo starting postfix +/etc/init.d/postfix restart + +echo starting redis +redis-server & + +echo starting react processor +cd /go/src/github.com/mattermost/platform/web/react && npm start & + +echo starting go web server +cd /go/src/github.com/mattermost/platform/; go run mattermost.go -config=config_docker.json & + +echo starting compass watch +cd /go/src/github.com/mattermost/platform/web/sass-files && compass watch diff --git a/docker/main.cf b/docker/main.cf new file mode 100644 index 0000000000..ed97d37efd --- /dev/null +++ b/docker/main.cf @@ -0,0 +1,28 @@ +myorigin = mattermost.com +myhostname = mattermost.com + +smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu) +biff = no + +append_dot_mydomain = no + +readme_directory = no + +# TLS parameters +smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem +smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key +smtpd_use_tls=no +smtp_use_tls=no +smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache +smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache + +smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination +alias_maps = hash:/etc/aliases +alias_database = hash:/etc/aliases +mydestination = localhost, localhost.localdomain, localhost +relayhost = +mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 +mailbox_size_limit = 0 +recipient_delimiter = + +inet_interfaces = all +inet_protocols = all diff --git a/store/sql_team_store_test.go b/store/sql_team_store_test.go index 981817d90b..1b03b75fde 100644 --- a/store/sql_team_store_test.go +++ b/store/sql_team_store_test.go @@ -109,7 +109,10 @@ func TestTeamStoreGetByDomain(t *testing.T) { o1.Domain = "a" + model.NewId() + "b" o1.Email = model.NewId() + "@nowhere.com" o1.Type = model.TEAM_OPEN - <-store.Team().Save(&o1) + + if err := (<-store.Team().Save(&o1)).Err; err != nil { + t.Fatal(err) + } if r1 := <-store.Team().GetByDomain(o1.Domain); r1.Err != nil { t.Fatal(r1.Err) diff --git a/utils/config.go b/utils/config.go index 1060c75508..4180417064 100644 --- a/utils/config.go +++ b/utils/config.go @@ -79,6 +79,7 @@ type EmailSettings struct { SMTPUsername string SMTPPassword string SMTPServer string + UseTLS bool FeedbackEmail string FeedbackName string ApplePushServer string @@ -216,6 +217,16 @@ func LoadConfig(fileName string) { panic("Error decoding configuration " + err.Error()) } + // Grabs the domain from enviroment variable if not in configuration + if config.ServiceSettings.Domain == "" { + config.ServiceSettings.Domain = os.Getenv("MATTERMOST_DOMAIN") + } + + // Validates our mail settings + if err := CheckMailSettings(); err != nil { + l4g.Error("Email settings are not valid err=%v", err) + } + configureLog(config.LogSettings) Cfg = &config diff --git a/utils/config_test.go b/utils/config_test.go index 4d37b4e88f..9067dc6478 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -4,9 +4,24 @@ package utils import ( + "os" "testing" ) func TestConfig(t *testing.T) { LoadConfig("config.json") } + +func TestEnvOverride(t *testing.T) { + os.Setenv("MATTERMOST_DOMAIN", "testdomain.com") + + LoadConfig("config_docker.json") + if Cfg.ServiceSettings.Domain != "testdomain.com" { + t.Fail() + } + + LoadConfig("config.json") + if Cfg.ServiceSettings.Domain == "testdomain.com" { + t.Fail() + } +} diff --git a/utils/mail.go b/utils/mail.go index b8c2f4f9bd..2fb7f801d5 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -13,6 +13,69 @@ import ( "net/smtp" ) +func CheckMailSettings() *model.AppError { + if len(Cfg.EmailSettings.SMTPServer) == 0 { + return model.NewAppError("CheckMailSettings", "No email settings present, mail will not be sent", "") + } + conn, err := connectToSMTPServer() + if err != nil { + return err + } + defer conn.Close() + c, err2 := newSMTPClient(conn) + if err2 != nil { + return err + } + defer c.Quit() + defer c.Close() + + return nil +} + +func connectToSMTPServer() (net.Conn, *model.AppError) { + host, _, _ := net.SplitHostPort(Cfg.EmailSettings.SMTPServer) + + var conn net.Conn + var err error + + if Cfg.EmailSettings.UseTLS { + tlsconfig := &tls.Config{ + InsecureSkipVerify: true, + ServerName: host, + } + + conn, err = tls.Dial("tcp", Cfg.EmailSettings.SMTPServer, tlsconfig) + if err != nil { + return nil, model.NewAppError("SendMail", "Failed to open TLS connection", err.Error()) + } + } else { + conn, err = net.Dial("tcp", Cfg.EmailSettings.SMTPServer) + if err != nil { + return nil, model.NewAppError("SendMail", "Failed to open connection", err.Error()) + } + } + + return conn, nil +} + +func newSMTPClient(conn net.Conn) (*smtp.Client, *model.AppError) { + host, _, _ := net.SplitHostPort(Cfg.EmailSettings.SMTPServer) + c, err := smtp.NewClient(conn, host) + if err != nil { + l4g.Error("Failed to open a connection to SMTP server %v", err) + return nil, model.NewAppError("SendMail", "Failed to open TLS connection", err.Error()) + } + // GO does not support plain auth over a non encrypted connection. + // so if not tls then no auth + auth := smtp.PlainAuth("", Cfg.EmailSettings.SMTPUsername, Cfg.EmailSettings.SMTPPassword, host) + if Cfg.EmailSettings.UseTLS { + if err = c.Auth(auth); err != nil { + return nil, model.NewAppError("SendMail", "Failed to authenticate on SMTP server", err.Error()) + } + } + return c, nil +} + func SendMail(to, subject, body string) *model.AppError { fromMail := mail.Address{"", Cfg.EmailSettings.FeedbackEmail} @@ -36,38 +99,24 @@ func SendMail(to, subject, body string) *model.AppError { return nil } - host, _, _ := net.SplitHostPort(Cfg.EmailSettings.SMTPServer) - - auth := smtp.PlainAuth("", Cfg.EmailSettings.SMTPUsername, Cfg.EmailSettings.SMTPPassword, host) - - tlsconfig := &tls.Config{ - InsecureSkipVerify: true, - ServerName: host, - } - - conn, err := tls.Dial("tcp", Cfg.EmailSettings.SMTPServer, tlsconfig) - if err != nil { - return model.NewAppError("SendMail", "Failed to open TLS connection", err.Error()) + conn, err1 := connectToSMTPServer() + if err1 != nil { + return err1 } defer conn.Close() - c, err := smtp.NewClient(conn, host) - if err != nil { - l4g.Error("Failed to open a connection to SMTP server %v", err) - return model.NewAppError("SendMail", "Failed to open TLS connection", err.Error()) + c, err2 := newSMTPClient(conn) + if err2 != nil { + return err2 } defer c.Quit() defer c.Close() - if err = c.Auth(auth); err != nil { - return model.NewAppError("SendMail", "Failed to authenticate on SMTP server", err.Error()) - } - - if err = c.Mail(fromMail.Address); err != nil { + if err := c.Mail(fromMail.Address); err != nil { return model.NewAppError("SendMail", "Failed to add from email address", err.Error()) } - if err = c.Rcpt(toMail.Address); err != nil { + if err := c.Rcpt(toMail.Address); err != nil { return model.NewAppError("SendMail", "Failed to add to email address", err.Error()) } diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 8d82a4b62b..65f1da1f88 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -52,20 +52,20 @@ var FindTeamDomain = React.createClass({
{"Pick something " + strings.Team + "mates will recognize. Your username is how you will appear to others"}
+{"Pick something " + strings.Team + "mates will recognize. Your username is how you will appear to others."}
+It can be made of lowercase letters and numbers.
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 0fcdc92b0b..146419cf55 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -120,6 +120,7 @@ module.exports = React.createClass({
{"Choose your username and password for the " + this.props.team_name + " " + strings.Team +"."}
+Your username can be made of lowercase letters and numbers.