authentication resolves #3

This commit is contained in:
Josh Baker 2016-03-07 17:37:39 -07:00
parent 4f0d65f184
commit 73ea8b8ee4
13 changed files with 450 additions and 95 deletions

View File

@ -113,6 +113,10 @@ func main() {
if !parseArgs() { if !parseArgs() {
return return
} }
if len(oneCommand) > 0 && (oneCommand[0] == 'h' || oneCommand[0] == 'H') && strings.Split(strings.ToLower(oneCommand), " ")[0] == "help" {
showHelp()
return
}
addr := fmt.Sprintf("%s:%d", hostname, port) addr := fmt.Sprintf("%s:%d", hostname, port)
conn, err := client.Dial(addr) conn, err := client.Dial(addr)

View File

@ -8,6 +8,7 @@ import (
"os" "os"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"github.com/tidwall/tile38/controller" "github.com/tidwall/tile38/controller"
"github.com/tidwall/tile38/controller/log" "github.com/tidwall/tile38/controller/log"
@ -15,23 +16,48 @@ import (
) )
var ( var (
dir string dir string
port int port int
host string host string
verbose bool verbose bool
veryVerbose bool veryVerbose bool
devMode bool devMode bool
quiet bool quiet bool
protectedMode bool = true
) )
func main() { func main() {
// parse non standard args.
nargs := []string{os.Args[0]}
for i := 1; i < len(os.Args); i++ {
switch os.Args[i] {
case "--protected-mode", "-protected-mode":
i++
if i < len(os.Args) {
switch strings.ToLower(os.Args[i]) {
case "no":
protectedMode = false
case "yes":
protectedMode = true
}
continue
}
fmt.Fprintf(os.Stderr, "protected-mode must be 'yes' or 'no'\n")
os.Exit(1)
case "--dev", "-dev":
devMode = true
continue
}
nargs = append(nargs, os.Args[i])
}
os.Args = nargs
flag.IntVar(&port, "p", 9851, "The listening port.") flag.IntVar(&port, "p", 9851, "The listening port.")
flag.StringVar(&host, "h", "127.0.0.1", "The listening host.") flag.StringVar(&host, "h", "", "The listening host.")
flag.StringVar(&dir, "d", "data", "The data directory.") flag.StringVar(&dir, "d", "data", "The data directory.")
flag.BoolVar(&verbose, "v", false, "Enable verbose logging.") flag.BoolVar(&verbose, "v", false, "Enable verbose logging.")
flag.BoolVar(&quiet, "q", false, "Quiet logging. Totally silent.") flag.BoolVar(&quiet, "q", false, "Quiet logging. Totally silent.")
flag.BoolVar(&veryVerbose, "vv", false, "Enable very verbose logging.") flag.BoolVar(&veryVerbose, "vv", false, "Enable very verbose logging.")
flag.BoolVar(&devMode, "dev", false, "Activates dev mode. DEV ONLY.")
flag.Parse() flag.Parse()
var logw io.Writer = os.Stderr var logw io.Writer = os.Stderr
if quiet { if quiet {
@ -43,6 +69,12 @@ func main() {
}) })
core.DevMode = devMode core.DevMode = devMode
core.ShowDebugMessages = veryVerbose core.ShowDebugMessages = veryVerbose
core.ProtectedMode = protectedMode
hostd := ""
if host != "" {
hostd = "Addr: " + host + ", "
}
// _____ _ _ ___ ___ // _____ _ _ ___ ___
// |_ _|_| |___|_ | . | // |_ _|_| |___|_ | . |
@ -53,11 +85,11 @@ func main() {
_______ _______ _______ _______
| | | | | |
|____ | _ | Tile38 %s (%s) %d bit (%s/%s) |____ | _ | Tile38 %s (%s) %d bit (%s/%s)
| | | Host: %s, Port: %d, PID: %d | | | %sPort: %d, PID: %d
|____ | _ | |____ | _ |
| | | tile38.com | | | tile38.com
|_______|_______| |_______|_______|
`+"\n", core.Version, core.GitSHA, strconv.IntSize, runtime.GOARCH, runtime.GOOS, host, port, os.Getpid()) `+"\n", core.Version, core.GitSHA, strconv.IntSize, runtime.GOARCH, runtime.GOOS, hostd, port, os.Getpid())
if err := controller.ListenAndServe(host, port, dir); err != nil { if err := controller.ListenAndServe(host, port, dir); err != nil {
log.Fatal(err) log.Fatal(err)

15
controller/auth.go Normal file
View File

@ -0,0 +1,15 @@
package controller
func (c *Controller) cmdAuth(line string) error {
var password string
if line, password = token(line); password == "" {
return errInvalidNumberOfArguments
}
if line != "" {
return errInvalidNumberOfArguments
}
println(password)
return nil
}

162
controller/config.go Normal file
View File

@ -0,0 +1,162 @@
package controller
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
)
// Config is a tile38 config
type Config struct {
FollowHost string `json:"follow_host,omitempty"`
FollowPort int `json:"follow_port,omitempty"`
FollowID string `json:"follow_id,omitempty"`
FollowPos int `json:"follow_pos,omitempty"`
ServerID string `json:"server_id,omitempty"`
ReadOnly bool `json:"read_only,omitempty"`
// Properties
RequirePassP string `json:"requirepass,omitempty"`
RequirePass string `json:"-"`
LeaderAuthP string `json:"leaderauth,omitempty"`
LeaderAuth string `json:"-"`
ProtectedModeP string `json:"protected-mode,omitempty"`
ProtectedMode string `json:"-"`
}
func (c *Controller) loadConfig() error {
data, err := ioutil.ReadFile(c.dir + "/config")
if err != nil {
if os.IsNotExist(err) {
return c.initConfig()
}
return err
}
err = json.Unmarshal(data, &c.config)
if err != nil {
return err
}
// load properties
if err := c.setConfigProperty("requirepass", c.config.RequirePassP, true); err != nil {
return err
}
if err := c.setConfigProperty("leaderauth", c.config.LeaderAuthP, true); err != nil {
return err
}
if err := c.setConfigProperty("protected-mode", c.config.ProtectedModeP, true); err != nil {
return err
}
return nil
}
func (c *Controller) setConfigProperty(name, value string, fromLoad bool) error {
var invalid bool
switch name {
default:
return fmt.Errorf("Unsupported CONFIG parameter: %s", name)
case "requirepass":
c.config.RequirePass = value
case "leaderauth":
c.config.LeaderAuth = value
case "protected-mode":
switch strings.ToLower(value) {
case "":
if fromLoad {
c.config.ProtectedMode = "yes"
} else {
invalid = true
}
case "yes", "no":
c.config.ProtectedMode = strings.ToLower(value)
default:
invalid = true
}
}
if invalid {
return fmt.Errorf("Invalid argument '%s' for CONFIG SET '%s'", value, name)
}
return nil
}
func (c *Controller) getConfigProperty(name string) string {
switch name {
default:
return ""
case "requirepass":
return c.config.RequirePass
case "leaderauth":
return c.config.LeaderAuth
case "protected-mode":
return c.config.ProtectedMode
}
}
func (c *Controller) initConfig() error {
c.config = Config{ServerID: randomKey(16)}
return c.writeConfig(true)
}
func (c *Controller) writeConfig(writeProperties bool) error {
var err error
bak := c.config
defer func() {
if err != nil {
// revert changes
c.config = bak
}
}()
if writeProperties {
// save properties
c.config.RequirePassP = c.config.RequirePass
c.config.LeaderAuthP = c.config.LeaderAuth
c.config.ProtectedModeP = c.config.ProtectedMode
}
var data []byte
data, err = json.MarshalIndent(c.config, "", "\t")
if err != nil {
return err
}
err = ioutil.WriteFile(c.dir+"/config", data, 0600)
if err != nil {
return err
}
return nil
}
func (c *Controller) cmdConfig(line string) (string, error) {
var start = time.Now()
var cmd, name, value string
if line, cmd = token(line); cmd == "" {
return "", errInvalidNumberOfArguments
}
var buf bytes.Buffer
buf.WriteString(`{"ok":true`)
switch strings.ToLower(cmd) {
default:
return "", errInvalidArgument(cmd)
case "get":
if line, name = token(line); name == "" || line != "" {
return "", errInvalidNumberOfArguments
}
value = c.getConfigProperty(name)
buf.WriteString(`,"value":` + jsonString(value))
case "set":
if line, name = token(line); name == "" {
return "", errInvalidNumberOfArguments
}
value = strings.TrimSpace(line)
if err := c.setConfigProperty(name, value, false); err != nil {
return "", err
}
case "rewrite":
if err := c.writeConfig(true); err != nil {
return "", err
}
}
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return buf.String(), nil
}

View File

@ -4,14 +4,12 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net"
"os" "os"
"runtime" "runtime"
"strings"
"sync" "sync"
"time" "time"
@ -64,16 +62,6 @@ type Controller struct {
shrinking bool // aof shrinking flag shrinking bool // aof shrinking flag
} }
// Config is a tile38 config
type Config struct {
FollowHost string `json:"follow_host,omitempty"`
FollowPort int `json:"follow_port,omitempty"`
FollowID string `json:"follow_id,omitempty"`
FollowPos int `json:"follow_pos,omitempty"`
ServerID string `json:"server_id,omitempty"`
ReadOnly bool `json:"read_only,omitempty"`
}
// ListenAndServe starts a new tile38 server // ListenAndServe starts a new tile38 server
func ListenAndServe(host string, port int, dir string) error { func ListenAndServe(host string, port int, dir string) error {
log.Infof("Server started, Tile38 version %s, git %s", core.Version, core.GitSHA) log.Infof("Server started, Tile38 version %s, git %s", core.Version, core.GitSHA)
@ -113,8 +101,8 @@ func ListenAndServe(host string, port int, dir string) error {
c.mu.Unlock() c.mu.Unlock()
}() }()
go c.processLives() go c.processLives()
handler := func(command []byte, conn net.Conn, rd *bufio.Reader, w io.Writer, websocket bool) error { handler := func(conn *server.Conn, command []byte, rd *bufio.Reader, w io.Writer, websocket bool) error {
err := c.handleInputCommand(string(command), w) err := c.handleInputCommand(conn, string(command), w)
if err != nil { if err != nil {
if err.Error() == "going live" { if err.Error() == "going live" {
return c.goLive(err, conn, rd, websocket) return c.goLive(err, conn, rd, websocket)
@ -123,7 +111,21 @@ func ListenAndServe(host string, port int, dir string) error {
} }
return nil return nil
} }
return server.ListenAndServe(host, port, handler) protected := func() bool {
if !core.ProtectedMode {
// --protected-mode no
return false
}
if host != "" && host != "127.0.0.1" && host != "::1" && host != "localhost" {
// -h address
return false
}
c.mu.RLock()
is := c.config.ProtectedMode != "no" && c.config.RequirePass == ""
c.mu.RUnlock()
return is
}
return server.ListenAndServe(host, port, protected, handler)
} }
func (c *Controller) setCol(key string, col *collection.Collection) { func (c *Controller) setCol(key string, col *collection.Collection) {
@ -156,12 +158,12 @@ func isReservedFieldName(field string) bool {
return false return false
} }
func (c *Controller) handleInputCommand(line string, w io.Writer) error { func (c *Controller) handleInputCommand(conn *server.Conn, line string, w io.Writer) error {
if core.ShowDebugMessages && line != "pInG" { if core.ShowDebugMessages && line != "pInG" {
log.Debug(line) log.Debug(line)
} }
start := time.Now() start := time.Now()
// Ping and Help. Just send back the response. No need to put through the pipeline. // Ping. Just send back the response. No need to put through the pipeline.
if len(line) == 4 && (line[0] == 'p' || line[0] == 'P') && lc(line, "ping") { if len(line) == 4 && (line[0] == 'p' || line[0] == 'P') && lc(line, "ping") {
w.Write([]byte(`{"ok":true,"ping":"pong","elapsed":"` + time.Now().Sub(start).String() + `"}`)) w.Write([]byte(`{"ok":true,"ping":"pong","elapsed":"` + time.Now().Sub(start).String() + `"}`))
return nil return nil
@ -180,6 +182,26 @@ func (c *Controller) handleInputCommand(line string, w io.Writer) error {
if cmd == "" { if cmd == "" {
return writeErr(errors.New("empty command")) return writeErr(errors.New("empty command"))
} }
if !conn.Authenticated {
c.mu.RLock()
requirePass := c.config.RequirePass
c.mu.RUnlock()
if requirePass != "" {
// This better be an AUTH command.
if cmd != "auth" {
// Just shut down the pipeline now. The less the client connection knows the better.
return writeErr(errors.New("authentication required"))
}
password, _ := token(line)
if requirePass == strings.TrimSpace(password) {
conn.Authenticated = true
} else {
return writeErr(errors.New("invalid password"))
}
}
}
// choose the locking strategy // choose the locking strategy
switch cmd { switch cmd {
default: default:
@ -203,7 +225,7 @@ func (c *Controller) handleInputCommand(line string, w io.Writer) error {
if c.config.FollowHost != "" && !c.fcup { if c.config.FollowHost != "" && !c.fcup {
return writeErr(errors.New("catching up to leader")) return writeErr(errors.New("catching up to leader"))
} }
case "follow", "readonly": case "follow", "readonly", "config":
// system operations // system operations
// does not write to aof, but requires a write lock. // does not write to aof, but requires a write lock.
c.mu.Lock() c.mu.Lock()
@ -235,37 +257,6 @@ func (c *Controller) handleInputCommand(line string, w io.Writer) error {
return nil return nil
} }
func (c *Controller) loadConfig() error {
data, err := ioutil.ReadFile(c.dir + "/config")
if err != nil {
if os.IsNotExist(err) {
return c.initConfig()
}
return err
}
err = json.Unmarshal(data, &c.config)
if err != nil {
return err
}
return nil
}
func (c *Controller) initConfig() error {
c.config = Config{ServerID: randomKey(16)}
return c.writeConfig()
}
func (c *Controller) writeConfig() error {
data, err := json.MarshalIndent(c.config, "", "\t")
if err != nil {
return err
}
if err := ioutil.WriteFile(c.dir+"/config", data, 0600); err != nil {
return err
}
return nil
}
func randomKey(n int) string { func randomKey(n int) string {
b := make([]byte, n) b := make([]byte, n)
nn, err := rand.Read(b) nn, err := rand.Read(b)
@ -322,9 +313,14 @@ func (c *Controller) command(line string, w io.Writer) (resp string, d commandDe
case "follow": case "follow":
err = c.cmdFollow(nline) err = c.cmdFollow(nline)
resp = okResp() resp = okResp()
case "config":
resp, err = c.cmdConfig(nline)
case "readonly": case "readonly":
err = c.cmdReadOnly(nline) err = c.cmdReadOnly(nline)
resp = okResp() resp = okResp()
case "auth":
err = c.cmdAuth(nline)
resp = okResp()
case "stats": case "stats":
resp, err = c.cmdServer(nline) resp, err = c.cmdServer(nline)
case "server": case "server":

View File

@ -71,7 +71,7 @@ func (c *Controller) cmdFollow(line string) error {
c.config.FollowHost = host c.config.FollowHost = host
c.config.FollowPort = port c.config.FollowPort = port
} }
if err := c.writeConfig(); err != nil { if err := c.writeConfig(false); err != nil {
c.config = pconfig // revert c.config = pconfig // revert
return err return err
} }

View File

@ -31,7 +31,7 @@ func (c *Controller) cmdReadOnly(line string) error {
c.config.ReadOnly = false c.config.ReadOnly = false
log.Info("read write") log.Info("read write")
} }
err := c.writeConfig() err := c.writeConfig(false)
if err != nil { if err != nil {
c.config = backup c.config = backup
return err return err

View File

@ -14,12 +14,44 @@ import (
"github.com/tidwall/tile38/core" "github.com/tidwall/tile38/core"
) )
// This phrase is copied nearly verbatim from Redis.
// https://github.com/antirez/redis/blob/cf42c48adcea05c1bd4b939fcd36a01f23ec6303/src/networking.c
var deniedMessage = []byte(strings.TrimSpace(`
ACCESS DENIED
Tile38 is running in protected mode because protected mode is enabled, no host
address was specified, no authentication password is requested to clients. In
this mode connections are only accepted from the loopback interface. If you
want to connect from external computers to Tile38 you may adopt one of the
following solutions:
1) Disable protected mode sending the command 'CONFIG SET protected-mode no'
from the loopback interface by connecting to Tile38 from the same host
the server is running, however MAKE SURE Tile38 is not publicly accessible
from internet if you do so. Use CONFIG REWRITE to make this change
permanent.
2) Alternatively you can just disable the protected mode by editing the Tile38
configuration file, and setting the protected mode option to 'no', and then
restarting the server.
3) If you started the server manually just for testing, restart it with the
'--protected-mode no' option.
4) Setup a host address or an authentication password.
NOTE: You only need to do one of the above things in order for the server
to start accepting connections from the outside.
`) + "\r\n")
type Conn struct {
net.Conn
Authenticated bool
}
var errCloseHTTP = errors.New("close http") var errCloseHTTP = errors.New("close http")
// ListenAndServe starts a tile38 server at the specified address. // ListenAndServe starts a tile38 server at the specified address.
func ListenAndServe( func ListenAndServe(
host string, port int, host string, port int,
handler func(command []byte, conn net.Conn, rd *bufio.Reader, w io.Writer, websocket bool) error, protected func() bool,
handler func(conn *Conn, command []byte, rd *bufio.Reader, w io.Writer, websocket bool) error,
) error { ) error {
ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port)) ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil { if err != nil {
@ -32,13 +64,14 @@ func ListenAndServe(
log.Error(err) log.Error(err)
continue continue
} }
go handleConn(conn, handler) go handleConn(&Conn{Conn: conn}, protected, handler)
} }
} }
func handleConn( func handleConn(
conn net.Conn, conn *Conn,
handler func(command []byte, conn net.Conn, rd *bufio.Reader, w io.Writer, websocket bool) error, protected func() bool,
handler func(conn *Conn, command []byte, rd *bufio.Reader, w io.Writer, websocket bool) error,
) { ) {
if core.ShowDebugMessages { if core.ShowDebugMessages {
addr := conn.RemoteAddr().String() addr := conn.RemoteAddr().String()
@ -46,6 +79,14 @@ func handleConn(
defer func() { defer func() {
log.Debugf("closed connection: %s", addr) log.Debugf("closed connection: %s", addr)
}() }()
if !strings.HasPrefix(addr, "127.0.0.1:") && !strings.HasPrefix(addr, "[::1]:") {
if protected() {
// This is a protected server. Only loopback is allowed.
conn.Write(deniedMessage)
conn.Close()
return
}
}
} }
defer conn.Close() defer conn.Close()
rd := bufio.NewReader(conn) rd := bufio.NewReader(conn)
@ -59,7 +100,8 @@ func handleConn(
return io.EOF return io.EOF
} }
var b bytes.Buffer var b bytes.Buffer
if err := handler(command, conn, rd, &b, proto == client.WebSocket); err != nil {
if err := handler(conn, command, rd, &b, proto == client.WebSocket); err != nil {
if proto == client.HTTP { if proto == client.HTTP {
conn.Write([]byte(`HTTP/1.1 500 ` + err.Error() + "\r\nConnection: close\r\n\r\n")) conn.Write([]byte(`HTTP/1.1 500 ` + err.Error() + "\r\nConnection: close\r\n\r\n"))
} }

View File

@ -33,13 +33,13 @@ func (c Command) String() string {
func (c Command) TermOutput(indent string) string { func (c Command) TermOutput(indent string) string {
line1 := bright + strings.Replace(c.String(), " ", " "+clear+gray, 1) + clear line1 := bright + strings.Replace(c.String(), " ", " "+clear+gray, 1) + clear
line2 := yellow + "summary: " + clear + c.Summary line2 := yellow + "summary: " + clear + c.Summary
line3 := yellow + "since: " + clear + c.Since //line3 := yellow + "since: " + clear + c.Since
return indent + line1 + "\n" + indent + line2 + "\n" + indent + line3 + "\n" return indent + line1 + "\n" + indent + line2 + "\n" //+ indent + line3 + "\n"
} }
type EnumArg struct { type EnumArg struct {
Name string `json:"name"` Name string `json:"name"`
Arguments []Argument `json:"arguments` Arguments []Argument `json:"arguments"`
} }
func (a EnumArg) String() string { func (a EnumArg) String() string {
@ -846,12 +846,43 @@ var commandsJSON = `{
"since": "1.0.0", "since": "1.0.0",
"group": "search" "group": "search"
}, },
"PING": { "CONFIG GET": {
"summary":"Ping the server", "summary": "Authenticate to the server",
"complexity": "O(1)", "arguments": [
"arguments": [], {
"since": "1.0.0", "name": "which",
"group": "server" "enumargs": [
{
"name": "GET",
"arguments":[
{
"name": "parameter",
"type": "string"
}
]
},
{
"name": "SET",
"arguments":[
{
"name": "parameter",
"type": "string"
},
{
"name": "value",
"type": "string",
"optional": true
}
]
},
{
"name": "REWRITE",
"arguments":[]
}
]
}
],
"group": "connection"
}, },
"SERVER": { "SERVER": {
"summary":"Show server stats and details", "summary":"Show server stats and details",
@ -931,10 +962,25 @@ var commandsJSON = `{
}, },
"AOFSHRINK": { "AOFSHRINK": {
"summary": "Shrinks the aof in the background", "summary": "Shrinks the aof in the background",
"complexity": "O(1)",
"arguments": [],
"since": "1.0.0",
"group": "replication" "group": "replication"
},
"PING": {
"summary": "Ping the server",
"group": "connection"
},
"QUIT": {
"summary": "Close the connection",
"group": "connection"
},
"AUTH": {
"summary": "Authenticate to the server",
"arguments": [
{
"name": "password",
"type": "string"
}
],
"group": "connection"
} }
}` }`
@ -960,6 +1006,8 @@ var commandsJSON = `{
// HELP -- Prints this menu. -- O(1) -- F() // HELP -- Prints this menu. -- O(1) -- F()
// READONLY value -- Turn on or off readonly mode. -- O(1) -- F(value boolean) // READONLY value -- Turn on or off readonly mode. -- O(1) -- F(value boolean)
// FLUSHDB -- Removes all keys. -- O(1) -- F() // FLUSHDB -- Removes all keys. -- O(1) -- F()
// CONFIG SET property value -- Set a config property. Is not yet permanent.
// CONFIG REWRITE -- Make config changes permanent.
// --- Replication --- // --- Replication ---
// FOLLOW host port -- Follows a leader host. -- O(1) F(host string, port integer) // FOLLOW host port -- Follows a leader host. -- O(1) F(host string, port integer)
@ -990,3 +1038,6 @@ var commandsJSON = `{
// QUADKEY... QUADKEY key -- Quadkey. -- F(key quadkey) // QUADKEY... QUADKEY key -- Quadkey. -- F(key quadkey)
// TILE... TILE x y z -- Google XYZ tile. -- F(x double, y double, z double) // TILE... TILE x y z -- Google XYZ tile. -- F(x double, y double, z double)
// GET... GET key id -- An internal object. -- F(key string, id string) // GET... GET key id -- An internal object. -- F(key string, id string)
// --- Security ---
// AUTH password -- Authenticate to server.

View File

@ -701,12 +701,43 @@
"since": "1.0.0", "since": "1.0.0",
"group": "search" "group": "search"
}, },
"PING": { "CONFIG": {
"summary":"Ping the server", "summary": "Authenticate to the server",
"complexity": "O(1)", "arguments": [
"arguments": [], {
"since": "1.0.0", "name": "which",
"group": "server" "enumargs": [
{
"name": "GET",
"arguments":[
{
"name": "parameter",
"type": "string"
}
]
},
{
"name": "SET",
"arguments":[
{
"name": "parameter",
"type": "string"
},
{
"name": "value",
"type": "string",
"optional": true
}
]
},
{
"name": "REWRITE",
"arguments":[]
}
]
}
],
"group": "connection"
}, },
"SERVER": { "SERVER": {
"summary":"Show server stats and details", "summary":"Show server stats and details",
@ -786,9 +817,24 @@
}, },
"AOFSHRINK": { "AOFSHRINK": {
"summary": "Shrinks the aof in the background", "summary": "Shrinks the aof in the background",
"complexity": "O(1)",
"arguments": [],
"since": "1.0.0",
"group": "replication" "group": "replication"
},
"PING": {
"summary": "Ping the server",
"group": "connection"
},
"QUIT": {
"summary": "Close the connection",
"group": "connection"
},
"AUTH": {
"summary": "Authenticate to the server",
"arguments": [
{
"name": "password",
"type": "string"
}
],
"group": "connection"
} }
} }

View File

@ -35,13 +35,13 @@ func (c Command) String() string {
func (c Command) TermOutput(indent string) string { func (c Command) TermOutput(indent string) string {
line1 := bright + strings.Replace(c.String(), " ", " "+clear+gray, 1) + clear line1 := bright + strings.Replace(c.String(), " ", " "+clear+gray, 1) + clear
line2 := yellow + "summary: " + clear + c.Summary line2 := yellow + "summary: " + clear + c.Summary
line3 := yellow + "since: " + clear + c.Since //line3 := yellow + "since: " + clear + c.Since
return indent + line1 + "\n" + indent + line2 + "\n" + indent + line3 + "\n" return indent + line1 + "\n" + indent + line2 + "\n" //+ indent + line3 + "\n"
} }
type EnumArg struct { type EnumArg struct {
Name string `json:"name"` Name string `json:"name"`
Arguments []Argument `json:"arguments` Arguments []Argument `json:"arguments"`
} }
func (a EnumArg) String() string { func (a EnumArg) String() string {
@ -169,6 +169,8 @@ var commandsJSON = `{{.CommandsJSON}}`
// HELP -- Prints this menu. -- O(1) -- F() // HELP -- Prints this menu. -- O(1) -- F()
// READONLY value -- Turn on or off readonly mode. -- O(1) -- F(value boolean) // READONLY value -- Turn on or off readonly mode. -- O(1) -- F(value boolean)
// FLUSHDB -- Removes all keys. -- O(1) -- F() // FLUSHDB -- Removes all keys. -- O(1) -- F()
// CONFIG SET property value -- Set a config property. Is not yet permanent.
// CONFIG REWRITE -- Make config changes permanent.
// --- Replication --- // --- Replication ---
// FOLLOW host port -- Follows a leader host. -- O(1) F(host string, port integer) // FOLLOW host port -- Follows a leader host. -- O(1) F(host string, port integer)
@ -199,3 +201,6 @@ var commandsJSON = `{{.CommandsJSON}}`
// QUADKEY... QUADKEY key -- Quadkey. -- F(key quadkey) // QUADKEY... QUADKEY key -- Quadkey. -- F(key quadkey)
// TILE... TILE x y z -- Google XYZ tile. -- F(x double, y double, z double) // TILE... TILE x y z -- Google XYZ tile. -- F(x double, y double, z double)
// GET... GET key id -- An internal object. -- F(key string, id string) // GET... GET key id -- An internal object. -- F(key string, id string)
// --- Security ---
// AUTH password -- Authenticate to server.

View File

@ -5,3 +5,5 @@ var DevMode = false
// ShowDebugMessages allows for log.Debug to print to console. // ShowDebugMessages allows for log.Debug to print to console.
var ShowDebugMessages = false var ShowDebugMessages = false
var ProtectedMode = true

View File

@ -1,7 +1,7 @@
package core package core
var ( var (
Version string Version = "0.0.0"
BuildTime string BuildTime = ""
GitSHA string GitSHA = "0000000"
) )