ezshare/cmd/ezshare.go

453 lines
9.6 KiB
Go
Raw Normal View History

2021-12-03 22:04:09 +00:00
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/signal"
"path/filepath"
"time"
"gitea.benny.dog/torjus/ezshare/certs"
2021-12-04 02:25:09 +00:00
"gitea.benny.dog/torjus/ezshare/config"
2021-12-03 22:04:09 +00:00
"gitea.benny.dog/torjus/ezshare/pb"
"gitea.benny.dog/torjus/ezshare/server"
"github.com/urfave/cli/v2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func main() {
app := cli.App{
Name: "ezshare",
2021-12-04 02:25:09 +00:00
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Usage: "Path to config-file.",
},
2021-12-04 10:42:44 +00:00
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Usage: "Be more verbose",
},
2021-12-04 02:25:09 +00:00
},
2021-12-03 22:04:09 +00:00
Commands: []*cli.Command{
{
Name: "serve",
Usage: "Start ezshare server",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "no-grpc",
Usage: "Do not enable grpc.",
},
&cli.BoolFlag{
Name: "no-http",
Usage: "Do not enable http.",
},
&cli.StringFlag{
Name: "grpc-addr",
Usage: "Address to listen for grpc.",
},
&cli.StringFlag{
Name: "http-addr",
Usage: "Address to listen for http.",
},
&cli.StringFlag{
Name: "hostname",
Usage: "Hostname used in links",
},
},
Action: ActionServe,
},
{
Name: "client",
Usage: "Client commands",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "addr",
Usage: "Address of server.",
},
},
Subcommands: []*cli.Command{
{
Name: "get",
Usage: "Get file with id",
ArgsUsage: "ID [ID]..",
Action: ActionClientGet,
},
{
Name: "upload",
Usage: "Upload file(s)",
ArgsUsage: "PATH [PATH]..",
Action: ActionClientUpload,
},
2021-12-04 10:42:44 +00:00
{
Name: "delete",
Usage: "Delete file with id",
ArgsUsage: "ID [ID]..",
Action: ActionClientDelete,
},
2021-12-04 10:30:42 +00:00
{
Name: "list",
Usage: "List files",
Action: ActionClientList,
},
{
Name: "config-init",
Usage: "Initialize default config",
Action: ActionInitConfig,
},
2021-12-03 22:04:09 +00:00
},
},
{
2021-12-04 02:25:09 +00:00
Name: "cert",
Usage: "Certificate commands",
Subcommands: []*cli.Command{
{
Name: "gen-all",
Usage: "Generate CA, Server and Client certificates",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "out-dir",
Usage: "Directory where certificates will be stored.",
},
2021-12-04 09:19:59 +00:00
&cli.StringFlag{
Name: "hostname",
Usage: "Hostname used for server certificate.",
},
2021-12-04 02:25:09 +00:00
},
Action: ActionGencerts,
2021-12-03 22:04:09 +00:00
},
},
},
},
}
err := app.Run(os.Args)
if err != nil {
log.Printf("Error: %s\n", err)
}
}
func ActionServe(c *cli.Context) error {
2021-12-04 02:25:09 +00:00
cfg, err := getConfig(c)
if err != nil {
return err
}
// Read certificates
srvCertBytes, err := cfg.Server.GRPC.Certs.GetCertBytes()
if err != nil {
return err
}
srvKeyBytes, err := cfg.Server.GRPC.Certs.GetKeyBytes()
if err != nil {
return err
}
caCertBytes, err := cfg.Server.GRPC.CACerts.GetCertBytes()
if err != nil {
return err
}
2021-12-04 08:58:16 +00:00
// Setup store
s, closeFunc, err := cfg.Server.StoreConfig.GetStore()
if err != nil {
return fmt.Errorf("unable to initialize store: %w", err)
}
defer closeFunc()
2021-12-03 22:04:09 +00:00
// Setup shutdown-handling
rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer rootCancel()
// Used to initiate grpc shutdown
grpcCtx, grpcCancel := context.WithCancel(rootCtx)
defer grpcCancel()
// Cancelled once grpc is successfully shut down
grpcShutdownCtx, grpcShutdownCancel := context.WithCancel(context.Background())
defer grpcShutdownCancel()
// Start grpc server
go func() {
2021-12-04 02:25:09 +00:00
grpcAddr := cfg.Server.GRPC.ListenAddr
2021-12-03 22:04:09 +00:00
if c.IsSet("grpc-addr") {
grpcAddr = c.String("grpc-addr")
}
2021-12-04 08:58:16 +00:00
grpcFileServer := server.NewGRPCFileServiceServer(s)
2021-12-04 09:35:26 +00:00
grpcFileServer.Hostname = cfg.Server.Hostname
2021-12-03 22:04:09 +00:00
if c.IsSet("hostname") {
grpcFileServer.Hostname = c.String("hostname")
}
lis, err := net.Listen("tcp", grpcAddr)
if err != nil {
log.Printf("Unable to setup grpc listener: %s\n", err)
rootCancel()
}
2021-12-04 02:25:09 +00:00
srvCert, err := tls.X509KeyPair(srvCertBytes, srvKeyBytes)
2021-12-03 22:04:09 +00:00
if err != nil {
log.Printf("Unable load server certs: %s\n", err)
rootCancel()
}
certPool := x509.NewCertPool()
2021-12-04 02:25:09 +00:00
if !certPool.AppendCertsFromPEM(caCertBytes) {
2021-12-03 22:04:09 +00:00
log.Println("Unable to load CA cert")
rootCancel()
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{srvCert},
ClientAuth: tls.RequestClientCert,
ClientCAs: certPool,
}
creds := credentials.NewTLS(tlsConfig)
grpcServer := grpc.NewServer(
grpc.Creds(creds),
)
pb.RegisterFileServiceServer(grpcServer, grpcFileServer)
// wait for cancel
go func() {
<-grpcCtx.Done()
grpcServer.GracefulStop()
}()
log.Printf("Starting grpc server")
if err = grpcServer.Serve(lis); err != nil {
log.Printf("GRPC Shutdown with error: %s\n", err)
rootCancel()
}
log.Println("GRPC Shutdown")
grpcShutdownCancel()
}()
httpCtx, httpCancel := context.WithCancel(rootCtx)
defer httpCancel()
httpShutdownCtx, httpShutdownCancel := context.WithCancel(context.Background())
defer httpShutdownCancel()
// Start http server
go func() {
httpAddr := ":8088"
if c.IsSet("http-addr") {
httpAddr = c.String("http-addr")
}
2021-12-04 08:58:16 +00:00
httpServer := server.NewHTTPSever(s)
2021-12-03 22:04:09 +00:00
httpServer.Addr = httpAddr
// wait for cancel
go func() {
<-httpCtx.Done()
timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
httpServer.Shutdown(timeoutCtx)
}()
log.Printf("Starting http server")
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("HTTP Server shutdown with error: %s\n", err)
rootCancel()
}
log.Println("HTTP Shutdown")
httpShutdownCancel()
}()
<-grpcShutdownCtx.Done()
<-httpShutdownCtx.Done()
return nil
}
func ActionClientGet(c *cli.Context) error {
addr := c.String("addr")
conn, err := grpc.DialContext(c.Context, addr, grpc.WithInsecure())
if err != nil {
return err
}
defer conn.Close()
client := pb.NewFileServiceClient(conn)
for _, arg := range c.Args().Slice() {
req := &pb.GetFileRequest{Id: arg}
resp, err := client.GetFile(c.Context, req)
if err != nil {
return err
}
filename := resp.File.FileId
if resp.File.Metadata.OriginalFilename != "" {
filename = resp.File.Metadata.OriginalFilename
}
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
if _, err := f.Write(resp.File.Data); err != nil {
return err
}
fmt.Printf("Wrote file '%s'\n", filename)
}
return nil
}
func ActionClientUpload(c *cli.Context) error {
2021-12-04 02:25:09 +00:00
cfg, err := getConfig(c)
if err != nil {
return err
}
addr := cfg.Client.DefaultServer
if c.IsSet("addr") {
addr = c.String("addr")
}
clientCreds, err := cfg.Client.Creds()
2021-12-03 22:04:09 +00:00
if err != nil {
return err
}
conn, err := grpc.DialContext(c.Context, addr, grpc.WithTransportCredentials(clientCreds))
if err != nil {
return err
}
defer conn.Close()
client := pb.NewFileServiceClient(conn)
for _, arg := range c.Args().Slice() {
f, err := os.Open(arg)
if err != nil {
return err
}
data, err := io.ReadAll(f)
if err != nil {
return err
}
req := &pb.UploadFileRequest{Data: data, OriginalFilename: filepath.Base(arg)}
resp, err := client.UploadFile(c.Context, req)
if err != nil {
return err
}
fmt.Printf("%s uploaded with id %s. Available at %s\n", arg, resp.Id, resp.FileUrl)
}
return nil
}
2021-12-04 10:30:42 +00:00
func ActionClientList(c *cli.Context) error {
cfg, err := getConfig(c)
if err != nil {
return err
}
addr := cfg.Client.DefaultServer
if c.IsSet("addr") {
addr = c.String("addr")
}
clientCreds, err := cfg.Client.Creds()
if err != nil {
return err
}
conn, err := grpc.DialContext(c.Context, addr, grpc.WithTransportCredentials(clientCreds))
if err != nil {
return err
}
defer conn.Close()
client := pb.NewFileServiceClient(conn)
resp, err := client.ListFiles(c.Context, &pb.ListFilesRequest{})
if err != nil {
return err
}
for _, elem := range resp.Files {
fmt.Println(elem.FileId)
}
return nil
}
2021-12-04 10:42:44 +00:00
func ActionClientDelete(c *cli.Context) error {
cfg, err := getConfig(c)
if err != nil {
return err
}
addr := cfg.Client.DefaultServer
if c.IsSet("addr") {
addr = c.String("addr")
}
clientCreds, err := cfg.Client.Creds()
if err != nil {
return err
}
conn, err := grpc.DialContext(c.Context, addr, grpc.WithTransportCredentials(clientCreds))
if err != nil {
return err
}
defer conn.Close()
client := pb.NewFileServiceClient(conn)
for _, arg := range c.Args().Slice() {
_, err := client.DeleteFile(c.Context, &pb.DeleteFileRequest{Id: arg})
if err != nil {
return fmt.Errorf("error deleting file: %w", err)
}
fmt.Printf("Deleted file %s\n", arg)
}
return nil
}
2021-12-03 22:04:09 +00:00
func ActionGencerts(c *cli.Context) error {
2021-12-04 02:25:09 +00:00
outDir := "."
if c.IsSet("out-dir") {
outDir = c.String("out-dir")
}
2021-12-04 09:19:59 +00:00
if !c.IsSet("hostname") {
return fmt.Errorf("--hostname required")
}
hostname := c.String("hostname")
return certs.GenAllCerts(outDir, hostname)
2021-12-03 22:04:09 +00:00
}
func ActionInitConfig(c *cli.Context) error {
defaultCfg := config.FromDefault()
return defaultCfg.ToDefaultFile()
}
2021-12-04 02:25:09 +00:00
func getConfig(c *cli.Context) (*config.Config, error) {
if c.IsSet("config") {
cfgPath := c.String("config")
return config.FromFile(cfgPath)
2021-12-03 22:04:09 +00:00
}
2021-12-04 02:25:09 +00:00
cfg, err := config.FromDefaultLocations()
if err == nil {
2021-12-04 10:42:44 +00:00
verbosePrint(c, fmt.Sprintf("Config loaded from %s", cfg.Location()))
2021-12-03 22:04:09 +00:00
}
2021-12-04 02:25:09 +00:00
return cfg, err
2021-12-03 22:04:09 +00:00
}
2021-12-04 10:42:44 +00:00
func verbosePrint(c *cli.Context, message string) {
if c.Bool("verbose") {
fmt.Println(message)
}
}