summaryrefslogtreecommitdiff
path: root/caddy.go
blob: 585b9df8eeb30cbba2e96f0f24f5fa24ef704468 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
package caddy2

import (
	"encoding/json"
	"fmt"
	"log"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/mholt/certmagic"
)

// Run runs Caddy with the given config.
func Run(cfg *Config) error {
	// allow only one call to Start at a time,
	// since various calls to LoadModule()
	// access shared map moduleInstances
	startMu.Lock()
	defer startMu.Unlock()

	// because we will need to roll back any state
	// modifications if this function errors, we
	// keep a single error value and scope all
	// sub-operations to their own functions to
	// ensure this error value does not get
	// overridden or missed when it should have
	// been set by a short assignment
	var err error

	// prepare the new config for use
	cfg.apps = make(map[string]App)
	cfg.moduleStates = make(map[string]interface{})

	// reset the shared moduleInstances map; but
	// keep a temporary reference to the current
	// one so we can transfer over any necessary
	// state to the new modules or to roll back
	// if necessary
	oldModuleInstances := moduleInstances
	defer func() {
		if err != nil {
			moduleInstances = oldModuleInstances
		}
	}()
	moduleInstances = make(map[string][]interface{})

	// set up storage and make it CertMagic's default storage, too
	err = func() error {
		if cfg.StorageRaw != nil {
			val, err := LoadModuleInline("system", "caddy.storage", cfg.StorageRaw)
			if err != nil {
				return fmt.Errorf("loading storage module: %v", err)
			}
			stor, err := val.(StorageConverter).CertMagicStorage()
			if err != nil {
				return fmt.Errorf("creating storage value: %v", err)
			}
			cfg.storage = stor
			cfg.StorageRaw = nil // allow GC to deallocate - TODO: Does this help?
		}
		if cfg.storage == nil {
			cfg.storage = &certmagic.FileStorage{Path: dataDir()}
		}
		certmagic.Default.Storage = cfg.storage

		return nil
	}()
	if err != nil {
		return err
	}

	// Load, Provision, Validate
	err = func() error {
		for modName, rawMsg := range cfg.AppsRaw {
			val, err := LoadModule(modName, rawMsg)
			if err != nil {
				return fmt.Errorf("loading app module '%s': %v", modName, err)
			}
			cfg.apps[modName] = val.(App)
		}
		return nil
	}()
	if err != nil {
		return err
	}

	// swap old config with the new one, and
	// roll back this change if anything fails
	currentCfgMu.Lock()
	oldCfg := currentCfg
	currentCfg = cfg
	currentCfgMu.Unlock()
	defer func() {
		if err != nil {
			currentCfgMu.Lock()
			currentCfg = oldCfg
			currentCfgMu.Unlock()
		}
	}()

	// Start
	err = func() error {
		h := Handle{cfg}
		for name, a := range cfg.apps {
			err := a.Start(h)
			if err != nil {
				for otherAppName, otherApp := range cfg.apps {
					err := otherApp.Stop()
					if err != nil {
						log.Printf("aborting app %s: %v", otherAppName, err)
					}
				}
				return fmt.Errorf("%s app module: start: %v", name, err)
			}
		}
		return nil
	}()
	if err != nil {
		return err
	}

	// Stop
	if oldCfg != nil {
		for name, a := range oldCfg.apps {
			err := a.Stop()
			if err != nil {
				log.Printf("[ERROR] stop %s: %v", name, err)
			}
		}
	}

	// shut down listeners that are no longer being used
	err = func() error {
		listenersMu.Lock()
		for key, info := range listeners {
			if atomic.LoadInt32(&info.usage) == 0 {
				err := info.ln.Close()
				if err != nil {
					log.Printf("[ERROR] closing listener %s: %v", info.ln.Addr(), err)
					continue
				}
				delete(listeners, key)
			}
		}
		listenersMu.Unlock()
		return nil
	}()
	if err != nil {
		return err
	}

	return nil
}

// App is a thing that Caddy runs.
type App interface {
	Start(Handle) error
	Stop() error
}

// Config represents a Caddy configuration.
type Config struct {
	StorageRaw json.RawMessage `json:"storage"`
	storage    certmagic.Storage

	TestVal string                     `json:"testval"`
	AppsRaw map[string]json.RawMessage `json:"apps"`

	// apps stores the decoded Apps values,
	// keyed by module name.
	apps map[string]App

	// moduleStates stores the optional "global" state
	// values of every module used by this configuration,
	// keyed by module name.
	moduleStates map[string]interface{}
}

// Handle allows app modules to access
// the top-level Config in a controlled
// manner without needing to rely on
// global state.
type Handle struct {
	current *Config
}

// App returns the configured app named name. If no app with
// that name is currently configured, a new empty one will be
// instantiated. (The app module must still be registered.)
func (h Handle) App(name string) (interface{}, error) {
	if app, ok := h.current.apps[name]; ok {
		return app, nil
	}
	modVal, err := LoadModule(name, nil)
	if err != nil {
		return nil, fmt.Errorf("instantiating new module %s: %v", name, err)
	}
	h.current.apps[name] = modVal.(App)
	return modVal, nil
}

// GetStorage returns the configured Caddy storage implementation.
// If no storage implementation is explicitly configured, the
// default one is returned instead, as long as there is a current
// configuration loaded.
func GetStorage() certmagic.Storage {
	currentCfgMu.RLock()
	defer currentCfgMu.RUnlock()
	if currentCfg == nil {
		return nil
	}
	return currentCfg.storage
}

// Duration is a JSON-string-unmarshable duration type.
type Duration time.Duration

// UnmarshalJSON satisfies json.Unmarshaler.
func (d *Duration) UnmarshalJSON(b []byte) error {
	dd, err := time.ParseDuration(strings.Trim(string(b), `"`))
	if err != nil {
		return err
	}
	cd := Duration(dd)
	d = &cd
	return nil
}

// CtxKey is a value type for use with context.WithValue.
type CtxKey string

// currentCfg is the currently-loaded configuration.
var (
	currentCfg   *Config
	currentCfgMu sync.RWMutex
)

// moduleInstances stores the individual instantiated
// values of modules, keyed by module name. The list
// of instances of each module get passed into the
// respective module's OnLoad callback, so they can
// set up any global state and/or make sure their
// configuration, when viewed as a whole, is valid.
// Since this list is shared, only one Start() routine
// must be allowed to happen at any given time.
var moduleInstances = make(map[string][]interface{})

// startMu ensures that only one Start() happens at a time.
// This is important since moduleInstances is shared state.
var startMu sync.Mutex