1package cmd 2 3import ( 4 "fmt" 5 "net" 6 "net/http" 7 "net/url" 8 "os" 9 10 "github.com/aptly-dev/aptly/api" 11 "github.com/aptly-dev/aptly/systemd/activation" 12 "github.com/aptly-dev/aptly/utils" 13 "github.com/smira/commander" 14 "github.com/smira/flag" 15) 16 17func aptlyAPIServe(cmd *commander.Command, args []string) error { 18 var ( 19 err error 20 ) 21 22 if len(args) != 0 { 23 cmd.Usage() 24 return commander.ErrCommandError 25 } 26 27 // There are only two working options for aptly's rootDir: 28 // 1. rootDir does not exist, then we'll create it 29 // 2. rootDir exists and is writable 30 // anything else must fail. 31 // E.g.: Running the service under a different user may lead to a rootDir 32 // that exists but is not usable due to access permissions. 33 err = utils.DirIsAccessible(context.Config().RootDir) 34 if err != nil { 35 return err 36 } 37 38 // Try to recycle systemd fds for listening 39 listeners, err := activation.Listeners(true) 40 if len(listeners) > 1 { 41 panic("Got more than 1 listener from systemd. This is currently not supported!") 42 } 43 if err == nil && len(listeners) == 1 { 44 listener := listeners[0] 45 defer listener.Close() 46 fmt.Printf("\nTaking over web server at: %s (press Ctrl+C to quit)...\n", listener.Addr().String()) 47 err = http.Serve(listener, api.Router(context)) 48 if err != nil { 49 return fmt.Errorf("unable to serve: %s", err) 50 } 51 return nil 52 } 53 54 // If there are none: use the listen argument. 55 listen := context.Flags().Lookup("listen").Value.String() 56 fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen) 57 58 listenURL, err := url.Parse(listen) 59 if err == nil && listenURL.Scheme == "unix" { 60 file := listenURL.Path 61 62 var stat os.FileInfo 63 stat, err = os.Stat(file) 64 shouldRemove := true 65 66 if err == nil && stat.Mode()&os.ModeSocket == os.ModeSocket { 67 shouldRemove = false 68 } 69 70 if err != nil && os.IsNotExist(err) { 71 shouldRemove = false 72 } 73 74 if shouldRemove { 75 err = os.Remove(file) 76 if err != nil { 77 fmt.Printf("Warning: error removing file %s: %s\n", file, err) 78 } 79 } 80 81 var listener net.Listener 82 listener, err = net.Listen("unix", file) 83 if err != nil { 84 return fmt.Errorf("failed to listen on: %s\n%s", file, err) 85 } 86 defer listener.Close() 87 88 err = http.Serve(listener, api.Router(context)) 89 if err != nil { 90 return fmt.Errorf("unable to serve: %s", err) 91 } 92 return nil 93 } 94 95 err = http.ListenAndServe(listen, api.Router(context)) 96 if err != nil { 97 return fmt.Errorf("unable to serve: %s", err) 98 } 99 100 return err 101} 102 103func makeCmdAPIServe() *commander.Command { 104 cmd := &commander.Command{ 105 Run: aptlyAPIServe, 106 UsageLine: "serve", 107 Short: "start API HTTP service", 108 Long: ` 109Start HTTP server with aptly REST API. The server can listen to either a port 110or Unix domain socket. When using a socket, Aptly will fully manage the socket 111file. This command also supports taking over from a systemd file descriptors to 112enable systemd socket activation. 113 114Example: 115 116 $ aptly api serve -listen=:8080 117 $ aptly api serve -listen=unix:///tmp/aptly.sock 118`, 119 Flag: *flag.NewFlagSet("aptly-serve", flag.ExitOnError), 120 } 121 122 cmd.Flag.String("listen", ":8080", "host:port for HTTP listening or unix://path to listen on a Unix domain socket") 123 cmd.Flag.Bool("no-lock", false, "don't lock the database") 124 125 return cmd 126 127} 128