├── .gitignore ├── Godeps ├── Godeps.json └── Readme ├── README.md ├── bash_autocomplete ├── build.all.sh └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | Godeps/_workspace 2 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/patcito/composehub", 3 | "GoVersion": "go1.5rc1", 4 | "Deps": [ 5 | { 6 | "ImportPath": "github.com/codegangsta/cli", 7 | "Comment": "1.2.0-139-g142e6cd", 8 | "Rev": "142e6cd241a4dfbf7f07a018f1f8225180018da4" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ComposeHub CLI 2 | 3 | A tool to publish, search and install apps using docker-compose. You can also search on ComposeHub using the web UI online https://composehub.com 4 | 5 | The Docker hub is awesome to search for great images to build your own apps. 6 | However, for existing apps such as wordpress or gitlab, you need more than one container and linking them all together is no fun as it requires typing never-ending docker CLI commands. So you end up googling for a docker-compose.yml to solve your problem and end up copy/pasting the result in your own docker-compose.yml file, crossing fingers it works out. 7 | Composehub solves this problem by providing an easy way to search for docker-compose apps stored on git repos. You can also use it to publish your own public or private apps. Give it a try! 8 | 9 | 10 | ## Overview 11 | 12 | * Publish apps 13 | * Search for apps 14 | * Install apps in seconds 15 | * Run apps on the fly 16 | 17 | # Getting Started 18 | 19 | ## Install on OSX: 20 | 21 | ``` 22 | curl -L https://composehub.com/install/darwin > /usr/local/bin/ch && chmod +x /usr/local/bin/ch 23 | ``` 24 | 25 | ## Install on Linux: 26 | 27 | ``` 28 | curl -L https://composehub.com/install/linux > /usr/local/bin/ch && chmod +x /usr/local/bin/ch 29 | ``` 30 | 31 | ## Install on Windows: 32 | 33 | ``` 34 | curl -L https://composehub.com/install/windows > /usr/local/bin/ch 35 | ``` 36 | 37 | 38 | ## Install from source (requires a go install): 39 | 40 | ``` 41 | go get -u github.com/composehub/cli 42 | ``` 43 | 44 | ## Documentation 45 | 46 | 47 | ## Table of Contents 48 | 49 | - [Search apps](#search-apps) 50 | - [Install apps](#install-apps) 51 | - [Run apps](#run-apps) 52 | - [Manage your user account (optional)](#manage-account) 53 | - [Create an account](#create-an-account) 54 | - [Update an account](#update-an-account) 55 | - [Reset password](#reset-password) 56 | - [Publish apps](#publish-apps) 57 | - [Command](#command) 58 | - [Config format](#config-format) 59 | 60 | 61 | ## Search apps 62 | 63 | ``` 64 | ch search gitlab 65 | ``` 66 | 67 | This will return a list of packages having gitlab in their name or description, ordered by most downloaded. 68 | 69 | ## Install apps 70 | 71 | ``` 72 | ch install gitlab && cd gitlab 73 | ``` 74 | 75 | Before installing an app, make sure your current directory is empty. Installing the app will clone the repo containing the docker-compose.yml file. Once the installation is done, just run the usual ```docker-compose up```. If there are additional commands to execute before, they will be shown at the end of the install. 76 | 77 | ## Run apps 78 | 79 | ``` 80 | ch run wordpress 81 | ``` 82 | 83 | This will install wordpress in the current directory and run it automatically. 84 | 85 | ## Manage your user account (optional) 86 | 87 | You only need an account if you want to publish your own apps. 88 | 89 | ### Create an account 90 | 91 | ``` 92 | ch adduser 93 | ``` 94 | 95 | You'll be asked to enter email, handle and password. 96 | 97 | ### Update an account 98 | 99 | ``` 100 | ch updateuser 101 | ``` 102 | 103 | Use this to update any of your user information. 104 | 105 | 106 | ### Reset password 107 | 108 | ``` 109 | ch resetpassword 110 | ``` 111 | 112 | Use this if you've forgotten your password. 113 | 114 | ## Publish apps 115 | 116 | ### Command 117 | 118 | ``` 119 | ch init 120 | ``` 121 | 122 | This will create a composehub.yml file in the current directory. 123 | 124 | ### Config format 125 | 126 | ```yml 127 | --- 128 | name: package-name 129 | blurb: 80 chars line blurb 130 | description: | 131 | longer description 132 | email: foo@bar.com 133 | repo_url: http://github.com/foo/bar 134 | tags: tag1,tag2 135 | private: false 136 | cmd: docker-compose up 137 | ``` 138 | 139 | The description will be displayed at the end of the install process of your package, use it to document any post-install required tasks. ```cmd``` is the command that will be ran when the user executes ```ch run ```, it is optional and can just be left blank. ```private``` if you set private to true, only you will be able to install the app and it will not appear online or in search results, this requires you to have your composehub account configured on your host. 140 | -------------------------------------------------------------------------------- /bash_autocomplete: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | ##: ${PROG:=$(basename ${BASH_SOURCE})} 4 | PROG="dcm" 5 | 6 | _cli_bash_autocomplete() { 7 | local cur prev opts base 8 | COMPREPLY=() 9 | cur="${COMP_WORDS[COMP_CWORD]}" 10 | prev="${COMP_WORDS[COMP_CWORD-1]}" 11 | opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) 12 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 13 | return 0 14 | } 15 | 16 | complete -F _cli_bash_autocomplete $PROG 17 | 18 | -------------------------------------------------------------------------------- /build.all.sh: -------------------------------------------------------------------------------- 1 | echo "building for darwin 386..." 2 | env GOOS=darwin GOARCH=386 go build -o ch-darwin-386 main.go 3 | echo "done." 4 | echo "building for darwin amd64..." 5 | env GOOS=darwin GOARCH=amd64 go build -o ch-darwin-amd64 main.go 6 | echo "done." 7 | echo "building for windows amd64..." 8 | env GOOS=windows GOARCH=amd64 go build -o ch-windows-amd64 main.go 9 | echo "done." 10 | echo "building for linux amd64..." 11 | env GOOS=linux GOARCH=amd64 go build -o ch-linux-amd64 main.go 12 | echo "All done." 13 | 14 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "crypto/rand" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "log" 12 | "net/http" 13 | "net/url" 14 | "os" 15 | "os/exec" 16 | "regexp" 17 | "runtime" 18 | "strings" 19 | "time" 20 | 21 | "github.com/codegangsta/cli" 22 | "github.com/howeyc/gopass" 23 | "github.com/mitchellh/go-homedir" 24 | "github.com/parnurzeal/gorequest" 25 | "gopkg.in/yaml.v2" 26 | ) 27 | 28 | type User struct { 29 | Id int64 `json:"id"` 30 | Name string `json:"name"` 31 | Handle string `json:"handle"` 32 | Email string `json:"email"` 33 | Password string `json:password` 34 | LatestCheck time.Time 35 | Packages []Package 36 | CreatedAt time.Time 37 | UpdatedAt time.Time 38 | } 39 | 40 | type Package struct { 41 | Id int64 `json:"id"` 42 | Name string `json:"name"` 43 | Email string `json:"email"` 44 | Tags string `json:"tags"` 45 | Blurb string `json:"blurb"` 46 | Description string `json:"description"` 47 | RepoUrl string `json:"repo_url" yaml:"repo_url"` 48 | Commit string `json:"commit"` 49 | Cmd string `json:"cmd"` 50 | Private bool `json:"private"` 51 | User User 52 | UserId int64 `json:"user_id"` 53 | TotalDownloads int64 `json:"total_downloads"` 54 | CreatedAt time.Time 55 | UpdatedAt time.Time 56 | } 57 | 58 | var CurrentUser = User{} 59 | var CurrentPackage = Package{} 60 | var EndPoint = "https://composehub.com" 61 | var Dev, Version string 62 | 63 | func init() { 64 | Version = "0.0.1" 65 | if os.Getenv("ENDPOINT") != "" { 66 | EndPoint = os.Getenv("ENDPOINT") 67 | } 68 | createConfigDir() 69 | CurrentUser = getCurrentUser() 70 | checkUpdateCheckFile() 71 | updateCheckFile() 72 | CurrentPackage = getCurrentPackage("") 73 | Dev = os.Getenv("DEV") 74 | devlog(CurrentUser) 75 | } 76 | 77 | func main() { 78 | app := cli.NewApp() 79 | app.Name = "ComposeHub" 80 | app.Usage = "Install and publish docker compose packages." 81 | app.Version = Version 82 | app.EnableBashCompletion = true 83 | app.Action = cli.ShowAppHelp 84 | app.Commands = []cli.Command{ 85 | { 86 | Name: "install", 87 | Aliases: []string{"i"}, 88 | Usage: "ch install ", 89 | Action: installAction, 90 | }, 91 | { 92 | Name: "search", 93 | Aliases: []string{"s"}, 94 | Usage: "ch search ", 95 | Action: search, 96 | }, 97 | { 98 | Name: "adduser", 99 | Aliases: []string{"a"}, 100 | Usage: "ch adduser", 101 | Action: adduserAction, 102 | }, 103 | { 104 | Name: "updateuser", 105 | Aliases: []string{"uu"}, 106 | Usage: "ch updateuser", 107 | Action: updateuserAction, 108 | }, 109 | { 110 | Name: "publish", 111 | Aliases: []string{"p"}, 112 | Usage: "ch publish", 113 | Action: publishAction, 114 | }, 115 | { 116 | Name: "init", 117 | Aliases: []string{"in"}, 118 | Usage: "ch init", 119 | Action: initAction, 120 | }, 121 | { 122 | Name: "configuser", 123 | Aliases: []string{"cu"}, 124 | Usage: "ch init", 125 | Action: updateuserAction, 126 | }, 127 | { 128 | Name: "resetpassword", 129 | Aliases: []string{"rp"}, 130 | Usage: "ch resetpassword", 131 | Action: resetpassordAction, 132 | }, 133 | { 134 | Name: "run", 135 | Aliases: []string{"r"}, 136 | Usage: "ch run ", 137 | Action: runAction, 138 | }, 139 | } 140 | app.Run(os.Args) 141 | } 142 | func runAction(c *cli.Context) { 143 | install(c, false) 144 | /*wg := new(sync.WaitGroup)*/ 145 | 146 | if CurrentPackage.Cmd != "" { 147 | /*out, err := sh.Command("cd", CurrentPackage.Name).Command("sh", "-c", CurrentPackage.Cmd, ".").SetStdin(os.Stdin).Output()*/ 148 | /*log.Println(err)*/ 149 | /*log.Println(out)*/ 150 | os.Chdir(CurrentPackage.Name) 151 | cmd := exec.Command("sh", "-c", CurrentPackage.Cmd, ".") 152 | cmd.Stdout = os.Stdout 153 | cmd.Stderr = os.Stderr 154 | cmd.Run() 155 | } else { 156 | println(CurrentPackage.Description) 157 | } 158 | } 159 | 160 | func search(c *cli.Context) { 161 | q := c.Args().First() 162 | println("Searching for", q+" on https://composehub.com...") /*"+EndPoint+"...")*/ 163 | if resp, err := http.Get(EndPoint + "/search/" + q + timestamp()); err != nil { 164 | println("Sorry, the query failed with the following message: ", err) 165 | return 166 | } else { 167 | defer resp.Body.Close() 168 | if body, err := ioutil.ReadAll(resp.Body); err != nil { 169 | println("Sorry, the query failed with the following message: ", err) 170 | println(string(body)) 171 | return 172 | } else { 173 | packages := []Package{} 174 | json.Unmarshal(body, &packages) 175 | for _, p := range packages { 176 | println(p.Name+":", p.Blurb, "(by "+p.User.Handle+")") 177 | 178 | } 179 | println("found", len(packages), "packages") 180 | } 181 | } 182 | } 183 | 184 | func initAction(c *cli.Context) { 185 | println("creating composehub.yml (done)") 186 | yml := `--- 187 | name: package-name 188 | blurb: 80 chars line blurb 189 | description: | 190 | longer description 191 | email: ` + CurrentUser.Email + ` 192 | repo_url: http://github.com/foo/bar 193 | tags: tag1,tag2 194 | private: false 195 | cmd: 196 | ` 197 | println("please edit composehub.yml and the run `ch publish`") 198 | err := ioutil.WriteFile("composehub.yml", []byte(yml), 0644) 199 | if err != nil { 200 | println(err) 201 | } 202 | } 203 | 204 | func installAction(c *cli.Context) { 205 | install(c, true) 206 | } 207 | 208 | func publishAction(c *cli.Context) { 209 | if CurrentUser.Email == "" { 210 | message := ` 211 | Please create a user account first, run 'ch adduser' 212 | If you already have an account, please run 'ch updateuser' 213 | ` 214 | println(message) 215 | return 216 | } 217 | p := getCurrentPackage("") 218 | u := EndPoint + "/publish/" + p.Name 219 | request := goreq().SetBasicAuth(CurrentUser.Email, CurrentUser.Password) 220 | request.Post(u). 221 | Send(p). 222 | End(func(resp gorequest.Response, body string, errs []error) { 223 | if resp.StatusCode == 200 { 224 | println("Package update successfully!\n") 225 | } else { 226 | println(body) 227 | 228 | } 229 | }) 230 | 231 | /*type Config struct {*/ 232 | /*Foo string*/ 233 | /*Bar []string*/ 234 | /*}*/ 235 | 236 | /*filename := os.Args[2]*/ 237 | /*var config Config*/ 238 | /*source, err := ioutil.ReadFile(filename)*/ 239 | /*if err != nil {*/ 240 | /*panic(err)*/ 241 | /*}*/ 242 | /*err = yaml.Unmarshal(source, &config)*/ 243 | /*if err != nil {*/ 244 | /*panic(err)*/ 245 | /*}*/ 246 | /*fmt.Printf("Value: %#v\n", config.Bar[0])*/ 247 | } 248 | 249 | func updateuserAction(c *cli.Context) { 250 | if handle, email, password, err := promptUserInfo(c, true); err != nil { 251 | return 252 | } else { 253 | user := User{Handle: handle, Password: password, Email: email} 254 | fmt.Println("user: ", user) 255 | e, p := email, password 256 | if CurrentUser.Email != "" { 257 | e, p = CurrentUser.Email, CurrentUser.Password 258 | } 259 | u := EndPoint + "/users/" + e 260 | 261 | request := goreq().SetBasicAuth(e, p) 262 | request.Put(u). 263 | Send(user). 264 | End(func(resp gorequest.Response, body string, errs []error) { 265 | fmt.Println(resp.Status) 266 | err = json.Unmarshal([]byte(body), &user) 267 | if err != nil { 268 | log.Fatalf("no config found", err) 269 | return 270 | } 271 | newPassword := CurrentUser.Password 272 | if password != "" { 273 | newPassword = password 274 | } 275 | CurrentUser = user 276 | createUserConfig(user.Handle, user.Email, newPassword) 277 | }) 278 | } 279 | } 280 | func adduserAction(c *cli.Context) { 281 | if handle, email, password, err := promptUserInfo(c, false); err != nil { 282 | return 283 | } else { 284 | 285 | if resp, err := http.PostForm(EndPoint+"/users", 286 | url.Values{"handle": {handle}, "email": {email}, "password": {password}}); err != nil { 287 | println("Sorry, the query failed with the following message: ", err) 288 | return 289 | } else { 290 | defer resp.Body.Close() 291 | if body, err := ioutil.ReadAll(resp.Body); err != nil { 292 | fmt.Println(err, string(body)) 293 | return 294 | } else { 295 | devlog(err, string(body)) 296 | createUserConfig(handle, email, password) 297 | CurrentUser = getCurrentUser() 298 | } 299 | } 300 | } 301 | } 302 | func resetpassordAction(c *cli.Context) { 303 | reader := bufio.NewReader(os.Stdin) 304 | fmt.Print("Enter email: ") 305 | email, _ := reader.ReadString('\n') 306 | email = strings.Replace(email, "\n", "", -1) 307 | 308 | u := EndPoint + "/users/" + email + "/reset-password" 309 | request := goreq() 310 | request.Post(u). 311 | End(func(resp gorequest.Response, body string, errs []error) { 312 | if resp.StatusCode == 200 { 313 | fmt.Print("Please check your email, copy the token and paste it here: ") 314 | token, _ := reader.ReadString('\n') 315 | token = strings.Replace(token, "\n", "", -1) 316 | fmt.Printf("Password: ") 317 | pass := gopass.GetPasswd() // Silent, for *'s use gopass.GetPasswdMasked() 318 | password := strings.Replace(string(pass), "\n", "", -1) 319 | 320 | resetPassword(email, token, password) 321 | } else { 322 | println(body) 323 | } 324 | }) 325 | } 326 | 327 | func resetPassword(email, token, password string) { 328 | devlog(email, token, password) 329 | u := EndPoint + "/users/" + email + "/reset-password/" + token 330 | request := goreq() 331 | request.Put(u). 332 | Send(User{Password: password}). 333 | End(func(resp gorequest.Response, body string, errs []error) { 334 | if resp.StatusCode == 200 { 335 | fmt.Print("Your password has been updated successfully!") 336 | user := User{} 337 | err := json.Unmarshal([]byte(body), &user) 338 | if err != nil { 339 | log.Fatalf("no config found", err) 340 | return 341 | } 342 | user.Password = password 343 | CurrentUser = user 344 | createUserConfig(user.Handle, user.Email, user.Password) 345 | } else { 346 | println(body) 347 | } 348 | }) 349 | } 350 | 351 | func promptUserInfo(c *cli.Context, update bool) (string, string, string, error) { 352 | reader := bufio.NewReader(os.Stdin) 353 | updateMsg := "" 354 | if update { 355 | updateMsg = "(leave blank if you don't want to update) " 356 | } 357 | handle := "" 358 | if CurrentUser.Handle != "" || !update { 359 | fmt.Print("Enter handle: " + updateMsg) 360 | handle, _ = reader.ReadString('\n') 361 | } 362 | fmt.Print("Enter email: " + updateMsg) 363 | email, _ := reader.ReadString('\n') 364 | if !update { 365 | fmt.Printf("Password: (leave blank to auto-generate one)") 366 | 367 | } else { 368 | fmt.Printf("Password: " + updateMsg) 369 | 370 | } 371 | pass := gopass.GetPasswd() // Silent, for *'s use gopass.GetPasswdMasked() 372 | // Do something with pass' 373 | handle = strings.Replace(handle, "\n", "", -1) 374 | password := strings.Replace(string(pass), "\n", "", -1) 375 | email = strings.Replace(email, "\n", "", -1) 376 | 377 | if !update && password == "" { 378 | uuid, err := newUUID() 379 | if err != nil { 380 | fmt.Printf("Sorry, something wrong happened\n", err) 381 | return handle, email, password, errors.New("") 382 | } else { 383 | password = uuid 384 | } 385 | } 386 | if !update && handle == "" { 387 | fmt.Printf("Invalid handle\n") 388 | return handle, email, password, errors.New("") 389 | } 390 | if !validateEmail(email) && !update && email != "" { 391 | fmt.Printf("invalid email\n") 392 | return handle, email, password, errors.New("") 393 | } 394 | return handle, email, password, nil 395 | 396 | } 397 | 398 | func createUserConfig(handle, email, password string) { 399 | if path, err := composeHubConfigPath(); err != nil { 400 | fmt.Println(err) 401 | return 402 | } else { 403 | 404 | config := `--- 405 | handle: ` + handle + ` 406 | email: ` + email + ` 407 | password: ` + password + ` 408 | ` 409 | if err := ioutil.WriteFile(path+"/config.yml", []byte(config), 0600); err != nil { 410 | if os.IsExist(err) { 411 | fmt.Println("Looks like " + path + "/config.yml already exists, please remove it or edit it manually.") 412 | } else { 413 | fmt.Println(err) 414 | } 415 | } 416 | } 417 | } 418 | 419 | func validateEmail(email string) bool { 420 | Re := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`) 421 | return Re.MatchString(email) 422 | } 423 | 424 | func newUUID() (string, error) { 425 | uuid := make([]byte, 16) 426 | n, err := io.ReadFull(rand.Reader, uuid) 427 | if n != len(uuid) || err != nil { 428 | return "", err 429 | } 430 | // variant bits; see section 4.1.1 431 | uuid[8] = uuid[8]&^0xc0 | 0x80 432 | // version 4 (pseudo-random); see section 4.1.3 433 | uuid[6] = uuid[6]&^0xf0 | 0x40 434 | return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil 435 | } 436 | 437 | func getCurrentUser() User { 438 | u := User{} 439 | if path, err := composeHubConfigPath(); err != nil { 440 | fmt.Println(err) 441 | return u 442 | } else { 443 | data, _ := ioutil.ReadFile(path + "/config.yml") 444 | err = yaml.Unmarshal(data, &u) 445 | if err != nil { 446 | log.Fatalf("no config found", err) 447 | } 448 | return u 449 | } 450 | } 451 | 452 | func getCurrentPackage(pkg string) Package { 453 | if pkg == "" { 454 | pkg = "composehub.yml" 455 | } 456 | p := Package{} 457 | data, _ := ioutil.ReadFile(pkg) 458 | err := yaml.Unmarshal(data, &p) 459 | if err != nil { 460 | log.Fatalf("error: %v", err) 461 | } 462 | devlog(p) 463 | return p 464 | } 465 | 466 | func devlog(v ...interface{}) { 467 | if Dev != "" { 468 | log.Println(v) 469 | } 470 | } 471 | 472 | func isEmpty(name string) (bool, error) { 473 | f, err := os.Open(name) 474 | if err != nil { 475 | return false, err 476 | } 477 | defer f.Close() 478 | 479 | _, err = f.Readdir(1) 480 | if err == io.EOF { 481 | return true, nil 482 | } 483 | return false, err // Either not empty or error, suits both cases 484 | } 485 | 486 | func composeHubConfigPath() (string, error) { 487 | if home, err := homedir.Dir(); err != nil { 488 | fmt.Println(err) 489 | return "", err 490 | } else { 491 | path := home + "/.composehub" 492 | return path, err 493 | } 494 | } 495 | 496 | func createConfigDir() { 497 | if path, err := composeHubConfigPath(); err != nil { 498 | fmt.Println(err) 499 | return 500 | } else { 501 | if _, err := os.Stat(path); err != nil { 502 | if os.IsNotExist(err) { 503 | if err := os.Mkdir(path, 0700); err != nil { 504 | fmt.Println(err) 505 | } 506 | // file does not exist 507 | } else { 508 | fmt.Println(err) 509 | // other error 510 | } 511 | } 512 | } 513 | } 514 | 515 | func updateCheckFile() { 516 | if path, err := composeHubConfigPath(); err != nil { 517 | fmt.Println(err) 518 | } else { 519 | err := ioutil.WriteFile(path+"/versioncheck", []byte(string(time.Now().Format(time.RFC3339))), 0644) 520 | if err != nil { 521 | println(err) 522 | } 523 | } 524 | } 525 | 526 | func checkUpdateCheckFile() { 527 | if path, err := composeHubConfigPath(); err != nil { 528 | fmt.Println(err) 529 | } else { 530 | data, _ := ioutil.ReadFile(path + "/versioncheck") 531 | t, err := time.Parse(time.RFC3339, string(data)) 532 | devlog("since:", time.Since(t), err) 533 | 534 | if time.Since(t).Hours() > float64(48) { 535 | /*if true {*/ 536 | checkForUpdate() 537 | } 538 | } 539 | } 540 | 541 | func checkForUpdate() { 542 | if resp, err := http.Get(EndPoint + "/checkupdate/" + Version + timestamp()); err != nil { 543 | println("Sorry, the query failed with the following message: ", err) 544 | return 545 | } else { 546 | defer resp.Body.Close() 547 | if body, err := ioutil.ReadAll(resp.Body); err != nil { 548 | println("Sorry, the query failed with the following message: ", err) 549 | println(string(body)) 550 | return 551 | } else { 552 | b := string(body) 553 | if b != "\"ok\"" { 554 | println("There's a new version " + b + " available") 555 | /*println("curl -L https://composehub.org/install/" + runtime.GOOS + "&GOARCH=" + runtime.GOARCH + " > /usr/local/bin/docker-compose")*/ 556 | println("curl -L https://composehub.com/install/" + runtime.GOOS) 557 | } 558 | } 559 | 560 | } 561 | } 562 | 563 | func install(c *cli.Context, showDescription bool) Package { 564 | p := Package{} 565 | q := c.Args().First() 566 | /*if res, err := isEmpty("."); !res || err != nil {*/ 567 | /*println("This dir is not empty. You can only install packages in empty directories.")*/ 568 | /*println("Try `mkdir " + q + " && cd " + q + "`")*/ 569 | /*return p*/ 570 | /*}*/ 571 | u := EndPoint + "/packages/" + q + timestamp() 572 | request := goreq().SetBasicAuth(CurrentUser.Email, CurrentUser.Password) 573 | request.Get(u). 574 | End(func(resp gorequest.Response, body string, errs []error) { 575 | if resp.StatusCode == 200 { 576 | if err := json.Unmarshal([]byte(body), &p); err != nil { 577 | log.Fatalf("no config found", err) 578 | return 579 | } else { 580 | CurrentPackage = p 581 | devlog(p) 582 | } 583 | /*println("Cloning repo...\n")*/ 584 | cmd := exec.Command("git", "clone", p.RepoUrl, q) 585 | cmd.Stdout = os.Stdout 586 | cmd.Stderr = os.Stderr 587 | cmd.Run() 588 | println("Package installed successfully in " + q + "!\n") 589 | if showDescription { 590 | println(p.Description) 591 | } else { 592 | /*cmd := exec.Command("cd", q)*/ 593 | /*cmd.Stdout = os.Stdout*/ 594 | /*cmd.Stderr = os.Stderr*/ 595 | /*cmd.Run()*/ 596 | 597 | } 598 | } else { 599 | println(body) 600 | } 601 | }) 602 | return p 603 | } 604 | 605 | func timestamp() string { 606 | return "?t=" + time.Now().UTC().Format("20060102150405") 607 | } 608 | 609 | func goreq() *gorequest.SuperAgent { 610 | request := gorequest.New() 611 | if os.Getenv("https_proxy") != "" { 612 | request.Proxy(os.Getenv("https_proxy")) 613 | } 614 | return request 615 | } 616 | 617 | /*mkdir my-gitlab*/ 618 | /*cd my-gitlab*/ 619 | /*ch search gitlab*/ 620 | /*ch install gitlab*/ 621 | /*docker-compose up*/ 622 | 623 | /*cd ..*/ 624 | /*mkdir my-wordpress*/ 625 | /*cd my-wordpress*/ 626 | /*ch search run wordpress*/ 627 | 628 | /*mkdir my-rails-app*/ 629 | /*cd my-rails-app*/ 630 | /*ch install rails*/ 631 | /*docker-compose run web bundle install*/ 632 | --------------------------------------------------------------------------------