package main import ( "bufio" "fmt" "io/ioutil" "log" "os" "path" "path/filepath" "strings" "syscall" "text/template" ) // ACTIVE_SITES_PATH is within /etc/nginx so we can mount /srv/nginx-conf as // read-only const ( NGINX_CONF_PATH = "/srv/nginx-conf" SYSTEM_NGINX_CONF_PATH = "/etc/nginx" ACTIVE_SITES_PATH = "/etc/nginx/sites-enabled" ) type EnvMap map[string]string func (m EnvMap) Get(key string) string { if value, ok := m[key]; ok { return value } else { return "" } } func NewEnvMap() EnvMap { envs := EnvMap{} for _, env := range os.Environ() { split := strings.SplitN(env, "=", 2) if strings.HasPrefix(split[0], "NGINX_PP_") { envs[split[0]] = split[1] } } return envs } func processTemplate(filepath string) { fp, err := os.Open(filepath) if err != nil { log.Printf("error: reading template file %s: %s", filepath, err) return } defer fp.Close() data, err := ioutil.ReadAll(fp) if err != nil { log.Printf("error: reading template file %s: %s", filepath, err) return } t, err := template.New("tpl").Parse(string(data)) if err != nil { log.Printf("error: processing template file %s: %s", filepath, err) return } newPath := filepath[:len(filepath)-len(".tpl")] fo, err := os.OpenFile(newPath, os.O_RDWR|os.O_CREATE, 0644) if err != nil { log.Printf("error: reading template file %s: %s", filepath, err) return } defer fo.Close() if err = t.Execute(fo, NewEnvMap()); err != nil { log.Printf("error: processing template file %s: %s", filepath, err) fo.Close() _ = os.Remove(newPath) return } log.Printf("processed template: %s", filepath) } func shouldIncludeFile(filepath string, info os.FileInfo) bool { return !info.IsDir() && !strings.Contains(filepath, "/.git/") && info.Mode()&os.ModeType == 0 } func shouldLinkForProfile(filepath, profile string) bool { line := fmt.Sprintf("# preprocess: link_for %s", profile) fp, err := os.Open(filepath) if err != nil { log.Printf("error: reading config file %s", filepath) return false } defer fp.Close() scanner := bufio.NewScanner(fp) for scanner.Scan() { if strings.TrimSpace(scanner.Text()) == line { return true } } return false } type LinkWalker struct { ActiveSitesPath string ActiveProfile string } func (w *LinkWalker) Walk(filepath string, info os.FileInfo, err error) error { if !shouldIncludeFile(filepath, info) { return nil } if shouldLinkForProfile(filepath, w.ActiveProfile) { log.Printf("linking: %s", filepath) out_path := path.Join(w.ActiveSitesPath, path.Base(filepath)) _ = os.Symlink(filepath, out_path) } if strings.HasSuffix(filepath, ".tpl") { processTemplate(filepath) } return nil } func TemplateWalker(filepath string, info os.FileInfo, err error) error { if !shouldIncludeFile(filepath, info) { return nil } if strings.HasSuffix(filepath, ".tpl") { processTemplate(filepath) } return nil } func cleanLinkFarm(linkFarm string) { _ = os.RemoveAll(linkFarm) _ = os.Mkdir(linkFarm, 0755) } func main() { profile := os.Getenv("ACTIVE_PROFILE") if profile == "" { log.Fatalf("error: ACTIVE_PROFILE is not in environment") } cleanLinkFarm(ACTIVE_SITES_PATH) filepath.Walk(NGINX_CONF_PATH, (&LinkWalker{ACTIVE_SITES_PATH, profile}).Walk) filepath.Walk(SYSTEM_NGINX_CONF_PATH, TemplateWalker) log.Printf("running: %s %+v", os.Args[1], os.Args[1:]) if err := syscall.Exec(os.Args[1], os.Args[1:], []string{}); err != nil { log.Fatalf("error: exec failed %s", err) } }