;;; openrc.el --- OpenRC integration -*- lexical-binding: t -*- ;; Copyright 2022 Gentoo Authors ;; This file is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 2 of the License, or ;; (at your option) any later version. ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;; Author: Maciej Barć ;; Homepage: https://gitweb.gentoo.org/proj/emacs-openrc.git ;; Keywords: processes ;; Maintainer: ;; Package-Requires: ((emacs "24.3")) ;; Version: 1.0.0 ;;; Commentary: ;; OpenRC integration. ;; This library was originally written as part of "emacs-gentoo" ;; by Maciej Barć. ;; It was later relicensed by the author under the GPL-2-or-later license ;; and republished under the Gentoo GNU Emacs project. ;; Original repository: https://gitlab.com/xgqt/emacs-gentoo ;;; Code: ;; Commands to consider: ;; - rc-service -l -- all of services ;; - rc-status -f "ini" boot ;; - rc-update show -v (defconst openrc-version "1.0.0" "Emacs-Openrc version.") (defgroup openrc nil "OpenRC integration." :group 'external) ;; Executables (defcustom openrc-rc-command "/sbin/rc" "Path to the \"rc\" binary." :safe 'stringp :type 'file :group 'openrc) (defcustom openrc-rc-update-command (concat openrc-rc-command "-update") "Path to the \"rc-update\" binary." :safe 'stringp :type 'file :group 'openrc) (defcustom openrc-rc-service-command (concat openrc-rc-command "-service") "Path to the \"rc-service\" binary." :safe 'stringp :type 'file :group 'openrc) (defcustom openrc-sudo-command "sudo" "Path to the \"sudo\" binary. Used to gain privilege for some commands." :safe 'stringp :type 'file :group 'openrc) ;; Directories (defcustom openrc-run-dir "/run/openrc" "Path to OpenRC run directory (defaults to \"/run/openrc\")." :safe 'stringp :type 'file :group 'openrc) (defcustom openrc-started-dir (expand-file-name "started" openrc-run-dir) "Path to OpenRC directory containing started services." :safe 'stringp :type 'file :group 'openrc) ;; Other (defcustom openrc-use-sudo (or (executable-find openrc-sudo-command) (file-executable-p openrc-sudo-command)) "Whether to use \"sudo\" or \"su\" for commands that need root privileges. The invoked \"sudo\" executable location is controlled by the ‘openrc-sudo-command’ variable." :type 'boolean :group 'openrc) ;; Helpers (defun openrc--service-started? (service) "Check if SERVICE is started." (file-exists-p (expand-file-name service openrc-started-dir))) (defun openrc--get-services () "Get all OpenRC services." (mapcar (lambda (s) (let* ((lst (split-string s "|" t " *")) (service (car lst)) (runlevel (cdr lst))) (vector service (if (openrc--service-started? service) "YES" "NO") (if (equal runlevel nil) "none" (car runlevel))))) (split-string (shell-command-to-string (concat openrc-rc-update-command " show -v")) "\n" t))) (defun openrc--tabulated-list (vectors list-length tabulated-list) "Create a TABULATED-LIST from list of VECTORS of length LIST-LENGTH." (cond ((equal vectors nil) tabulated-list) (t (openrc--tabulated-list (cdr vectors) list-length (append tabulated-list (list (list (- list-length (length vectors)) (car vectors)))))))) (defun openrc-refresh-services () "Refresh the list of OpenRC services." (interactive) (message "Refreshing OpenRC services list...") (let ((services (openrc--get-services))) (setq tabulated-list-entries (openrc--tabulated-list services (length services) '()))) (tabulated-list-init-header) (tabulated-list-print t) (message "...OpenRC services list refresh done!")) (defun openrc--async-shell-command (privileged &rest args) "Run `async-shell-command' with ARGS. If PRIVILEGED is true, then use the \"sudo\" or \"su\" to run the command." (let ((buffer-name "*OpenRC Command*") (error-buffer-name "*OpenRC Command ERRORS*") (command (apply 'concat (mapcar (lambda (s) (concat " " s)) args)))) (let ((buffer (get-buffer-create buffer-name))) (with-current-buffer buffer (if privileged (if openrc-use-sudo (async-shell-command (concat "sudo " command) buffer-name error-buffer-name) (async-shell-command (concat "su -c \"" command "\"") buffer-name error-buffer-name)) (async-shell-command command buffer-name error-buffer-name)) (view-mode))))) ;; Describing services (defun openrc--describe (service) "Describe a SERVICE." (openrc--async-shell-command nil openrc-rc-service-command service "describe" "--verbose")) (defun openrc-describe-entry () "Describe a service under the tabulated list entry." (interactive) (openrc--describe (aref (tabulated-list-get-entry) 0))) ;; Stopping and starting services (defun openrc--toggle (service started?) "Start or stop a SERVICE depending on STARTED? state." (openrc--async-shell-command 'privileged openrc-rc-service-command service (if started? "stop" "start") "--verbose")) (defun openrc-toggle-entry () "Start or stop a service under the tabulated list entry depending on state." (interactive) (let ((service (aref (tabulated-list-get-entry) 0))) (openrc--toggle service (openrc--service-started? service)))) (defun openrc--restart (service) "Restart a SERVICE." (openrc--async-shell-command 'privileged openrc-rc-service-command service "restart" "--verbose")) (defun openrc-restart-entry () "Restart a service under the tabulated list entry." (interactive) (openrc--restart (aref (tabulated-list-get-entry) 0))) ;; Adding and removing services from runlevels (defconst openrc--runlevels '("boot" "default" "nonetwork" "shutdown" "sysinit") "List of available runlevels.") (defun openrc--read-runlevel () "Read a runlevel selected by the user." (completing-read "Runlevel:" openrc--runlevels)) (defun openrc--add (service runlevel) "Add a SERVICE to a RUNLEVEL." (openrc--async-shell-command 'privileged openrc-rc-update-command "add" service runlevel "--verbose" )) (defun openrc-add-entry () "Add a service under the tabulated list entry to a runlevel." (interactive) (openrc--add (aref (tabulated-list-get-entry) 0) (openrc--read-runlevel))) (defun openrc--del (service runlevel) "Remove a SERVICE from a RUNLEVEL." (openrc--async-shell-command 'privileged openrc-rc-update-command "del" service runlevel "--verbose")) (defun openrc-del-entry () "Remove a service under the tabulated list entry from a runlevel." (interactive) (openrc--del (aref (tabulated-list-get-entry) 0) (openrc--read-runlevel))) ;; Mode (defvar openrc-services-menu-mode-hook nil "Hook for `openrc-services-menu' major mode.") (defvar openrc-services-menu-mode-map (let ((openrc-services-menu-mode-map (make-keymap))) (define-key openrc-services-menu-mode-map (kbd "/") #'isearch-forward) (define-key openrc-services-menu-mode-map (kbd "e") #'openrc-describe-entry) (define-key openrc-services-menu-mode-map (kbd "a") #'openrc-add-entry) (define-key openrc-services-menu-mode-map (kbd "d") #'openrc-del-entry) (define-key openrc-services-menu-mode-map (kbd "g") #'openrc-refresh-services) (define-key openrc-services-menu-mode-map (kbd "r") #'openrc-restart-entry) (define-key openrc-services-menu-mode-map (kbd "t") #'openrc-toggle-entry) openrc-services-menu-mode-map) "Key map for `openrc-services-menu' major mode.") (easy-menu-define openrc-services-menu-menu openrc-services-menu-mode-map "Menu for `el-fetch-mode'." '("OpenRC" ["Refresh" openrc-refresh-services] ["Describe service" openrc-describe-entry] ["Restart service" openrc-restart-entry] ["Toggle service" openrc-toggle-entry] ["Add to runlevel" openrc-add-entry] ["Delete from runlevel" openrc-del-entry] ["Quit" quit-window] ["Help" describe-mode])) (define-derived-mode openrc-services-menu-mode tabulated-list-mode "OpenRC Services Menu" "Major mode for listing the OpenRC services." (setq tabulated-list-format [("Service" 30 t) ("Started" 10 t) ("Runlevel" 20 t)]) (setq tabulated-list-sort-key (cons "Runlevel" nil)) (run-hooks 'openrc-services-menu-mode-hook) (use-local-map openrc-services-menu-mode-map)) ;; Main provided features ;;;###autoload (defun openrc-list-services () "Display a list of OpenRC services." (interactive) (let ((buffer (get-buffer-create "*OpenRC Services*"))) (with-current-buffer buffer (openrc-services-menu-mode) (openrc-refresh-services)) (display-buffer buffer))) (provide 'openrc) ;;; openrc.el ends here