import (
"bytes"
+ "crypto/sha512"
+ "encoding/hex"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"syscall"
+ "time"
"github.com/pkg/errors"
)
func handleFiles(cfg *Config, state *State) error {
+ // Split into fetch and deploy phase to prevent updates of only some
+ // files which might lead to inconsistent state; obviously this won't
+ // work during the deploy phase, but it helps if the web server fails
+ // to deliver some files
+
for i, f := range cfg.Files {
err := fetchFile(&cfg.Files[i], state)
if err != nil {
- return errors.Wrapf(err, "%q (%s)", f.Url, f.Type)
+ return errors.Wrapf(err, "%q (%v)", f.Url, f.Type)
}
}
err := deployFile(&cfg.Files[i])
if err != nil {
- return errors.Wrapf(err, "%q (%s)", f.Url, f.Type)
+ return errors.Wrapf(err, "%q (%v)", f.Url, f.Type)
}
}
return nil
}
+func checksumFile(file *File) (string, error) {
+ x, err := ioutil.ReadFile(file.Path)
+ if err != nil {
+ return "", err
+ }
+ return checksumBytes(x), nil
+}
+
+func checksumBytes(x []byte) string {
+ h := sha512.New()
+ h.Write(x)
+ return hex.EncodeToString(h.Sum(nil))
+}
+
func fetchFile(file *File, state *State) error {
t := state.LastModified[file.Url]
+
+ hash, err := checksumFile(file)
+ if err != nil {
+ // See below in deployFile() for the reason
+ return errors.Wrapf(err, "file.path %q must exist", file.Path)
+ }
+ if hash != state.Checksum[file.Url] {
+ log.Printf("%q -> %q: hash has changed", file.Url, file.Path)
+ var zero time.Time
+ t = zero // force download
+ }
+
status, body, err := fetchIfModified(file.Url, &t)
if err != nil {
return err
} else {
return fmt.Errorf("unsupported file type %v", file.Type)
}
+
+ state.Checksum[file.Url] = checksumBytes(file.body)
return nil
}
defer os.Remove(f.Name())
defer f.Close()
- // Apply permissions/user/group from the target file
+ // Apply permissions/user/group from the target file but remove the
+ // write permissions to discourage manual modifications, use Stat
+ // instead of Lstat as only the target's permissions are relevant
stat, err := os.Stat(file.Path)
if err != nil {
// We do not create the path if it doesn't exist, because we
// do not know the proper permissions
return errors.Wrapf(err, "file.path %q must exist", file.Path)
}
- err = f.Chmod(stat.Mode())
+ err = f.Chmod(stat.Mode() & ^os.FileMode(0222)) // remove write perms
if err != nil {
return err
}