20 unpackedPath
= flag
.String("unpacked_path",
22 "Path to the unpacked sources")
23 localdcsPath
= flag
.String("localdcs_path",
24 "~/.config/dcs-localdcs",
25 "Directory in which to keep state for dcs-localdcs (TLS certificates, PID files, etc.)")
26 stop
= flag
.Bool("stop",
28 "Whether to stop the currently running localdcs instead of starting a new one")
29 listenPackageImporter
= flag
.String("listen_package_importer",
31 "listen address ([host]:port) for dcs-package-importer")
32 listenIndexBackend
= flag
.String("listen_index_backend",
34 "listen address ([host]:port) for dcs-index-backend")
35 listenSourceBackend
= flag
.String("listen_source_backend",
37 "listen address ([host]:port) for dcs-source-backend")
38 listenWeb
= flag
.String("listen_web",
40 "listen address ([host]:port) for dcs-web")
41 listenWebTLS
= flag
.String("listen_web_tls",
43 "listen address ([host]:port) for dcs-web (TLS)")
46 func installBinaries() error
{
47 cmd
:= exec
.Command("go", "install", "-ldflags", "-X github.com/Debian/dcs/cmd/dcs-web/common.Version=git", "github.com/Debian/dcs/cmd/...")
48 cmd
.Stderr
= os
.Stderr
49 log
.Printf("Compiling and installing binaries: %v\n", cmd
.Args
)
53 func help(binary
string) error
{
54 err
:= exec
.Command(binary
, "-help").Run()
55 if exiterr
, ok
:= err
.(*exec
.ExitError
); ok
{
56 status
, ok
:= exiterr
.Sys().(syscall
.WaitStatus
)
58 log
.Panicf("cannot run on this platform: exec.ExitError.Sys() does not return syscall.WaitStatus")
60 // -help results in exit status 2, so that’s expected.
61 if status
.ExitStatus() == 2 {
68 func verifyBinariesAreExecutable() error
{
70 "dcs-package-importer",
74 "dcs-compute-ranking",
76 log
.Printf("Verifying binaries are executable: %v\n", binaries
)
77 for _
, binary
:= range binaries
{
78 if err
:= help(binary
); err
!= nil {
85 func compileStaticAssets() error
{
86 cmd
:= exec
.Command("make")
87 cmd
.Stderr
= os
.Stderr
89 log
.Printf("Compiling static assets: %v\n", cmd
.Args
)
93 // recordResource appends a line to a file in -localnet_dir so that we can
94 // clean up resources (tempdirs, pids) when being called with -stop later.
95 func recordResource(rtype
, value
string) error
{
96 f
, err
:= os
.OpenFile(filepath
.Join(*localdcsPath
, rtype
+"s"), os
.O_CREATE|os
.O_WRONLY|os
.O_APPEND
, 0644)
101 _
, err
= fmt
.Fprintf(f
, "%s\n", value
)
106 pidsFile
:= filepath
.Join(*localdcsPath
, "pids")
107 if _
, err
:= os
.Stat(pidsFile
); os
.IsNotExist(err
) {
108 return fmt
.Errorf("-stop specified, but no localdcs instance found in -localdcs_path=%q", *localdcsPath
)
111 pidsBytes
, err
:= ioutil
.ReadFile(pidsFile
)
113 return fmt
.Errorf("Could not read %q: %v", pidsFile
, err
)
115 pids
:= strings
.Split(string(pidsBytes
), "\n")
116 for _
, pidline
:= range pids
{
120 pid
, err
:= strconv
.Atoi(pidline
)
122 return fmt
.Errorf("Invalid line in %q: %v", pidsFile
, err
)
125 process
, err
:= os
.FindProcess(pid
)
127 log
.Printf("Could not find process %d: %v", pid
, err
)
130 if err
:= process
.Kill(); err
!= nil {
131 log
.Printf("Could not kill process %d: %v", pid
, err
)
140 func launchInBackground(binary
string, args
...string) error
{
141 // TODO: redirect stderr into a file
142 cmd
:= exec
.Command(binary
, args
...)
143 cmd
.Stderr
= os
.Stderr
144 // Put the robustirc servers into a separate process group, so that they
145 // survive when robustirc-localnet terminates.
146 cmd
.SysProcAttr
= &syscall
.SysProcAttr
{
149 if err
:= cmd
.Start(); err
!= nil {
150 return fmt
.Errorf("Could not start %q: %v", binary
, err
)
152 if err
:= recordResource("pid", strconv
.Itoa(cmd
.Process
.Pid
)); err
!= nil {
153 return fmt
.Errorf("Could not record pid of %q: %v", binary
, err
)
158 func httpReachable(addr
string) error
{
159 // Poll the configured listening port to see if the server started up successfully.
162 for try
:= 0; !running
&& try
< 20; try
++ {
163 _
, err
= http
.Get("http://" + addr
)
165 time
.Sleep(250 * time
.Millisecond
)
169 // Any HTTP response is okay.
175 func feed(pkg
, file
string) error
{
176 f
, err
:= os
.Open(file
)
181 url
:= fmt
.Sprintf("http://%s/import/%s/%s", *listenPackageImporter
, pkg
, filepath
.Base(file
))
182 request
, err
:= http
.NewRequest("PUT", url
, f
)
186 resp
, err
:= http
.DefaultClient
.Do(request
)
190 defer resp
.Body
.Close()
192 if resp
.StatusCode
!= http
.StatusOK
{
193 return fmt
.Errorf("Unexpected HTTP status: got %d, want %d", resp
.StatusCode
, http
.StatusOK
)
199 func importTestdata() error
{
200 testdataFiles
:= make(map[string][]string)
201 if err
:= filepath
.Walk("testdata/pool", func(path
string, info os
.FileInfo
, err error
) error
{
205 dir
:= filepath
.Dir(path
)
206 testdataFiles
[dir
] = append(testdataFiles
[dir
], path
)
211 // e.g. testdataFiles = map[
212 // testdata/pool/main/i/i3-wm:[
213 // testdata/pool/main/i/i3-wm/i3-wm_4.5.1-2.debian.tar.gz
214 // testdata/pool/main/i/i3-wm/i3-wm_4.5.1-2.dsc
215 // testdata/pool/main/i/i3-wm/i3-wm_4.5.1.orig.tar.bz2]
216 // testdata/pool/main/z/zsh:[
217 // testdata/pool/main/z/zsh/zsh_5.2-3.debian.tar.xz
218 // testdata/pool/main/z/zsh/zsh_5.2-3.dsc
219 // testdata/pool/main/z/zsh/zsh_5.2.orig.tar.xz]]
221 for _
, files
:= range testdataFiles
{
224 for _
, file
:= range files
{
225 if filepath
.Ext(file
) == ".dsc" {
228 rest
= append(rest
, file
)
236 // dsc "testdata/pool/main/i/i3-wm/i3-wm_4.5.1-2.dsc"
238 // testdata/pool/main/i/i3-wm/i3-wm_4.5.1-2.debian.tar.gz
239 // testdata/pool/main/i/i3-wm/i3-wm_4.5.1.orig.tar.bz2]
240 pkg
:= filepath
.Base(dsc
)
241 pkg
= pkg
[:len(pkg
)-len(filepath
.Ext(pkg
))]
242 log
.Printf("Importing package %q (files %v, dsc %s)\n", pkg
, rest
, dsc
)
243 for _
, file
:= range append(rest
, dsc
) {
244 if err
:= feed(pkg
, file
); err
!= nil {
250 // Wait with merging until all packages were imported, but up to 5s max.
251 for try
:= 0; try
< 5; try
++ {
252 resp
, err
:= http
.Get("http://" + *listenPackageImporter
+ "/metrics")
254 time
.Sleep(250 * time
.Millisecond
)
257 body
, err
:= ioutil
.ReadAll(resp
.Body
)
262 if strings
.Contains(string(body
), fmt
.Sprintf("package_indexes_successful %d", numPackages
)) {
265 time
.Sleep(1 * time
.Second
)
268 _
, err
:= http
.Get("http://" + *listenPackageImporter
+ "/merge")
272 // Wait for up to 20s until the merge completed
273 for try
:= 0; try
< 20; try
++ {
274 resp
, err
:= http
.Get("http://" + *listenPackageImporter
+ "/metrics")
276 time
.Sleep(250 * time
.Millisecond
)
279 body
, err
:= ioutil
.ReadAll(resp
.Body
)
284 if strings
.Contains(string(body
), "merges_successful 1") {
287 time
.Sleep(1 * time
.Second
)
295 if len(*localdcsPath
) >= 2 && (*localdcsPath
)[:2] == "~/" {
296 usr
, err
:= user
.Current()
298 log
.Fatalf("Cannot expand -localdcs_path: %v", err
)
300 *localdcsPath
= strings
.Replace(*localdcsPath
, "~/", usr
.HomeDir
+"/", 1)
303 if err
:= os
.MkdirAll(*localdcsPath
, 0700); err
!= nil {
304 log
.Fatalf("Could not create directory %q for dcs-localdcs state: %v", *localdcsPath
, err
)
308 if err
:= kill(); err
!= nil {
309 log
.Fatalf("Could not stop localdcs: %v", err
)
314 if _
, err
:= os
.Stat(filepath
.Join(*localdcsPath
, "pids")); !os
.IsNotExist(err
) {
315 log
.Fatalf("There already is a localdcs instance running. Either use -stop or specify a different -localdcs_path")
318 if err
:= os
.MkdirAll(*unpackedPath
, 0755); err
!= nil {
319 log
.Fatalf("Could not create directory %q for unpacked files/index: %v", *unpackedPath
, err
)
322 if err
:= installBinaries(); err
!= nil {
323 log
.Fatalf("Compiling and installing binaries failed: %v", err
)
326 if err
:= verifyBinariesAreExecutable(); err
!= nil {
327 log
.Fatalf("Could not find all required binaries: %v", err
)
330 if err
:= compileStaticAssets(); err
!= nil {
331 log
.Fatalf("Compiling static assets failed: %v", err
)
334 if _
, err
:= os
.Stat(filepath
.Join(*localdcsPath
, "key.pem")); os
.IsNotExist(err
) {
335 log
.Printf("Generating TLS certificate\n")
336 if err
:= generatecert(*localdcsPath
); err
!= nil {
337 log
.Fatalf("Could not generate TLS certificate: %v", err
)
341 rankingPath
:= filepath
.Join(*localdcsPath
, "ranking.json")
342 if stat
, err
:= os
.Stat(rankingPath
); err
!= nil || time
.Since(stat
.ModTime()) > 7*24*time
.Hour
{
343 log
.Printf("Computing ranking data\n")
345 "dcs-compute-ranking",
346 "-output_path="+rankingPath
)
347 cmd
.Env
= append(os
.Environ(),
348 "TMPDIR="+*localdcsPath
)
349 cmd
.Stderr
= os
.Stderr
350 if err
:= cmd
.Run(); err
!= nil {
351 log
.Fatalf("Could not compute ranking data: %v", err
)
354 log
.Printf("Recent-enough rankings file %q found, not re-generating (delete to force)\n", rankingPath
)
357 // Start package importer and import testdata/
358 if err
:= launchInBackground(
359 "dcs-package-importer",
362 "-tls_cert_path="+filepath
.Join(*localdcsPath
, "cert.pem"),
363 "-tls_key_path="+filepath
.Join(*localdcsPath
, "key.pem"),
364 "-unpacked_path="+*unpackedPath
,
365 "-listen_address="+*listenPackageImporter
); err
!= nil {
366 log
.Fatal(err
.Error())
368 if err
:= httpReachable(*listenPackageImporter
); err
!= nil {
369 log
.Fatalf("dcs-package-importer not reachable: %v", err
)
371 if err
:= importTestdata(); err
!= nil {
372 log
.Fatalf("Could not import testdata/: %v", err
)
375 if err
:= launchInBackground(
378 "-index_path="+*unpackedPath
+"/full.idx",
379 "-tls_cert_path="+filepath
.Join(*localdcsPath
, "cert.pem"),
380 "-tls_key_path="+filepath
.Join(*localdcsPath
, "key.pem"),
381 "-listen_address="+*listenIndexBackend
,
382 "-tls_require_client_auth=false"); err
!= nil {
383 log
.Fatal(err
.Error())
386 // TODO: check for healthiness
388 log
.Printf("dcs-index-backend running at https://%s\n", *listenIndexBackend
)
390 if err
:= launchInBackground(
391 "dcs-source-backend",
393 "-unpacked_path="+*unpackedPath
,
394 "-ranking_data_path="+rankingPath
,
395 "-tls_cert_path="+filepath
.Join(*localdcsPath
, "cert.pem"),
396 "-tls_key_path="+filepath
.Join(*localdcsPath
, "key.pem"),
397 "-listen_address="+*listenSourceBackend
,
398 "-tls_require_client_auth=false"); err
!= nil {
399 log
.Fatal(err
.Error())
402 // TODO: check for healthiness
404 log
.Printf("dcs-source-backend running at https://%s\n", *listenSourceBackend
)
406 if err
:= launchInBackground(
409 "-headroom_percentage=0",
410 "-template_pattern=cmd/dcs-web/templates/*",
411 "-static_path=static/",
412 "-source_backends="+*listenSourceBackend
,
413 "-tls_cert_path="+filepath
.Join(*localdcsPath
, "cert.pem"),
414 "-tls_key_path="+filepath
.Join(*localdcsPath
, "key.pem"),
415 "-listen_address="+*listenWeb
,
416 "-listen_address_tls="+*listenWebTLS
); err
!= nil {
417 log
.Fatal(err
.Error())
420 listenAddresses
:= "http://" + *listenWeb
421 if *listenWebTLS
!= "" {
422 listenAddresses
= listenAddresses
+ " and https://" + *listenWebTLS
424 log
.Printf("dcs-web running at %s\n", listenAddresses
)