예제 #1
0
func (c *cli) matchMirror(text string) (list []string, err error) {
	if len(text) == 0 {
		return nil, errors.New("Nothing to match")
	}

	r := database.NewRedis()
	conn, err := r.Connect()
	if err != nil {
		log.Fatal("Redis: ", err)
	}
	defer conn.Close()

	list = make([]string, 0, 0)

	mirrorsIDs, err := redis.Strings(conn.Do("LRANGE", "MIRRORS", "0", "-1"))
	if err != nil {
		return nil, errors.New("Cannot fetch the list of mirrors")
	}

	for _, e := range mirrorsIDs {
		if text == e {
			return []string{e}, nil
		}
		if strings.Contains(e, text) {
			list = append(list, e)
		}
	}
	return
}
예제 #2
0
func (c *cli) CmdShow(args ...string) error {
	cmd := SubCmd("show", "[IDENTIFIER]", "Print a mirror configuration")

	if err := cmd.Parse(args); err != nil {
		return nil
	}
	if cmd.NArg() != 1 {
		cmd.Usage()
		return nil
	}

	// Guess which mirror to use
	list, err := c.matchMirror(cmd.Arg(0))
	if err != nil {
		return err
	}
	if len(list) == 0 {
		fmt.Fprintf(os.Stderr, "No match for %s\n", cmd.Arg(0))
		return nil
	} else if len(list) > 1 {
		for _, e := range list {
			fmt.Fprintf(os.Stderr, "%s\n", e)
		}
		return nil
	}

	id := list[0]

	// Connect to the database
	r := database.NewRedis()
	conn, err := r.Connect()
	if err != nil {
		log.Fatal("Redis: ", err)
	}
	defer conn.Close()

	// Get the mirror information
	key := fmt.Sprintf("MIRROR_%s", id)
	m, err := redis.Values(conn.Do("HGETALL", key))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Cannot fetch mirror details: %s\n", err)
		return err
	}

	var mirror mirrors.Mirror
	err = redis.ScanStruct(m, &mirror)
	if err != nil {
		return err
	}

	// Generate a yaml configuration string from the struct
	out, err := yaml.Marshal(mirror)

	fmt.Printf("Mirror: %s\n%s\nComment:\n%s\n", id, out, mirror.Comment)
	return nil
}
예제 #3
0
func (c *cli) CmdRefresh(args ...string) error {
	cmd := SubCmd("refresh", "", "Scan the local repository")

	if err := cmd.Parse(args); err != nil {
		return nil
	}
	if cmd.NArg() != 0 {
		cmd.Usage()
		return nil
	}

	err := scan.ScanSource(database.NewRedis(), nil)
	return err
}
예제 #4
0
func (c *cli) CmdDisable(args ...string) error {
	cmd := SubCmd("disable", "[IDENTIFIER]", "Disable a mirror")

	if err := cmd.Parse(args); err != nil {
		return nil
	}
	if cmd.NArg() != 1 {
		cmd.Usage()
		return nil
	}

	// Guess which mirror to use

	list, err := c.matchMirror(cmd.Arg(0))
	if err != nil {
		return err
	}
	if len(list) == 0 {
		fmt.Fprintf(os.Stderr, "No match for %s\n", cmd.Arg(0))
		return nil
	} else if len(list) > 1 {
		for _, e := range list {
			fmt.Fprintf(os.Stderr, "%s\n", e)
		}
		return nil
	}

	err = mirrors.DisableMirror(database.NewRedis(), list[0])
	if err != nil {
		log.Fatal("Couldn't disable the mirror:", err)
	}

	fmt.Println("Mirror disabled successfully")

	return nil
}
예제 #5
0
func (c *cli) CmdExport(args ...string) error {
	cmd := SubCmd("export", "[format]", "Export the mirror database.\n\nAvailable formats: mirmon")
	rsync := cmd.Bool("rsync", true, "Export rsync URLs")
	http := cmd.Bool("http", true, "Export http URLs")
	ftp := cmd.Bool("ftp", true, "Export ftp URLs")
	disabled := cmd.Bool("disabled", true, "Export disabled mirrors")

	if err := cmd.Parse(args); err != nil {
		return nil
	}
	if cmd.NArg() != 1 {
		cmd.Usage()
		return nil
	}

	if cmd.Arg(0) != "mirmon" {
		fmt.Fprintf(os.Stderr, "Unsupported format\n")
		cmd.Usage()
		return nil
	}

	r := database.NewRedis()
	conn, err := r.Connect()
	if err != nil {
		log.Fatal("Redis: ", err)
	}
	defer conn.Close()

	mirrorsIDs, err := redis.Strings(conn.Do("LRANGE", "MIRRORS", "0", "-1"))
	if err != nil {
		log.Fatal("Cannot fetch the list of mirrors: ", err)
	}

	conn.Send("MULTI")

	for _, e := range mirrorsIDs {
		conn.Send("HGETALL", fmt.Sprintf("MIRROR_%s", e))
	}

	res, err := redis.Values(conn.Do("EXEC"))
	if err != nil {
		log.Fatal("Redis: ", err)
	}

	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 0, '\t', 0)

	for _, e := range res {
		var mirror mirrors.Mirror
		res, ok := e.([]interface{})
		if !ok {
			log.Fatal("Typecast failed")
		} else {
			err := redis.ScanStruct([]interface{}(res), &mirror)
			if err != nil {
				log.Fatal("ScanStruct:", err)
			}
			if *disabled == false {
				if mirror.Enabled == false {
					continue
				}
			}
			ccodes := strings.Fields(mirror.CountryCodes)

			urls := make([]string, 0, 3)
			if *rsync == true && mirror.RsyncURL != "" {
				urls = append(urls, mirror.RsyncURL)
			}
			if *http == true && mirror.HttpURL != "" {
				urls = append(urls, mirror.HttpURL)
			}
			if *ftp == true && mirror.FtpURL != "" {
				urls = append(urls, mirror.FtpURL)
			}

			for _, u := range urls {
				fmt.Fprintf(w, "%s\t%s\t%s\n", ccodes[0], u, mirror.AdminEmail)
			}
		}
	}

	w.Flush()

	return nil
}
예제 #6
0
func (c *cli) CmdEdit(args ...string) error {
	cmd := SubCmd("edit", "[IDENTIFIER]", "Edit a mirror")

	if err := cmd.Parse(args); err != nil {
		return nil
	}
	if cmd.NArg() != 1 {
		cmd.Usage()
		return nil
	}

	// Find the editor to use
	editor := os.Getenv("EDITOR")

	if editor == "" {
		log.Fatal("Environment variable $EDITOR not set")
	}

	// Guess which mirror to use
	list, err := c.matchMirror(cmd.Arg(0))
	if err != nil {
		return err
	}
	if len(list) == 0 {
		fmt.Fprintf(os.Stderr, "No match for %s\n", cmd.Arg(0))
		return nil
	} else if len(list) > 1 {
		for _, e := range list {
			fmt.Fprintf(os.Stderr, "%s\n", e)
		}
		return nil
	}

	id := list[0]

	// Connect to the database
	r := database.NewRedis()
	conn, err := r.Connect()
	if err != nil {
		log.Fatal("Redis: ", err)
	}
	defer conn.Close()

	// Get the mirror information
	key := fmt.Sprintf("MIRROR_%s", id)
	m, err := redis.Values(conn.Do("HGETALL", key))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Cannot fetch mirror details: %s\n", err)
		return err
	}

	var mirror mirrors.Mirror
	err = redis.ScanStruct(m, &mirror)
	if err != nil {
		return err
	}

	// Generate a yaml configuration string from the struct
	out, err := yaml.Marshal(mirror)

	// Open a temporary file
	f, err := ioutil.TempFile(os.TempDir(), "edit")
	if err != nil {
		log.Fatal("Cannot create temporary file:", err)
	}
	defer os.Remove(f.Name())
	f.WriteString("# You can now edit this mirror configuration.\n" +
		"# Just save and quit when you're done.\n\n")
	f.WriteString(string(out))
	f.WriteString(fmt.Sprintf("\n%s\n\n%s\n", commentSeparator, mirror.Comment))
	f.Close()

	// Checksum the original file
	chk, _ := filesystem.HashFile(f.Name())

