dockerhost`
4. Run `boot2docker shellinit` and copy the export statements to your ~/.bash_profile
Any issues? Please let us know on our forums at: http://bit.ly/1MY1kul
-GO
+GO SETUP
1. Download Go from http://golang.org/dl/
NODE.JS SETUP
-1. Install homebrew from brew.sh
+1. Install homebrew from http://brew.sh
2. `brew install node`
COMPASS SETUP
@@ -47,23 +48,19 @@ COMPASS SETUP
MATTERMOST SETUP
-1. Make a project directory for Mattermost, which will for the rest of this document be referred to as $PROJECT
-2. Make a go directory in your $PROJECT directory
-3. Create/Open your ~/.bash_profile and add the following lines: `export GOPATH=$PROJECT/go export PATH=$PATH:$GOPATH/bin`
-4. Refresh your bash profile with `source ~/.bash_profile`
-5. `cd $GOPATH`
-6. `mkdir -p src/github.com/mattermost` then cd into this directory
-7. `git clone github.com/mattermost/platform.git`
-8. If you do not have Mercurial, download it with: `brew install mercurial`
-9. `cd platform`
-10. `make test`
-11. Provided the test runs fine, you now have a complete build environment. Use `make run` to run your code
+1. Make a project directory for Mattermost, which we'll call **$PROJECT** for the rest of these instructions
+2. Make a `go` directory in your $PROJECT directory
+3. Open or create your ~/.bash_profile and add the following lines:
`export GOPATH=$PROJECT/go`
`export PATH=$PATH:$GOPATH/bin`
then refresh your bash profile with `source ~/.bash_profile`
+4. Then use `cd $GOPATH` and `mkdir -p src/github.com/mattermost` then cd into this directory and run `git clone github.com/mattermost/platform.git`
+5. If you do not have Mercurial, download it with: `brew install mercurial`
+6. Then do `cd platform` and `make test`. Provided the test runs fine, you now have a complete build environment.
+7. Use `make run` to run your code
Any issues? Please let us know on our forums at: http://bit.ly/1MY1kul
License
-------
-Mattermost Preview uses the Apache 2.0 open source license. For more details see: http://bit.ly/1Lc25Sv
+This software uses the Apache 2.0 open source license. For more details see: http://bit.ly/1Lc25Sv
**XXXXXX TODO: Test install procedures**
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({
{ config.SiteName }
- Enter your {strings.TeamPlural} domain.
+ Enter your {strings.Team}'s domain.
@@ -188,7 +188,7 @@ module.exports = React.createClass({
I forgot my password
-
{"Want to create your own " + strings.Team + "?"} Sign up now
+
{"Want to create your own " + strings.Team + "?"} Sign up now
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 65247b7052..97f9fa943b 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -444,17 +444,13 @@ module.exports = React.createClass({
if (post.create_at > last_viewed && !rendered_last_viewed) {
rendered_last_viewed = true;
postCtls.push(
-
+
);
- } else {
- postCtls.push(postCtl);
}
+ postCtls.push(postCtl);
previousPostDay = utils.getDateForUnixTicks(post.create_at);
}
diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx
index 066161a101..b038679e6c 100644
--- a/web/react/components/signup_team_complete.jsx
+++ b/web/react/components/signup_team_complete.jsx
@@ -467,7 +467,8 @@ UsernamePage = React.createClass({
{ name_error }
- {"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({
Welcome to { config.SiteName }
{"Choose your username and password for the " + this.props.team_name + " " + strings.Team +"."}
+ Your username can be made of lowercase letters and numbers.
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index 45798809ff..7a4762e074 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -204,7 +204,7 @@ module.exports = React.createClass({
// If there is a space after the last @, nothing to do.
if (lastSpace > atIndex || lastCharSpace > atIndex) {
- this.setState({ mentionText: '-1' });
+ this.updateMentionTab('-1', null);
return;
}
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 72ed48faf5..628d923423 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -730,3 +730,15 @@ Image.prototype.load = function(url, progressCallback) {
};
Image.prototype.completedPercentage = 0;
+
+module.exports.getHomeLink = function() {
+ if (config.HomeLink != "") {
+ return config.HomeLink;
+ }
+ var parts = window.location.host.split(".");
+ if (parts.length <= 1) {
+ return window.location.protocol + "//" + window.location.host;
+ }
+ parts[0] = "www";
+ return window.location.protocol + "//" + parts.join(".");
+}
diff --git a/web/static/config/config.js b/web/static/config/config.js
index 080f16a304..82d4bbf702 100644
--- a/web/static/config/config.js
+++ b/web/static/config/config.js
@@ -24,7 +24,7 @@ var config = {
AboutLink: "/static/help/configure_links.html",
HelpLink: "/static/help/configure_links.html",
ReportProblemLink: "/static/help/configure_links.html",
- HomeLink: "http://localhost:8065",
+ HomeLink: "",
ThemeColors: ["#2389d7", "#008a17", "#dc4fad", "#ac193d", "#0072c6", "#d24726", "#ff8f32", "#82ba00", "#03b3b2", "#008299", "#4617b4", "#8c0095", "#004b8b", "#004b8b", "#570000", "#380000", "#585858", "#000000"]
};
@@ -35,4 +35,4 @@ var strings = {
TeamPlural: "teams",
Company: "company",
CompanyPlural: "companies"
-}
+};