gitlab.com/SkynetLabs/skyd@v1.6.9/cmd/skyc/skynetcmd.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "mime/multipart" 9 "os" 10 "path/filepath" 11 "strings" 12 "sync" 13 "text/tabwriter" 14 15 "github.com/spf13/cobra" 16 "github.com/vbauerster/mpb/v5" 17 "github.com/vbauerster/mpb/v5/decor" 18 "go.sia.tech/siad/crypto" 19 "go.sia.tech/siad/modules" 20 21 "gitlab.com/SkynetLabs/skyd/node/api" 22 "gitlab.com/SkynetLabs/skyd/siatest" 23 "gitlab.com/SkynetLabs/skyd/skymodules" 24 "gitlab.com/SkynetLabs/skyd/skymodules/renter" 25 ) 26 27 var ( 28 skynetCmd = &cobra.Command{ 29 Use: "skynet", 30 Short: "Perform actions related to Skynet", 31 Long: `Perform actions related to Skynet, a file sharing and data publication platform 32 on top of Sia.`, 33 Run: skynetcmd, 34 } 35 36 skynetBackupCmd = &cobra.Command{ 37 Use: "backup [skylink] [backup path]", 38 Short: "Backup a skyfile to a file on disk.", 39 Long: "Create a backup of a skyfile as a file on disk.", 40 Run: wrap(skynetbackupcmd), 41 } 42 43 skynetBlocklistCmd = &cobra.Command{ 44 Use: "blocklist", 45 Short: "Add, remove, or list skylinks from the blocklist.", 46 Long: "Add, remove, or list skylinks from the blocklist.", 47 Run: skynetblocklistgetcmd, 48 } 49 50 skynetBlocklistAddCmd = &cobra.Command{ 51 Use: "add [skylink] ...", 52 Short: "Add skylinks to the blocklist", 53 Long: "Add space separated skylinks to the blocklist.", 54 Run: skynetblocklistaddcmd, 55 } 56 57 skynetBlocklistRemoveCmd = &cobra.Command{ 58 Use: "remove [skylink] ...", 59 Short: "Remove skylinks from the blocklist", 60 Long: "Remove space separated skylinks from the blocklist.", 61 Run: skynetblocklistremovecmd, 62 } 63 64 skynetConvertCmd = &cobra.Command{ 65 Use: "convert [source siaPath] [destination siaPath]", 66 Short: "Convert a siafile to a skyfile with a skylink.", 67 Long: `Convert a siafile to a skyfile and then generate its skylink. A new skylink 68 will be created in the user's skyfile directory. The skyfile and the original 69 siafile are both necessary to pin the file and keep the skylink active. The 70 skyfile will consume an additional 40 MiB of storage.`, 71 Run: wrap(skynetconvertcmd), 72 } 73 74 skynetDownloadCmd = &cobra.Command{ 75 Use: "download [skylink] [destination]", 76 Short: "Download a skylink from skynet.", 77 Long: `Download a file from skynet using a skylink. The download may fail unless this 78 node is configured as a skynet portal. Use the --portal flag to fetch a skylink 79 file from a chosen skynet portal.`, 80 Run: skynetdownloadcmd, 81 } 82 83 skynetIsBlockedCmd = &cobra.Command{ 84 Use: "isblocked [skylink] ...", 85 Short: "Checks if a skylink is on the blocklist.", 86 Long: `Checks if a skylink, or a list of space separated skylinks, is on the blocklist 87 since the list returned from 'skyc skynet blocklist' is a list of hashes of the skylinks' 88 merkleroots so they cannot be visually verified.`, 89 Run: skynetisblockedcmd, 90 } 91 92 skynetLsCmd = &cobra.Command{ 93 Use: "ls", 94 Short: "List all skyfiles that the user has pinned.", 95 Long: `List all skyfiles that the user has pinned along with the corresponding 96 skylinks. By default, only files in var/skynet/ will be displayed. The --root 97 flag can be used to view skyfiles pinned in other folders.`, 98 Run: skynetlscmd, 99 } 100 101 skynetPinCmd = &cobra.Command{ 102 Use: "pin [skylink] [destination siapath]", 103 Short: "Pin a skylink from skynet by re-uploading it yourself.", 104 Long: `Pin the file associated with this skylink by re-uploading an exact copy. This 105 ensures that the file will still be available on skynet as long as you continue 106 maintaining the file in your renter.`, 107 Run: wrap(skynetpincmd), 108 } 109 110 skynetPortalsCmd = &cobra.Command{ 111 Use: "portals", 112 Short: "Add, remove, or list registered Skynet portals.", 113 Long: "Add, remove, or list registered Skynet portals.", 114 Run: wrap(skynetportalsgetcmd), 115 } 116 117 skynetPortalsAddCmd = &cobra.Command{ 118 Use: "add [url]", 119 Short: "Add a Skynet portal as public or private to the persisted portals list.", 120 Long: `Add a Skynet portal as public or private. Specify the url of the Skynet portal followed 121 by --public if you want it to be publicly available.`, 122 Run: wrap(skynetportalsaddcmd), 123 } 124 125 skynetPortalsRemoveCmd = &cobra.Command{ 126 Use: "remove [url]", 127 Short: "Remove a Skynet portal from the persisted portals list.", 128 Long: "Remove a Skynet portal from the persisted portals list.", 129 Run: wrap(skynetportalsremovecmd), 130 } 131 132 skynetRestoreCmd = &cobra.Command{ 133 Use: "restore [backup source]", 134 Short: "Restore a skyfile from a backup file.", 135 Long: "Restore a skyfile from a backup file.", 136 Run: wrap(skynetrestorecmd), 137 } 138 139 skynetSkylinkCmd = &cobra.Command{ 140 Use: "skylink", 141 Short: "Perform various util functions for a skylink.", 142 Long: `Perform various util functions for a skylink like check the layout 143 metadata, or recomputing.`, 144 Run: skynetskylinkcmd, 145 } 146 147 skynetSkylinkCompareCmd = &cobra.Command{ 148 Use: "compare [skylink] [metadata filename]", 149 Short: "Compare a skylink to a regenerated skylink", 150 Long: `This command regenerates a skylink by doing the following: 151 First, it reads some provided metadata from the provided filename. 152 Second, it downloads the skylink and records the metadata, layout, and filedata. 153 Third, it compares the downloaded metadata to the metadata read from disk. 154 Fourth, it computesthe base sector and then the skylink from the downloaded information. 155 Lastly, it compares the generated skylink to the skylink that was passed in.`, 156 Run: wrap(skynetskylinkcomparecmd), 157 } 158 159 skynetSkylinkHealthCmd = &cobra.Command{ 160 Use: "health [skylink]", 161 Short: "Print the health of a skylink", 162 Long: "Print the health of a skylink", 163 Run: wrap(skynetskylinkhealthcmd), 164 } 165 166 skynetSkylinkLayoutCmd = &cobra.Command{ 167 Use: "layout [skylink]", 168 Short: "Print the layout associated with a skylink", 169 Long: "Print the layout associated with a skylink", 170 Run: wrap(skynetskylinklayoutcmd), 171 } 172 173 skynetSkylinkMetadataCmd = &cobra.Command{ 174 Use: "metadata [skylink]", 175 Short: "Print the metadata associated with a skylink", 176 Long: "Print the metadata associated with a skylink", 177 Run: wrap(skynetskylinkmetadatacmd), 178 } 179 180 skynetUnpinCmd = &cobra.Command{ 181 Use: "unpin [skylink]", 182 Short: "Unpin pinned skyfiles by skylink.", 183 Long: `Unpin one or more pinned skyfiles by skylink. The files and 184 directories will continue to be available on Skynet if other nodes have pinned 185 them. 186 187 NOTE: To use the prior functionality of unpinning by SiaPath, use the 'skyc 188 renter delete' command and set the --root flag.`, 189 Run: skynetunpincmd, 190 } 191 192 skynetUploadCmd = &cobra.Command{ 193 Use: "upload [source path] [destination siapath]", 194 Short: "Upload a file or a directory to Skynet.", 195 Long: `Upload a file or a directory to Skynet. A skylink will be 196 produced which can be shared and used to retrieve the file. If the given path is 197 a directory it will be uploaded as a single skylink unless the --separately flag 198 is passed, in which case all files under that directory will be uploaded 199 individually and an individual skylink will be produced for each. All files that 200 get uploaded will be pinned to this Sia node, meaning that this node will pay 201 for storage and repairs until the files are manually deleted. Use the --dry-run 202 flag to fetch the skylink without actually uploading the file.`, 203 Run: skynetuploadcmd, 204 } 205 ) 206 207 // skynetcmd displays the usage info for the command. 208 // 209 // TODO: Could put some stats or summaries or something here. 210 func skynetcmd(cmd *cobra.Command, _ []string) { 211 _ = cmd.UsageFunc()(cmd) 212 os.Exit(exitCodeUsage) 213 } 214 215 // skynetbackupcmd will backup a skyfile by writing it to a backup writer. 216 func skynetbackupcmd(skylinkStr, backupPath string) { 217 // Create backup file 218 f, err := os.Create(backupPath) 219 if err != nil { 220 die("Unable to create backup file:", err) 221 } 222 defer func() { 223 if err := f.Close(); err != nil { 224 die("Unable to close backup file:", err) 225 } 226 }() 227 228 // Create backup 229 err = httpClient.SkynetSkylinkBackup(skylinkStr, f) 230 if err != nil { 231 die("Unable to create backup:", err) 232 } 233 fmt.Println("Backup successfully created at ", backupPath) 234 } 235 236 // skynetblocklistaddcmd adds skylinks to the blocklist 237 func skynetblocklistaddcmd(cmd *cobra.Command, args []string) { 238 skynetBlocklistUpdate(args, nil) 239 } 240 241 // skynetblocklistremovecmd removes skylinks from the blocklist 242 func skynetblocklistremovecmd(cmd *cobra.Command, args []string) { 243 skynetBlocklistUpdate(nil, args) 244 } 245 246 // skynetBlocklistUpdate adds/removes trimmed skylinks to the blocklist 247 func skynetBlocklistUpdate(additions, removals []string) { 248 additions = sanitizeSkylinks(additions) 249 removals = sanitizeSkylinks(removals) 250 251 res, err := httpClient.SkynetBlocklistHashPost(additions, removals, skynetBlocklistHash) 252 if err != nil { 253 die("Unable to update skynet blocklist:", err) 254 } 255 256 if len(res.Invalids) > 0 { 257 fmt.Printf("Skynet Blocklist partially updated, invalid inputs: %+v\n", res.Invalids) 258 return 259 } 260 fmt.Println("Skynet Blocklist updated") 261 } 262 263 // skynetblocklistgetcmd will return the list of hashed merkleroots that are blocked 264 // from Skynet. 265 func skynetblocklistgetcmd(_ *cobra.Command, _ []string) { 266 response, err := httpClient.SkynetBlocklistGet() 267 if err != nil { 268 die("Unable to get skynet blocklist:", err) 269 } 270 271 fmt.Printf("Listing %d blocked skylink(s) merkleroots:\n", len(response.Blocklist)) 272 for _, hash := range response.Blocklist { 273 fmt.Printf("\t%s\n", hash) 274 } 275 } 276 277 // skynetconvertcmd will convert an existing siafile to a skyfile and skylink on 278 // the Sia network. 279 func skynetconvertcmd(sourceSiaPathStr, destSiaPathStr string) { 280 // Create the siapaths. 281 sourceSiaPath, err := skymodules.NewSiaPath(sourceSiaPathStr) 282 if err != nil { 283 die("Could not parse source siapath:", err) 284 } 285 destSiaPath, err := skymodules.NewSiaPath(destSiaPathStr) 286 if err != nil { 287 die("Could not parse destination siapath:", err) 288 } 289 290 // Perform the conversion and print the result. 291 sup := skymodules.SkyfileUploadParameters{ 292 SiaPath: destSiaPath, 293 } 294 sup = parseAndAddSkykey(sup) 295 sshp, err := httpClient.SkynetConvertSiafileToSkyfilePost(sup, sourceSiaPath) 296 if err != nil { 297 die("could not convert siafile to skyfile:", err) 298 } 299 skylink := sshp.Skylink 300 301 // Calculate the siapath that was used for the upload. 302 var skypath skymodules.SiaPath 303 if skynetUploadRoot { 304 skypath = destSiaPath 305 } else { 306 skypath, err = skymodules.SkynetFolder.Join(destSiaPath.String()) 307 if err != nil { 308 die("could not fetch skypath:", err) 309 } 310 } 311 fmt.Printf("Skyfile uploaded successfully to %v\nSkylink: sia://%v\n", skypath, skylink) 312 } 313 314 // skynetdownloadcmd will perform the download of a skylink. 315 func skynetdownloadcmd(cmd *cobra.Command, args []string) { 316 if len(args) != 2 { 317 _ = cmd.UsageFunc()(cmd) 318 os.Exit(exitCodeUsage) 319 } 320 321 // Open the file. 322 skylink := args[0] 323 skylink = strings.TrimPrefix(skylink, "sia://") 324 filename := args[1] 325 file, err := os.Create(filename) 326 if err != nil { 327 die("Unable to create destination file:", err) 328 } 329 defer func() { 330 if err := file.Close(); err != nil { 331 die(err) 332 } 333 }() 334 335 // Try to perform a download using the client package. 336 reader, err := httpClient.SkynetSkylinkReaderGet(skylink) 337 if err != nil { 338 die("Unable to fetch skylink:", err) 339 } 340 defer func() { 341 err = reader.Close() 342 if err != nil { 343 die("unable to close reader:", err) 344 } 345 }() 346 347 _, err = io.Copy(file, reader) 348 if err != nil { 349 die("Unable to write full data:", err) 350 } 351 } 352 353 // skynetisblockedcmd will check if a skylink, or list of skylinks, is on the 354 // blocklist. 355 func skynetisblockedcmd(_ *cobra.Command, skylinkStrs []string) { 356 // Get the blocklist 357 var err error 358 response, err := httpClient.SkynetBlocklistGet() 359 if err != nil { 360 die("Unable to get skynet blocklist:", err) 361 } 362 363 // Parse the slice response into a map 364 blocklistMap := make(map[crypto.Hash]struct{}) 365 for _, hash := range response.Blocklist { 366 blocklistMap[hash] = struct{}{} 367 } 368 369 // Check the skylinks 370 // 371 // NOTE: errors are printed and won't cause the function to exit. 372 for _, skylinkStr := range skylinkStrs { 373 // Load the string 374 var skylink skymodules.Skylink 375 err := skylink.LoadString(skylinkStr) 376 if err != nil { 377 fmt.Printf("Skylink %v \tis an invalid skylink: %v\n", skylinkStr, err) 378 continue 379 } 380 // Generate the hash of the merkleroot and check the blocklist 381 hash := crypto.HashObject(skylink.MerkleRoot()) 382 _, blocked := blocklistMap[hash] 383 if blocked { 384 fmt.Printf("Skylink %v \tis on the blocklist\n", skylinkStr) 385 } 386 } 387 } 388 389 // skynetlscmd is the handler for the command `skyc skynet ls`. Works very 390 // similar to 'skyc renter ls' but defaults to the SkynetFolder and only 391 // displays files that are pinning skylinks. 392 func skynetlscmd(cmd *cobra.Command, args []string) { 393 // Parse the SiaPath 394 sp, err := parseLSArgs(args) 395 if err != nil { 396 fmt.Fprintln(os.Stderr, err) 397 _ = cmd.UsageFunc()(cmd) 398 os.Exit(exitCodeUsage) 399 } 400 401 // Check whether the command is based in root or based in the skynet folder. 402 if !skynetLsRoot { 403 if sp.IsRoot() { 404 sp = skymodules.SkynetFolder 405 } else { 406 sp, err = skymodules.SkynetFolder.Join(sp.String()) 407 if err != nil { 408 die("could not build siapath:", err) 409 } 410 } 411 } 412 413 // Check if the command is hitting a single file. 414 if !sp.IsRoot() { 415 tryDir, err := printSingleFile(sp, true, true) 416 if err != nil { 417 die(err) 418 } 419 if !tryDir { 420 return 421 } 422 } 423 424 // Get the full set of files and directories. They will be sorted by siapath 425 // 426 // NOTE: we always pass in true for root as this is referring to the client 427 // method needed to query the directories, not whether or not the siapath is 428 // relative to root or the skynet folder. 429 // 430 // NOTE: We want to get the directories recursively if we are either checking 431 // from root or the user wants recursive. 432 getRecursive := skynetLsRecursive || skynetLsRoot 433 dirs := getDirSorted(sp, true, getRecursive, verbose) 434 435 // Determine the total number of Skyfiles and Skynet Directories. A Skynet 436 // directory is a directory that is either in the Skynet Folder or it contains 437 // at least one skyfile. 438 var numFilesDirs uint64 439 root := dirs[0] // Root directory we are querying. 440 441 // Grab the starting value for the number of Skyfiles and Skynet Directories. 442 if skynetLsRecursive { 443 numFilesDirs = root.dir.AggregateSkynetFiles + root.dir.AggregateNumSubDirs 444 } else { 445 numFilesDirs = root.dir.SkynetFiles + root.dir.NumSubDirs 446 } 447 448 // If we are referencing the root of the file system then we need to check all 449 // the directories we queried and see which ones need to be omitted. 450 if skynetLsRoot { 451 // Figure out how many directories don't contain skyfiles 452 var nonSkynetDir uint64 453 for _, dir := range dirs { 454 if !skymodules.IsSkynetDir(dir.dir.SiaPath) && dir.dir.SkynetFiles == 0 { 455 nonSkynetDir++ 456 } 457 } 458 459 // Subtract the number of non skynet directories from the total. 460 numFilesDirs -= nonSkynetDir 461 } 462 463 // Print totals. 464 totalStoredStr := modules.FilesizeUnits(root.dir.AggregateSkynetSize) 465 fmt.Printf("\nListing %v files/dirs:\t%9s\n\n", numFilesDirs, totalStoredStr) 466 467 // Print Dirs 468 err = printSkynetDirs(dirs, skynetLsRecursive) 469 if err != nil { 470 die(err) 471 } 472 } 473 474 // skynetPin will pin the Skyfile associated with the provided Skylink at the 475 // provided SiaPath 476 func skynetPin(skylink string, siaPath skymodules.SiaPath) (string, error) { 477 spp := skymodules.SkyfilePinParameters{ 478 SiaPath: siaPath, 479 Root: skynetUploadRoot, 480 } 481 fmt.Println("Pinning Skyfile ...") 482 return skylink, httpClient.SkynetSkylinkPinPost(skylink, spp) 483 } 484 485 // skynetpincmd will pin the file from this skylink. 486 func skynetpincmd(sourceSkylink, destSiaPath string) { 487 skylink := strings.TrimPrefix(sourceSkylink, "sia://") 488 // Create the siapath. 489 siaPath, err := skymodules.NewSiaPath(destSiaPath) 490 if err != nil { 491 die("Could not parse destination siapath:", err) 492 } 493 494 // Pin the Skyfile 495 skylink, err = skynetPin(skylink, siaPath) 496 if err != nil { 497 die("Unable to Pin Skyfile:", err) 498 } 499 fmt.Printf("Skyfile pinned successfully\nSkylink: sia://%v\n", skylink) 500 } 501 502 // skynetrestorecmd will restore a skyfile from a backup writer. 503 func skynetrestorecmd(backupPath string) { 504 // Open the backup file 505 f, err := os.Open(backupPath) 506 if err != nil { 507 die("Unable to open backup file:", err) 508 } 509 defer func() { 510 // Attempt to close the file, API call appears to close file so ignore the 511 // error to avoid getting an error for closing a closed file. 512 _ = f.Close() 513 }() 514 515 // Create backup 516 skylink, err := httpClient.SkynetSkylinkRestorePost(f) 517 if err != nil { 518 die("Unable to restore skyfile:", err) 519 } 520 fmt.Println("Restore successful! Skylink: ", skylink) 521 } 522 523 // skynetskylinkcmd displays the usage info for the command. 524 func skynetskylinkcmd(cmd *cobra.Command, args []string) { 525 _ = cmd.UsageFunc()(cmd) 526 os.Exit(exitCodeUsage) 527 } 528 529 // skynetskylinkcomparecmd compares a provided skylink to with a re-generated 530 // skylink based on metadata provided in a metadata.json file and downloading 531 // the file data and the layout from the skylink. 532 func skynetskylinkcomparecmd(expectedSkylink string, filename string) { 533 // Read Metadata file and trim a potential newline. 534 skyfileMetadataFromFile := fileData(filename) 535 skyfileMetadataFromFile = bytes.TrimSuffix(skyfileMetadataFromFile, []byte{'\n'}) 536 537 // Download the skyfile 538 skyfileDownloadedData, layoutFromHeader, skyfileMetadataFromHeader, err := smallSkyfileDownload(expectedSkylink) 539 if err != nil { 540 die(err) 541 } 542 543 // Check if the metadata download is the same as the metadata loaded from disk 544 if !bytes.Equal(skyfileMetadataFromFile, skyfileMetadataFromHeader) { 545 var sb strings.Builder 546 sb.WriteString(fmt.Sprintf("Metadata read from file %d\n", len(skyfileMetadataFromFile))) 547 sb.WriteString(string(skyfileMetadataFromFile)) 548 sb.WriteString(fmt.Sprintf("Metadata read from header %d\n", len(skyfileMetadataFromHeader))) 549 sb.WriteString(string(skyfileMetadataFromHeader)) 550 die(sb.String(), "Metadatas not equal") 551 } 552 fmt.Println("Metadatas Equal") 553 554 // build base sector 555 baseSector, fetchSize, _ := skymodules.BuildBaseSector(layoutFromHeader.Encode(), nil, skyfileMetadataFromFile, skyfileDownloadedData) 556 baseSectorRoot := crypto.MerkleRoot(baseSector) 557 skylink, err := skymodules.NewSkylinkV1(baseSectorRoot, 0, fetchSize) 558 if err != nil { 559 die(err) 560 } 561 562 if skylink.String() != expectedSkylink { 563 comp := fmt.Sprintf("Expected %s\nGenerated %s\n", expectedSkylink, skylink.String()) 564 die(comp, "Generated Skylink not Equal to Expected") 565 } 566 fmt.Println("Generated Skylink as Expected!") 567 } 568 569 // skynetskylinkhealthcmd prints the health of the skylink. 570 func skynetskylinkhealthcmd(skylinkStr string) { 571 // Load the Skylink 572 var skylink skymodules.Skylink 573 err := skylink.LoadString(skylinkStr) 574 if err != nil { 575 die(err) 576 } 577 578 // Get the health 579 health, err := httpClient.SkylinkHealthGET(skylink) 580 if err != nil { 581 die(err) 582 } 583 // Print the layout 584 healthStr, err := siatest.PrintJSONProd(health) 585 if err != nil { 586 die(err) 587 } 588 589 fmt.Println("Skylink Health:") 590 fmt.Println(healthStr) 591 } 592 593 // skynetskylinklayoutcmd prints the SkyfileLayout of the skylink. 594 func skynetskylinklayoutcmd(skylink string) { 595 // Download the layout 596 _, sl, _, err := smallSkyfileDownload(skylink) 597 if err != nil { 598 die(err) 599 } 600 // Print the layout 601 str, err := siatest.PrintJSONProd(sl) 602 if err != nil { 603 die(err) 604 } 605 fmt.Println("Skyfile Layout:") 606 fmt.Println(str) 607 } 608 609 // skynetskylinkmetadatacmd downloads and prints the SkyfileMetadata for a 610 // skylink. 611 func skynetskylinkmetadatacmd(skylink string) { 612 // Download the metadata 613 _, _, sm, err := smallSkyfileDownload(skylink) 614 if err != nil { 615 die(err) 616 } 617 // Print the metadata 618 fmt.Println("Skyfile Metadata:") 619 fmt.Println(string(sm)) 620 } 621 622 // skynetunpincmd will unpin and delete either a single or multiple skylinks 623 // from the renter. 624 func skynetunpincmd(cmd *cobra.Command, skylinks []string) { 625 if len(skylinks) == 0 { 626 _ = cmd.UsageFunc()(cmd) 627 os.Exit(exitCodeUsage) 628 } 629 630 for _, skylink := range skylinks { 631 // Unpin skylink 632 err := httpClient.SkynetSkylinkUnpinPost(skylink) 633 if err != nil { 634 fmt.Printf("Unable to unpin skylink %v: %v\n", skylink, err) 635 } 636 } 637 } 638 639 // skynetuploadcmd will upload a file or directory to Skynet. If --dry-run is 640 // passed, it will fetch the skylinks without uploading. 641 func skynetuploadcmd(_ *cobra.Command, args []string) { 642 if len(args) == 1 { 643 skynetuploadpipecmd(args[0]) 644 return 645 } 646 if len(args) != 2 { 647 die("wrong number of arguments") 648 } 649 sourcePath, destSiaPath := args[0], args[1] 650 fi, err := os.Stat(sourcePath) 651 if err != nil { 652 die("Unable to fetch source fileinfo:", err) 653 } 654 655 // create a new progress bar set: 656 pbs := mpb.New(mpb.WithWidth(40)) 657 658 if !fi.IsDir() { 659 skynetUploadFile(sourcePath, sourcePath, destSiaPath, pbs) 660 if skynetUploadDryRun { 661 fmt.Print("[dry run] ") 662 } 663 pbs.Wait() 664 fmt.Printf("Successfully uploaded skyfile!\n") 665 return 666 } 667 668 if skynetUploadSeparately { 669 skynetUploadFilesSeparately(sourcePath, destSiaPath, pbs) 670 return 671 } 672 skynetUploadDirectory(sourcePath, destSiaPath) 673 } 674 675 // skynetuploadpipecmd will upload a file or directory to Skynet. If --dry-run is 676 // passed, it will fetch the skylinks without uploading. 677 func skynetuploadpipecmd(destSiaPath string) { 678 fi, err := os.Stdin.Stat() 679 if err != nil { 680 die(err) 681 } 682 if fi.Mode()&os.ModeNamedPipe == 0 { 683 die("Command is meant to be used with either a pipe or src file") 684 } 685 // Create the siapath. 686 siaPath, err := skymodules.NewSiaPath(destSiaPath) 687 if err != nil { 688 die("Could not parse destination siapath:", err) 689 } 690 filename := siaPath.Name() 691 692 // create a new progress bar set: 693 pbs := mpb.New(mpb.WithWidth(40)) 694 // Create the single bar. 695 bar := pbs.AddSpinner( 696 -1, // size is unknown 697 mpb.SpinnerOnLeft, 698 mpb.SpinnerStyle([]string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}), 699 mpb.BarFillerClearOnComplete(), 700 mpb.PrependDecorators( 701 decor.AverageSpeed(decor.UnitKiB, "% .1f", decor.WC{W: 4}), 702 decor.Counters(decor.UnitKiB, " - %.1f / %.1f", decor.WC{W: 4}), 703 ), 704 ) 705 // Create the proxy reader from stdin. 706 r := bar.ProxyReader(os.Stdin) 707 // Set a spinner to start after the upload is finished 708 pSpinner := newProgressSpinner(pbs, bar, filename) 709 // Perform the upload 710 skylink := skynetUploadFileFromReader(r, filename, siaPath, skymodules.DefaultFilePerm) 711 // Replace the spinner with the skylink and stop it 712 newProgressSkylink(pbs, pSpinner, filename, skylink) 713 return 714 } 715 716 // skynetportalsgetcmd displays the list of persisted Skynet portals 717 func skynetportalsgetcmd() { 718 portals, err := httpClient.SkynetPortalsGet() 719 if err != nil { 720 die("Could not get portal list:", err) 721 } 722 723 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 724 725 fmt.Fprintf(w, "Address\tPublic\n") 726 fmt.Fprintf(w, "-------\t------\n") 727 728 for _, portal := range portals.Portals { 729 fmt.Fprintf(w, "%s\t%t\n", portal.Address, portal.Public) 730 } 731 732 if err = w.Flush(); err != nil { 733 die(err) 734 } 735 } 736 737 // skynetportalsaddcmd adds a Skynet portal as either public or private 738 func skynetportalsaddcmd(portalURL string) { 739 addition := skymodules.SkynetPortal{ 740 Address: modules.NetAddress(portalURL), 741 Public: skynetPortalPublic, 742 } 743 744 err := httpClient.SkynetPortalsPost([]skymodules.SkynetPortal{addition}, nil) 745 if err != nil { 746 die("Could not add portal:", err) 747 } 748 } 749 750 // skynetportalsremovecmd removes a Skynet portal 751 func skynetportalsremovecmd(portalUrl string) { 752 removal := modules.NetAddress(portalUrl) 753 754 err := httpClient.SkynetPortalsPost(nil, []modules.NetAddress{removal}) 755 if err != nil { 756 die("Could not remove portal:", err) 757 } 758 } 759 760 // skynetUploadFile uploads a file to Skynet 761 func skynetUploadFile(basePath, sourcePath string, destSiaPath string, pbs *mpb.Progress) { 762 // Create the siapath. 763 siaPath, err := skymodules.NewSiaPath(destSiaPath) 764 if err != nil { 765 die("Could not parse destination siapath:", err) 766 } 767 filename := filepath.Base(sourcePath) 768 769 // Open the source. 770 file, err := os.Open(sourcePath) 771 if err != nil { 772 die("Unable to open source path:", err) 773 } 774 defer func() { _ = file.Close() }() 775 776 fi, err := file.Stat() 777 if err != nil { 778 die("Unable to fetch source fileinfo:", err) 779 } 780 781 if skynetUploadSilent { 782 // Silently upload the file and print a simple source -> skylink 783 // matching after it's done. 784 skylink := skynetUploadFileFromReader(file, filename, siaPath, fi.Mode()) 785 fmt.Printf("%s -> %s\n", sourcePath, skylink) 786 return 787 } 788 789 // Display progress bars while uploading and processing the file. 790 var relPath string 791 if sourcePath == basePath { 792 // when uploading a single file we only display the filename 793 relPath = filename 794 } else { 795 // when uploading multiple files we strip the common basePath 796 relPath, err = filepath.Rel(basePath, sourcePath) 797 if err != nil { 798 die("Could not get relative path:", err) 799 } 800 } 801 // Wrap the file reader in a progress bar reader 802 pUpload, rc := newProgressReader(pbs, fi.Size(), relPath, file) 803 // Set a spinner to start after the upload is finished 804 pSpinner := newProgressSpinner(pbs, pUpload, relPath) 805 // Perform the upload 806 skylink := skynetUploadFileFromReader(rc, filename, siaPath, fi.Mode()) 807 // Replace the spinner with the skylink and stop it 808 newProgressSkylink(pbs, pSpinner, relPath, skylink) 809 return 810 } 811 812 // skynetUploadFilesSeparately uploads a number of files to Skynet, printing out 813 // separate skylink for each 814 func skynetUploadFilesSeparately(sourcePath, destSiaPath string, pbs *mpb.Progress) { 815 // Walk the target directory and collect all files that are going to be 816 // uploaded. 817 filesToUpload := make([]string, 0) 818 err := filepath.Walk(sourcePath, func(path string, info os.FileInfo, err error) error { 819 if err != nil { 820 fmt.Println("Warning: skipping file:", err) 821 return nil 822 } 823 if !info.IsDir() { 824 filesToUpload = append(filesToUpload, path) 825 } 826 return nil 827 }) 828 if err != nil { 829 die(err) 830 } 831 832 // Confirm with the user that they want to upload all of them. 833 if skynetUploadDryRun { 834 fmt.Print("[dry run] ") 835 } 836 ok := askForConfirmation(fmt.Sprintf("Are you sure that you want to upload %d files to Skynet?", len(filesToUpload))) 837 if !ok { 838 os.Exit(0) 839 } 840 841 // Start the workers. 842 filesChan := make(chan string) 843 var wg sync.WaitGroup 844 for i := 0; i < SimultaneousSkynetUploads; i++ { 845 wg.Add(1) 846 go func() { 847 defer wg.Done() 848 for filename := range filesChan { 849 // get only the filename and path, relative to the original destSiaPath 850 // in order to figure out where to put the file 851 newDestSiaPath := filepath.Join(destSiaPath, strings.TrimPrefix(filename, sourcePath)) 852 skynetUploadFile(sourcePath, filename, newDestSiaPath, pbs) 853 } 854 }() 855 } 856 // Send all files for upload. 857 for _, path := range filesToUpload { 858 filesChan <- path 859 } 860 // Signal the workers that there is no more work. 861 close(filesChan) 862 wg.Wait() 863 pbs.Wait() 864 if skynetUploadDryRun { 865 fmt.Print("[dry run] ") 866 } 867 fmt.Printf("Successfully uploaded %d skyfiles!\n", len(filesToUpload)) 868 } 869 870 // skynetUploadDirectory uploads a directory as a single skyfile 871 func skynetUploadDirectory(sourcePath, destSiaPath string) { 872 skyfilePath, err := skymodules.NewSiaPath(destSiaPath) 873 if err != nil { 874 die(fmt.Sprintf("Failed to create siapath %s\n", destSiaPath), err) 875 } 876 if skynetUploadDisableDefaultPath && skynetUploadDefaultPath != "" { 877 die("Illegal combination of parameters: --defaultpath and --disabledefaultpath are mutually exclusive.") 878 } 879 if skynetUploadTryFiles != "" && (skynetUploadDisableDefaultPath || skynetUploadDefaultPath != "") { 880 die("Illegal combination of parameters: --tryfiles is not compatible with --defaultpath and --disabledefaultpath.") 881 } 882 tryfiles := strings.Split(skynetUploadTryFiles, ",") 883 errPages, err := api.UnmarshalErrorPages(skynetUploadErrorPages) 884 if err != nil { 885 die(err) 886 } 887 pr, pw := io.Pipe() 888 defer pr.Close() 889 writer := multipart.NewWriter(pw) 890 go func() { 891 defer pw.Close() 892 // Walk the target directory and collect all files that are going to be 893 // uploaded. 894 var offset uint64 895 err = filepath.Walk(sourcePath, func(path string, info os.FileInfo, err error) error { 896 if err != nil { 897 die(fmt.Sprintf("Failed to read file %s.\n", path), err) 898 } 899 if info.IsDir() { 900 return nil 901 } 902 data, err := ioutil.ReadFile(path) 903 if err != nil { 904 die(fmt.Sprintf("Failed to read file %s.\n", path), err) 905 } 906 _, err = skymodules.AddMultipartFile(writer, data, "files[]", info.Name(), skymodules.DefaultFilePerm, &offset) 907 if err != nil { 908 die(fmt.Sprintf("Failed to add file %s to multipart upload.\n", path), err) 909 } 910 return nil 911 }) 912 if err != nil { 913 die(err) 914 } 915 if err = writer.Close(); err != nil { 916 die(err) 917 } 918 }() 919 920 sup := skymodules.SkyfileMultipartUploadParameters{ 921 SiaPath: skyfilePath, 922 Force: false, 923 Root: false, 924 BaseChunkRedundancy: renter.SkyfileDefaultBaseChunkRedundancy, 925 Reader: pr, 926 Filename: skyfilePath.Name(), 927 DefaultPath: skynetUploadDefaultPath, 928 DisableDefaultPath: skynetUploadDisableDefaultPath, 929 TryFiles: tryfiles, 930 ErrorPages: errPages, 931 ContentType: writer.FormDataContentType(), 932 } 933 skylink, _, err := httpClient.SkynetSkyfileMultiPartPost(sup) 934 if err != nil { 935 die("Failed to upload directory.", err) 936 } 937 fmt.Println("Successfully uploaded directory:", skylink) 938 } 939 940 // skynetUploadFileFromReader is a helper method that uploads a file to Skynet 941 func skynetUploadFileFromReader(source io.Reader, filename string, siaPath skymodules.SiaPath, mode os.FileMode) (skylink string) { 942 // Upload the file and return a skylink 943 sup := skymodules.SkyfileUploadParameters{ 944 SiaPath: siaPath, 945 Root: skynetUploadRoot, 946 947 Filename: filename, 948 Mode: mode, 949 950 DryRun: skynetUploadDryRun, 951 Reader: source, 952 } 953 sup = parseAndAddSkykey(sup) 954 skylink, _, err := httpClient.SkynetSkyfilePost(sup) 955 if err != nil { 956 die("could not upload file to Skynet:", err) 957 } 958 return skylink 959 } 960 961 // newProgressSkylink creates a static progress bar that starts after `afterBar` 962 // and displays the skylink. The bar is stopped immediately. 963 func newProgressSkylink(pbs *mpb.Progress, afterBar *mpb.Bar, filename, skylink string) *mpb.Bar { 964 bar := pbs.AddBar( 965 1, // we'll increment it once to stop it 966 mpb.BarQueueAfter(afterBar), 967 mpb.PrependDecorators( 968 decor.Name(pBarJobDone, decor.WC{W: 10}), 969 decor.Name(skylink), 970 ), 971 mpb.AppendDecorators( 972 decor.Name(filename, decor.WC{W: len(filename) + 1, C: decor.DidentRight}), 973 ), 974 ) 975 afterBar.Increment() 976 bar.Increment() 977 // Wait for finished bars to be rendered. 978 pbs.Wait() 979 return bar 980 }