diff --git a/crypt/crypt.go b/crypt/crypt.go new file mode 100644 index 0000000..364d798 --- /dev/null +++ b/crypt/crypt.go @@ -0,0 +1,60 @@ +package crypt + +import ( + "crypto/cipher" + "crypto/rand" + "log" + + "golang.org/x/crypto/chacha20poly1305" +) + +type Crypt struct { + key []byte + ads []byte + aead cipher.AEAD +} + +func NewChacha20poly1305Key() []byte { + k := make([]byte, chacha20poly1305.KeySize) + if _, err := rand.Read(k); err != nil { + panic(err) + } + return k +} + +func NewCrypt(key []byte, ads []byte) *Crypt { + if len(key) == 0 { + // log.Printf(" == GENERATE NEW KEY ==\n") + key = NewChacha20poly1305Key() + } + aead, err := chacha20poly1305.New(key) + if err != nil { + panic(err) + } + return &Crypt{key: key, ads: ads, aead: aead} +} + +func (c *Crypt) GenNonce(length int) []byte { + nonce := make([]byte, c.aead.NonceSize(), c.aead.NonceSize()+length+c.aead.Overhead()) + if _, err := rand.Read(nonce); err != nil { + panic(err) + } + return nonce +} + +func (c *Crypt) Encrypt(msg []byte) []byte { + nonce := c.GenNonce(len(msg)) + encryptedMsg := c.aead.Seal(nonce, nonce, msg, c.ads) + return encryptedMsg +} + +func (c *Crypt) Decrypt(encrypted []byte) ([]byte, bool) { + done := true + nonce, ciphertext := encrypted[:c.aead.NonceSize()], encrypted[c.aead.NonceSize():] + plaintext, err := c.aead.Open(nil, nonce, ciphertext, c.ads) + if err != nil { + done = false + log.Printf(" error decrypt data : %v\n", err) + } + return plaintext, done +} diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..00a95ca --- /dev/null +++ b/db/db.go @@ -0,0 +1,171 @@ +package db + +import ( + "database/sql" + "fmt" + "log" + "time" + + "gitea.meta-tech.academy/go/core/util" + + "github.com/go-sql-driver/mysql" +) + +const USER_DEFAULT_PRIVILEGES = "CREATE, CREATE VIEW, CREATE TEMPORARY TABLES, SELECT, INSERT, UPDATE, DELETE, EXECUTE, LOCK TABLES, TRIGGER" + +var MAX_OPEN_CONNS = 500 +var MAX_IDLE_CONNS = 0 + +type Db struct { + pool map[string]*sql.DB +} + +type DbConfig struct { + host string + dbname string + user string + passwd string + port int +} + +type PasswordDecode func(in string) (string, bool) + +func (dbc *DbConfig) GetHost() string { + return fmt.Sprintf("%s:%d", dbc.host, dbc.port) +} + +func (dbc *DbConfig) GetSqlConfig(passwdGet ...PasswordDecode) *mysql.Config { + var plain string = dbc.passwd + var done bool = len(passwdGet) == 0 + if !done { + plain, done = passwdGet[0](dbc.passwd) + } + if done { + return &mysql.Config{ + User: dbc.user, + Net: "tcp", + Addr: dbc.GetHost(), + DBName: dbc.dbname, + Passwd: plain, + AllowNativePasswords: true, + } + } else { + fmt.Printf("invalid password unable to connect") + time.Sleep(3 * time.Second) + } + return nil +} + +func (dbc *DbConfig) NewDbConfig(host string, dbname string, user string, passwd string, port int) *DbConfig { + return &DbConfig{host, dbname, user, passwd, port} +} + +func (db *Db) AddDb(dbconf *DbConfig, passwdGet ...PasswordDecode) { + cnx, err := sql.Open("mysql", dbconf.GetSqlConfig(passwdGet...).FormatDSN()) + if err != nil { + log.Fatal(err) + } + pingErr := cnx.Ping() + if pingErr != nil { + log.Fatal(pingErr) + } + cnx.SetMaxIdleConns(MAX_IDLE_CONNS) + cnx.SetMaxOpenConns(MAX_OPEN_CONNS) + db.pool[dbconf.dbname] = cnx +} + +func NewDb() *Db { + pool := make(map[string]*sql.DB) + return &Db{pool} +} + +func getFullUser(username string, hostname string) string { + return fmt.Sprintf("'%s'@'%s'", util.EscapeSquote(username), util.EscapeSquote(hostname)) +} + +func (db *Db) onDbExec(dbname string, instr DbInstruct) bool { + var done bool + if sqlDb, ok := db.pool[dbname]; ok { + return instr(sqlDb) + } else { + fmt.Printf("you should add db mysql first\n") + done = ok + } + return done +} + +type DbInstruct func(database *sql.DB) bool + +func (db *Db) CreateUser(username string, hostname string, passwd string) bool { + return db.onDbExec("mysql", func(sqlDb *sql.DB) bool { + query := fmt.Sprintf( + "CREATE USER IF NOT EXISTS %s IDENTIFIED BY '%s';", + getFullUser(username, hostname), + util.EscapeSquote(passwd), + ) + _, err := sqlDb.Exec(query) + done := err == nil + if !done { + fmt.Printf("unable to add user %s\n%v\n", username, err) + } + return done + }) +} + +func (db *Db) GrantUserPrivileges(dbname string, username string, hostname string) bool { + return db.onDbExec("mysql", func(sqlDb *sql.DB) bool { + query := fmt.Sprintf( + "GRANT %s ON `%s`.* TO %s;", + USER_DEFAULT_PRIVILEGES, + util.EscapeBt(dbname), + getFullUser(username, hostname), + ) + _, err := sqlDb.Exec(query) + done := err == nil + if !done { + fmt.Printf("query : %s\n", query) + fmt.Printf("unable to grant user privilege %s\n%v\n", username, err) + } + return done + }) +} + +func (db *Db) CreateDb(dbname string, collate string) bool { + return db.onDbExec("mysql", func(sqlDb *sql.DB) bool { + query := fmt.Sprintf( + "CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET '%s' COLLATE '%s_unicode_ci'", + util.EscapeBt(dbname), + util.EscapeSquote(collate), + util.EscapeSquote(collate), + ) + _, err := db.pool["mysql"].Exec(query) + done := err == nil + if !done { + fmt.Printf("unable to create database %s\n%v\n", dbname, err) + } + return done + }) +} + +func (db *Db) SizeDb(dbname string) float32 { + type RsSize struct { + Dbname string + Size float32 + } + rs := RsSize{Size: -1} + db.onDbExec("information_schema", func(sqlDb *sql.DB) bool { + query := "SELECT table_schema 'dbname', SUM(data_length + index_length)/1024/1024 'size' FROM TABLES WHERE table_schema = ? GROUP BY table_schema;" + row := sqlDb.QueryRow(query, dbname) + err := row.Scan(&rs.Dbname, &rs.Size) + done := err != nil + if !done { + if err == sql.ErrNoRows { + fmt.Printf(" cannot find db %s or db is empty\n", dbname) + } else { + fmt.Printf(" what the fuck error : %+v\n", err) + } + } + return done + }) + return rs.Size +} diff --git a/echo/echo.go b/echo/echo.go index c25ac05..5c9865b 100644 --- a/echo/echo.go +++ b/echo/echo.go @@ -10,10 +10,11 @@ import ( var curStyles *style.Styles var hasCurStyle bool = false +var msgUninit = "Missing current *style.Styles, you should call echo.SetCurrentStyles(styles *style.Styles) first" func Cstyle(name string) *style.Style { if !hasCurStyle { - panic("Missing current style, you should call style.SetCurrentStyles(styles *style.Styles) first\n") + panic(msgUninit) } return curStyles.Get(name) } @@ -63,6 +64,13 @@ func State(done bool) { Ln() } +func Keyval(key string, val string, names ...string) { + if !hasCurStyle { + panic(msgUninit) + } + curStyles.Keyval(key, val, names...) +} + func Msg(msg string) { fmt.Printf("%s%s\n", strings.Repeat(" ", 4), msg) } diff --git a/go.mod b/go.mod index 8e486a4..09eef5b 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,12 @@ module gitea.meta-tech.academy/go/core go 1.20 require ( + github.com/go-sql-driver/mysql v1.7.1 github.com/gookit/color v1.5.4 - golang.org/x/sys v0.13.0 + github.com/ulikunitz/xz v0.5.11 + golang.org/x/crypto v0.14.0 + golang.org/x/sys v0.14.0 + golang.org/x/text v0.14.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index f71ea80..5bd6d47 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,20 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/sys/xtract.go b/sys/xtract.go new file mode 100644 index 0000000..54ccd78 --- /dev/null +++ b/sys/xtract.go @@ -0,0 +1,132 @@ +package sys + +import ( + "archive/zip" + "bufio" + "bytes" + "compress/bzip2" + "compress/gzip" + "fmt" + "io" + "log" + "os" + + "github.com/ulikunitz/xz" +) + +type Xtract struct { + dir string + reader *bufio.Reader + bar *io.Writer + outFile string + withBar bool +} + +func NewXtract(dir string, outFile string) *Xtract { + return &Xtract{dir: dir, outFile: outFile, withBar: false} +} + +func (x *Xtract) addBar(bar *io.Writer) { + x.bar = bar + x.withBar = true +} + +func (x *Xtract) setCompressedFile(dumpName string) { + path := fmt.Sprintf("%s/%s", x.dir, dumpName) + file, err := os.Open(path) + if err != nil { + panic(err) + } + x.reader = bufio.NewReader(file) +} + +func (x *Xtract) writeUncompressedData(out *bytes.Buffer) bool { + err := os.WriteFile(x.outFile, out.Bytes(), 0644) + if err != nil { + panic(err) + } + return true +} + +func (x *Xtract) UnGzip(dumpName string) bool { + x.setCompressedFile(dumpName) + var out bytes.Buffer + r, err := gzip.NewReader(x.reader) + if err != nil { + panic(err) + } + if x.withBar { + _, err = io.Copy(io.MultiWriter(&out, *x.bar), r) + } else { + _, err = io.Copy(&out, r) + } + if err != nil { + panic(err) + } + // if x.withBar { + // x.bar.Clear() + // } + return x.writeUncompressedData(&out) +} + +func (x *Xtract) UnBz2(dumpName string) bool { + x.setCompressedFile(dumpName) + var out bytes.Buffer + r := bzip2.NewReader(x.reader) + var err error + if x.withBar { + _, err = io.Copy(io.MultiWriter(&out, *x.bar), r) + } else { + _, err = io.Copy(&out, r) + } + if err != nil { + panic(err) + } + return x.writeUncompressedData(&out) +} + +func (x *Xtract) UnXz(dumpName string) bool { + x.setCompressedFile(dumpName) + var out bytes.Buffer + r, err := xz.NewReader(x.reader) + if err != nil { + panic(err) + } + if x.withBar { + _, err = io.Copy(io.MultiWriter(&out, *x.bar), r) + } else { + _, err = io.Copy(&out, r) + } + if err != nil { + panic(err) + } + return x.writeUncompressedData(&out) +} + +func (x *Xtract) UnZip(dumpName string) bool { + var done bool = false + path := fmt.Sprintf("%s/%s", x.dir, dumpName) + r, err := zip.OpenReader(path) + if err != nil { + panic(err) + } + defer r.Close() + if len(r.File) > 0 { + rc, err := r.File[0].Open() + if err != nil { + log.Fatal(err) + } + var out bytes.Buffer + if x.withBar { + _, err = io.Copy(io.MultiWriter(&out, *x.bar), rc) + } else { + _, err = io.Copy(&out, rc) + } + if err != nil { + log.Fatal(err) + } + rc.Close() + done = x.writeUncompressedData(&out) + } + return done +} diff --git a/util/util.go b/util/util.go index 0352940..15db22d 100644 --- a/util/util.go +++ b/util/util.go @@ -1,8 +1,17 @@ package util import ( + "bytes" + "encoding/hex" + "fmt" + "log" + "math" + "path" "strconv" "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" ) func PrependToSliceStr(strs *[]string, prefix string) { @@ -38,3 +47,62 @@ func CastStrings2ints(strs *[]string) []int { } return a } + +func PrettyByteSize(b int64) string { + bf := float64(b) + for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} { + if math.Abs(bf) < 1024.0 { + return fmt.Sprintf("%3.2f%s", bf, unit) + } + bf /= 1024.0 + } + return fmt.Sprintf("%.2fYi", bf) +} + +func Ucfirst(str *string) { + caser := cases.Title(language.English) + *str = caser.String(*str) +} + +func RemoveExtension(fileName string) string { + return strings.TrimSuffix(fileName, path.Ext(fileName)) +} + +func GetBytesAsHex(key []byte) []byte { + dst := make([]byte, hex.EncodedLen(len(key))) + hex.Encode(dst, key) + return dst +} + +func GetBytesFromHex(key []byte) []byte { + dst := make([]byte, hex.DecodedLen(len(key))) + n, err := hex.Decode(dst, key) + if err != nil { + log.Fatal(err) + } + return dst[:n] +} + +func Escape(str string, c rune) string { + var buf bytes.Buffer + for _, char := range str { + switch char { + case c: + buf.WriteRune('\\') + } + buf.WriteRune(char) + } + return buf.String() +} + +func EscapeBt(str string) string { + return Escape(str, '`') +} + +func EscapeDquote(str string) string { + return Escape(str, '"') +} + +func EscapeSquote(str string) string { + return Escape(str, '\'') +}