-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
154 lines (135 loc) · 3.45 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package main
import (
"errors"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/user"
"path/filepath"
"strconv"
"syscall"
"github.com/krisukox/bazels3cache/app"
"github.com/sevlyar/go-daemon"
)
func sendShutdownToDaemon(port int) error {
resp, err := http.Get(fmt.Sprintf(app.ShutdownUrlTmpl, port))
if err != nil {
if errors.Is(err, syscall.ECONNREFUSED) {
return fmt.Errorf("server is not running")
}
return fmt.Errorf("unexpected error: %v", err)
}
defer resp.Body.Close()
response, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("internal application error: %v", err)
}
fmt.Println(string(response))
return nil
}
func createDaemonContext(rootPort int) *daemon.Context {
usr, err := user.Current()
workDir := ""
if err == nil {
workDir = usr.HomeDir
}
return &daemon.Context{
PidFileName: filepath.Join(workDir, ".bazels3cache.pid"),
PidFilePerm: 0644,
LogFileName: filepath.Join(workDir, ".bazels3cache.log"),
LogFilePerm: 0640,
Umask: 027,
Env: append(os.Environ(), app.RootPortEnv+"="+strconv.Itoa(rootPort)),
Args: append(os.Args, "[bazels3cache-daemon]"),
}
}
func rootProcess(port int, logFile string, ln net.Listener) error {
defer ln.Close()
conn, err := ln.Accept()
if err != nil {
return err
}
defer conn.Close()
buf, err := io.ReadAll(conn)
if err != nil {
return err
}
if string(buf) == app.SuccessMsg {
executablePath, err := os.Executable()
if err != nil {
executablePath = ""
}
executableName := filepath.Base(executablePath)
portSwitch := ""
if port != app.DefaultPort {
portSwitch = " --port " + strconv.Itoa(port)
}
fmt.Printf(
"Server `%[1]s` is running, to stop it run `%[1]s --stop%[2]s` or `curl %[3]s`\n",
executableName, portSwitch, fmt.Sprintf(app.ShutdownUrlTmpl, port),
)
fmt.Printf("Logging to %s\n", logFile)
return nil
}
return fmt.Errorf("failed to initialize application: %s", string(buf))
}
func listenOnAny(avoidPort int) (net.Listener, int, error) {
if !daemon.WasReborn() {
for port := 2000; port <= 65535; port++ {
if port == avoidPort {
continue
}
ln, err := net.Listen("tcp", ":"+strconv.Itoa(port))
if err == nil {
return ln, port, nil
}
}
return nil, 0, fmt.Errorf("could not find an available port")
}
return nil, 0, nil
}
func main() {
bucketName := flag.String("bucket", "", "S3 bucket name")
daemonPort := flag.Int("port", app.DefaultPort, "Server HTTP port number")
stop := flag.Bool("stop", false, "Stop application")
s3url := flag.String("s3url", "", "S3 url used for testing")
flag.Parse()
if *stop {
if err := sendShutdownToDaemon(*daemonPort); err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
return
}
if *bucketName == "" {
fmt.Printf("Error: Please specify S3 bucket name: --bucket string\n")
return
}
ln, rootPort, err := listenOnAny(*daemonPort)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
cntxt := createDaemonContext(rootPort)
d, err := cntxt.Reborn()
if err != nil {
log.Fatal("Internal application error: ", err)
}
if d != nil {
if err := rootProcess(*daemonPort, cntxt.LogFileName, ln); err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
return
}
defer cntxt.Release()
infoLog := log.New(os.Stdout, "INFO\t", log.Ltime)
errorLog := log.New(os.Stderr, "ERROR\t", log.Ltime)
if err := app.DaemonProcess(*bucketName, *s3url, *daemonPort, infoLog, errorLog); err != nil {
errorLog.Fatal(err)
}
}