reopen:
	// Launch the editor with the filename as first parameter
	exe := exec.Command(editor, f.Name())
	exe.Stdin = os.Stdin
	exe.Stdout = os.Stdout
	exe.Stderr = os.Stderr

	err = exe.Run()
	if err != nil {
		log.Fatal(err)
	}

	// Read the file back
	out, err = ioutil.ReadFile(f.Name())
	if err != nil {
		log.Fatal("Cannot read file", f.Name())
	}

	// Checksum the file back and compare
	chk2, _ := filesystem.HashFile(f.Name())
	if chk == chk2 {
		fmt.Println("Aborted")
		return nil
	}

	var (
		yamlstr string = string(out)
		comment string
	)

	commentIndex := strings.Index(yamlstr, commentSeparator)
	if commentIndex > 0 {
		comment = strings.TrimSpace(yamlstr[commentIndex+len(commentSeparator):])
		yamlstr = yamlstr[:commentIndex]
	}

	// Fill the struct from the yaml
	err = yaml.Unmarshal([]byte(yamlstr), &mirror)
	if err != nil {
	eagain:
		fmt.Printf("%s\nRetry? [Y/n]", err.Error())
		reader := bufio.NewReader(os.Stdin)
		s, _ := reader.ReadString('\n')
		switch s[0] {
		case 'y', 'Y', 10:
			goto reopen
		case 'n', 'N':
			fmt.Println("Aborted")
			return nil
		default:
			goto eagain
		}
	}

	// Reformat contry codes
	mirror.CountryCodes = strings.Replace(mirror.CountryCodes, ",", " ", -1)
	ccodes := strings.Fields(mirror.CountryCodes)
	mirror.CountryCodes = ""
	for _, c := range ccodes {
		mirror.CountryCodes += strings.ToUpper(c) + " "
	}
	mirror.CountryCodes = strings.TrimRight(mirror.CountryCodes, " ")

	// Reformat continent code
	//FIXME sanitize
	mirror.ContinentCode = strings.ToUpper(mirror.ContinentCode)

	// Normalize URLs
	if mirror.HttpURL != "" {
		mirror.HttpURL = utils.NormalizeURL(mirror.HttpURL)
	}
	if mirror.RsyncURL != "" {
		mirror.RsyncURL = utils.NormalizeURL(mirror.RsyncURL)
	}
	if mirror.FtpURL != "" {
		mirror.FtpURL = utils.NormalizeURL(mirror.FtpURL)
	}

	mirror.Comment = comment

	// Save the values back into redis
	_, err = conn.Do("HMSET", key,
		"ID", id,
		"http", mirror.HttpURL,
		"rsync", mirror.RsyncURL,
		"ftp", mirror.FtpURL,
		"sponsorName", mirror.SponsorName,
		"sponsorURL", mirror.SponsorURL,
		"sponsorLogo", mirror.SponsorLogoURL,
		"adminName", mirror.AdminName,
		"adminEmail", mirror.AdminEmail,
		"customData", mirror.CustomData,
		"continentOnly", mirror.ContinentOnly,
		"countryOnly", mirror.CountryOnly,
		"asOnly", mirror.ASOnly,
		"score", mirror.Score,
		"latitude", mirror.Latitude,
		"longitude", mirror.Longitude,
		"continentCode", mirror.ContinentCode,
		"countryCodes", mirror.CountryCodes,
		"asnum", mirror.Asnum,
		"comment", mirror.Comment,
		"enabled", mirror.Enabled)

	if err != nil {
		log.Fatal("Couldn't save the configuration into redis:", err)
	}

	// Publish update
	database.Publish(conn, database.MIRROR_UPDATE, id)

	fmt.Println("Mirror edited successfully")

	return nil
}
예제 #7
0
func (c *cli) CmdScan(args ...string) error {
	cmd := SubCmd("scan", "[IDENTIFIER]", "(Re-)Scan a mirror")
	enable := cmd.Bool("enable", false, "Enable the mirror automatically if the scan is successful")
	all := cmd.Bool("all", false, "Scan all mirrors at once")
	ftp := cmd.Bool("ftp", false, "Force a scan using FTP")
	rsync := cmd.Bool("rsync", false, "Force a scan using rsync")

	if err := cmd.Parse(args); err != nil {
		return nil
	}
	if !*all && cmd.NArg() != 1 || *all && cmd.NArg() != 0 {
		cmd.Usage()
		return nil
	}

	r := database.NewRedis()
	conn, err := r.Connect()
	if err != nil {
		log.Fatal("Redis: ", err)
	}
	defer conn.Close()

	// Check if the local repository has been scanned already
	exists, err := redis.Bool(conn.Do("EXISTS", "FILES"))
	if err != nil {
		return err
	}
	if !exists {
		fmt.Fprintf(os.Stderr, "Local repository not yet indexed.\nYou should run 'refresh' first!\n")
		os.Exit(-1)
	}

	var list []string

	if *all == true {
		list, err = redis.Strings(conn.Do("LRANGE", "MIRRORS", "0", "-1"))
		if err != nil {
			return errors.New("Cannot fetch the list of mirrors")
		}
	} else {
		list, err = c.matchMirror(cmd.Arg(0))
		if err != nil {
			return err
		}
		if len(list) == 0 {
			fmt.Fprintf(os.Stderr, "No match for %s\n", cmd.Arg(0))
			return nil
		} else if len(list) > 1 {
			for _, e := range list {
				fmt.Fprintf(os.Stderr, "%s\n", e)
			}
			return nil
		}
	}

	for _, id := range list {

		key := fmt.Sprintf("MIRROR_%s", id)
		m, err := redis.Values(conn.Do("HGETALL", key))
		if err != nil {
			fmt.Fprintf(os.Stderr, "Cannot fetch mirror details: %s\n", err)
			return err
		}

		var mirror mirrors.Mirror
		err = redis.ScanStruct(m, &mirror)
		if err != nil {
			return err
		}

		log.Noticef("Scanning %s...", id)

		err = NoSyncMethod

		if *rsync == true || *ftp == true {
			// Use the requested protocol
			if *rsync == true && mirror.RsyncURL != "" {
				err = scan.Scan(scan.RSYNC, r, mirror.RsyncURL, id, nil)
			} else if *ftp == true && mirror.FtpURL != "" {
				err = scan.Scan(scan.FTP, r, mirror.FtpURL, id, nil)
			}
		} else {
			// Use rsync (if applicable) and fallback to FTP
			if mirror.RsyncURL != "" {
				err = scan.Scan(scan.RSYNC, r, mirror.RsyncURL, id, nil)
			}
			if err != nil && mirror.FtpURL != "" {
				err = scan.Scan(scan.FTP, r, mirror.FtpURL, id, nil)
			}
		}

		if err != nil {
			log.Errorf("Scanning %s failed: %s", id, err.Error())
		}

		// Finally enable the mirror if requested
		if err == nil && *enable == true {
			if err := mirrors.EnableMirror(r, id); err != nil {
				log.Fatal("Couldn't enable the mirror: ", err)
			}
			fmt.Println("Mirror enabled successfully")
		}
	}
	return nil
}
예제 #8
0
func (c *cli) CmdRemove(args ...string) error {
	cmd := SubCmd("remove", "IDENTIFIER", "Remove an existing mirror")

	if err := cmd.Parse(args); err != nil {
		return nil
	}
	if cmd.NArg() != 1 {
		cmd.Usage()
		return nil
	}

	// Guess which mirror to use
	list, err := c.matchMirror(cmd.Arg(0))
	if err != nil {
		return err
	}
	if len(list) == 0 {
		fmt.Fprintf(os.Stderr, "No match for %s\n", cmd.Arg(0))
		return nil
	} else if len(list) > 1 {
		for _, e := range list {
			fmt.Fprintf(os.Stderr, "%s\n", e)
		}
		return nil
	}

	identifier := list[0]

	r := database.NewRedis()
	conn, err := r.Connect()
	if err != nil {
		log.Fatal("Redis: ", err)
	}
	defer conn.Close()

	// First disable the mirror
	mirrors.DisableMirror(r, identifier)

	// Get all files supported by the given mirror
	files, err := redis.Strings(conn.Do("SMEMBERS", fmt.Sprintf("MIRROR_%s_FILES", identifier)))
	if err != nil {
		log.Fatal("Error: Cannot fetch file list: ", err)
	}

	conn.Send("MULTI")

	// Remove each FILEINFO / FILEMIRRORS
	for _, file := range files {
		conn.Send("DEL", fmt.Sprintf("FILEINFO_%s_%s", identifier, file))
		conn.Send("SREM", fmt.Sprintf("FILEMIRRORS_%s", file), identifier)
		conn.Send("PUBLISH", database.MIRROR_FILE_UPDATE, fmt.Sprintf("%s %s", identifier, file))
	}

	_, err = conn.Do("EXEC")
	if err != nil {
		log.Fatal("Error: FILEINFO/FILEMIRRORS keys could not be removed: ", err)
	}

	// Remove all other keys
	_, err = conn.Do("DEL",
		fmt.Sprintf("MIRROR_%s", identifier),
		fmt.Sprintf("MIRROR_%s_FILES", identifier),
		fmt.Sprintf("MIRROR_%s_FILES_TMP", identifier),
		fmt.Sprintf("HANDLEDFILES_%s", identifier),
		fmt.Sprintf("SCANNING_%s", identifier))

	if err != nil {
		log.Fatal("Error: MIRROR keys could not be removed: ", err)
	}

	// Remove the last reference
	_, err = conn.Do("LREM", "MIRRORS", 0, identifier)

	if err != nil {
		log.Fatal("Error: Could not remove the reference from key MIRRORS")
	}

	// Publish update
	database.Publish(conn, database.MIRROR_UPDATE, identifier)

	fmt.Println("Mirror removed successfully")
	return nil
}
예제 #9
0
func (c *cli) CmdAdd(args ...string) error {
	cmd := SubCmd("add", "[OPTIONS] IDENTIFIER", "Add a new mirror")
	http := cmd.String("http", "", "HTTP base URL")
	rsync := cmd.String("rsync", "", "RSYNC base URL (for scanning only)")
	ftp := cmd.String("ftp", "", "FTP base URL (for scanning only)")
	sponsorName := cmd.String("sponsor-name", "", "Name of the sponsor")
	sponsorURL := cmd.String("sponsor-url", "", "URL of the sponsor")
	sponsorLogo := cmd.String("sponsor-logo", "", "URL of a logo to display for this mirror")
	adminName := cmd.String("admin-name", "", "Admin's name")
	adminEmail := cmd.String("admin-email", "", "Admin's email")
	customData := cmd.String("custom-data", "", "Associated data to return when the mirror is selected (i.e. json document)")
	continentOnly := cmd.Bool("continent-only", false, "The mirror should only handle its continent")
	countryOnly := cmd.Bool("country-only", false, "The mirror should only handle its country")
	asOnly := cmd.Bool("as-only", false, "The mirror should only handle clients in the same AS number")
	score := cmd.Int("score", 0, "Weight to give to the mirror during selection")
	comment := cmd.String("comment", "", "Comment")

	if err := cmd.Parse(args); err != nil {
		return nil
	}
	if cmd.NArg() < 1 {
		cmd.Usage()
		return nil
	}

	if strings.Contains(cmd.Arg(0), " ") {
		fmt.Fprintf(os.Stderr, "The identifier cannot contain a space\n")
		os.Exit(-1)
	}

	if *http == "" {
		fmt.Fprintf(os.Stderr, "You *must* pass at least an HTTP URL\n")
		os.Exit(-1)
	}

	if !strings.HasPrefix(*http, "http://") && !strings.HasPrefix(*http, "https://") {
		*http = "http://" + *http
	}

	u, err := url.Parse(*http)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Can't parse HTTP url\n")
		os.Exit(-1)
	}

	ip, err := network.LookupMirrorIP(u.Host)
	if err == network.ErrMultipleAddresses {
		fmt.Fprintf(os.Stderr, "Warning: the hostname returned more than one address! This is highly unreliable.\n")
	} else if err != nil {
		log.Fatal("IP lookup failed: ", err.Error())
	}

	geo := network.NewGeoIP()
	if err := geo.LoadGeoIP(); err != nil {
		log.Fatal(err.Error())
	}

	geoRec := geo.GetRecord(ip)

	r := database.NewRedis()
	conn, err := r.Connect()
	if err != nil {
		log.Fatal("Redis: ", err)
	}
	defer conn.Close()

	key := fmt.Sprintf("MIRROR_%s", cmd.Arg(0))
	exists, err := redis.Bool(conn.Do("EXISTS", key))
	if err != nil {
		return err
	}
	if exists {
		fmt.Fprintf(os.Stderr, "Mirror %s already exists!\n", cmd.Arg(0))
		os.Exit(-1)
	}

	// Normalize the URLs
	if http != nil {
		*http = utils.NormalizeURL(*http)
	}
	if rsync != nil {
		*rsync = utils.NormalizeURL(*rsync)
	}
	if ftp != nil {
		*ftp = utils.NormalizeURL(*ftp)
	}

	var latitude, longitude float32
	var continentCode, countryCode string

	if geoRec.GeoIPRecord != nil {
		latitude = geoRec.GeoIPRecord.Latitude
		longitude = geoRec.GeoIPRecord.Longitude
		continentCode = geoRec.GeoIPRecord.ContinentCode
		countryCode = geoRec.GeoIPRecord.CountryCode
	} else {
		fmt.Fprintf(os.Stderr, "Warning: unable to guess the geographic location of %s\n", cmd.Arg(0))
	}

	_, err = conn.Do("HMSET", key,
		"ID", cmd.Arg(0),
		"http", *http,
		"rsync", *rsync,
		"ftp", *ftp,
		"sponsorName", *sponsorName,
		"sponsorURL", *sponsorURL,
		"sponsorLogo", *sponsorLogo,
		"adminName", *adminName,
		"adminEmail", *adminEmail,
		"customData", *customData,
		"continentOnly", *continentOnly,
		"countryOnly", *countryOnly,
		"asOnly", *asOnly,
		"score", *score,
		"latitude", fmt.Sprintf("%f", latitude),
		"longitude", fmt.Sprintf("%f", longitude),
		"continentCode", continentCode,
		"countryCodes", countryCode,
		"asnum", geoRec.ASNum,
		"comment", strings.TrimSpace(*comment),
		"enabled", false,
		"up", false)
	if err != nil {
		goto oops
	}

	_, err = conn.Do("LPUSH", "MIRRORS", cmd.Arg(0))
	if err != nil {
		goto oops
	}

	// Publish update
	database.Publish(conn, database.MIRROR_UPDATE, cmd.Arg(0))

	fmt.Println("Mirror added successfully")
	return nil
oops:
	fmt.Fprintf(os.Stderr, "Oops: %s", err)
	os.Exit(-1)
	return nil
}
예제 #10
0
func (c *cli) CmdList(args ...string) error {
	cmd := SubCmd("list", "", "Get the list of mirrors")
	http := cmd.Bool("http", false, "Print HTTP addresses")
	rsync := cmd.Bool("rsync", false, "Print rsync addresses")
	ftp := cmd.Bool("ftp", false, "Print FTP addresses")
	location := cmd.Bool("location", false, "Print the country and continent code")
	state := cmd.Bool("state", true, "Print the state of the mirror")
	disabled := cmd.Bool("disabled", false, "List disabled mirrors only")
	enabled := cmd.Bool("enabled", false, "List enabled mirrors only")
	down := cmd.Bool("down", false, "List only mirrors currently down")

	if err := cmd.Parse(args); err != nil {
		return nil
	}
	if cmd.NArg() != 0 {
		cmd.Usage()
		return nil
	}

	r := database.NewRedis()
	conn, err := r.Connect()
	if err != nil {
		log.Fatal("Redis: ", err)
	}
	defer conn.Close()

	mirrorsIDs, err := redis.Strings(conn.Do("LRANGE", "MIRRORS", "0", "-1"))
	if err != nil {
		log.Fatal("Cannot fetch the list of mirrors: ", err)
	}

	conn.Send("MULTI")

	for _, e := range mirrorsIDs {
		conn.Send("HGETALL", fmt.Sprintf("MIRROR_%s", e))
	}

	res, err := redis.Values(conn.Do("EXEC"))
	if err != nil {
		log.Fatal("Redis: ", err)
	}

	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 0, '\t', 0)
	fmt.Fprint(w, "Identifier ")
	if *http == true {
		fmt.Fprint(w, "\tHTTP ")
	}
	if *rsync == true {
		fmt.Fprint(w, "\tRSYNC ")
	}
	if *ftp == true {
		fmt.Fprint(w, "\tFTP ")
	}
	if *location == true {
		fmt.Fprint(w, "\tLOCATION ")
	}
	if *state == true {
		fmt.Fprint(w, "\tSTATE\tSINCE")
	}
	fmt.Fprint(w, "\n")

	for _, e := range res {
		var mirror mirrors.Mirror
		res, ok := e.([]interface{})
		if !ok {
			log.Fatal("Typecast failed")
		} else {
			err := redis.ScanStruct([]interface{}(res), &mirror)
			if err != nil {
				log.Fatal("ScanStruct:", err)
			}
			if *disabled == true {
				if mirror.Enabled == true {
					continue
				}
			}
			if *enabled == true {
				if mirror.Enabled == false {
					continue
				}
			}
			if *down == true {
				if mirror.Up == true {
					continue
				}
			}
			fmt.Fprintf(w, "%s ", mirror.ID)
			if *http == true {
				fmt.Fprintf(w, "\t%s ", mirror.HttpURL)
			}
			if *rsync == true {
				fmt.Fprintf(w, "\t%s ", mirror.RsyncURL)
			}
			if *ftp == true {
				fmt.Fprintf(w, "\t%s ", mirror.FtpURL)
			}
			if *location == true {
				countries := strings.Split(mirror.CountryCodes, " ")
				countryCode := "/"
				if len(countries) >= 1 {
					countryCode = countries[0]
				}
				fmt.Fprintf(w, "\t%s (%s) ", countryCode, mirror.ContinentCode)
			}
			if *state == true {
				if mirror.Enabled == false {
					fmt.Fprintf(w, "\tdisabled")
				} else if mirror.Up == true {
					fmt.Fprintf(w, "\tup")
				} else {
					fmt.Fprintf(w, "\tdown")
				}
				fmt.Fprintf(w, " \t(%s)", time.Unix(mirror.StateSince, 0).Format(time.RFC1123))
			}
			fmt.Fprint(w, "\n")
		}
	}

	w.Flush()

	return nil
}
예제 #11
0
func (c *cli) CmdStats(args ...string) error {
	cmd := SubCmd("stats", "[OPTIONS] [mirror|file] [IDENTIFIER|PATTERN]", "Show download stats for a particular mirror or a file pattern")
	dateStart := cmd.String("start-date", "", "Starting date (format YYYY-MM-DD)")
	dateEnd := cmd.String("end-date", "", "Ending date (format YYYY-MM-DD)")
	human := cmd.Bool("h", true, "Human readable version")

	if err := cmd.Parse(args); err != nil {
		return nil
	}
	if cmd.NArg() != 2 || (cmd.Arg(0) != "mirror" && cmd.Arg(0) != "file") {
		cmd.Usage()
		return nil
	}

	r := database.NewRedis()
	conn, err := r.Connect()
	if err != nil {
		log.Fatal("Redis: ", err)
	}
	defer conn.Close()

	start, err := time.Parse("2006-1-2", *dateStart)
	if err != nil {
		start = time.Now()
	}

	end, err := time.Parse("2006-1-2", *dateEnd)
	if err != nil {
		end = time.Now()
	}

	tkcoverage := utils.TimeKeyCoverage(start, end)
	for _, b := range tkcoverage {
		log.Debugf("Requesting %s", b)
	}

	if cmd.Arg(0) == "file" {
		// File stats

		re, err := regexp.Compile(cmd.Arg(1))
		if err != nil {
			return err
		}

		conn.Send("MULTI")

		for _, k := range tkcoverage {
			conn.Send("HGETALL", "STATS_FILE_"+k)
		}

		stats, err := redis.Values(conn.Do("EXEC"))
		if err != nil {
			log.Critical("Cannot fetch stats: %s", err)
			return err
		}

		var requests int64
		m := make(map[string]int64)

		for _, res := range stats {
			line, ok := res.([]interface{})
			if !ok {
				log.Fatal("Typecast failed")
			} else {
				stats := []interface{}(line)
				for i := 0; i < len(stats); i += 2 {
					path, _ := redis.String(stats[i], nil)
					matched := re.MatchString(path)
					if err != nil {
						log.Error(err.Error())
					} else if matched {
						reqs, _ := redis.Int64(stats[i+1], nil)
						m[path] += reqs
						requests += reqs
					}
				}
			}
		}

		// Format the results
		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 0, 8, 0, '\t', 0)

		// Sort keys
		var keys []string
		for k := range m {
			keys = append(keys, k)
		}
		sort.Strings(keys)

		for _, k := range keys {
			fmt.Fprintf(w, "%s:\t%d\n", k, m[k])
		}

		if len(keys) > 0 {
			// Add a line separator
			fmt.Fprintf(w, "\t\n")
		}

		fmt.Fprintf(w, "Total download requests:\t%d\n", requests)
		w.Flush()
	} else if cmd.Arg(0) == "mirror" {
		// Mirror stats

		list, err := c.matchMirror(cmd.Arg(1))
		if err != nil {
			return err
		}
		if len(list) == 0 {
			fmt.Fprintf(os.Stderr, "No match for mirror %s\n", cmd.Arg(1))
			return nil
		} else if len(list) > 1 {
			for _, e := range list {
				fmt.Fprintf(os.Stderr, "%s\n", e)
			}
			return nil
		}

		conn.Send("MULTI")

		// Fetch the stats
		for _, k := range tkcoverage {
			conn.Send("HGET", "STATS_MIRROR_"+k, list[0])
			conn.Send("HGET", "STATS_MIRROR_BYTES_"+k, list[0])
		}

		stats, err := redis.Strings(conn.Do("EXEC"))
		if err != nil {
			log.Critical("Cannot fetch stats: %s", err)
			return err
		}

		// Fetch the mirror struct
		m, err := redis.Values(conn.Do("HGETALL", fmt.Sprintf("MIRROR_%s", list[0])))
		if err != nil {
			fmt.Fprintf(os.Stderr, "Cannot fetch mirror details: %s\n", err)
			return err
		}

		var mirror mirrors.Mirror
		err = redis.ScanStruct(m, &mirror)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Cannot fetch mirror details: %s\n", err)
			return err
		}

		var (
			requests int64
			bytes    int64
		)

		for i := 0; i < len(stats); i += 2 {
			v1, _ := strconv.ParseInt(stats[i], 10, 64)
			v2, _ := strconv.ParseInt(stats[i+1], 10, 64)
			requests += v1
			bytes += v2
		}

		// Format the results
		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 0, 8, 0, '\t', 0)

		fmt.Fprintf(w, "Identifier:\t%s\n", list[0])
		if !mirror.Enabled {
			fmt.Fprintf(w, "Status:\tdisabled\n")
		} else if mirror.Up {
			fmt.Fprintf(w, "Status:\tup\n")
		} else {
			fmt.Fprintf(w, "Status:\tdown\n")
		}
		fmt.Fprintf(w, "Download requests:\t%d\n", requests)
		fmt.Fprint(w, "Bytes transfered:\t")
		if *human {
			fmt.Fprintln(w, utils.ReadableSize(bytes))
		} else {
			fmt.Fprintln(w, bytes)
		}
		w.Flush()
	}

	return nil
}
예제 #12
0
파일: main.go 프로젝트: wsnipex/mirrorbits
func main() {

	if core.CpuProfile != "" {
		f, err := os.Create(core.CpuProfile)
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	if core.Daemon {
		process.WritePidFile()

		// Show our nice welcome logo
		fmt.Printf(core.Banner+"\n\n", core.VERSION)

		/* Connect to the database */
		r := database.NewRedis()
		r.ConnectPubsub()
		c := mirrors.NewCache(r)
		h := http.HTTPServer(r, c)

		if r.CheckVersion() == database.ErrUpgradeRequired {
			log.Fatalf("Unsupported Redis version, please upgrade to Redis >= %s", database.RedisMinimumVersion)
			return
		}

		/* Start the background monitor */
		m := daemon.NewMonitor(r, c)
		if core.Monitor {
			go m.MonitorLoop()
		}

		/* Handle SIGNALS */
		k := make(chan os.Signal, 1)
		signal.Notify(k,
			syscall.SIGINT,  // Terminate
			syscall.SIGTERM, // Terminate
			syscall.SIGQUIT, // Stop gracefully
			syscall.SIGHUP,  // Reload config
			syscall.SIGUSR1, // Reopen log files
			syscall.SIGUSR2, // Seamless binary upgrade
		)
		go func() {
			for {
				sig := <-k
				switch sig {
				case syscall.SIGINT:
					fallthrough
				case syscall.SIGTERM:
					process.RemovePidFile()
					os.Exit(0)
				case syscall.SIGQUIT:
					m.Stop()
					if h.Listener != nil {
						log.Notice("Waiting for running tasks to finish...")
						h.Stop(5 * time.Second)
					} else {
						process.RemovePidFile()
						os.Exit(0)
					}
				case syscall.SIGHUP:
					listenAddress := GetConfig().ListenAddress
					if err := ReloadConfig(); err != nil {
						log.Warningf("SIGHUP Received: %s\n", err)
					} else {
						log.Notice("SIGHUP Received: Reloading configuration...")
					}
					if GetConfig().ListenAddress != listenAddress {
						h.Restarting = true
						h.Stop(1 * time.Second)
					}
					h.Reload()
				case syscall.SIGUSR1:
					log.Notice("SIGUSR1 Received: Re-opening logs...")
					logs.ReloadLogs()
				case syscall.SIGUSR2:
					log.Notice("SIGUSR2 Received: Seamless binary upgrade...")
					err := process.Relaunch(*h.Listener)
					if err != nil {
						log.Errorf("Relaunch failed: %s\n", err)
					} else {
						m.Stop()
						h.Stop(10 * time.Second)
					}
				}
			}
		}()

		// Recover an existing listener (see process.go)
		if l, ppid, err := process.Recover(); err == nil {
			h.SetListener(l)
			go func() {
				time.Sleep(500 * time.Millisecond)
				process.KillParent(ppid)
			}()
		}

		/* Finally start the HTTP server */
		var err error
		for {
			err = h.RunServer()
			if h.Restarting {
				h.Restarting = false
				continue
			}
			// This check is ugly but there's still no way to detect this error by type
			if err != nil && strings.Contains(err.Error(), "use of closed network connection") {
				// This error is expected during a graceful shutdown
				err = nil
			}
			break
		}

		log.Debug("Waiting for monitor termination")
		m.Wait()

		log.Debug("Terminating server")
		h.Terminate()

		r.Close()

		process.RemovePidFile()

		if err != nil {
			log.Fatal(err)
		} else {
			log.Notice("Server stopped gracefully.")
		}
	} else {
		if err := cli.ParseCommands(core.Args()...); err != nil {
			log.Fatal(err)
		}
	}
	os.Exit(0)
}