... | ... |
@@ -33,6 +33,12 @@ fstab syntax: |
33 | 33 |
|
34 | 34 |
Refer to `configuration.reference.yaml` to write your own configuration file. |
35 | 35 |
|
36 |
+It is possible to reload the configuration file without remounting the filesystem. |
|
37 |
+``` |
|
38 |
+cat /mount/point/reload |
|
39 |
+``` |
|
40 |
+This will output either "reload ok" or "reload fail". |
|
41 |
+ |
|
36 | 42 |
## Why? |
37 | 43 |
|
38 | 44 |
Certbot already offers hooks to handle pretty much everything, from mere concatenations to complex deployments to various kinds of clusters. |
... | ... |
@@ -34,6 +34,12 @@ DEFAULT_DIR_MODE = 0o555 |
34 | 34 |
DEFAULT_REG_MODE = 0o444 |
35 | 35 |
DEFAULT_KEY_MODE = 0o400 |
36 | 36 |
DEFAULT_SENSITIVE_PATTERN = '/privkey.pem$' |
37 |
+ |
|
38 |
+RELOAD_PATH = '/reload' |
|
39 |
+RELOAD_MSG_OK = b'reload ok\n' |
|
40 |
+RELOAD_MSG_FAIL = b'reload fail\n' |
|
41 |
+RELOAD_FILESIZE = max(len(RELOAD_MSG_OK), len(RELOAD_MSG_FAIL)) |
|
42 |
+ |
|
37 | 43 |
TIME_PROPS = ('st_atime', 'st_ctime', 'st_mtime') |
38 | 44 |
|
39 | 45 |
def read_mode_setting(obj, key, default): |
... | ... |
@@ -188,6 +194,36 @@ class CombinedFS(Operations): |
188 | 194 |
self.filedesc[new_fd_index] = file_descriptor |
189 | 195 |
return new_fd_index |
190 | 196 |
|
197 |
+ def reload(self, conf=None): |
|
198 |
+ try: |
|
199 |
+ if conf is None: |
|
200 |
+ conf = self.get_conf() |
|
201 |
+ new_conf = CombinedFSConfiguration(conf.path) |
|
202 |
+ # Assuming CPython, this should result in a single STORE_ATTR opcode. |
|
203 |
+ # Since this class features no __setattr__ implementation, the |
|
204 |
+ # resulting execution should be atomic. |
|
205 |
+ self.configuration = new_conf |
|
206 |
+ return new_conf |
|
207 |
+ except: |
|
208 |
+ return None |
|
209 |
+ |
|
210 |
+ def handle_reload_getattr(self, conf, fh): |
|
211 |
+ return { |
|
212 |
+ 'st_nlink': 1, |
|
213 |
+ 'st_uid': conf.uid, |
|
214 |
+ 'st_gid': conf.gid, |
|
215 |
+ 'st_size': RELOAD_FILESIZE, |
|
216 |
+ 'st_mode': stat.S_IFREG | conf.key_mode, |
|
217 |
+ } |
|
218 |
+ |
|
219 |
+ def handle_reload_open(self, conf, flags): |
|
220 |
+ new_conf = self.reload(conf) |
|
221 |
+ new_fd = { |
|
222 |
+ 'conf': conf if new_conf is None else new_conf, |
|
223 |
+ 'data': RELOAD_MSG_FAIL if new_conf is None else RELOAD_MSG_OK, |
|
224 |
+ } |
|
225 |
+ return self.register_fd(new_fd) |
|
226 |
+ |
|
191 | 227 |
# Filesystem methods |
192 | 228 |
|
193 | 229 |
def access(self, path, mode): |
... | ... |
@@ -202,10 +238,22 @@ class CombinedFS(Operations): |
202 | 238 |
|
203 | 239 |
def getattr(self, path, fh=None): |
204 | 240 |
conf = self.get_conf() |
241 |
+ if path == RELOAD_PATH: |
|
242 |
+ return self.handle_reload_getattr(conf, fh) |
|
205 | 243 |
cert, filename, file_spec = conf.analyse_path(path) |
206 | 244 |
if filename is None: # Directory |
207 | 245 |
full_path = os.path.join(conf.root, path.lstrip('/')) |
208 |
- dir_attrs = self.attributes(full_path) |
|
246 |
+ try: |
|
247 |
+ dir_attrs = self.attributes(full_path) |
|
248 |
+ except OSError as ose: |
|
249 |
+ if ose.errno == errno.ENOENT and path == '/': |
|
250 |
+ # Non-existent "live" directory, most likely a misconf, |
|
251 |
+ # fake it to preserve access to RELOAD_PATH: |
|
252 |
+ dir_attrs = {'st_nlink': 2, 'st_size': 4096} |
|
253 |
+ for prop in TIME_PROPS: |
|
254 |
+ dir_attrs[prop] = 0 |
|
255 |
+ else: |
|
256 |
+ raise |
|
209 | 257 |
dir_attrs['st_uid'] = conf.uid |
210 | 258 |
dir_attrs['st_gid'] = conf.gid |
211 | 259 |
dir_attrs['st_mode'] = stat.S_IFDIR | conf.dir_mode |
... | ... |
@@ -276,6 +324,8 @@ class CombinedFS(Operations): |
276 | 324 |
|
277 | 325 |
def open(self, path, flags): |
278 | 326 |
conf = self.get_conf() |
327 |
+ if path == RELOAD_PATH: |
|
328 |
+ return self.handle_reload_open(conf, flags) |
|
279 | 329 |
cert, filename, file_spec = conf.analyse_path(path) |
280 | 330 |
if not cert or not filename: |
281 | 331 |
raise FuseOSError(errno.ENOENT) |