2017-12-02 14:25:54 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2021-08-25 23:32:20 +00:00
|
|
|
"embed"
|
2017-12-02 14:25:54 +00:00
|
|
|
"fmt"
|
2018-09-19 09:55:25 +00:00
|
|
|
"io"
|
2017-12-02 14:25:54 +00:00
|
|
|
"os"
|
2021-08-25 23:32:20 +00:00
|
|
|
"strings"
|
2017-12-02 14:25:54 +00:00
|
|
|
|
2021-08-25 23:32:20 +00:00
|
|
|
"github.com/pkg/errors"
|
2024-03-18 12:42:19 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2019-05-12 22:05:06 +00:00
|
|
|
|
2021-01-27 20:11:32 +00:00
|
|
|
"github.com/Luzifer/rconfig/v2"
|
2017-12-02 14:25:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
cfg = struct {
|
2021-10-19 21:28:40 +00:00
|
|
|
BaseURL string `flag:"base-url" default:"" description:"URL to prepend before filename"`
|
2017-12-02 18:37:29 +00:00
|
|
|
Bootstrap bool `flag:"bootstrap" default:"false" description:"Upload frontend files into bucket"`
|
2021-10-19 21:28:40 +00:00
|
|
|
Bucket string `flag:"bucket" default:"" description:"S3 bucket to upload files to" validate:"nonzero"`
|
2021-10-19 21:42:56 +00:00
|
|
|
ContentType string `flag:"content-type,c" default:"" description:"Force content-type to be set to this value"`
|
2023-06-05 16:04:36 +00:00
|
|
|
Endpoint string `flag:"endpoint" default:"" description:"Override AWS S3 endpoint (i.e. to use MinIO)"`
|
2021-10-19 21:42:56 +00:00
|
|
|
FileTemplate string `flag:"file-template" vardefault:"file_template" description:"Full name template of the uploaded file"`
|
2021-10-19 21:28:40 +00:00
|
|
|
Listen string `flag:"listen" default:"" description:"Enable HTTP server if set to IP/Port (e.g. ':3000')"`
|
2021-10-19 21:40:42 +00:00
|
|
|
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
|
2018-10-11 18:15:12 +00:00
|
|
|
Progress bool `flag:"progress" default:"false" description:"Show progress bar while uploading"`
|
2017-12-02 14:25:54 +00:00
|
|
|
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
|
|
|
|
}{}
|
|
|
|
|
2021-08-25 23:32:20 +00:00
|
|
|
//go:embed frontend/*
|
|
|
|
frontend embed.FS
|
|
|
|
|
2017-12-02 14:25:54 +00:00
|
|
|
version = "dev"
|
|
|
|
)
|
|
|
|
|
2024-03-18 12:42:19 +00:00
|
|
|
func initApp() (err error) {
|
2018-09-19 09:55:25 +00:00
|
|
|
rconfig.AutoEnv(true)
|
2021-10-19 21:28:40 +00:00
|
|
|
rconfig.SetVariableDefaults(map[string]string{
|
|
|
|
"file_template": `file/{{ printf "%.6s" .Hash }}/{{ .SafeFileName }}`,
|
|
|
|
})
|
|
|
|
|
2024-03-18 12:42:19 +00:00
|
|
|
if err = rconfig.ParseAndValidate(&cfg); err != nil {
|
|
|
|
return fmt.Errorf("parsing CLI options: %w", err)
|
2017-12-02 14:25:54 +00:00
|
|
|
}
|
|
|
|
|
2024-03-18 12:42:19 +00:00
|
|
|
l, err := logrus.ParseLevel(cfg.LogLevel)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("parsing log-level: %w", err)
|
2021-10-19 21:40:42 +00:00
|
|
|
}
|
2024-03-18 12:42:19 +00:00
|
|
|
logrus.SetLevel(l)
|
2021-10-19 21:40:42 +00:00
|
|
|
|
2024-03-18 12:42:19 +00:00
|
|
|
return nil
|
2017-12-02 14:25:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
2024-03-18 12:42:19 +00:00
|
|
|
var err error
|
|
|
|
if err = initApp(); err != nil {
|
|
|
|
logrus.WithError(err).Fatal("initializing app")
|
|
|
|
}
|
2021-10-19 21:28:40 +00:00
|
|
|
|
2024-03-18 12:42:19 +00:00
|
|
|
if cfg.VersionAndExit {
|
|
|
|
fmt.Printf("share %s\n", version) //nolint:forbidigo // Fine for version info
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
2017-12-02 18:37:29 +00:00
|
|
|
|
2024-03-18 12:42:19 +00:00
|
|
|
switch {
|
2017-12-29 22:35:57 +00:00
|
|
|
case cfg.Bootstrap:
|
|
|
|
if err := doBootstrap(); err != nil {
|
2024-03-18 12:42:19 +00:00
|
|
|
logrus.WithError(err).Fatal("bootstrapping resources")
|
2017-12-29 22:35:57 +00:00
|
|
|
}
|
2024-03-18 12:42:19 +00:00
|
|
|
logrus.Info("Bucket bootstrap finished: Frontend uploaded")
|
2017-12-02 14:25:54 +00:00
|
|
|
|
2017-12-29 22:35:57 +00:00
|
|
|
case cfg.Listen != "":
|
2024-03-18 12:42:19 +00:00
|
|
|
logrus.WithFields(logrus.Fields{
|
|
|
|
"addr": cfg.Listen,
|
|
|
|
"version": version,
|
|
|
|
}).Info("share HTTP server started")
|
2017-12-29 22:35:57 +00:00
|
|
|
if err := doListen(); err != nil {
|
2024-03-18 12:42:19 +00:00
|
|
|
logrus.WithError(err).Fatal("running HTTP server")
|
2017-12-29 22:35:57 +00:00
|
|
|
}
|
2017-12-02 15:34:21 +00:00
|
|
|
|
2017-12-29 22:35:57 +00:00
|
|
|
default:
|
|
|
|
if err := doCLIUpload(); err != nil {
|
2024-03-18 12:42:19 +00:00
|
|
|
logrus.WithError(err).Fatal("uploading file")
|
2017-12-29 22:35:57 +00:00
|
|
|
}
|
2017-12-02 18:37:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-29 22:35:57 +00:00
|
|
|
func doCLIUpload() error {
|
|
|
|
if len(rconfig.Args()) == 1 {
|
2021-08-25 23:32:20 +00:00
|
|
|
return errors.New("missing argument: file to upload")
|
2017-12-02 14:25:54 +00:00
|
|
|
}
|
2017-12-02 18:37:29 +00:00
|
|
|
|
2017-12-29 22:35:57 +00:00
|
|
|
if cfg.BaseURL == "" {
|
2024-03-18 12:42:19 +00:00
|
|
|
logrus.Warn("No BaseURL configured, output will be no complete URL")
|
2017-12-02 14:25:54 +00:00
|
|
|
}
|
|
|
|
|
2018-09-19 09:55:25 +00:00
|
|
|
var inFile io.ReadSeeker
|
|
|
|
|
2017-12-29 22:35:57 +00:00
|
|
|
inFileName := rconfig.Args()[1]
|
2018-09-19 09:55:25 +00:00
|
|
|
|
|
|
|
if inFileName == "-" {
|
|
|
|
if cfg.ContentType == "" {
|
|
|
|
// If we don't have an explicitly set content-type assume stdin contains text
|
2021-10-19 21:28:40 +00:00
|
|
|
inFileName = "stdin"
|
2018-09-19 09:55:25 +00:00
|
|
|
cfg.ContentType = "text/plain"
|
2021-10-19 21:28:40 +00:00
|
|
|
} else if ext, err := mimeResolver.ExtensionsByType(cfg.ContentType); err == nil {
|
|
|
|
inFileName = strings.Join([]string{"stdin", ext}, "")
|
2018-09-19 09:55:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stdin is not seekable, so we need to buffer it
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
if _, err := io.Copy(buf, os.Stdin); err != nil {
|
2024-03-18 12:42:19 +00:00
|
|
|
logrus.WithError(err).Fatal("reading stdin")
|
2018-09-19 09:55:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
inFile = bytes.NewReader(buf.Bytes())
|
|
|
|
} else {
|
2024-03-18 12:42:19 +00:00
|
|
|
inFileHandle, err := os.Open(inFileName) //#nosec:G304 // Inentional read of arbitrary file
|
2018-09-19 09:55:25 +00:00
|
|
|
if err != nil {
|
2021-08-25 23:32:20 +00:00
|
|
|
return errors.Wrap(err, "opening source file")
|
2018-09-19 09:55:25 +00:00
|
|
|
}
|
2024-03-18 12:42:19 +00:00
|
|
|
defer inFileHandle.Close() //nolint:errcheck // Irrelevant, file is closed by process exit
|
2018-09-19 09:55:25 +00:00
|
|
|
inFile = inFileHandle
|
2017-12-02 14:25:54 +00:00
|
|
|
}
|
|
|
|
|
2019-05-12 22:05:06 +00:00
|
|
|
url, err := executeUpload(inFileName, inFile, true, cfg.ContentType, false)
|
2017-12-02 14:25:54 +00:00
|
|
|
if err != nil {
|
2021-08-25 23:32:20 +00:00
|
|
|
return errors.Wrap(err, "uploading file")
|
2017-12-02 14:25:54 +00:00
|
|
|
}
|
2017-12-29 22:35:57 +00:00
|
|
|
|
2024-03-18 12:42:19 +00:00
|
|
|
fmt.Println(url) //nolint:forbidigo // Intended as programmatic payload
|
2017-12-29 22:35:57 +00:00
|
|
|
return nil
|
2017-12-02 14:25:54 +00:00
|
|
|
}
|
|
|
|
|
2017-12-29 22:35:57 +00:00
|
|
|
func doBootstrap() error {
|
2021-08-26 13:29:25 +00:00
|
|
|
for _, asset := range []string{"index.html", "app.js", "bundle.css", "bundle.js"} {
|
2021-08-25 23:32:20 +00:00
|
|
|
content, err := frontend.ReadFile(strings.Join([]string{"frontend", asset}, "/"))
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "reading baked asset")
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := executeUpload(asset, bytes.NewReader(content), false, "", true); err != nil {
|
|
|
|
return errors.Wrapf(err, "uploading bootstrap asset %q", asset)
|
2017-12-29 22:35:57 +00:00
|
|
|
}
|
2017-12-02 14:25:54 +00:00
|
|
|
}
|
2017-12-29 22:35:57 +00:00
|
|
|
return nil
|
2017-12-02 14:25:54 +00:00
|
|
|
}
|