summaryrefslogtreecommitdiff
path: root/scripts/.local/bin
diff options
context:
space:
mode:
authorivan-boikov <ik.boikov.public@protonmail.com>2022-11-27 17:22:55 +0100
committerivan-boikov <ik.boikov.public@protonmail.com>2022-11-27 17:22:55 +0100
commitfa44b597d9272e06144c3e9d1acb273a6f1e89ab (patch)
treec760bb28fcfd1e59bb5cb32780995e3db7d804be /scripts/.local/bin
parente949ec7a6a07a884d99e1ec4ccb7a5d7d683eeaf (diff)
Got rid of NvChad childish idiocy, init.vim all the way!
slock instead of i3lock + removed related icons added startxautolock copied pass-git-helper automatic startx
Diffstat (limited to 'scripts/.local/bin')
-rwxr-xr-xscripts/.local/bin/ilock59
-rwxr-xr-xscripts/.local/bin/pass-git-helper461
-rwxr-xr-xscripts/.local/bin/startxautolock2
-rwxr-xr-xscripts/.local/bin/tab-buttons10
4 files changed, 498 insertions, 34 deletions
diff --git a/scripts/.local/bin/ilock b/scripts/.local/bin/ilock
index 40f1f9d..885c60f 100755
--- a/scripts/.local/bin/ilock
+++ b/scripts/.local/bin/ilock
@@ -1,29 +1,42 @@
#!/usr/bin/env bash
-icon_file="$HOME/.local/share/icons/lock.png"
-icon_scaled_file='/tmp/lock-icon.png'
-scr_png='/tmp/screen.png'
-scr_bmp='/tmp/screen.bmp'
-scr_gl_bmp="/tmp/sclock_g.bmp"
-
-(( $# )) && { icon=$1; }
-
# flush passwords
if [ -n $(which gnome-keyring-daemon) ]; then
- gnome-keyring-daemon -r -d
+ gnome-keyring-daemon -r -d >/dev/null
+fi
+if [ -n $(which ssh-agent) ]; then
+ ssh-agent -k >/dev/null
+fi
+if [ -n $(which gpgconf) ]; then
+ gpgconf --kill gpg-agent >/dev/null
+fi
+
+# stop sounds
+if [ -n $(which playerctl) ]; then
+ playerctl -a pause >/dev/null
+fi
+if [ -n $(which mpc) ]; then
+ mpc pause >/dev/null
fi
-ssh-agent -k
-gpgconf --kill gpg-agent
# generate fancy lock screen
-rm "$scr_png"
-maim > "$scr_png"
-# just pixelate
-convert "$scr_png" -scale 10% -scale 1000% "$scr_png"
+#icon_file="$HOME/.local/share/icons/lock.png"
+#icon_scaled_file='/tmp/lock-icon.png'
+#scr_png='/tmp/screen.png'
+#scr_bmp='/tmp/screen.bmp'
+#scr_gl_bmp="/tmp/sclock_g.bmp"
+#
+#(( $# )) && { icon=$1; }
+
+#rm "$scr_png"
+#maim > "$scr_png"
-# add heavy distortion
+# pixelate
+#convert "$scr_png" -scale 10% -scale 1000% "$scr_png"
+
+# heavy distortion
#convert "$scr_png" -resize 500x "$scr_png"
#convert -rotate -90 $scr_png $scr_bmp
#for a in {1,2,4,5,2}
@@ -39,14 +52,12 @@ convert "$scr_png" -scale 10% -scale 1000% "$scr_png"
# add lock icon
# first, rescale to match half screen height
-resolutiony=$(xrandr | fgrep '*' | cut -d'x' -f2 | awk '{print $1}')
-locksizey=$(echo "$resolutiony / 2" | bc -l)
-convert "$icon_file" -resize x"$locksizey" "$icon_scaled_file"
+#resolutiony=$(xrandr | fgrep '*' | cut -d'x' -f2 | awk '{print $1}')
+#locksizey=$(echo "$resolutiony / 2" | bc -l)
+#convert "$icon_file" -resize x"$locksizey" "$icon_scaled_file"
# and add icon on top of screenshot
-convert "$scr_png" "$icon_scaled_file" -gravity center -composite -matte "$scr_png"
-
-playerctl pause
+#convert "$scr_png" "$icon_scaled_file" -gravity center -composite -matte "$scr_png"
-#sxiv $scr_png
+#i3lock --nofork -u -i "$scr_png"
-i3lock -u -i "$scr_png"
+slock
diff --git a/scripts/.local/bin/pass-git-helper b/scripts/.local/bin/pass-git-helper
new file mode 100755
index 0000000..5fe8e71
--- /dev/null
+++ b/scripts/.local/bin/pass-git-helper
@@ -0,0 +1,461 @@
+#!/usr/bin/env python3
+
+"""
+Implementation of the pass-git-helper utility.
+
+.. codeauthor:: Johannes Wienke
+"""
+
+
+import abc
+import argparse
+import configparser
+import fnmatch
+import logging
+import os
+import os.path
+import re
+import subprocess
+import sys
+from typing import Dict, IO, Mapping, Optional, Pattern, Sequence, Text
+
+import xdg.BaseDirectory
+
+
+LOGGER = logging.getLogger()
+CONFIG_FILE_NAME = "git-pass-mapping.ini"
+DEFAULT_CONFIG_FILE = os.path.join(
+ xdg.BaseDirectory.save_config_path("pass-git-helper"), CONFIG_FILE_NAME
+)
+
+
+def parse_arguments(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
+ """
+ Parse the command line arguments.
+
+ Args:
+ argv:
+ If not ``None``, use the provided command line arguments for
+ parsing. Otherwise, extract them automatically.
+
+ Returns:
+ The argparse object representing the parsed arguments.
+ """
+ parser = argparse.ArgumentParser(
+ description="Git credential helper using pass as the data source.",
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ )
+ parser.add_argument(
+ "-m",
+ "--mapping",
+ type=argparse.FileType("r"),
+ metavar="MAPPING_FILE",
+ default=None,
+ help="A mapping file to be used, specifying how hosts "
+ "map to pass entries. Overrides the default mapping files from "
+ "XDG config locations, usually: {config_file}".format(
+ config_file=DEFAULT_CONFIG_FILE
+ ),
+ )
+ parser.add_argument(
+ "-l",
+ "--logging",
+ action="store_true",
+ default=False,
+ help="Print debug messages on stderr. " "Might include sensitive information",
+ )
+ parser.add_argument(
+ "action",
+ type=str,
+ metavar="ACTION",
+ help="Action to perform as specified in the git credential API",
+ )
+
+ return parser.parse_args(argv)
+
+
+def parse_mapping(mapping_file: Optional[IO]) -> configparser.ConfigParser:
+ """
+ Parse the file containing the mappings from hosts to pass entries.
+
+ Args:
+ mapping_file:
+ Name of the file to parse. If ``None``, the default file from the
+ XDG location is used.
+ """
+ LOGGER.debug("Parsing mapping file. Command line: %s", mapping_file)
+
+ def parse(mapping_file: IO) -> configparser.ConfigParser:
+ config = configparser.ConfigParser()
+ config.read_file(mapping_file)
+ return config
+
+ # give precedence to the user-specified file
+ if mapping_file is not None:
+ LOGGER.debug("Parsing command line mapping file")
+ return parse(mapping_file)
+
+ # fall back on XDG config location
+ xdg_config_dir = xdg.BaseDirectory.load_first_config("pass-git-helper")
+ if xdg_config_dir is None:
+ raise RuntimeError(
+ "No mapping configured so far at any XDG config location. "
+ "Please create {config_file}".format(config_file=DEFAULT_CONFIG_FILE)
+ )
+ default_file = os.path.join(xdg_config_dir, CONFIG_FILE_NAME)
+ LOGGER.debug("Parsing mapping file %s", mapping_file)
+ with open(default_file, "r") as file_handle:
+ return parse(file_handle)
+
+
+def parse_request() -> Dict[str, str]:
+ """
+ Parse the request of the git credential API from stdin.
+
+ Returns:
+ A dictionary with all key-value pairs of the request
+ """
+ in_lines = sys.stdin.readlines()
+ LOGGER.debug('Received request "%s"', in_lines)
+
+ request = {}
+ for line in in_lines:
+ # skip empty lines to be a bit resilient against protocol errors
+ if not line.strip():
+ continue
+
+ parts = line.split("=", 1)
+ assert len(parts) == 2
+ request[parts[0].strip()] = parts[1].strip()
+
+ return request
+
+
+class DataExtractor(abc.ABC):
+ """Interface for classes that extract values from pass entries."""
+
+ def __init__(self, option_suffix: Text = "") -> None:
+ """
+ Create a new instance.
+
+ Args:
+ option_suffix:
+ Suffix to put behind names of configuration keys for this
+ instance. Subclasses must use this for their own options.
+ """
+ self._option_suffix = option_suffix
+
+ @abc.abstractmethod
+ def configure(self, config: configparser.SectionProxy) -> None:
+ """
+ Configure the extractor from the mapping section.
+
+ Args:
+ config:
+ configuration section for the entry
+ """
+
+ @abc.abstractmethod
+ def get_value(
+ self, entry_name: Text, entry_lines: Sequence[Text]
+ ) -> Optional[Text]:
+ """
+ Return the extracted value.
+
+ Args:
+ entry_name:
+ Name of the pass entry the value shall be extracted from
+ entry_lines:
+ The entry contents as a sequence of text lines
+
+ Returns:
+ The extracted value or ``None`` if nothing applicable can be found
+ in the entry.
+ """
+
+
+class SkippingDataExtractor(DataExtractor):
+ """
+ Extracts data from a pass entry and optionally strips a prefix.
+
+ The prefix is a fixed amount of characters.
+ """
+
+ def __init__(self, prefix_length: int, option_suffix: Text = "") -> None:
+ """
+ Create a new instance.
+
+ Args:
+ prefix_length:
+ Amount of characters to skip at the beginning of the entry
+ """
+ super().__init__(option_suffix)
+ self._prefix_length = prefix_length
+
+ @abc.abstractmethod
+ def configure(self, config: configparser.SectionProxy) -> None:
+ """Configure the amount of characters to skip."""
+ self._prefix_length = config.getint(
+ "skip{suffix}".format(suffix=self._option_suffix),
+ fallback=self._prefix_length,
+ )
+
+ @abc.abstractmethod
+ def _get_raw(self, entry_name: Text, entry_lines: Sequence[Text]) -> Optional[Text]:
+ pass
+
+ def get_value(
+ self, entry_name: Text, entry_lines: Sequence[Text]
+ ) -> Optional[Text]:
+ """See base class method."""
+ raw_value = self._get_raw(entry_name, entry_lines)
+ if raw_value is not None:
+ return raw_value[self._prefix_length :]
+ else:
+ return None
+
+
+class SpecificLineExtractor(SkippingDataExtractor):
+ """Extracts a specific line number from an entry."""
+
+ def __init__(self, line: int, prefix_length: int, option_suffix: Text = "") -> None:
+ """
+ Create a new instance.
+
+ Args:
+ line:
+ the line to extract, counting from zero
+ prefix_length:
+ Amount of characters to skip at the beginning of the line
+ option_suffix:
+ Suffix for each configuration option
+ """
+ super().__init__(prefix_length, option_suffix)
+ self._line = line
+
+ def configure(self, config: configparser.SectionProxy) -> None:
+ """See base class method."""
+ super().configure(config)
+ self._line = config.getint(
+ "line{suffix}".format(suffix=self._option_suffix), fallback=self._line
+ )
+
+ def _get_raw(self, entry_name: Text, entry_lines: Sequence[Text]) -> Optional[Text]:
+ if len(entry_lines) > self._line:
+ return entry_lines[self._line]
+ else:
+ return None
+
+
+class RegexSearchExtractor(DataExtractor):
+ """Extracts data using a regular expression with capture group."""
+
+ def __init__(self, regex: str, option_suffix: str) -> None:
+ """
+ Create a new instance.
+
+ Args:
+ regex:
+ The regular expression describing the entry line to match. The
+ first matching line is selected. The expression must contain a
+ single capture group that contains the data to return.
+ option_suffix:
+ Suffix for each configuration option
+ """
+ super().__init__(option_suffix)
+ self._regex = self._build_matcher(regex)
+
+ def _build_matcher(self, regex: str) -> Pattern:
+ matcher = re.compile(regex)
+ if matcher.groups != 1:
+ raise ValueError(
+ 'Provided regex "{regex}" must contain a single '
+ "capture group for the value to return.".format(regex=regex)
+ )
+ return matcher
+
+ def configure(self, config: configparser.SectionProxy) -> None:
+ """See base class method."""
+ super().configure(config)
+
+ self._regex = self._build_matcher(
+ config.get(
+ "regex{suffix}".format(suffix=self._option_suffix),
+ fallback=self._regex.pattern,
+ )
+ )
+
+ def get_value(
+ self, entry_name: Text, entry_lines: Sequence[Text]
+ ) -> Optional[Text]:
+ """See base class method."""
+ # Search through all lines and return the first matching one
+ for line in entry_lines:
+ match = self._regex.match(line)
+ if match:
+ return match.group(1)
+ # nothing matched
+ return None
+
+
+class EntryNameExtractor(DataExtractor):
+ """Return the last path fragment of the pass entry as the desired value."""
+
+ def configure(self, config: configparser.SectionProxy) -> None:
+ """Configure nothing."""
+
+ def get_value(
+ self, entry_name: Text, entry_lines: Sequence[Text]
+ ) -> Optional[Text]:
+ """See base class method."""
+ return os.path.split(entry_name)[1]
+
+
+_line_extractor_name = "specific_line"
+_username_extractors = {
+ _line_extractor_name: SpecificLineExtractor(1, 0, option_suffix="_username"),
+ "regex_search": RegexSearchExtractor(
+ r"^username: +(.*)$", option_suffix="_username"
+ ),
+ "entry_name": EntryNameExtractor(option_suffix="_username"),
+}
+
+
+def find_mapping_section(
+ mapping: configparser.ConfigParser, request_header: str
+) -> configparser.SectionProxy:
+ """Select the mapping entry matching the request header."""
+
+ LOGGER.debug('Searching mapping to match against header "%s"', request_header)
+ for section in mapping.sections():
+ if fnmatch.fnmatch(request_header, section):
+ LOGGER.debug(
+ 'Section "%s" matches requested header "%s"', section, request_header
+ )
+ return mapping[section]
+
+ raise ValueError(
+ f"No mapping section in {mapping.sections()} matches request {request_header}"
+ )
+
+
+def get_request_section_header(request: Mapping[str, str]) -> str:
+ """Return the canonical host + optional path for section header matching."""
+
+ if "host" not in request:
+ LOGGER.error("host= entry missing in request. Cannot query without a host")
+ raise ValueError("Request lacks host entry")
+
+ host = request["host"]
+ if "path" in request:
+ host = "/".join([host, request["path"]])
+ return host
+
+
+def define_pass_target(
+ section: configparser.SectionProxy, request: Mapping[str, str]
+) -> str:
+ """Determine the pass target by filling in potentially used variables."""
+
+ pass_target = section.get("target").replace("${host}", request["host"])
+ if "username" in request:
+ pass_target = pass_target.replace("${username}", request["username"])
+ return pass_target
+
+
+def get_password(
+ request: Mapping[str, str], mapping: configparser.ConfigParser
+) -> None:
+ """
+ Resolve the given credential request in the provided mapping definition.
+
+ The result is printed automatically.
+
+ Args:
+ request:
+ The credential request specified as a dict of key-value pairs.
+ mapping:
+ The mapping configuration as a ConfigParser instance.
+ """
+
+ LOGGER.debug('Received request "%s"', request)
+
+ header = get_request_section_header(request)
+ section = find_mapping_section(mapping, header)
+
+ pass_target = define_pass_target(section, request)
+
+ password_extractor = SpecificLineExtractor(0, 0, option_suffix="_password")
+ password_extractor.configure(section)
+ username_extractor = _username_extractors[
+ section.get("username_extractor", fallback=_line_extractor_name)
+ ]
+ username_extractor.configure(section)
+
+ LOGGER.debug('Requesting entry "%s" from pass', pass_target)
+ # silence the subprocess injection warnings as it is the user's
+ # responsibility to provide a safe mapping and execution environment
+ output = subprocess.check_output( # noqa: S603, S607
+ ["pass", "show", pass_target]
+ ).decode(section.get("encoding", "UTF-8"))
+ lines = output.splitlines()
+
+ password = password_extractor.get_value(pass_target, lines)
+ username = username_extractor.get_value(pass_target, lines)
+ if password:
+ print("password={password}".format(password=password)) # noqa: T201
+ if "username" not in request and username:
+ print("username={username}".format(username=username)) # noqa: T201
+
+
+def handle_skip() -> None:
+ """Terminate the process if skipping is requested via an env variable."""
+ if "PASS_GIT_HELPER_SKIP" in os.environ:
+ LOGGER.info("Skipping processing as requested via environment variable")
+ sys.exit(1)
+
+
+def main(argv: Optional[Sequence[str]] = None) -> None:
+ """
+ Start the pass-git-helper script.
+
+ Args:
+ argv:
+ If not ``None``, use the provided command line arguments for
+ parsing. Otherwise, extract them automatically.
+ """
+ args = parse_arguments(argv=argv)
+
+ if args.logging:
+ logging.basicConfig(level=logging.DEBUG)
+
+ handle_skip()
+
+ action = args.action
+ request = parse_request()
+ LOGGER.debug("Received action %s with request:\n%s", action, request)
+
+ try:
+ mapping = parse_mapping(args.mapping)
+ except Exception as error: # noqa: PIE786 ok'ish for the main function
+ LOGGER.critical("Unable to parse mapping file", exc_info=True)
+ print( # noqa: T201
+ "Unable to parse mapping file: {error}".format(error=error), file=sys.stderr
+ )
+ sys.exit(1)
+
+ if action == "get":
+ try:
+ get_password(request, mapping)
+ except Exception as error: # noqa: PIE786 ok'ish for the main function
+ print( # noqa: T201
+ "Unable to retrieve entry: {error}".format(error=error), file=sys.stderr
+ )
+ sys.exit(1)
+ else:
+ LOGGER.info("Action %s is currently not supported", action)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/.local/bin/startxautolock b/scripts/.local/bin/startxautolock
new file mode 100755
index 0000000..d7e6e68
--- /dev/null
+++ b/scripts/.local/bin/startxautolock
@@ -0,0 +1,2 @@
+#!/bin/sh
+xautolock -time 1 -notify 5 -locker ilock -notifier 'notify-send "Screen will lock in 5s"' -secure -corners "+-+-" -cornerdelay 5 -detectsleep &
diff --git a/scripts/.local/bin/tab-buttons b/scripts/.local/bin/tab-buttons
deleted file mode 100755
index a056d19..0000000
--- a/scripts/.local/bin/tab-buttons
+++ /dev/null
@@ -1,10 +0,0 @@
-pad=$(xsetwacom list | grep pad | cut -f 1 | xargs)
-
-xsetwacom --set "$pad" Button 1 "key +shift +alt p -alt -shift"
-xsetwacom --set "$pad" Button 2 "key +shift +alt a -alt -shift"
-xsetwacom --set "$pad" Button 3 12
-xsetwacom --set "$pad" Button 8 13
-xsetwacom --set "$pad" Button 9 14
-xsetwacom --set "$pad" Button 10 "key +shift +alt l -alt -shift"
-xsetwacom --set "$pad" Button 11 "key +shift +alt s -alt -shift"
-xsetwacom --set "$pad" Button 12 "key +ctrl z -ctrl"