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
|
2019-08-31 22:04:23 +00:00
|
|
|
#+DATE: [2019-08-18 Sun]
|
2019-07-28 16:44:25 +00:00
|
|
|
#+KEYWORDS: programming, blog, org-mode
|
2019-09-12 12:41:53 +00:00
|
|
|
#+DESCRIPTION: A script I use to load safely an eLISP file when entering a new project directory.
|
2019-07-28 16:44:25 +00:00
|
|
|
#+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.
|
|
|
|
|
2019-08-20 13:35:54 +00:00
|
|
|
1. When opening a new file in a project, check for the presence of a
|
|
|
|
=project.el= and =project.el.sig= file at the root directory of the
|
|
|
|
project.
|
|
|
|
2. Check the file was signed with by a trusted fingerprint.
|
|
|
|
3. Load the file.
|
|
|
|
|
|
|
|
Other solutions I found on the internet asked you each time you enter in a
|
|
|
|
project if you trust the file or not.
|
2019-07-28 16:44:25 +00:00
|
|
|
This was both quite annoying and insecure as it is kind of easy to type 'y'
|
2019-08-20 13:35:54 +00:00
|
|
|
instead of 'n' and to load 3rd party script.
|
2019-07-28 16:44:25 +00:00
|
|
|
|
2019-08-20 13:35:54 +00:00
|
|
|
Note that checking who signed a file with an external signature is not as
|
|
|
|
straightforward as it should be:
|
2019-07-28 16:44:25 +00:00
|
|
|
|
|
|
|
#+begin_src elisp
|
2019-08-20 13:35:54 +00:00
|
|
|
(defun auto-load-project/get-sign-key (file)
|
|
|
|
"Return the fingerprint of they key that signed FILE.
|
|
|
|
|
|
|
|
To sign a file you should used
|
|
|
|
|
|
|
|
`gpg --local-user my@email --output project.el.sig --detach-sign project.el`"
|
2019-07-28 16:44:25 +00:00
|
|
|
(string-trim-right
|
|
|
|
(shell-command-to-string
|
|
|
|
(concat
|
2019-08-20 13:35:54 +00:00
|
|
|
(format "gpg --status-fd 1 --verify %s.sig %s 2>/dev/null " file file)
|
|
|
|
"|grep VALIDSIG"
|
|
|
|
"|awk '{print $3}'"))))
|
2019-07-28 16:44:25 +00:00
|
|
|
#+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 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.
|
|
|
|
|
2019-08-20 13:35:54 +00:00
|
|
|
So now, each time I modify the =project.el= I sign it with the following
|
|
|
|
command line:
|
|
|
|
|
|
|
|
#+begin_src bash
|
|
|
|
gpg --local-user my@email --output project.el.sig --detach-sign project.el
|
|
|
|
#+end_src
|
|
|
|
|
2019-07-28 16:44:25 +00:00
|
|
|
* Solution
|
|
|
|
:PROPERTIES:
|
|
|
|
:CUSTOM_ID: solution
|
|
|
|
:END:
|
|
|
|
|
2019-08-20 13:35:54 +00:00
|
|
|
The project is hosted here: https://gitlab.esy.fun/yogsototh/auto-load-project-el
|
|
|
|
|
|
|
|
You can setup the emacs package in spacemacs with:
|
2019-07-30 09:49:03 +00:00
|
|
|
|
2019-07-28 16:44:25 +00:00
|
|
|
#+begin_src elisp
|
2019-08-20 13:35:54 +00:00
|
|
|
;; ...
|
|
|
|
dotspacemacs-additional-packages
|
|
|
|
'((auto-load-project :location
|
|
|
|
(recipe
|
|
|
|
:fetcher git
|
|
|
|
:url "https://gitlab.esy.fun/yogsototh/auto-load-project-el"
|
|
|
|
:files ("auto-load-project.el"))))
|
|
|
|
;; ...
|
|
|
|
(defun dotspacemacs/user-config ()
|
|
|
|
;; ...
|
|
|
|
(require 'auto-load-project)
|
|
|
|
(setq auto-load-project/trusted-gpg-key-fingerprints
|
2019-10-19 21:27:54 +00:00
|
|
|
'("0000000000000000000000000000000000000000"
|
2019-08-20 13:35:54 +00:00
|
|
|
"1111111111111111111111111111111111111111"
|
|
|
|
"2222222222222222222222222222222222222222"
|
|
|
|
)))
|
|
|
|
;; ...
|
2019-07-28 16:44:25 +00:00
|
|
|
#+end_src
|
2019-08-20 13:56:05 +00:00
|
|
|
|
|
|
|
The full current code should be easy to follow if you have basic notions
|
|
|
|
of eLISP:
|
|
|
|
|
|
|
|
#+begin_src elisp
|
|
|
|
(require 'projectile)
|
|
|
|
|
|
|
|
(defvar auto-load-project/trusted-gpg-key-fingerprints
|
|
|
|
'()
|
|
|
|
"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 auto-load-project/get-sign-key (file)
|
|
|
|
"Return the fingerprint of they key that signed FILE.
|
|
|
|
|
|
|
|
To sign a file you should used
|
|
|
|
|
|
|
|
`gpg --local-user my@email --output project.el.sig --detach-sign project.el`"
|
|
|
|
(string-trim-right
|
|
|
|
(shell-command-to-string
|
|
|
|
(concat
|
|
|
|
(format "gpg --status-fd 1 --verify %s.sig %s 2>/dev/null " file file)
|
|
|
|
"|grep VALIDSIG"
|
|
|
|
"|awk '{print $3}'"))))
|
|
|
|
|
|
|
|
(defun auto-load-project/trusted-gpg-origin-p (file)
|
|
|
|
"Return non-nil if the FILE is encrypted with a trusted key."
|
|
|
|
(member (auto-load-project/get-sign-key file)
|
|
|
|
auto-load-project/trusted-gpg-key-fingerprints))
|
|
|
|
|
|
|
|
(defconst auto-load-project/project-file "project.el"
|
|
|
|
"Project configuration file name.")
|
|
|
|
|
|
|
|
(defvar auto-load-project/loaded-projects (list)
|
|
|
|
"Projects that have been loaded by `auto-load-project/load'.")
|
|
|
|
|
|
|
|
(defun auto-load-project/load ()
|
|
|
|
"Loads the `auto-load-project/project-file' for a project.
|
|
|
|
This is run once 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 auto-load-project/project-file current-project-root))
|
|
|
|
(project-sign-file (concat project-init-file ".sig")))
|
|
|
|
(when (and (not (member current-project-root auto-load-project/loaded-projects))
|
|
|
|
(file-exists-p project-init-file)
|
|
|
|
(file-exists-p project-sign-file)
|
|
|
|
(auto-load-project/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 'auto-load-project/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 #'auto-load-project/load t)
|
|
|
|
(add-hook 'dired-mode-hook #'auto-load-project/load t)
|
|
|
|
|
|
|
|
(provide 'auto-load-project)
|
|
|
|
#+end_src
|