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