diff --git a/README.md b/README.md
index 0c8de42e0b02e7e454badc5b36eed5e3bff734b4..8a99fdaf2935791d272f35843f3c192c6c99dafb 100644
--- a/README.md
+++ b/README.md
@@ -21,13 +21,11 @@ Usage of gqgmcd:
       --listen-address string   Address for HTTP requests (default ":8080")
       --model string            Model of Geiger Counter (default "gqgmc")
       --sleep-cycle int         Seconds to sleep per cycle. (default 5)
-      --static-dir string       Static files directory (default "static")
-      --template-dir string     Template directory (default "templates")
 ```
 
 The config file uses the same variables (not `config` obviously)
-but with any dashes replaced with underscores. So
-`static_dir = /var/lib/gqgmcd/static` for instance.
+but with dashes replaced with underscores. So `sleep-cycle = 10`
+for instance.
 
 ## Prometheus variables
 
diff --git a/cmd/gqgmcd/main.go b/cmd/gqgmcd/main.go
index fba7492013d7043239365c19a31ea57b964f4a1e..7cd474bc2947323695a9956b4b0afdb92ab36e37 100644
--- a/cmd/gqgmcd/main.go
+++ b/cmd/gqgmcd/main.go
@@ -19,13 +19,11 @@ import (
 )
 
 var (
-	addr        = flag.String("listen-address", ":8080", "Address for HTTP requests")
-	device      = flag.String("device", "/dev/gqgmc", "Device for Geiger Counter")
-	model       = flag.String("model", "gqgmc", "Model of Geiger Counter")
-	templateDir = flag.String("template-dir", "templates", "Template directory")
-	staticDir   = flag.String("static-dir", "static", "Static files directory")
-	sleepCycle  = flag.Int64("sleep-cycle", 5, "Seconds to sleep per cycle.")
-	cfg         = flag.String("config", "gqgmc.conf", "Config file")
+	addr       = flag.String("listen-address", ":8080", "Address for HTTP requests")
+	device     = flag.String("device", "/dev/gqgmc", "Device for Geiger Counter")
+	model      = flag.String("model", "gqgmc", "Model of Geiger Counter")
+	sleepCycle = flag.Int64("sleep-cycle", 5, "Seconds to sleep per cycle.")
+	cfg        = flag.String("config", "", "Config file")
 )
 
 func main() {
@@ -36,12 +34,17 @@ func main() {
 		return
 	}
 
-	gc, _ := geiger.New(geiger.Config{Model: c.Model, Device: c.Device})
+	gc, gcErr := geiger.New(geiger.Config{Model: c.Model, Device: c.Device})
+	if gcErr != nil {
+		log.Printf("Error: %s\n", gcErr)
+	}
 
-	p := pages.New(gc, c.StaticDir, c.TemplateDir)
+	p := pages.New(gc, gcErr)
 	p.Register()
-	m := metrics.Register(gc)
 
-	go m.Gather(c.SleepCycle)
+	if gcErr == nil {
+		m := metrics.Register(gc)
+		go m.Gather(c.SleepCycle)
+	}
 	log.Fatal(http.ListenAndServe(c.ListenAddress, nil))
 }
diff --git a/config/config.go b/config/config.go
index ee482474a7e66640b2ae0a941838dd9c947764b9..0683b15d18c2ce92e1896dc0727068a59c71b1f2 100644
--- a/config/config.go
+++ b/config/config.go
@@ -17,8 +17,6 @@ type Config struct {
 	ListenAddress string `mapstructure:"listen_address"`
 	Device        string `mapstructure:"device"`
 	Model         string `mapstructure:"model"`
-	TemplateDir   string `mapstructure:"template_dir"`
-	StaticDir     string `mapstructure:"static_dir"`
 	SleepCycle    int64  `mapstructure:"sleep_cycle"`
 }
 
@@ -26,18 +24,18 @@ func setDefaults() {
 	viper.BindPFlag("listen_address", pflag.Lookup("listen-address"))
 	viper.BindPFlag("device", pflag.Lookup("device"))
 	viper.BindPFlag("model", pflag.Lookup("model"))
-	viper.BindPFlag("template_dir", pflag.Lookup("template-dir"))
-	viper.BindPFlag("static_dir", pflag.Lookup("static-dir"))
 	viper.BindPFlag("sleep_cycle", pflag.Lookup("sleep-cycle"))
 }
 
 // ReadConfig reads the client configuration from a file into a Config struct.
 func ReadConfig(cfg string) (*Config, error) {
 	setDefaults()
-	viper.SetConfigFile(cfg)
-	viper.SetConfigType("hcl")
-	if err := viper.ReadInConfig(); err != nil {
-		return nil, err
+	if cfg != "" {
+		viper.SetConfigFile(cfg)
+		viper.SetConfigType("hcl")
+		if err := viper.ReadInConfig(); err != nil {
+			return nil, err
+		}
 	}
 	c := &Config{}
 	if err := viper.Unmarshal(c); err != nil {
diff --git a/run_tests.sh b/run_tests.sh
index 10667104b5d32357fed8c380725bb2161ff8540a..4fc4b27173945e27b1fc25028a707b6b0581be74 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -6,6 +6,7 @@
 
 set -xue
 
+go generate ./...
 go install -v ./cmd/*
 go list ./... |grep -v vendor/ |xargs go test
 gofmt -d $(find * -type f -name '*.go' -not -path 'vendor/*')
diff --git a/server/pages/pages.go b/server/pages/pages.go
index ef62b6b37c0b207ba20e717978053b2ca8f4ab52..f002edb35aea5b5f45a1f6bbd4947b3272664d22 100644
--- a/server/pages/pages.go
+++ b/server/pages/pages.go
@@ -5,22 +5,23 @@
 // Distributed under terms of the MIT license.
 //
 
+//go:generate go get -u github.com/mjibson/esc
+//go:generate esc -o static.go -pkg pages static templates
+
 package pages
 
 import (
 	"html/template"
 	"log"
 	"net/http"
-	"path"
 
 	"gitlab.com/lyda/gqgmc/devices/geiger"
 )
 
 // Pages where data for pages goes
 type Pages struct {
-	gc geiger.Counter
-	staticDir,
-	templateDir string
+	gc    geiger.Counter
+	gcErr error
 }
 
 type indexPage struct {
@@ -32,36 +33,40 @@ type indexPage struct {
 }
 
 // New create new Pages.
-func New(gc geiger.Counter, staticDir, templateDir string) Pages {
-	return Pages{gc: gc, staticDir: staticDir, templateDir: templateDir}
+func New(gc geiger.Counter, gcErr error) Pages {
+	return Pages{gc: gc, gcErr: gcErr}
 }
 
 // Register pages.
 func (p Pages) Register() {
 	http.HandleFunc("/", p.indexHandler)
-	http.HandleFunc("/favicon.ico", p.staticHandler)
-	http.HandleFunc("/robots.txt", p.staticHandler)
-	http.HandleFunc("/humans.txt", p.staticHandler)
-	http.Handle("/static", http.StripPrefix("/static/", http.FileServer(http.Dir(p.staticDir))))
+	http.Handle("/favicon.ico", http.FileServer(Dir(false, "/static/")))
+	http.Handle("/robots.txt", http.FileServer(Dir(false, "/static/")))
+	http.Handle("/humans.txt", http.FileServer(Dir(false, "/static/")))
 }
 
 func (p Pages) indexHandler(w http.ResponseWriter, r *http.Request) {
 	var indexPg indexPage
 
-	indexPg.CPM, _ = p.gc.GetCPM()
-	indexPg.Volts, _ = p.gc.Volts()
-	indexPg.Model = p.gc.Model()
-	indexPg.Version = p.gc.Version()
-	indexPg.Serial = p.gc.Serial()
+	if p.gcErr == nil {
+		indexPg.CPM, _ = p.gc.GetCPM()
+		indexPg.Volts, _ = p.gc.Volts()
+		indexPg.Model = p.gc.Model()
+		indexPg.Version = p.gc.Version()
+		indexPg.Serial = p.gc.Serial()
+	} else {
+		indexPg.CPM = 0
+		indexPg.Volts = 0
+		indexPg.Model = "ERROR"
+		indexPg.Version = "ERROR"
+		indexPg.Serial = "ERROR"
+	}
 
-	t, err := template.ParseFiles(path.Join(p.templateDir, "index.html"))
+	index, _ := FSString(false, "index.html")
+	t, err := template.New("index.html").Parse(index)
 	if err != nil {
 		log.Printf("Template error: %s\n", err)
+		return // TODO: 404
 	}
 	t.Execute(w, &indexPg)
 }
-
-func (p Pages) staticHandler(w http.ResponseWriter, r *http.Request) {
-	staticFile := path.Join(p.staticDir, path.Base(r.URL.Path))
-	http.ServeFile(w, r, staticFile)
-}
diff --git a/server/pages/static.go b/server/pages/static.go
new file mode 100644
index 0000000000000000000000000000000000000000..ebb7613adb01ee332465fc1ecdfd15a7fbb778df
--- /dev/null
+++ b/server/pages/static.go
@@ -0,0 +1,256 @@
+package pages
+
+import (
+	"bytes"
+	"compress/gzip"
+	"encoding/base64"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path"
+	"sync"
+	"time"
+)
+
+type _escLocalFS struct{}
+
+var _escLocal _escLocalFS
+
+type _escStaticFS struct{}
+
+var _escStatic _escStaticFS
+
+type _escDirectory struct {
+	fs   http.FileSystem
+	name string
+}
+
+type _escFile struct {
+	compressed string
+	size       int64
+	modtime    int64
+	local      string
+	isDir      bool
+
+	once sync.Once
+	data []byte
+	name string
+}
+
+func (_escLocalFS) Open(name string) (http.File, error) {
+	f, present := _escData[path.Clean(name)]
+	if !present {
+		return nil, os.ErrNotExist
+	}
+	return os.Open(f.local)
+}
+
+func (_escStaticFS) prepare(name string) (*_escFile, error) {
+	f, present := _escData[path.Clean(name)]
+	if !present {
+		return nil, os.ErrNotExist
+	}
+	var err error
+	f.once.Do(func() {
+		f.name = path.Base(name)
+		if f.size == 0 {
+			return
+		}
+		var gr *gzip.Reader
+		b64 := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(f.compressed))
+		gr, err = gzip.NewReader(b64)
+		if err != nil {
+			return
+		}
+		f.data, err = ioutil.ReadAll(gr)
+	})
+	if err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+func (fs _escStaticFS) Open(name string) (http.File, error) {
+	f, err := fs.prepare(name)
+	if err != nil {
+		return nil, err
+	}
+	return f.File()
+}
+
+func (dir _escDirectory) Open(name string) (http.File, error) {
+	return dir.fs.Open(dir.name + name)
+}
+
+func (f *_escFile) File() (http.File, error) {
+	type httpFile struct {
+		*bytes.Reader
+		*_escFile
+	}
+	return &httpFile{
+		Reader:   bytes.NewReader(f.data),
+		_escFile: f,
+	}, nil
+}
+
+func (f *_escFile) Close() error {
+	return nil
+}
+
+func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) {
+	return nil, nil
+}
+
+func (f *_escFile) Stat() (os.FileInfo, error) {
+	return f, nil
+}
+
+func (f *_escFile) Name() string {
+	return f.name
+}
+
+func (f *_escFile) Size() int64 {
+	return f.size
+}
+
+func (f *_escFile) Mode() os.FileMode {
+	return 0
+}
+
+func (f *_escFile) ModTime() time.Time {
+	return time.Unix(f.modtime, 0)
+}
+
+func (f *_escFile) IsDir() bool {
+	return f.isDir
+}
+
+func (f *_escFile) Sys() interface{} {
+	return f
+}
+
+// FS returns a http.Filesystem for the embedded assets. If useLocal is true,
+// the filesystem's contents are instead used.
+func FS(useLocal bool) http.FileSystem {
+	if useLocal {
+		return _escLocal
+	}
+	return _escStatic
+}
+
+// Dir returns a http.Filesystem for the embedded assets on a given prefix dir.
+// If useLocal is true, the filesystem's contents are instead used.
+func Dir(useLocal bool, name string) http.FileSystem {
+	if useLocal {
+		return _escDirectory{fs: _escLocal, name: name}
+	}
+	return _escDirectory{fs: _escStatic, name: name}
+}
+
+// FSByte returns the named file from the embedded assets. If useLocal is
+// true, the filesystem's contents are instead used.
+func FSByte(useLocal bool, name string) ([]byte, error) {
+	if useLocal {
+		f, err := _escLocal.Open(name)
+		if err != nil {
+			return nil, err
+		}
+		b, err := ioutil.ReadAll(f)
+		f.Close()
+		return b, err
+	}
+	f, err := _escStatic.prepare(name)
+	if err != nil {
+		return nil, err
+	}
+	return f.data, nil
+}
+
+// FSMustByte is the same as FSByte, but panics if name is not present.
+func FSMustByte(useLocal bool, name string) []byte {
+	b, err := FSByte(useLocal, name)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+// FSString is the string version of FSByte.
+func FSString(useLocal bool, name string) (string, error) {
+	b, err := FSByte(useLocal, name)
+	return string(b), err
+}
+
+// FSMustString is the string version of FSMustByte.
+func FSMustString(useLocal bool, name string) string {
+	return string(FSMustByte(useLocal, name))
+}
+
+var _escData = map[string]*_escFile{
+
+	"/static/favicon.ico": {
+		local:   "static/favicon.ico",
+		size:    1150,
+		modtime: 1485950788,
+		compressed: `
+H4sIAAAJbogA/6RSTevpYRCdf/13d6O7uGvL+yl8BFufgG5SspaFl0hJFt7Jy0okC6F8BLHAjqxkIcqS
+EHM7o3lC1+qemp7zm3PmaZ6ZH9EXfZHFgtNKf76JfhHRbyKyEJGVHnnBN9HPH49Q8BPu97vht9vtn/zZ
+847z+cyj0Yi3262p01rkoMHzDvU0m02u1+ucTqe51+sZHRw5aPB86imfz/PxeBReqVS43+9LgAPQ4Pn0
+1mKxyPv93mher1dC/dAKhcJLDc7r9Sq80+nwdDoVPpvNpGcEOAANHgA1esflcuHVasWpVIrdbjc3Gg22
+2+2cyWQkwJGDBg+8qNE7k8kkt1otns/nopdKJR4Oh+Z94OVyWTR44EUNakOh0MueMGuXyyXcZrNJAE6n
+0+xE/eFwmKPRKK/XazMzzFtn5vF4JHSW3W7XzBI1qN1sNtIvZj8YDDgSici+sGsFOHLBYFA88KIGtdo3
+dlur1TgQCMh7HQ6H3I8AR87v93O1WuXT6WTegV50lu12m5fLpfDdbmf2Bw4sFguZne5M36wnZnw4HMy3
+z+eTUA809P7+/ynP5XLSG+5OJBI8Ho8lwJGDls1mP/6/2E8sFuN4PM6TycTo4MhBe94hQP+JvwEAAP//
+uq6JNX4EAAA=
+`,
+	},
+
+	"/static/humans.txt": {
+		local:   "static/humans.txt",
+		size:    303,
+		modtime: 1485951109,
+		compressed: `
+H4sIAAAJbogA/0SOsWrzQBCE+3uK6X4wQuqvM7L5nUSOA6cm5SGt7cW6XXPas+O3D3IC6WY+mOFrVui3
+6z1WjXP9YXNwn1oyjG0ij2eWmKh2gY08KEWeKkwsF5giYlCxOBiOmlMFsqF2/Z3NKHs8lvVvQ5kp/zx1
+OkRjFY+W7VGh1SKWH7Vzi8tu/f4W/mwWFl767ZMEizLGPM4eHRkS4SJ6Bx9hZ5bTjJhJ/hluceIRu37f
+NW0IzWtwraarConNHv+1wkfWRHamMrugR7vHTB4butGkVxpRZpYTOpbyVeHGCVFGnNhq9x0AAP//oOY3
+by8BAAA=
+`,
+	},
+
+	"/static/robots.txt": {
+		local:   "static/robots.txt",
+		size:    44,
+		modtime: 1485950875,
+		compressed: `
+H4sIAAAJbogA/wotTi3SdUxPzSuxUtDicsksTszJyS+3UkBi6uemlhRlJhdzAQIAAP//2UBAzSwAAAA=
+`,
+	},
+
+	"/templates/index.html": {
+		local:   "templates/index.html",
+		size:    358,
+		modtime: 1485942389,
+		compressed: `
+H4sIAAAJbogA/3SQwUrEMBBAz8lXjL1rriJjLlE8FQuK4DHa0QbSBtLJYSn992UzhV0ozSUv4fFgBu9e
+3t3nd/cKA4/RapRL4UC+txq2gyOxh9/B55n4uSn8d//YgLFaKeTAkewbhX/K4FKZmDIa+dUKjZQU/qT+
+dC0CYIm3TwCMwbapp/gEy/JQaV3RxLDXvijPIU1V3PhI/aAcvCQFD5sp8izFCx1prmur5Lp2r6ApdXtG
+hkUj6zwHAAD//4sAPXxmAQAA
+`,
+	},
+
+	"/": {
+		isDir: true,
+		local: "",
+	},
+
+	"/static": {
+		isDir: true,
+		local: "static",
+	},
+
+	"/templates": {
+		isDir: true,
+		local: "templates",
+	},
+}
diff --git a/static/favicon.ico b/server/pages/static/favicon.ico
similarity index 100%
rename from static/favicon.ico
rename to server/pages/static/favicon.ico
diff --git a/static/humans.txt b/server/pages/static/humans.txt
similarity index 100%
rename from static/humans.txt
rename to server/pages/static/humans.txt
diff --git a/static/robots.txt b/server/pages/static/robots.txt
similarity index 100%
rename from static/robots.txt
rename to server/pages/static/robots.txt
diff --git a/templates/index.html b/server/pages/templates/index.html
similarity index 100%
rename from templates/index.html
rename to server/pages/templates/index.html