her.esy.fun/src/posts/project-el/index.org

136 lines
4.9 KiB
Org Mode
Raw Normal View History

2019-07-28 16:44:25 +00:00
#+TITLE: Autoload Script by project
#+SUBTITLE: fast, secure, easy autoload
#+AUTHOR: Yann Esposito
2019-07-29 22:10:54 +00:00
#+EMAIL: yann@esposito.host
#+DATE: [2019-07-28]
2019-07-28 16:44:25 +00:00
#+KEYWORDS: programming, blog, org-mode
#+OPTIONS: auto-id:t
#+begin_quote
/tl;dr/: A script that use projectile and GPG to securely load
an emacs lisp script when opening a new project.
Check the [[#solution]] section to get the code.
#+end_quote
* Problem
:PROPERTIES:
:CUSTOM_ID: problem
:END:
When providing a repository containing only org files of my blog.
I also wanted to provide everything necessary for users to be able to publish my
website.
Emacs, org-publish mostly assume you should put all those details in a
centralised place in your =~/.emacs.d/.init.el= file.
The main principle is quite simple.
1. When finding a new file in a project, check for the presence of a
=.project.el.gpg= file.
2. Check the file was encrypted with by a trusted fingerprint
3. load the file
I found a solution that asked you each time you enter in a project if you trust
it.
This was both quite annoying and insecure as it is kind of easy to type 'y'
instead of 'n' and to load an untrusted 3rd party script.
With emacs epa, opening encrypted gpg files is seamless.
But here we not only want to open the gpg file but also check we trust the
person that did it.
Note that checking who's encrypted a gpg file is not straightforward:
#+begin_src elisp
(defun y/get-encryption-key (file)
"given a gpg encrypted file, returns the fingerprint of they
key that encrypted it"
(string-trim-right
(shell-command-to-string
(concat
"gpg --status-fd 1 --decrypt -o /dev/null " file " 2>/dev/null"
"|grep DECRYPTION_KEY"
"|awk '{print $4}'"))))
#+end_src
- The `--status-fd` should provide more script friendly output.
GPG provide localized output by default which are therefore hard to use in
script (for grep for example).
- we do not want to decrypt to a file so we redirect the output to =/dev/null=
We use =projectile= to detect the project-root and when we are in a new project.
Unfortunately the =projectile-after-switch-project-hooks= doesn't work as I
expected.
So I use the hooks =find-file-hook= and =dired-mode-hook= to try to load the
file.
In order not to load the code each time, I need to keep a local state of project
already loaded.
* Solution
:PROPERTIES:
:CUSTOM_ID: solution
:END:
2019-07-30 09:49:03 +00:00
Dowload the code [[file:auto-load-project.el][auto-load-project.el]]
2019-07-28 16:44:25 +00:00
#+begin_src elisp
(defun y/init-project-el-auto-load ()
"Initialize the autoload of .project.el.gpg for projects"
(with-eval-after-load 'projectile
(defvar y/trusted-gpg-key-fingerprints
'("448E9FEF4F5B86DE79C1669B0000000000000000")
"The list of GPG fingerprint you trust when decrypting a gpg file.
You can retrieve the fingerprints of your own private keys
with: `gpg --list-secret-keys' (take care of removing the
spaces when copy/pasting here)")
(defun y/get-encryption-key (file)
"given a gpg encrypted file, returns the fingerprint of
they key that encrypted it"
(string-trim-right
(shell-command-to-string
(concat
"gpg --status-fd 1 --decrypt -o /dev/null " file " 2>/dev/null"
"|grep DECRYPTION_KEY"
"|awk '{print $4}'"))))
(defun y/trusted-gpg-origin-p (file)
"Returns true if the file is encrypted with a trusted key"
(member (y/get-encryption-key file) y/trusted-gpg-key-fingerprints))
(defconst y/project-file ".project.el.gpg"
"Project configuration file name.")
(defvar y/loaded-projects (list)
"Projects that have been loaded by `y/load-project-file'.")
(defun y/load-project-file ()
"Loads the `y/project-file' for a project. This is run once
after the project is loaded signifying project setup."
(interactive)
(when (projectile-project-p)
(lexical-let* ((current-project-root (projectile-project-root))
(project-init-file (expand-file-name y/project-file current-project-root)))
(when (and (not (member current-project-root y/loaded-projects))
(file-exists-p project-init-file)
(y/trusted-gpg-origin-p project-init-file))
(message "Loading project init file for %s" (projectile-project-name))
(condition-case ex
(progn (load project-init-file)
(add-to-list 'y/loaded-projects current-project-root)
(message "%s loaded successfully" project-init-file))
('error
(message
"There was an error loading %s: %s"
project-init-file
(error-message-string ex))))))))
(add-hook 'find-file-hook #'y/load-project-file t)
(add-hook 'dired-mode-hook #'y/load-project-file t)))
(y/init-project-el-auto-load)
#+end_src