360 lines
9.9 KiB
Go
Raw Permalink Normal View History

2015-10-22 10:00:39 -07:00
package main
import (
crand "crypto/rand"
2015-10-22 10:00:39 -07:00
"encoding/json"
"flag"
2015-10-22 10:00:39 -07:00
"io/ioutil"
"log"
"math/rand"
"os"
"path/filepath"
2019-06-07 15:56:08 -07:00
"strconv"
"strings"
"time"
2023-01-07 18:06:15 -08:00
"github.com/tinode/chat/server/auth"
2019-11-01 12:27:59 +05:00
_ "github.com/tinode/chat/server/db/mongodb"
2018-02-17 15:36:11 -08:00
_ "github.com/tinode/chat/server/db/mysql"
2023-04-05 00:26:38 +03:00
_ "github.com/tinode/chat/server/db/postgres"
2018-02-17 15:36:11 -08:00
_ "github.com/tinode/chat/server/db/rethinkdb"
"github.com/tinode/chat/server/store"
"github.com/tinode/chat/server/store/types"
jcr "github.com/tinode/jsonco"
2015-10-22 10:00:39 -07:00
)
2015-11-19 17:44:04 -08:00
type configType struct {
StoreConfig json.RawMessage `json:"store_config"`
2015-11-19 17:44:04 -08:00
}
type theCard struct {
Fn string `json:"fn"`
Photo string `json:"photo"`
Type string `json:"type"`
}
type tPrivate struct {
Comment string `json:"comment"`
}
2021-06-24 17:29:08 -07:00
type tTrusted struct {
Verified bool `json:"verified,omitempty"`
Staff bool `json:"staff,omitempty"`
}
func (t tTrusted) IsZero() bool {
return !t.Verified && !t.Staff
2021-06-24 17:29:08 -07:00
}
2018-12-08 22:55:19 +03:00
// DefAccess is default access mode.
2018-10-09 10:40:24 +03:00
type DefAccess struct {
Auth string `json:"auth"`
Anon string `json:"anon"`
}
/*
2018-01-07 22:21:16 -08:00
User object in data.json
"createdAt": "-140h",
"email": "alice@example.com",
"tel": "17025550001",
"passhash": "alice123",
"private": {"comment": "some comment 123"},
"public": {"fn": "Alice Johnson", "photo": "alice-64.jpg", "type": "jpg"},
"state": "ok",
"authLevel": "auth",
"status": {
"text": "DND"
},
"username": "alice",
"tags": ["tag1"],
"addressBook": ["email:bob@example.com", "email:carol@example.com", "email:dave@example.com",
"email:eve@example.com","email:frank@example.com","email:george@example.com","email:tob@example.com",
"tel:17025550001", "tel:17025550002", "tel:17025550003", "tel:17025550004", "tel:17025550005",
"tel:17025550006", "tel:17025550007", "tel:17025550008", "tel:17025550009"]
}
*/
type User struct {
CreatedAt string `json:"createdAt"`
Email string `json:"email"`
Tel string `json:"tel"`
2018-10-06 13:49:32 +03:00
AuthLevel string `json:"authLevel"`
Username string `json:"username"`
Password string `json:"passhash"`
Private tPrivate `json:"private"`
Public theCard `json:"public"`
2021-06-24 17:29:08 -07:00
Trusted tTrusted `json:"trusted"`
2020-01-26 16:29:53 +03:00
State string `json:"state"`
Status interface{} `json:"status"`
AddressBook []string `json:"addressBook"`
2018-10-09 09:24:58 +02:00
Tags []string `json:"tags"`
}
/*
2018-01-07 22:21:16 -08:00
GroupTopic object in data.json
"createdAt": "-128h",
"name": "*ABC",
"owner": "carol",
"channel": true,
"public": {"fn": "Let's talk about flowers", "photo": "abc-64.jpg", "type": "jpg"}
*/
type GroupTopic struct {
2018-10-09 10:40:24 +03:00
CreatedAt string `json:"createdAt"`
Name string `json:"name"`
Owner string `json:"owner"`
2020-08-27 15:54:12 -07:00
Channel bool `json:"channel"`
Public theCard `json:"public"`
2021-06-24 17:29:08 -07:00
Trusted tTrusted `json:"trusted"`
2018-10-09 10:40:24 +03:00
Access DefAccess `json:"access"`
Tags []string `json:"tags"`
OwnerPrivate tPrivate `json:"ownerPrivate"`
}
/*
2018-01-07 22:21:16 -08:00
GroupSub object in data.json
"createdAt": "-112h",
"private": "My super cool group topic",
"topic": "*ABC",
"user": "alice",
"asChan: false,
"want": "JRWPSA",
"have": "JRWP"
*/
type GroupSub struct {
CreatedAt string `json:"createdAt"`
Private tPrivate `json:"private"`
Topic string `json:"topic"`
User string `json:"user"`
2020-08-27 15:54:12 -07:00
AsChan bool `json:"asChan"`
Want string `json:"want"`
Have string `json:"have"`
}
/*
2018-01-07 22:21:16 -08:00
P2PUser topic in data.json
"createdAt": "-117h",
"users": [
{"name": "eve", "private": {"comment":"ho ho"}, "want": "JRWP", "have": "N"},
{"name": "alice", "private": {"comment": "ha ha"}}
]
*/
type P2PUser struct {
Name string `json:"name"`
Private tPrivate `json:"private"`
Want string `json:"want"`
Have string `json:"have"`
}
2018-01-07 22:21:16 -08:00
// P2PSub is a p2p subscription in data.json
type P2PSub struct {
CreatedAt string `json:"createdAt"`
Users []P2PUser `json:"users"`
// Cached value 'user1:user2' as a surrogare topic name
pair string
}
2018-01-07 22:21:16 -08:00
// Data is a message in data.json.
2015-10-22 10:00:39 -07:00
type Data struct {
Users []User `json:"users"`
Grouptopics []GroupTopic `json:"grouptopics"`
Groupsubs []GroupSub `json:"groupsubs"`
P2psubs []P2PSub `json:"p2psubs"`
Messages []string `json:"messages"`
Forms []map[string]interface{} `json:"forms"`
datapath string
2015-10-22 10:00:39 -07:00
}
// Generate random string as a name of the group topic
2015-10-22 10:00:39 -07:00
func genTopicName() string {
return "grp" + store.Store.GetUidString()
2015-10-22 10:00:39 -07:00
}
// Generates password of length n
func getPassword(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/.+?=&"
rbuf := make([]byte, n)
if _, err := crand.Read(rbuf); err != nil {
log.Fatalln("Unable to generate password", err)
}
passwd := make([]byte, n)
for i, r := range rbuf {
passwd[i] = letters[int(r)%len(letters)]
}
2021-04-30 16:15:23 -07:00
return string(passwd)
}
2015-10-22 10:00:39 -07:00
func main() {
2021-04-30 16:15:23 -07:00
reset := flag.Bool("reset", false, "force database reset")
upgrade := flag.Bool("upgrade", false, "perform database version upgrade")
noInit := flag.Bool("no_init", false, "check that database exists but don't create if missing")
2023-01-07 18:06:15 -08:00
addRoot := flag.String("add_root", "", "create ROOT user, auth scheme 'basic'")
makeRoot := flag.String("make_root", "", "promote ordinary user to ROOT, auth scheme 'basic'")
2021-04-30 16:15:23 -07:00
datafile := flag.String("data", "", "name of file with sample data to load")
conffile := flag.String("config", "./tinode.conf", "config of the database connection")
2018-09-22 19:08:45 +03:00
flag.Parse()
2015-10-22 10:00:39 -07:00
var data Data
2019-06-07 15:56:08 -07:00
if *datafile != "" && *datafile != "-" {
2015-10-28 16:14:09 -07:00
raw, err := ioutil.ReadFile(*datafile)
if err != nil {
2020-03-27 13:01:26 +03:00
log.Fatalln("Failed to read sample data file:", err)
}
err = json.Unmarshal(raw, &data)
if err != nil {
2020-03-27 13:01:26 +03:00
log.Fatalln("Failed to parse sample data:", err)
}
2015-10-22 10:00:39 -07:00
}
rand.Seed(time.Now().UnixNano())
data.datapath, _ = filepath.Split(*datafile)
2015-10-28 16:14:09 -07:00
var config configType
if file, err := os.Open(*conffile); err != nil {
2020-03-27 13:01:26 +03:00
log.Fatalln("Failed to read config file:", err)
} else {
jr := jcr.New(file)
if err = json.NewDecoder(jr).Decode(&config); err != nil {
switch jerr := err.(type) {
case *json.UnmarshalTypeError:
lnum, cnum, _ := jr.LineAndChar(jerr.Offset)
log.Fatalf("Unmarshall error in config file in %s at %d:%d (offset %d bytes): %s",
jerr.Field, lnum, cnum, jerr.Offset, jerr.Error())
case *json.SyntaxError:
lnum, cnum, _ := jr.LineAndChar(jerr.Offset)
log.Fatalf("Syntax error in config file at %d:%d (offset %d bytes): %s",
lnum, cnum, jerr.Offset, jerr.Error())
default:
log.Fatal("Failed to parse config file: ", err)
}
}
2015-10-28 16:14:09 -07:00
}
err := store.Store.Open(1, config.StoreConfig)
defer store.Store.Close()
adapterVersion := store.Store.GetAdapterVersion()
databaseVersion := 0
if store.Store.IsOpen() {
databaseVersion = store.Store.GetDbVersion()
}
log.Printf("Database adapter: '%s'; version: %d", store.Store.GetAdapterName(), adapterVersion)
var created bool
if err != nil {
if strings.Contains(err.Error(), "Database not initialized") {
2020-03-27 19:03:28 +03:00
if *noInit {
log.Fatalln("Database not found.")
}
2019-08-13 20:00:50 +03:00
log.Println("Database not found. Creating.")
2023-02-28 18:43:12 -08:00
err = store.Store.InitDb(config.StoreConfig, false)
if err == nil {
log.Println("Database successfully created.")
created = true
2023-02-28 18:43:12 -08:00
}
} else if strings.Contains(err.Error(), "Invalid database version") {
msg := "Wrong DB version: expected " + strconv.Itoa(adapterVersion) + ", got " +
strconv.Itoa(databaseVersion) + "."
2023-02-28 18:43:12 -08:00
if *reset {
2023-02-28 18:43:12 -08:00
log.Println(msg, "Reset Requested. Dropping and recreating the database.")
err = store.Store.InitDb(config.StoreConfig, true)
if err == nil {
log.Println("Database successfully reset.")
}
} else if *upgrade {
if databaseVersion > adapterVersion {
log.Fatalln(msg, "Unable to upgrade: database has greater version than the adapter.")
}
2020-03-27 13:01:26 +03:00
log.Println(msg, "Upgrading the database.")
2023-02-28 18:43:12 -08:00
err = store.Store.UpgradeDb(config.StoreConfig)
if err == nil {
log.Println("Database successfully upgraded.")
}
} else {
2020-03-27 13:01:26 +03:00
log.Fatalln(msg, "Use --reset to reset, --upgrade to upgrade.")
}
} else {
2020-03-27 13:01:26 +03:00
log.Fatalln("Failed to init DB adapter:", err)
}
} else if *reset {
2023-02-28 18:43:12 -08:00
log.Println("Reset requested. Dropping and recreating the database.")
err = store.Store.InitDb(config.StoreConfig, true)
if err == nil {
2023-02-28 18:43:12 -08:00
log.Println("Database successfully reset.")
}
2023-02-28 18:43:12 -08:00
} else {
log.Println("Database exists, version is correct.")
}
2019-06-07 15:40:58 -07:00
if err != nil {
2023-02-28 18:43:12 -08:00
log.Fatalln("Failure:", err)
}
if *reset || created {
genDb(&data)
} else if len(data.Users) > 0 {
2022-11-26 15:47:11 -08:00
log.Println("Sample data ignored.")
2020-03-27 09:51:18 +03:00
}
2022-11-26 15:47:11 -08:00
2023-01-07 18:06:15 -08:00
// Promote existing user account to root
if *makeRoot != "" {
adapter := store.Store.GetAdapter()
userId := types.ParseUserId(*makeRoot)
if userId.IsZero() {
log.Fatalf("Must specify a valid user ID '%s' to promote to ROOT", *makeRoot)
}
if err := adapter.AuthUpdRecord(userId, "basic", "", auth.LevelRoot, nil, time.Time{}); err != nil {
log.Fatalln("Failed to promote user to ROOT", err)
}
log.Printf("User '%s' promoted to ROOT", *makeRoot)
}
2022-11-26 15:47:11 -08:00
2023-01-07 18:06:15 -08:00
// Create root user account.
if *addRoot != "" {
var password string
parts := strings.Split(*addRoot, ":")
uname := parts[0]
if len(uname) < 3 {
log.Fatalf("Failed to create a ROOT user: username '%s' is too short", uname)
}
2022-11-26 15:47:11 -08:00
2023-01-07 18:06:15 -08:00
if len(parts) == 1 || parts[1] == "" {
password = getPassword(10)
} else {
password = parts[1]
}
var user types.User
user.Public = &card{
Fn: "ROOT " + uname,
}
store.Users.Create(&user, nil)
if _, err := store.Users.Create(&user, nil); err != nil {
log.Fatalln("Failed to create ROOT user:", err)
}
authHandler := store.Store.GetAuthHandler("basic")
if _, err := authHandler.AddRecord(&auth.Rec{Uid: user.Uid(), AuthLevel: auth.LevelRoot},
[]byte(uname+":"+password), ""); err != nil {
2023-01-07 18:06:15 -08:00
store.Users.Delete(user.Uid(), true)
log.Fatalln("Failed to add ROOT auth record:", err)
2022-11-26 15:47:11 -08:00
}
2023-01-07 18:06:15 -08:00
log.Printf("ROOT user created: '%s:%s'", uname, password)
}
2022-11-26 15:47:11 -08:00
log.Println("All done.")
os.Exit(0)
2015-10-22 10:00:39 -07:00
}