Commit 84e040d6 by anun

update flags edge

parent ccd7441a
...@@ -32,3 +32,5 @@ _testmain.go ...@@ -32,3 +32,5 @@ _testmain.go
*.exe *.exe
*.test *.test
*.prof *.prof
.env
.tmp
VERSION=$(shell git describe --abbrev=0 --tags)
BUILD=$(shell git rev-parse HEAD)
DIRBASE=./build
DIR=${DIRBASE}/${VERSION}/${BUILD}/bin
LDFLAGS=-ldflags "-s -w ${XBUILD} -buildid=${BUILD} -X dev.nexpie.com/anun/chisel/share.BuildVersion=${VERSION}"
GOFILES=`go list ./...`
GOFILESNOTEST=`go list ./... | grep -v test`
# Make Directory to store executables
$(shell mkdir -p ${DIR})
all:
@goreleaser build --skip-validate --single-target --config .github/goreleaser.yml
freebsd: lint
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -trimpath ${LDFLAGS} ${GCFLAGS} ${ASMFLAGS} -o ${DIR}/chisel-freebsd_amd64 .
linux: lint
env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -trimpath ${LDFLAGS} ${GCFLAGS} ${ASMFLAGS} -o ${DIR}/chisel-linux_amd64 .
windows: lint
env CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -trimpath ${LDFLAGS} ${GCFLAGS} ${ASMFLAGS} -o ${DIR}/chisel-windows_amd64 .
darwin:
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath ${LDFLAGS} ${GCFLAGS} ${ASMFLAGS} -o ${DIR}/chisel-darwin_amd64 .
docker:
@docker build .
dep: ## Get the dependencies
@go get -u github.com/goreleaser/goreleaser
@go get -u github.com/boumenot/gocover-cobertura
@go get -v -d ./...
@go get -u all
@go mod tidy
lint: ## Lint the files
@go fmt ${GOFILES}
@go vet ${GOFILESNOTEST}
test: ## Run unit tests
@go test -coverprofile=${DIR}/coverage.out -race -short ${GOFILESNOTEST}
@go tool cover -html=${DIR}/coverage.out -o ${DIR}/coverage.html
@gocover-cobertura < ${DIR}/coverage.out > ${DIR}/coverage.xml
release: lint test
goreleaser release --config .github/goreleaser.yml
clean:
rm -rf ${DIRBASE}/*
.PHONY: all freebsd linux windows docker dep lint test release clean
\ No newline at end of file
No preview for this file type
...@@ -44,7 +44,7 @@ type Config struct { ...@@ -44,7 +44,7 @@ type Config struct {
TLS TLSConfig TLS TLSConfig
DialContext func(ctx context.Context, network, addr string) (net.Conn, error) DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
Verbose bool Verbose bool
Code string Edge EdgeConfig
} }
// TLSConfig for a Client // TLSConfig for a Client
...@@ -56,6 +56,12 @@ type TLSConfig struct { ...@@ -56,6 +56,12 @@ type TLSConfig struct {
ServerName string ServerName string
} }
// Edge NEXPIE
type EdgeConfig struct {
EdgeId string
EdgeSecret string
}
// Client represents a client instance // Client represents a client instance
type Client struct { type Client struct {
*cio.Logger *cio.Logger
...@@ -112,6 +118,7 @@ func NewClient(c *Config) (*Client, error) { ...@@ -112,6 +118,7 @@ func NewClient(c *Config) (*Client, error) {
config: c, config: c,
computed: settings.Config{ computed: settings.Config{
Version: chshare.BuildVersion, Version: chshare.BuildVersion,
// Edge: nil,
}, },
server: u.String(), server: u.String(),
tlsConfig: nil, tlsConfig: nil,
...@@ -120,12 +127,14 @@ func NewClient(c *Config) (*Client, error) { ...@@ -120,12 +127,14 @@ func NewClient(c *Config) (*Client, error) {
//set default log level //set default log level
client.Logger.Info = true client.Logger.Info = true
//Anun
/*
p, e := getPort(c.Server+"/register") p, e := getPort(c.Server+"/register")
client.Debugf("---> p = %s", p) client.Debugf("---> p = %s", p)
if e != nil { if e != nil {
client.Errorf("request %s/register : %s", c.Server, e) client.Errorf("request %s/register : %s", c.Server, e)
} }
*/
//configure tls //configure tls
if u.Scheme == "wss" { if u.Scheme == "wss" {
tc := &tls.Config{} tc := &tls.Config{}
...@@ -159,14 +168,24 @@ func NewClient(c *Config) (*Client, error) { ...@@ -159,14 +168,24 @@ func NewClient(c *Config) (*Client, error) {
} }
client.tlsConfig = tc client.tlsConfig = tc
} }
if c.Code == "" { // Anun
c.Code = settings.RandString(16) if c.Edge.EdgeId != "" {
client.computed.Edge.EdgeId = c.Edge.EdgeId
}
if c.Edge.EdgeSecret != "" {
client.computed.Edge.EdgeSecret = c.Edge.EdgeSecret
} }
client.Debugf("---> c.Edge = %s", c.Edge)
client.Debugf("---> c.EdgeId = %s", c.Edge.EdgeId)
client.Debugf("---> c.EdgeSecret = %s", c.Edge.EdgeSecret)
//validate remotes //validate remotes
// client.Debugf("---> c.Remotes = %s", c.Remotes) // client.Debugf("---> c.Remotes = %s", c.Remotes)
for _, s := range c.Remotes { for _, s := range c.Remotes {
//Anun
/*
s = "R:"+string(p)+":"+s s = "R:"+string(p)+":"+s
client.Debugf("s = %s", s) client.Debugf("s = %s", s)
*/
r, err := settings.DecodeRemote(s) r, err := settings.DecodeRemote(s)
client.Debugf("r = %s", r) client.Debugf("r = %s", r)
if err != nil { if err != nil {
...@@ -190,7 +209,7 @@ func NewClient(c *Config) (*Client, error) { ...@@ -190,7 +209,7 @@ func NewClient(c *Config) (*Client, error) {
} }
client.computed.Remotes = append(client.computed.Remotes, r) client.computed.Remotes = append(client.computed.Remotes, r)
} }
client.computed.Code = c.Code // client.computed.Edge = c.Edge
client.Infof("---> Remotes client.computed# %s", client.computed) client.Infof("---> Remotes client.computed# %s", client.computed)
//outbound proxy //outbound proxy
if p := c.Proxy; p != "" { if p := c.Proxy; p != "" {
......
...@@ -16,7 +16,9 @@ require ( ...@@ -16,7 +16,9 @@ require (
require ( require (
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/jpillora/ansi v1.0.3 // indirect github.com/jpillora/ansi v1.0.3 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
golang.org/x/sys v0.11.0 // indirect golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect golang.org/x/text v0.12.0 // indirect
......
dev.nexpie.com/anun/chisel v1.9.1 h1:nGOF58+45WHlvDcq6AZu7En8nWOBCZHqj9boo5rB4qU=
dev.nexpie.com/anun/chisel v1.9.1/go.mod h1:qvgGfFR9ZhiDoYJM4IM1omX1HLbQSkZag8miP9u4SsQ=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZOs7ygH5BgQp4N+aYrZ2DNpWZ1KG3VOSOM= github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZOs7ygH5BgQp4N+aYrZ2DNpWZ1KG3VOSOM=
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os= github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/jpillora/ansi v1.0.3 h1:nn4Jzti0EmRfDxm7JtEs5LzCbNwd5sv+0aE+LdS9/ZQ= github.com/jpillora/ansi v1.0.3 h1:nn4Jzti0EmRfDxm7JtEs5LzCbNwd5sv+0aE+LdS9/ZQ=
github.com/jpillora/ansi v1.0.3/go.mod h1:D2tT+6uzJvN1nBVQILYWkIdq7zG+b5gcFN5WI/VyjMY= github.com/jpillora/ansi v1.0.3/go.mod h1:D2tT+6uzJvN1nBVQILYWkIdq7zG+b5gcFN5WI/VyjMY=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
dev.nexpie.com/anun/chisel v1.9.1 h1:nGOF58+45WHlvDcq6AZu7En8nWOBCZHqj9boo5rB4qU=
dev.nexpie.com/anun/chisel v1.9.1/go.mod h1:qvgGfFR9ZhiDoYJM4IM1omX1HLbQSkZag8miP9u4SsQ=
github.com/jpillora/requestlog v1.0.0 h1:bg++eJ74T7DYL3DlIpiwknrtfdUA9oP/M4fL+PpqnyA= github.com/jpillora/requestlog v1.0.0 h1:bg++eJ74T7DYL3DlIpiwknrtfdUA9oP/M4fL+PpqnyA=
github.com/jpillora/requestlog v1.0.0/go.mod h1:HTWQb7QfDc2jtHnWe2XEIEeJB7gJPnVdpNn52HXPvy8= github.com/jpillora/requestlog v1.0.0/go.mod h1:HTWQb7QfDc2jtHnWe2XEIEeJB7gJPnVdpNn52HXPvy8=
github.com/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2ygw= github.com/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2ygw=
github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0= github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
......
...@@ -417,7 +417,9 @@ var clientHelp = ` ...@@ -417,7 +417,9 @@ var clientHelp = `
private key. The certificate must have client authentication private key. The certificate must have client authentication
enabled (mutual-TLS). enabled (mutual-TLS).
--code, serial number, token, text --edgeid, id of device
--edgesecret, secret of device
` + commonHelp ` + commonHelp
func client(args []string) { func client(args []string) {
...@@ -434,7 +436,8 @@ func client(args []string) { ...@@ -434,7 +436,8 @@ func client(args []string) {
flags.StringVar(&config.TLS.Cert, "tls-cert", "", "") flags.StringVar(&config.TLS.Cert, "tls-cert", "", "")
flags.StringVar(&config.TLS.Key, "tls-key", "", "") flags.StringVar(&config.TLS.Key, "tls-key", "", "")
flags.Var(&headerFlags{config.Headers}, "header", "") flags.Var(&headerFlags{config.Headers}, "header", "")
flags.StringVar(&config.Code, "code", "", "") flags.StringVar(&config.Edge.EdgeId, "edgeid", "", "")
flags.StringVar(&config.Edge.EdgeSecret, "edgesecret", "", "")
hostname := flags.String("hostname", "", "") hostname := flags.String("hostname", "", "")
sni := flags.String("sni", "", "") sni := flags.String("sni", "", "")
pid := flags.Bool("pid", false, "") pid := flags.Bool("pid", false, "")
......
REGISTRY_HOST=dock.nexpie.com
GROUPNAME=anun
PROJECTNAME=chisel
RELEASE=1.0.0
...@@ -19,8 +19,32 @@ import ( ...@@ -19,8 +19,32 @@ import (
"dev.nexpie.com/anun/chisel/share/settings" "dev.nexpie.com/anun/chisel/share/settings"
"github.com/jpillora/requestlog" "github.com/jpillora/requestlog"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"fmt"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // The database driver in use.
) )
// Anun
type DdConfig struct {
User string
Password string
Host string
Port int
Name string
DisableTLS bool
}
func DbOpen(cfg DdConfig) (*sqlx.DB, error) {
sslmode := "require"
if cfg.DisableTLS {
sslmode = "disable"
}
var dataSoruce = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Name, sslmode)
return sqlx.Connect("postgres", dataSoruce)
}
// Config is the configuration for the chisel service // Config is the configuration for the chisel service
type Config struct { type Config struct {
KeySeed string KeySeed string
...@@ -140,6 +164,47 @@ func NewServer(c *Config) (*Server, error) { ...@@ -140,6 +164,47 @@ func NewServer(c *Config) (*Server, error) {
if c.Reverse { if c.Reverse {
server.Infof("Reverse tunnelling enabled") server.Infof("Reverse tunnelling enabled")
} }
//Anun
dbenable:= settings.EnvBool("DB_ENABLE")
dbuser:= settings.EnvString("DB_USERNAME")
dbpassword:= settings.EnvString("DB_PASSWORD")
dbhost:= settings.EnvString("DB_HOST")
dbport:= settings.EnvInt("DB_PORT",0)
dbschema:= settings.EnvString("DB_NAME")
dbDisableTLS:= settings.EnvBool("DB_DISABLE_TLS")
dbConfig := DdConfig{
User: dbuser,
Password: dbpassword,
Host: dbhost,
Port: dbport,
Name: dbschema,
DisableTLS: dbDisableTLS,
}
server.Infof("dbConfig %s", dbConfig)
server.Infof("%s", dbenable)
server.Infof("%s", dbuser)
server.Infof("%s", dbpassword)
server.Infof("%s", dbhost)
server.Infof("%s", dbport)
server.Infof("%s", dbschema)
server.Infof("%s", dbDisableTLS)
db, err := DbOpen(dbConfig)
if err != nil {
server.Infof("connecting database fail", err)
}else{
server.Infof("connecting database, %s", db)
}
dbEnable := settings.EnvBool("DB_ENABLE")
server.Infof("dbEnable: %s", dbEnable)
if settings.EnvBool("DB_ENABLE") {
server.Infof("Edge Platform enabled")
}
return server, nil return server, nil
} }
...@@ -171,6 +236,7 @@ func (s *Server) StartContext(ctx context.Context, host, port string) error { ...@@ -171,6 +236,7 @@ func (s *Server) StartContext(ctx context.Context, host, port string) error {
if err != nil { if err != nil {
return err return err
} }
h := http.Handler(http.HandlerFunc(s.handleClientHandler)) h := http.Handler(http.HandlerFunc(s.handleClientHandler))
if s.Debug { if s.Debug {
o := requestlog.DefaultOptions o := requestlog.DefaultOptions
......
...@@ -9,11 +9,35 @@ import ( ...@@ -9,11 +9,35 @@ import (
"os" "os"
"os/user" "os/user"
"path/filepath" "path/filepath"
// "fmt"
"dev.nexpie.com/anun/chisel/share/settings" "dev.nexpie.com/anun/chisel/share/settings"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
// "github.com/jmoiron/sqlx"
// _ "github.com/lib/pq" // The database driver in use.
) )
// Anun
/*
type DbConfig struct {
User string
Password string
Host string
Port int
Name string
DisableTLS bool
}
func DbOpen(cfg DbConfig) (*sqlx.DB, error) {
sslmode := "require"
if cfg.DisableTLS {
sslmode = "disable"
}
var dataSoruce = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Name, sslmode)
return sqlx.Connect("postgres", dataSoruce)
}
*/
//TLSConfig enables configures TLS //TLSConfig enables configures TLS
type TLSConfig struct { type TLSConfig struct {
Key string Key string
......
package database
import (
"fmt"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // The database driver in use.
)
type Config struct {
User string
Password string
Host string
Port int
Name string
DisableTLS bool
}
func Open(cfg Config) (*sqlx.DB, error) {
sslmode := "require"
if cfg.DisableTLS {
sslmode = "disable"
}
var dataSoruce = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Name, sslmode)
return sqlx.Connect("postgres", dataSoruce)
}
package edge
import (
// "time"
"github.com/jmoiron/sqlx"
)
type PostgresDB struct {
DB *sqlx.DB
}
func (postgres PostgresDB) GetEdge(id string) (PostgresDB, error) {
var edge PostgresDB
const query = `SELECT * FROM EdgeDevice WHERE edgeid=$1`
err := postgres.DB.Get(&edge, query, id)
if err != nil {
return PostgresDB{}, err
}
// edge.createtime = edge.createtime.UTC()
return edge, nil
}
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
type Config struct { type Config struct {
Version string Version string
Remotes Remotes
Code string Edge
} }
func DecodeConfig(b []byte) (*Config, error) { func DecodeConfig(b []byte) (*Config, error) {
......
...@@ -33,3 +33,9 @@ func EnvBool(name string) bool { ...@@ -33,3 +33,9 @@ func EnvBool(name string) bool {
v := Env(name) v := Env(name)
return v == "1" || strings.ToLower(v) == "true" return v == "1" || strings.ToLower(v) == "true"
} }
//Anun
func EnvString(name string) string {
return os.Getenv(name)
}
...@@ -38,7 +38,11 @@ type Remote struct { ...@@ -38,7 +38,11 @@ type Remote struct {
LocalHost, LocalPort, LocalProto string LocalHost, LocalPort, LocalProto string
RemoteHost, RemotePort, RemoteProto string RemoteHost, RemotePort, RemoteProto string
Socks, Reverse, Stdio bool Socks, Reverse, Stdio bool
Code string }
// Anun
type Edge struct {
EdgeId, EdgeSecret string
} }
const revPrefix = "R:" const revPrefix = "R:"
......
...@@ -6,4 +6,4 @@ package chshare ...@@ -6,4 +6,4 @@ package chshare
//mismatch. //mismatch.
var ProtocolVersion = "chisel-v3" var ProtocolVersion = "chisel-v3"
var BuildVersion = "0.0.0-src" var BuildVersion = "24.06.00-edge"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment