Last active
December 3, 2020 11:09
-
-
Save alissonbrunosa/853d99f86854bddfc2a2bf51ad7a95f6 to your computer and use it in GitHub Desktop.
A small program to set your timezone based on your IP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"encoding/json" | |
"errors" | |
"fmt" | |
"net/http" | |
"os" | |
"path/filepath" | |
"sync" | |
"time" | |
) | |
var httpClient = &http.Client{ | |
Timeout: 30 * time.Second, | |
Transport: http.DefaultTransport, | |
} | |
type Services []string | |
var services = Services{ | |
"https://ipapi.co/json", | |
"http://ip-api.com/json", | |
"https://freegeoip.app/json/", | |
"http://worldtimeapi.org/api/ip", | |
} | |
const ( | |
DefaulLocalTimePath = "/etc/localtime" | |
DefaultZoneInfoPath = "/usr/share/zoneinfo" | |
) | |
type response struct { | |
Timezone string `json:"timezone"` | |
} | |
func main() { | |
ctx := context.Background() | |
resp, err := getTimezone(ctx, services) | |
if err != nil { | |
fmt.Printf("Could not retrieve any timezone from the services: %v\n", err) | |
os.Exit(1) | |
} | |
if err := setTimezone(resp.Timezone); err != nil { | |
fmt.Fprintln(os.Stderr, err) | |
if errors.Is(err, os.ErrPermission) { | |
fmt.Fprintln(os.Stderr, "Are you root?") | |
} | |
os.Exit(1) | |
} | |
fmt.Printf("Timezone updated to %s\n", resp.Timezone) | |
os.Exit(0) | |
} | |
func getTimezone(ctx context.Context, pool Services) (*response, error) { | |
ctx, cancel := context.WithCancel(ctx) | |
defer cancel() | |
var ( | |
resp *response | |
lastErr error | |
mu sync.Mutex | |
) | |
for _, service := range pool { | |
go func(s string) { | |
r, err := lookup(ctx, s) | |
switch err { | |
case nil: | |
cancel() | |
mu.Lock() | |
defer mu.Unlock() | |
resp = r | |
default: | |
mu.Lock() | |
defer mu.Unlock() | |
lastErr = err | |
} | |
}(service) | |
} | |
<-ctx.Done() | |
if err := ctx.Err(); err == context.DeadlineExceeded { | |
return nil, err | |
} | |
if resp == nil { | |
return nil, fmt.Errorf("cannot reach any service: %w", lastErr) | |
} | |
return resp, nil | |
} | |
func setTimezone(timezone string) error { | |
tzPath := filepath.Join(DefaultZoneInfoPath, timezone) | |
if _, err := os.Stat(tzPath); os.IsNotExist(err) { | |
return fmt.Errorf("Timezone %s not supported", timezone) | |
} else if err != nil { | |
return fmt.Errorf("unexpected error: %w", err) | |
} | |
if err := os.Remove(DefaulLocalTimePath); err != nil { | |
return fmt.Errorf("could not remove current timezone: %w", err) | |
} | |
if err := os.Symlink(tzPath, DefaulLocalTimePath); err != nil { | |
return fmt.Errorf("could not create symlink: %w", err) | |
} | |
return nil | |
} | |
func lookup(ctx context.Context, service string) (*response, error) { | |
req, err := http.NewRequestWithContext(ctx, http.MethodGet, service, nil) | |
if err != nil { | |
return nil, fmt.Errorf("could not create a new request: %w", err) | |
} | |
res, err := httpClient.Do(req) | |
if err != nil { | |
return nil, fmt.Errorf("request failed: %w", err) | |
} | |
defer res.Body.Close() | |
response := new(response) | |
if err := json.NewDecoder(res.Body).Decode(response); err != nil { | |
return nil, fmt.Errorf("unable to decode JSON response: %w", err) | |
} | |
return response, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment