Persist keyboard backlight color on Linux laptop System76 sells laptops that come preloaded with their Ubuntu spinoff Pop_OS. The laptop keyboards have backlights that can change color and brightness, but so far I haven't found any way to keep the setting when I restart the machine; it goes back to bright white.
Here's someone else struggling with the same problem somewhat recently. The comments link to a useful python script, but it doesn't seem to be finished to the point where it would do what's wanted. (It's primary goal seems to be a GUI, with persistence on the wishlist.)
I finally got sick of it and implemented the behavior I wanted using systemd, which I'm using to call a Python script.
Since other people have asked the same questions as me in the past, I figured I'd share my solution, but the first step is to ask for feedback.
Is this a good solution to the problem? Are there mistakes or other reasons people shouldn't use it?
#!/usr/bin/env python3

import argparse as ag
import os.path as path
import re

print(&quot;Attempting to set system76 keyboard backlight color and brightness.&quot;)

&quot;&quot;&quot;
Partially cribbed from
https://github.com/ahoneybun/keyboard-color-chooser/blob/master/keyboard-color-switcher.py

Called by 
/etc/systemd/system/system76-kb-backlight.service
as decribed here:
https://www.howtogeek.com/687970/how-to-run-a-linux-program-at-startup-with-systemd/

Example file /etc/systemd/system/system76-kb-backlight.service
``
[Unit]
Description=Set color and brightness of system-76 laptop keyboard backlight

[Service]
Type=simple
ExecStart=systemd-cat /path_to_this_executable/set_system76_kb_backlight -g 55 -r FF -B 150

[Install]
WantedBy=multi-user.target
``
(Don't forget to enable the service.)
&quot;&quot;&quot;

def color_fragment(string):
    if re.fullmatch(r'^[0-9A-F]{2}$', string):
        return string
    else:
        raise ag.ArgumentTypeError(f'&quot;{string}&quot; is not a two-digit hex value.')

def brightness_fragment(string):
    if re.fullmatch(r'^[0-9]{1,3}$', string) and 0 &lt;= int(string) and 255 &gt;= int(string):
        return string
    else:
        raise ag.ArgumentTypeError(f'&quot;{string}&quot; is not an integer 0-255.')

arg_parse = ag.ArgumentParser(description=&quot;Set the color and brightness of the system76 keyboard backlight.&quot;)
arg_parse.add_argument('-r', help=&quot;The red RGB value (00 to FF).&quot;, default=&quot;00&quot;, type=color_fragment)
arg_parse.add_argument('-g', help=&quot;The green RGB value (00 to FF).&quot;, default=&quot;00&quot;, type=color_fragment)
arg_parse.add_argument('-b', help=&quot;The blue RGB value (00 to FF).&quot;, default=&quot;00&quot;, type=color_fragment)
arg_parse.add_argument('-B', help=&quot;The brightness (0 to 255).&quot;, default=&quot;48&quot;, type=brightness_fragment)

args = arg_parse.parse_args()
red = args.r
green = args.g
blue = args.b
brightness = args.B
color = f'{red}{green}{blue}'

ledPath = &quot;/&quot; + path.join('sys', 'class', 'leds', 'system76_acpi::kbd_backlight') + '/'
if not path.exists(ledPath):
    ledPath = &quot;/&quot; + path.join('sys', 'class', 'leds', 'system76::kbd_backlight') + '/'

regions = ['left', 'center', 'right', 'extra']
region_paths = [ledPath + f'color_{r}' for r in regions]
brightness_path = ledPath + 'brightness'
settings = {brightness_path: brightness,
            **{rp: color for rp in region_paths}}

for (p,s) in settings.items():
    with open(p, 'w') as f:
        f.write(s)

print(&quot;Successfully set system76 keyboard backlight brightness.&quot;)
arg_parse.exit(0)
Namespaces...
... are not for creating taxonomies. They are there to avoid name collisions. Also the chosen module alias for argparse seems strange. It's name is derived from argument parser so, if any, the obvious abbreviation should be ap. However, it might be better to just import the needed names, instead of creating taxonomies, since there are no name collisions in your code base.
from argparse import ArgumentTypeError, ArgumentParser
from os.path import exists, join
from re import fullmatch

Don't run business logic on module load
Currently, your script parses the command line arguments and writes to files, as soon as the module is laoded. This can be avoided by using an if __name__ == '__main__': guard and / or a main() function.
def main():
    &quot;&quot;&quot;Runs the script.&quot;&quot;&quot;

    arg_parse = ag.ArgumentParser(description=&quot;Set the color and brightness of the system76 keyboard backlight.&quot;)
    arg_parse.add_argument('-r', help=&quot;The red RGB value (00 to FF).&quot;, default=&quot;00&quot;, type=color_fragment)
    arg_parse.add_argument('-g', help=&quot;The green RGB value (00 to FF).&quot;, default=&quot;00&quot;, type=color_fragment)
    arg_parse.add_argument('-b', help=&quot;The blue RGB value (00 to FF).&quot;, default=&quot;00&quot;, type=color_fragment)
    arg_parse.add_argument('-B', help=&quot;The brightness (0 to 255).&quot;, default=&quot;48&quot;, type=brightness_fragment)

    args = arg_parse.parse_args()
    …

if __name__ == '__main__':
    main()

It might be even better to outsource the argument parsing into an own function.
def get_args():
    &quot;&quot;&quot;Parses the command line arguments.&quot;&quot;&quot;

    arg_parse = ag.ArgumentParser(description=&quot;Set the color and brightness of the system76 keyboard backlight.&quot;)
    arg_parse.add_argument('-r', help=&quot;The red RGB value (00 to FF).&quot;, default=&quot;00&quot;, type=color_fragment)
    arg_parse.add_argument('-g', help=&quot;The green RGB value (00 to FF).&quot;, default=&quot;00&quot;, type=color_fragment)
    arg_parse.add_argument('-b', help=&quot;The blue RGB value (00 to FF).&quot;, default=&quot;00&quot;, type=color_fragment)
    arg_parse.add_argument('-B', help=&quot;The brightness (0 to 255).&quot;, default=&quot;48&quot;, type=brightness_fragment)
    return arg_parse.parse_args()


def main():
    &quot;&quot;&quot;Runs the script.&quot;&quot;&quot;

    args = get_args()
    …

Input validation
Your input validation seems complicated. In both cases, for the RGB values and the brightness, an integer within a certain range is expected.
def parse_rgb(text):
    &quot;&quot;&quot;Parses an RGB value from a string.&quot;&quot;&quot;
    
    rgb = int(text)
    
    if not 0 &lt;= rgb &lt;= 0xff:
        raise ValueError('RGB value out of bounds.')

    return rgb


def parse_brightness(text):
    &quot;&quot;&quot;Parses a value for the brightness from a string.&quot;&quot;&quot;
    
    brightness = int(text)
    
    if not 0 &lt;= brightness &lt;= 255:
        raise ValueError('Brightness out of bounds.')

    return brightness

Note that argparse will automatically display an error message if a parser specified at type= raises a ValueError. So you can also get rid of one import and work with a built-in exception instead.
The downside of the integer parsing is, that you'll have to convert the ints back to strings then:
red = hex(args.r)[2:].upper().zfill(2)
green = hex(args.g)[2:].upper().zfill(2)
blue = hex(args.b)[2:].upper().zfill(2)
brightness = str(args.B)
…

You may also subclass an int for this use case:
class RGB(int):
    def __init__(self, value):
        if not 0 &lt;= value &lt;= 0xff:
            raise ValueError('RGB value out of bounds.')

    def __str__(self):
        return hex(self)[2:].upper().zfill(2)

    …

arg_parse.add_argument('-r', help=&quot;The red RGB value (0x00 to 0xff).&quot;, default=RGB(0), type=RGB)
…
red = str(args.r)