Compare commits
13 Commits
813c45d36b
...
master
Author | SHA1 | Date | |
---|---|---|---|
17e3aa6841 | |||
795c8fab71 | |||
8496ec73ad | |||
631e228bd5 | |||
550445e0f9 | |||
336c4267c8 | |||
8c9e57f97b | |||
97cb513fd5 | |||
ea12d87356 | |||
1c500e015c | |||
32849eab29 | |||
01871949a3 | |||
030642e8cf |
2
firmware/.envrc
Normal file
2
firmware/.envrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
watch_file default.nix
|
||||||
|
use flake ..#firmware --override-input rootdir "file+file://"<(printf %s "$PWD")
|
2
firmware/.gitignore
vendored
Normal file
2
firmware/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/result
|
||||||
|
/*.img*
|
1
firmware/.keys/.gitignore
vendored
Normal file
1
firmware/.keys/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/*.key
|
1
firmware/.keys/management.pub
Normal file
1
firmware/.keys/management.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINd+aujm9F06jaQIAvk/0ptQNDHp57J429SqIquVmhbh qclk-management
|
63
firmware/app.nix
Normal file
63
firmware/app.nix
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{ lib, pkgs, config, ... }:
|
||||||
|
let
|
||||||
|
configurer = pkgs.replaceVarsWith {
|
||||||
|
src = ./configurer.py;
|
||||||
|
isExecutable = true;
|
||||||
|
|
||||||
|
replacements = {
|
||||||
|
python = pkgs.python3.withPackages (ps: with ps; [ pyyaml ]);
|
||||||
|
utilLinux = pkgs.util-linux;
|
||||||
|
wireguardTools = pkgs.wireguard-tools;
|
||||||
|
systemd = config.systemd.package;
|
||||||
|
iwd = config.networking.wireless.iwd.package;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
app = pkgs.replaceVarsWith {
|
||||||
|
src = ./app.py;
|
||||||
|
isExecutable = true;
|
||||||
|
|
||||||
|
replacements = {
|
||||||
|
python = pkgs.python3.withPackages (ps: with ps; [ pyyaml aiohttp ]);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
systemd = {
|
||||||
|
services = {
|
||||||
|
qclk-configurer = {
|
||||||
|
description = "qCLK dynamic configurer";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "notify-reload";
|
||||||
|
ExecStart = "${configurer} serve";
|
||||||
|
Restart = "on-failure";
|
||||||
|
};
|
||||||
|
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
qclk-app = {
|
||||||
|
description = "qCLK app";
|
||||||
|
after = [ "qclk-configurer.service" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${app}";
|
||||||
|
Restart = "on-failure";
|
||||||
|
};
|
||||||
|
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services = {
|
||||||
|
udev.extraRules = ''
|
||||||
|
SUBSYSTEM=="block", ACTION=="add", ENV{ID_BUS}=="usb", ENV{ID_FS_TYPE}=="vfat", RUN+="${configurer} ext-load %E{ID_PATH_TAG} %N"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
29
firmware/app.py
Executable file
29
firmware/app.py
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
#! @python@/bin/python -B
|
||||||
|
import os
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
CONF_FILE = os.getenv('CONFIG', '/etc/qclk/config.yaml')
|
||||||
|
conf_k = web.AppKey('config', dict)
|
||||||
|
|
||||||
|
def log(m):
|
||||||
|
print(m, file=sys.stderr)
|
||||||
|
|
||||||
|
routes = web.RouteTableDef()
|
||||||
|
|
||||||
|
@routes.get('/')
|
||||||
|
async def index(req):
|
||||||
|
return web.Response(text='Hello, world!')
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = web.Application()
|
||||||
|
|
||||||
|
with open(CONF_FILE) as f:
|
||||||
|
app[conf_k] = yaml.safe_load(f)
|
||||||
|
app.add_routes(routes)
|
||||||
|
|
||||||
|
web.run_app(app, port=8080)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
85
firmware/base.nix
Normal file
85
firmware/base.nix
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
system = {
|
||||||
|
stateVersion = "24.11";
|
||||||
|
nixos = {
|
||||||
|
distroName = "qCLKOS";
|
||||||
|
};
|
||||||
|
name = "qclk";
|
||||||
|
};
|
||||||
|
documentation.enable = false;
|
||||||
|
|
||||||
|
time.timeZone = "Europe/Dublin";
|
||||||
|
i18n.defaultLocale = "en_IE.UTF-8";
|
||||||
|
|
||||||
|
boot = {
|
||||||
|
loader = {
|
||||||
|
grub.enable = false;
|
||||||
|
};
|
||||||
|
initrd = {
|
||||||
|
systemd = {
|
||||||
|
enable = true;
|
||||||
|
emergencyAccess = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
consoleLogLevel = 7;
|
||||||
|
};
|
||||||
|
|
||||||
|
nix = {
|
||||||
|
# We're a flake-only gal
|
||||||
|
channel.enable = false;
|
||||||
|
settings = {
|
||||||
|
experimental-features = [ "nix-command" "flakes" "ca-derivations" ];
|
||||||
|
extra-substituters = [ "https://nix-cache.nul.ie" ];
|
||||||
|
extra-trusted-public-keys = [ "nix-cache.nul.ie-1:BzH5yMfF4HbzY1C977XzOxoPhEc9Zbu39ftPkUbH+m4=" ];
|
||||||
|
fallback = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users = {
|
||||||
|
users = {
|
||||||
|
root = {
|
||||||
|
openssh.authorizedKeys.keyFiles = [
|
||||||
|
.keys/management.pub
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
systemPackages = with pkgs; [
|
||||||
|
usbutils
|
||||||
|
tcpdump
|
||||||
|
|
||||||
|
(pkgs.vim.customize {
|
||||||
|
name = "vim";
|
||||||
|
vimrcConfig.packages.default = {
|
||||||
|
start = [ pkgs.vimPlugins.vim-nix ];
|
||||||
|
};
|
||||||
|
vimrcConfig.customRC = "syntax on";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
programs = {
|
||||||
|
command-not-found.enable = false;
|
||||||
|
htop.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
services = {
|
||||||
|
getty.autologinUser = "root";
|
||||||
|
|
||||||
|
openssh = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
PermitRootLogin = "prohibit-password";
|
||||||
|
PasswordAuthentication = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
229
firmware/configurer.py
Executable file
229
firmware/configurer.py
Executable file
@@ -0,0 +1,229 @@
|
|||||||
|
#! @python@/bin/python -B
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import tomllib
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
CONF_FILE = '/etc/qclk/config.yaml'
|
||||||
|
WG_KEY_FILE = '/etc/qclk/wg.key'
|
||||||
|
|
||||||
|
IWD_PATH = '/var/lib/iwd'
|
||||||
|
IWD_CONF = '''[Settings]
|
||||||
|
Hidden={hidden}
|
||||||
|
|
||||||
|
[Security]
|
||||||
|
Passphrase={psk}
|
||||||
|
'''
|
||||||
|
|
||||||
|
MANAGEMENT_NET = '''[Match]
|
||||||
|
Name=management
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
Address={ip}/32
|
||||||
|
'''
|
||||||
|
|
||||||
|
def log(m):
|
||||||
|
print(m, file=sys.stderr)
|
||||||
|
|
||||||
|
def iwd_ssid_basename(ssid: str) -> str:
|
||||||
|
if ssid.isalnum() and not any(c in ssid for c in '-_ '):
|
||||||
|
return ssid
|
||||||
|
|
||||||
|
return f"={ssid.encode('utf-8').hex()}"
|
||||||
|
|
||||||
|
class Configurer:
|
||||||
|
tmpdir = '/run/qclk'
|
||||||
|
|
||||||
|
def __init__(self, conf_file: str, wg_key_file: str):
|
||||||
|
self.conf_file = conf_file
|
||||||
|
self.load_config()
|
||||||
|
|
||||||
|
with open(wg_key_file, 'rb') as f:
|
||||||
|
output = subprocess.check_output(['@wireguardTools@/bin/wg', 'pubkey'], input=f.read())
|
||||||
|
pubkey = base64.b64decode(output)
|
||||||
|
self.id = pubkey[:4].hex()
|
||||||
|
|
||||||
|
self.load_dir = os.path.join(self.tmpdir, 'load')
|
||||||
|
self.sd_sock = None
|
||||||
|
|
||||||
|
os.makedirs(self.tmpdir, exist_ok=True)
|
||||||
|
os.makedirs(self.load_dir, exist_ok=True)
|
||||||
|
|
||||||
|
def load_config(self):
|
||||||
|
with open(self.conf_file) as f:
|
||||||
|
self.config = yaml.safe_load(f)
|
||||||
|
|
||||||
|
def write_config(self):
|
||||||
|
log(f'Updating config')
|
||||||
|
tmp = os.path.join(self.tmpdir, 'new-config.yaml')
|
||||||
|
with open(tmp, 'w') as f:
|
||||||
|
yaml.dump(self.config, f)
|
||||||
|
|
||||||
|
shutil.move(tmp, self.conf_file)
|
||||||
|
|
||||||
|
def _setup_hostname(self):
|
||||||
|
hostname = f'qclk-{self.id}'
|
||||||
|
log(f"Setting hostname to '{hostname}'")
|
||||||
|
socket.sethostname(hostname)
|
||||||
|
|
||||||
|
def _setup_wifi(self):
|
||||||
|
os.makedirs(IWD_CONF, exist_ok=True)
|
||||||
|
if 'wifi' in self.config:
|
||||||
|
conf = self.config['wifi']
|
||||||
|
tmp = os.path.join(self.tmpdir, 'wifi.psk')
|
||||||
|
with open(tmp, 'w') as f:
|
||||||
|
print(
|
||||||
|
IWD_CONF.format(
|
||||||
|
hidden=str(conf['hidden']).lower(),
|
||||||
|
psk=conf['password']),
|
||||||
|
file=f)
|
||||||
|
|
||||||
|
fname = f"{iwd_ssid_basename(conf['ssid'])}.psk"
|
||||||
|
log(f"Writing IWD config file '{fname}' for network '{conf['ssid']}'")
|
||||||
|
shutil.move(tmp, os.path.join(IWD_PATH, fname))
|
||||||
|
else:
|
||||||
|
fname = None
|
||||||
|
|
||||||
|
for f in os.listdir(IWD_PATH):
|
||||||
|
if (fname is not None and f != fname) and f.endswith('.psk'):
|
||||||
|
log(f"Cleaning up old IWD config '{f}'")
|
||||||
|
os.remove(os.path.join(IWD_PATH, f))
|
||||||
|
|
||||||
|
subprocess.call(['@iwd@/bin/iwctl', 'station', 'wifi', 'scan'])
|
||||||
|
|
||||||
|
def _setup_mgmt(self):
|
||||||
|
conf = self.config['management']
|
||||||
|
tmp = os.path.join(self.tmpdir, 'management.network')
|
||||||
|
with open(tmp, 'w') as f:
|
||||||
|
print(MANAGEMENT_NET.format(ip=conf['ip']), file=f)
|
||||||
|
|
||||||
|
log(f"Configuring management IP {conf['ip']}")
|
||||||
|
os.makedirs('/run/systemd/network', exist_ok=True)
|
||||||
|
shutil.move(tmp, '/run/systemd/network/20-management.network')
|
||||||
|
subprocess.check_call(['@systemd@/bin/networkctl', 'reload'])
|
||||||
|
|
||||||
|
def reconcile(self):
|
||||||
|
if self.sd_sock is not None:
|
||||||
|
self.sd_sock.sendall(b'STATUS=Reconciling configuration...')
|
||||||
|
|
||||||
|
self._setup_hostname()
|
||||||
|
self._setup_wifi()
|
||||||
|
self._setup_mgmt()
|
||||||
|
|
||||||
|
if self.sd_sock is not None:
|
||||||
|
self.sd_sock.sendall(b'STATUS=OK\nREADY=1')
|
||||||
|
|
||||||
|
def _ext_load(self, identifier: str, device: str):
|
||||||
|
mountpoint = os.path.join(self.tmpdir, 'mnt', identifier)
|
||||||
|
os.makedirs(mountpoint, exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
log(f'Mounting {device} -> {mountpoint}')
|
||||||
|
subprocess.check_call(['@utilLinux@/bin/mount', '-t', 'vfat', '-o', 'ro', device, mountpoint])
|
||||||
|
try:
|
||||||
|
path = os.path.join(mountpoint, 'qclk.toml')
|
||||||
|
if not os.path.exists(path):
|
||||||
|
log(f'No config file found on {device}')
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
ext_conf = tomllib.load(f)
|
||||||
|
finally:
|
||||||
|
subprocess.check_call(['@utilLinux@/bin/umount', mountpoint])
|
||||||
|
finally:
|
||||||
|
os.rmdir(mountpoint)
|
||||||
|
|
||||||
|
if 'wifi' in ext_conf:
|
||||||
|
log(f'Loading WiFi settings from {device}')
|
||||||
|
self.config['wifi'] = {
|
||||||
|
'ssid': ext_conf['wifi']['ssid'],
|
||||||
|
'password': ext_conf['wifi'].get('password', ''),
|
||||||
|
'hidden': ext_conf['wifi'].get('hidden', False),
|
||||||
|
}
|
||||||
|
self._setup_wifi()
|
||||||
|
self.write_config()
|
||||||
|
|
||||||
|
def serve(self, args: argparse.Namespace):
|
||||||
|
os.makedirs(os.path.join(self.tmpdir, 'load'), exist_ok=True)
|
||||||
|
|
||||||
|
addr = os.getenv('NOTIFY_SOCKET')
|
||||||
|
if addr is not None:
|
||||||
|
self.sd_sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||||
|
if addr[0] == '@':
|
||||||
|
addr = '\0' + addr[1:]
|
||||||
|
self.sd_sock.connect(addr)
|
||||||
|
|
||||||
|
running = True
|
||||||
|
def sighandler(sig, frame):
|
||||||
|
nonlocal running
|
||||||
|
if sig in (signal.SIGINT, signal.SIGTERM):
|
||||||
|
running = False
|
||||||
|
return
|
||||||
|
|
||||||
|
if sig == signal.SIGHUP:
|
||||||
|
if self.sd_sock is not None:
|
||||||
|
t = time.clock_gettime_ns(time.CLOCK_MONOTONIC) // 1000
|
||||||
|
self.sd_sock.sendall(f'RELOADING=1\nMONOTONIC_USEC={t}'.encode('ascii'))
|
||||||
|
self.load_config()
|
||||||
|
self.reconcile()
|
||||||
|
elif sig == signal.SIGUSR1:
|
||||||
|
for identifier in os.listdir(self.load_dir):
|
||||||
|
fname = os.path.join(self.load_dir, identifier)
|
||||||
|
with open(fname) as f:
|
||||||
|
device = f.read().strip()
|
||||||
|
os.remove(fname)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._ext_load(identifier, device)
|
||||||
|
except Exception as ex:
|
||||||
|
log(f'Failed to load config from {device}: {ex}')
|
||||||
|
|
||||||
|
for s in [signal.SIGINT, signal.SIGTERM, signal.SIGHUP, signal.SIGUSR1]:
|
||||||
|
signal.signal(s, sighandler)
|
||||||
|
|
||||||
|
self.reconcile()
|
||||||
|
while running:
|
||||||
|
signal.pause()
|
||||||
|
|
||||||
|
if self.sd_sock is not None:
|
||||||
|
self.sd_sock.close()
|
||||||
|
|
||||||
|
def ext_load(self, args: argparse.Namespace):
|
||||||
|
with open(os.path.join(self.load_dir, args.identifier), 'w') as f:
|
||||||
|
print(args.device, file=f)
|
||||||
|
output = subprocess.check_output(
|
||||||
|
['@systemd@/bin/systemctl', 'show', '--property', 'MainPID', '--value', 'qclk-configurer.service'], encoding='ascii')
|
||||||
|
pid = int(output.strip())
|
||||||
|
|
||||||
|
os.kill(pid, signal.SIGUSR1)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='qclkOS configurer')
|
||||||
|
cmds = parser.add_subparsers(title='commands', help='reconcile')
|
||||||
|
|
||||||
|
rec_parser = cmds.add_parser('serve')
|
||||||
|
rec_parser.set_defaults(func=Configurer.serve)
|
||||||
|
|
||||||
|
eload_parser = cmds.add_parser('ext-load')
|
||||||
|
eload_parser.set_defaults(func=Configurer.ext_load)
|
||||||
|
eload_parser.add_argument('identifier', help='Unique device name')
|
||||||
|
eload_parser.add_argument('device', help='Block device path')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
c = Configurer(CONF_FILE, WG_KEY_FILE)
|
||||||
|
if not hasattr(args, 'func'):
|
||||||
|
c.serve(args)
|
||||||
|
else:
|
||||||
|
args.func(c, args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
157
firmware/default.nix
Normal file
157
firmware/default.nix
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
{ self, inputs, ... }:
|
||||||
|
let
|
||||||
|
nixpkgsLib = inputs.nixpkgs.lib.extend (final: prev:
|
||||||
|
let
|
||||||
|
date = final.substring 0 8 (self.lastModifiedDate or self.lastModified or "19700101");
|
||||||
|
revCode = flake: flake.shortRev or "dirty";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
trivial = prev.trivial // {
|
||||||
|
release = "25.05:u-${prev.trivial.release}";
|
||||||
|
codeName = "Beta";
|
||||||
|
revisionWithDefault = default: self.rev or default;
|
||||||
|
versionSuffix = ".${date}.${revCode self}:u-${revCode inputs.nixpkgs}";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
mkSystem = target: nixpkgsLib.nixosSystem {
|
||||||
|
modules = [
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
inputs.impermanence.nixosModules.impermanence
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
./base.nix
|
||||||
|
./disk.nix
|
||||||
|
./network.nix
|
||||||
|
./app.nix
|
||||||
|
|
||||||
|
target
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
flake.nixosConfigurations = {
|
||||||
|
qclk-rpi3 = mkSystem target/rpi3.nix;
|
||||||
|
};
|
||||||
|
|
||||||
|
perSystem = { lib, libMy, pkgs, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) concatMapStringsSep;
|
||||||
|
|
||||||
|
devPython = pkgs.python3.withPackages (ps: with ps; [ pyyaml aiohttp ]);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devenv.shells.firmware = libMy.withRootdir {
|
||||||
|
packages = with pkgs; [
|
||||||
|
nixos-rebuild
|
||||||
|
nixVersions.latest
|
||||||
|
wireguard-tools
|
||||||
|
devPython
|
||||||
|
];
|
||||||
|
|
||||||
|
scripts =
|
||||||
|
let
|
||||||
|
shellUtils = ''
|
||||||
|
die() {
|
||||||
|
echo "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
exportPath = ps:
|
||||||
|
let pkgsPath = concatMapStringsSep ":" (p: "${p}/bin") ps; in ''export PATH="$PATH:${pkgsPath}"'';
|
||||||
|
|
||||||
|
buildImgRootHelper = pkgs.writeShellScript "fixup-perms" ''
|
||||||
|
pushd "$1"
|
||||||
|
# systemd-network GID
|
||||||
|
chgrp 152 etc/qclk/wg.key
|
||||||
|
chmod 640 etc/qclk/wg.key
|
||||||
|
popd
|
||||||
|
|
||||||
|
mkfs.ext4 -L qclkos-persist -d "$1" "$2"
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
build.exec = ''
|
||||||
|
nix build "..#nixosConfigurations.qclk-$1.config.system.build.toplevel"
|
||||||
|
'';
|
||||||
|
build-image.exec = ''
|
||||||
|
set -e
|
||||||
|
${exportPath (with pkgs; [ util-linux fakeroot e2fsprogs zstd python3 ])}
|
||||||
|
${shellUtils}
|
||||||
|
usage() {
|
||||||
|
die "usage: $0 <target> <IP>"
|
||||||
|
}
|
||||||
|
|
||||||
|
populate_persist() {
|
||||||
|
mkdir -p etc/qclk
|
||||||
|
|
||||||
|
old_umask="$(umask)"
|
||||||
|
umask 077
|
||||||
|
|
||||||
|
wg genkey > etc/qclk/wg.key
|
||||||
|
wg_pubkey="$(wg pubkey < etc/qclk/wg.key)"
|
||||||
|
id="$(python3 -c "import base64; print(base64.b64decode('$wg_pubkey')[:4].hex())")"
|
||||||
|
|
||||||
|
pin="$(python3 -c "import random; print('''.join(str(random.randint(0, 9)) for _ in range(6)))")"
|
||||||
|
cat << EOF > etc/qclk/config.yaml
|
||||||
|
management:
|
||||||
|
ip: $ip
|
||||||
|
pin: '$pin'
|
||||||
|
EOF
|
||||||
|
umask "$old_umask"
|
||||||
|
}
|
||||||
|
|
||||||
|
[ $# -eq 2 ] || usage
|
||||||
|
|
||||||
|
target=$1
|
||||||
|
ip=$2
|
||||||
|
|
||||||
|
nix build "..#nixosConfigurations.qclk-$target.config.my.disk.image"
|
||||||
|
|
||||||
|
persistRoot=$(mktemp --tmpdir -d qclkos-persist-XXXXX)
|
||||||
|
pushd "$persistRoot"
|
||||||
|
populate_persist
|
||||||
|
popd
|
||||||
|
|
||||||
|
out=qclkos-$target-$id.img
|
||||||
|
cp --sparse=always result/qclkos-$target.img $out
|
||||||
|
chmod u+w $out
|
||||||
|
|
||||||
|
eval $(partx $out -o START,SECTORS --nr 2 --pairs)
|
||||||
|
persistImg=$(mktemp --tmpdir qclkos-persist-XXXXX.img)
|
||||||
|
truncate -s $((SECTORS * 512)) $persistImg
|
||||||
|
fakeroot ${buildImgRootHelper} $persistRoot $persistImg
|
||||||
|
|
||||||
|
dd conv=notrunc if=$persistImg of=$out seek=$START count=$SECTORS
|
||||||
|
rm -r "$persistRoot" "$persistImg"
|
||||||
|
[ -z "$NO_COMPRESS" ] && zstd -7 -T0 --rm -f $out
|
||||||
|
|
||||||
|
echo "====== DONE! ======"
|
||||||
|
echo "WireGuard pubkey: $wg_pubkey"
|
||||||
|
echo "Control PIN: $pin"
|
||||||
|
'';
|
||||||
|
|
||||||
|
push-config.exec = ''
|
||||||
|
${shellUtils}
|
||||||
|
usage() {
|
||||||
|
die "usage: $0 <host> <target> <verb>"
|
||||||
|
}
|
||||||
|
[ $# -eq 3 ] || usage
|
||||||
|
|
||||||
|
host=$1; shift
|
||||||
|
target=$1; shift
|
||||||
|
verb=$1; shift
|
||||||
|
|
||||||
|
export NIX_SSHOPTS="-i .keys/management.key"
|
||||||
|
nixos-rebuild $verb --flake ..#qclk-$target --target-host root@"$host" --use-substitutes "$@"
|
||||||
|
'';
|
||||||
|
clean.exec = ''
|
||||||
|
rm -f qclkos-*.img*
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
208
firmware/disk.nix
Normal file
208
firmware/disk.nix
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
# Based on `nixos/modules/installer/sd-card/sd-image.nix`
|
||||||
|
{ lib, modulesPath, pkgs, config, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) concatMap mkOption;
|
||||||
|
|
||||||
|
cfg = config.my.disk;
|
||||||
|
|
||||||
|
nixImage = (pkgs.callPackage "${modulesPath}/../lib/make-ext4-fs.nix" {
|
||||||
|
volumeLabel = "qclkos-nix";
|
||||||
|
uuid = "38bd3706-c049-430d-9e33-5cd0e437279b";
|
||||||
|
storePaths = config.system.build.toplevel;
|
||||||
|
compressImage = false;
|
||||||
|
}).overrideAttrs (o: {
|
||||||
|
buildCommand = ''
|
||||||
|
# We need to move the store up a level since we're not using this as a rootfs but as /nix
|
||||||
|
# HACK: `populateImageCommands` is executed in a subshell _before_ the store paths are copied in...
|
||||||
|
shopt -s expand_aliases
|
||||||
|
nixUpThenFaketime() {
|
||||||
|
mv rootImage/{nix/store,}
|
||||||
|
rmdir rootImage/nix
|
||||||
|
|
||||||
|
faketime "$@"
|
||||||
|
}
|
||||||
|
alias faketime=nixUpThenFaketime
|
||||||
|
|
||||||
|
${o.buildCommand}
|
||||||
|
'';
|
||||||
|
});
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.my.disk = with lib.types; {
|
||||||
|
bootSize = mkOption {
|
||||||
|
description = "/boot size (MiB).";
|
||||||
|
type = ints.unsigned;
|
||||||
|
default = 1024;
|
||||||
|
};
|
||||||
|
persistSize = mkOption {
|
||||||
|
description = "/persist size (MiB).";
|
||||||
|
type = ints.unsigned;
|
||||||
|
default = 8192;
|
||||||
|
};
|
||||||
|
|
||||||
|
populateBootCommands = mkOption {
|
||||||
|
description = ''
|
||||||
|
Shell commands to populate the ./boot directory.
|
||||||
|
All files in that directory are copied to the
|
||||||
|
/boot partition on the image.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
imageBaseName = mkOption {
|
||||||
|
description = "Prefix of the name of the generated image file.";
|
||||||
|
default = "qclkos";
|
||||||
|
};
|
||||||
|
image = mkOption {
|
||||||
|
description = "Output disk image.";
|
||||||
|
type = unspecified;
|
||||||
|
};
|
||||||
|
|
||||||
|
rootSize = mkOption {
|
||||||
|
description = "tmpfs root size.";
|
||||||
|
type = str;
|
||||||
|
default = "2G";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
fileSystems = {
|
||||||
|
"/boot" = {
|
||||||
|
device = "/dev/disk/by-label/QCLKOS_BOOT";
|
||||||
|
fsType = "vfat";
|
||||||
|
};
|
||||||
|
"/persist" = {
|
||||||
|
device = "/dev/disk/by-label/qclkos-persist";
|
||||||
|
fsType = "ext4";
|
||||||
|
neededForBoot = true;
|
||||||
|
};
|
||||||
|
"/nix" = {
|
||||||
|
device = "/dev/disk/by-label/qclkos-nix";
|
||||||
|
fsType = "ext4";
|
||||||
|
};
|
||||||
|
|
||||||
|
"/" = {
|
||||||
|
device = "yeet";
|
||||||
|
fsType = "tmpfs";
|
||||||
|
options = [ "size=${cfg.rootSize}" "mode=755" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
boot = {
|
||||||
|
postBootCommands = ''
|
||||||
|
# On the first boot do some maintenance tasks
|
||||||
|
if [ -f /nix/nix-path-registration ]; then
|
||||||
|
set -euo pipefail
|
||||||
|
set -x
|
||||||
|
# Figure out device names for the boot device and nix filesystem.
|
||||||
|
nixPart=$(${pkgs.util-linux}/bin/findmnt -n -o SOURCE /nix)
|
||||||
|
bootDevice=$(lsblk -npo PKNAME $nixPart)
|
||||||
|
partNum=$(lsblk -npo MAJ:MIN $nixPart | ${pkgs.gawk}/bin/awk -F: '{print $2}')
|
||||||
|
|
||||||
|
# Resize the nix partition and the filesystem to fit the disk
|
||||||
|
echo ",+," | sfdisk -N$partNum --no-reread $bootDevice
|
||||||
|
${pkgs.parted}/bin/partprobe
|
||||||
|
${pkgs.e2fsprogs}/bin/resize2fs $nixPart
|
||||||
|
|
||||||
|
# Register the contents of the initial Nix store
|
||||||
|
${config.nix.package.out}/bin/nix-store --load-db < /nix/nix-path-registration
|
||||||
|
|
||||||
|
# nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag.
|
||||||
|
touch /etc/NIXOS
|
||||||
|
${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
|
||||||
|
|
||||||
|
# Prevents this from running on later boots.
|
||||||
|
rm -f /nix/nix-path-registration
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
users.mutableUsers = false;
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
# This is based on parts of `nixfiles/nixos/modules/tmproot.nix`
|
||||||
|
persistence."/persist" = {
|
||||||
|
directories = [
|
||||||
|
"/var/lib/nixos"
|
||||||
|
"/var/lib/systemd"
|
||||||
|
|
||||||
|
"/etc/qclk"
|
||||||
|
];
|
||||||
|
files = [
|
||||||
|
"/etc/machine-id"
|
||||||
|
] ++ (concatMap (k: [ k.path "${k.path}.pub" ]) config.services.openssh.hostKeys);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services = {
|
||||||
|
journald.storage = "volatile";
|
||||||
|
};
|
||||||
|
|
||||||
|
my.disk.image = pkgs.callPackage ({
|
||||||
|
stdenv, dosfstools, e2fsprogs, mtools, libfaketime, util-linux
|
||||||
|
}: stdenv.mkDerivation {
|
||||||
|
name = "${cfg.imageBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
|
||||||
|
nativeBuildInputs = [
|
||||||
|
dosfstools e2fsprogs libfaketime mtools util-linux
|
||||||
|
];
|
||||||
|
|
||||||
|
buildCommand = ''
|
||||||
|
mkdir -p $out
|
||||||
|
export img=$out/${cfg.imageBaseName}.img
|
||||||
|
|
||||||
|
nix_fs=${nixImage}
|
||||||
|
|
||||||
|
# Gap in front of the first partition, in MiB
|
||||||
|
gap=8
|
||||||
|
|
||||||
|
# Create the image file sized to fit /boot and /nix, plus slack for the gap.
|
||||||
|
nixSizeBlocks=$(du -B 512 --apparent-size $nix_fs | awk '{ print $1 }')
|
||||||
|
bootSizeBlocks=$((${toString cfg.bootSize} * 1024 * 1024 / 512))
|
||||||
|
persistSizeBlocks=$((${toString cfg.persistSize} * 1024 * 1024 / 512))
|
||||||
|
imageSize=$((nixSizeBlocks * 512 + persistSizeBlocks * 512 + bootSizeBlocks * 512 + gap * 1024 * 1024))
|
||||||
|
truncate -s $imageSize $img
|
||||||
|
|
||||||
|
# type=b is 'W95 FAT32', type=83 is 'Linux'.
|
||||||
|
# The "bootable" partition is where u-boot will look file for the bootloader
|
||||||
|
# information (dtbs, extlinux.conf file).
|
||||||
|
sfdisk --no-reread --no-tell-kernel $img <<EOF
|
||||||
|
label: dos
|
||||||
|
label-id: 0xabb10d5a
|
||||||
|
|
||||||
|
start=''${gap}M, size=$bootSizeBlocks, type=b, bootable
|
||||||
|
start=$((gap + ${toString cfg.bootSize}))M, size=$persistSizeBlocks, type=83
|
||||||
|
start=$((gap + ${toString cfg.bootSize} + ${toString cfg.persistSize}))M, type=83
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Copy the nixfs into the image
|
||||||
|
eval $(partx $img -o START,SECTORS --nr 3 --pairs)
|
||||||
|
dd conv=notrunc if=$nix_fs of=$img seek=$START count=$SECTORS
|
||||||
|
|
||||||
|
# Create a FAT32 /boot partition of suitable size into boot_part.img
|
||||||
|
eval $(partx $img -o START,SECTORS --nr 1 --pairs)
|
||||||
|
truncate -s $((SECTORS * 512)) boot_part.img
|
||||||
|
|
||||||
|
mkfs.vfat --invariant -n QCLKOS_BOOT boot_part.img
|
||||||
|
|
||||||
|
# Populate the files intended for /boot
|
||||||
|
mkdir boot
|
||||||
|
${cfg.populateBootCommands}
|
||||||
|
|
||||||
|
find boot -exec touch --date=2000-01-01 {} +
|
||||||
|
# Copy the populated /boot into the image
|
||||||
|
cd boot
|
||||||
|
# Force a fixed order in mcopy for better determinism, and avoid file globbing
|
||||||
|
for d in $(find . -type d -mindepth 1 | sort); do
|
||||||
|
faketime "2000-01-01 00:00:00" mmd -i ../boot_part.img "::/$d"
|
||||||
|
done
|
||||||
|
for f in $(find . -type f | sort); do
|
||||||
|
mcopy -pvm -i ../boot_part.img "$f" "::/$f"
|
||||||
|
done
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Verify the FAT partition before copying it.
|
||||||
|
fsck.vfat -vn boot_part.img
|
||||||
|
dd conv=notrunc if=boot_part.img of=$img seek=$START count=$SECTORS
|
||||||
|
'';
|
||||||
|
}) { };
|
||||||
|
};
|
||||||
|
}
|
58
firmware/network.nix
Normal file
58
firmware/network.nix
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{ lib, config, pkgs, ... }: {
|
||||||
|
environment = {
|
||||||
|
systemPackages = with pkgs; [
|
||||||
|
wireguard-tools
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
networking = {
|
||||||
|
hostName = config.system.name;
|
||||||
|
useDHCP = false;
|
||||||
|
useNetworkd = true;
|
||||||
|
|
||||||
|
firewall = {
|
||||||
|
interfaces.management.allowedTCPPorts = [ 8080 ];
|
||||||
|
};
|
||||||
|
|
||||||
|
wireless.iwd = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
# systemd-networkd gets confused if we hop between networks and doesn't redo DHCP
|
||||||
|
General.EnableNetworkConfiguration = true;
|
||||||
|
DriverQuirks.DefaultInterface = "*";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd = {
|
||||||
|
network = {
|
||||||
|
wait-online.enable = false;
|
||||||
|
netdevs = {
|
||||||
|
"10-management" = {
|
||||||
|
netdevConfig = {
|
||||||
|
Name = "management";
|
||||||
|
Kind = "wireguard";
|
||||||
|
};
|
||||||
|
wireguardConfig = {
|
||||||
|
PrivateKeyFile = "/etc/qclk/wg.key";
|
||||||
|
RouteTable = "main";
|
||||||
|
};
|
||||||
|
wireguardPeers = [
|
||||||
|
{
|
||||||
|
Endpoint = "94.142.240.44:51821";
|
||||||
|
PublicKey = "itMQ2DlPEMdJFlIZRQkwa+Mv7cLc9d4zgfzlljEtLn4=";
|
||||||
|
AllowedIPs = [ "10.100.4.1/32" ];
|
||||||
|
PersistentKeepalive = 15;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
networks = {
|
||||||
|
"10-ethernet" = {
|
||||||
|
matchConfig.Name = "ethernet";
|
||||||
|
DHCP = "yes";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
85
firmware/target/rpi3.nix
Normal file
85
firmware/target/rpi3.nix
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
{ pkgs, config, ... }: {
|
||||||
|
nixpkgs.system = "aarch64-linux";
|
||||||
|
|
||||||
|
boot = {
|
||||||
|
kernelParams = [
|
||||||
|
"console=ttyS1,115200n8"
|
||||||
|
"console=tty0"
|
||||||
|
];
|
||||||
|
|
||||||
|
loader.generic-extlinux-compatible = {
|
||||||
|
enable = true;
|
||||||
|
configurationLimit = 3;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
hardware = {
|
||||||
|
deviceTree.filter = "*rpi-3*.dtb";
|
||||||
|
enableRedistributableFirmware = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.network.links = {
|
||||||
|
"10-ethernet" = {
|
||||||
|
matchConfig.Path = "platform-3f980000.usb-usb-0:1.1.1:1.0";
|
||||||
|
linkConfig.Name = "ethernet";
|
||||||
|
};
|
||||||
|
"10-wifi" = {
|
||||||
|
matchConfig.Path = "platform-3f300000.mmc";
|
||||||
|
linkConfig.Name = "wifi";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
my = {
|
||||||
|
disk = {
|
||||||
|
bootSize = 512;
|
||||||
|
persistSize = 1024;
|
||||||
|
rootSize = "128M";
|
||||||
|
imageBaseName = "qclkos-rpi3";
|
||||||
|
|
||||||
|
# Based on `nixos/modules/installer/sd-card/sd-image-aarch64.nix`
|
||||||
|
populateBootCommands =
|
||||||
|
let
|
||||||
|
configTxt = pkgs.writeText "config.txt" ''
|
||||||
|
[pi3]
|
||||||
|
kernel=u-boot-rpi3.bin
|
||||||
|
|
||||||
|
[pi02]
|
||||||
|
kernel=u-boot-rpi3.bin
|
||||||
|
|
||||||
|
# Otherwise the resolution will be weird in most cases, compared to
|
||||||
|
# what the pi3 firmware does by default.
|
||||||
|
disable_overscan=1
|
||||||
|
|
||||||
|
# Supported in newer board revisions
|
||||||
|
arm_boost=1
|
||||||
|
|
||||||
|
[all]
|
||||||
|
# Boot in 64-bit mode.
|
||||||
|
arm_64bit=1
|
||||||
|
|
||||||
|
# U-Boot needs this to work, regardless of whether UART is actually used or not.
|
||||||
|
# Look in arch/arm/mach-bcm283x/Kconfig in the U-Boot tree to see if this is still
|
||||||
|
# a requirement in the future.
|
||||||
|
enable_uart=1
|
||||||
|
|
||||||
|
# Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
|
||||||
|
# when attempting to show low-voltage or overtemperature warnings.
|
||||||
|
avoid_warnings=1
|
||||||
|
'';
|
||||||
|
in ''
|
||||||
|
(cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/boot/)
|
||||||
|
|
||||||
|
# Add the config
|
||||||
|
cp ${configTxt} boot/config.txt
|
||||||
|
|
||||||
|
# Add pi3 specific files
|
||||||
|
cp ${pkgs.ubootRaspberryPi3_64bit}/u-boot.bin boot/u-boot-rpi3.bin
|
||||||
|
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-2-b.dtb boot/
|
||||||
|
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-3-b.dtb boot/
|
||||||
|
cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-3-b-plus.dtb boot/
|
||||||
|
|
||||||
|
${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./boot
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
499
flake.lock
generated
499
flake.lock
generated
@@ -2,30 +2,28 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"cachix": {
|
"cachix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"devenv": "devenv_2",
|
"devenv": [
|
||||||
|
"devenv"
|
||||||
|
],
|
||||||
"flake-compat": [
|
"flake-compat": [
|
||||||
"devenv",
|
"devenv"
|
||||||
"flake-compat"
|
|
||||||
],
|
],
|
||||||
"nixpkgs": [
|
"git-hooks": [
|
||||||
"devenv",
|
"devenv"
|
||||||
"nixpkgs"
|
|
||||||
],
|
],
|
||||||
"pre-commit-hooks": [
|
"nixpkgs": "nixpkgs"
|
||||||
"devenv",
|
|
||||||
"pre-commit-hooks"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1712055811,
|
"lastModified": 1742042642,
|
||||||
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
|
"narHash": "sha256-D0gP8srrX0qj+wNYNPdtVJsQuFzIng3q43thnHXQ/es=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "cachix",
|
"repo": "cachix",
|
||||||
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
|
"rev": "a624d3eaf4b1d225f918de8543ed739f2f574203",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
|
"ref": "latest",
|
||||||
"repo": "cachix",
|
"repo": "cachix",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -33,103 +31,35 @@
|
|||||||
"devenv": {
|
"devenv": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"cachix": "cachix",
|
"cachix": "cachix",
|
||||||
"flake-compat": "flake-compat_2",
|
"flake-compat": "flake-compat",
|
||||||
"nix": "nix_2",
|
"git-hooks": "git-hooks",
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"pre-commit-hooks": "pre-commit-hooks"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1715246369,
|
|
||||||
"narHash": "sha256-gpdfUGNLXr64y+EAUMA3YP/sUO0wBVthrlPIm2PDJpw=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "devenv",
|
|
||||||
"rev": "8f089ccfdef53f2ea5e0bfabe25dba4769f5390f",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "devenv",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devenv_2": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": [
|
|
||||||
"devenv",
|
|
||||||
"cachix",
|
|
||||||
"flake-compat"
|
|
||||||
],
|
|
||||||
"nix": "nix",
|
"nix": "nix",
|
||||||
"nixpkgs": "nixpkgs",
|
|
||||||
"poetry2nix": "poetry2nix",
|
|
||||||
"pre-commit-hooks": [
|
|
||||||
"devenv",
|
|
||||||
"cachix",
|
|
||||||
"pre-commit-hooks"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1708704632,
|
|
||||||
"narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "devenv",
|
|
||||||
"rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"ref": "python-rewrite",
|
|
||||||
"repo": "devenv",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devshell": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils_3",
|
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1713532798,
|
"lastModified": 1746423062,
|
||||||
"narHash": "sha256-wtBhsdMJA3Wa32Wtm1eeo84GejtI43pMrFrmwLXrsEc=",
|
"narHash": "sha256-BgiRweL6nMjeO2BQgnOyIquuviybI4S8Nc8r9hYjcBc=",
|
||||||
"owner": "numtide",
|
"owner": "cachix",
|
||||||
"repo": "devshell",
|
"repo": "devenv",
|
||||||
"rev": "12e914740a25ea1891ec619bb53cf5e6ca922e40",
|
"rev": "aba5cf8412827fdb637fceb2c305d10fcea907c6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "numtide",
|
"owner": "cachix",
|
||||||
"repo": "devshell",
|
"repo": "devenv",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1673956053,
|
"lastModified": 1733328505,
|
||||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||||
"owner": "edolstra",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat_2": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1696426674,
|
|
||||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -139,15 +69,37 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-parts": {
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": [
|
||||||
|
"devenv",
|
||||||
|
"nix",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1712014858,
|
||||||
|
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-parts_2": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1714641030,
|
"lastModified": 1743550720,
|
||||||
"narHash": "sha256-yzcRNDoyVP7+SCNX0wmuDju1NUCt8Dz9+lyUXEI0dbI=",
|
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "e5d10a24b66c3ea8f150e47dfdb0416ab7c3390e",
|
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -156,57 +108,28 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils": {
|
"git-hooks": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"flake-compat": [
|
||||||
|
"devenv"
|
||||||
|
],
|
||||||
|
"gitignore": "gitignore",
|
||||||
|
"nixpkgs": [
|
||||||
|
"devenv",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1689068808,
|
"lastModified": 1742649964,
|
||||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
"narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=",
|
||||||
"owner": "numtide",
|
"owner": "cachix",
|
||||||
"repo": "flake-utils",
|
"repo": "git-hooks.nix",
|
||||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
"rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "numtide",
|
"owner": "cachix",
|
||||||
"repo": "flake-utils",
|
"repo": "git-hooks.nix",
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_2": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1710146030,
|
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_3": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems_3"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1701680307,
|
|
||||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -214,7 +137,7 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"devenv",
|
"devenv",
|
||||||
"pre-commit-hooks",
|
"git-hooks",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -232,166 +155,124 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nix": {
|
"impermanence": {
|
||||||
"inputs": {
|
|
||||||
"flake-compat": "flake-compat",
|
|
||||||
"nixpkgs": [
|
|
||||||
"devenv",
|
|
||||||
"cachix",
|
|
||||||
"devenv",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"nixpkgs-regression": "nixpkgs-regression"
|
|
||||||
},
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1712911606,
|
"lastModified": 1737831083,
|
||||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
"narHash": "sha256-LJggUHbpyeDvNagTUrdhe/pRVp4pnS6wVKALS782gRI=",
|
||||||
"owner": "domenkozar",
|
"owner": "nix-community",
|
||||||
"repo": "nix",
|
"repo": "impermanence",
|
||||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
"rev": "4b3e914cdf97a5b536a889e939fb2fd2b043a170",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "domenkozar",
|
"owner": "nix-community",
|
||||||
"ref": "devenv-2.21",
|
"repo": "impermanence",
|
||||||
"repo": "nix",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nix-github-actions": {
|
"libgit2": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1697646580,
|
||||||
|
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
|
||||||
|
"owner": "libgit2",
|
||||||
|
"repo": "libgit2",
|
||||||
|
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "libgit2",
|
||||||
|
"repo": "libgit2",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"flake-compat": [
|
||||||
"devenv",
|
"devenv"
|
||||||
"cachix",
|
],
|
||||||
"devenv",
|
"flake-parts": "flake-parts",
|
||||||
"poetry2nix",
|
"libgit2": "libgit2",
|
||||||
"nixpkgs"
|
"nixpkgs": "nixpkgs_2",
|
||||||
|
"nixpkgs-23-11": [
|
||||||
|
"devenv"
|
||||||
|
],
|
||||||
|
"nixpkgs-regression": [
|
||||||
|
"devenv"
|
||||||
|
],
|
||||||
|
"pre-commit-hooks": [
|
||||||
|
"devenv"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1688870561,
|
"lastModified": 1745930071,
|
||||||
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
|
"narHash": "sha256-bYyjarS3qSNqxfgc89IoVz8cAFDkF9yPE63EJr+h50s=",
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nix-github-actions",
|
|
||||||
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nix-github-actions",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nix_2": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": [
|
|
||||||
"devenv",
|
|
||||||
"flake-compat"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"devenv",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"nixpkgs-regression": "nixpkgs-regression_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1712911606,
|
|
||||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
|
||||||
"owner": "domenkozar",
|
"owner": "domenkozar",
|
||||||
"repo": "nix",
|
"repo": "nix",
|
||||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
"rev": "b455edf3505f1bf0172b39a735caef94687d0d9c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "domenkozar",
|
"owner": "domenkozar",
|
||||||
"ref": "devenv-2.21",
|
"ref": "devenv-2.24",
|
||||||
"repo": "nix",
|
"repo": "nix",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1692808169,
|
"lastModified": 1733212471,
|
||||||
"narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
|
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
|
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixpkgs-unstable",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1714640452,
|
"lastModified": 1743296961,
|
||||||
"narHash": "sha256-QBx10+k6JWz6u7VsohfSw8g8hjdBZEf8CFzXH1/1Z94=",
|
"narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
|
||||||
"type": "tarball",
|
"owner": "nix-community",
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/50eb7ecf4cd0a5756d7275c8ba36790e5bd53e33.tar.gz"
|
"repo": "nixpkgs.lib",
|
||||||
},
|
"rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
|
||||||
"original": {
|
|
||||||
"type": "tarball",
|
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/50eb7ecf4cd0a5756d7275c8ba36790e5bd53e33.tar.gz"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs-regression": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1643052045,
|
|
||||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "nix-community",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs.lib",
|
||||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs-regression_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1643052045,
|
|
||||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs-stable": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1710695816,
|
|
||||||
"narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "614b4613980a522ba49f0d194531beddbb7220d3",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-23.11",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1715037484,
|
"lastModified": 1717432640,
|
||||||
"narHash": "sha256-OUt8xQFmBU96Hmm4T9tOWTu4oCswCzoVl+pxSq/kiFc=",
|
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ad7efee13e0d216bf29992311536fce1d3eefbef",
|
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "release-24.05",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1746332716,
|
||||||
|
"narHash": "sha256-VBmKSkmw9PYBCEGhBKzORjx+nwNZkPZyHcUHE21A/ws=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "6b1c028bce9c89e9824cde040d6986d428296055",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -399,65 +280,12 @@
|
|||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"poetry2nix": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nix-github-actions": "nix-github-actions",
|
|
||||||
"nixpkgs": [
|
|
||||||
"devenv",
|
|
||||||
"cachix",
|
|
||||||
"devenv",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1692876271,
|
|
||||||
"narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "poetry2nix",
|
|
||||||
"rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "poetry2nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pre-commit-hooks": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": [
|
|
||||||
"devenv",
|
|
||||||
"flake-compat"
|
|
||||||
],
|
|
||||||
"flake-utils": "flake-utils_2",
|
|
||||||
"gitignore": "gitignore",
|
|
||||||
"nixpkgs": [
|
|
||||||
"devenv",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"nixpkgs-stable": "nixpkgs-stable"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1713775815,
|
|
||||||
"narHash": "sha256-Wu9cdYTnGQQwtT20QQMg7jzkANKQjwBD9iccfGKkfls=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "pre-commit-hooks.nix",
|
|
||||||
"rev": "2ac4dcbf55ed43f3be0bae15e181f08a57af24a4",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "pre-commit-hooks.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"devenv": "devenv",
|
"devenv": "devenv",
|
||||||
"devshell": "devshell",
|
"flake-parts": "flake-parts_2",
|
||||||
"flake-parts": "flake-parts",
|
"impermanence": "impermanence",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_3",
|
||||||
"rootdir": "rootdir"
|
"rootdir": "rootdir"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -472,51 +300,6 @@
|
|||||||
"type": "file",
|
"type": "file",
|
||||||
"url": "file:///dev/null"
|
"url": "file:///dev/null"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems_3": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
29
flake.nix
29
flake.nix
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
devshell.url = "github:numtide/devshell";
|
|
||||||
devshell.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
devenv.url = "github:cachix/devenv";
|
devenv.url = "github:cachix/devenv";
|
||||||
devenv.inputs.nixpkgs.follows = "nixpkgs";
|
devenv.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
|
impermanence.url = "github:nix-community/impermanence";
|
||||||
|
|
||||||
rootdir = {
|
rootdir = {
|
||||||
url = "file+file:///dev/null";
|
url = "file+file:///dev/null";
|
||||||
flake = false;
|
flake = false;
|
||||||
@@ -16,11 +16,16 @@
|
|||||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
imports = [
|
imports = [
|
||||||
devenv.flakeModule
|
devenv.flakeModule
|
||||||
|
|
||||||
|
./mcu
|
||||||
|
./firmware
|
||||||
];
|
];
|
||||||
systems = [ "x86_64-linux" ];
|
systems = [ "x86_64-linux" ];
|
||||||
|
|
||||||
perSystem = { inputs', system, pkgs, config, ... }:
|
perSystem = { inputs', system, lib, pkgs, config, ... }:
|
||||||
let
|
let
|
||||||
|
inherit (lib) mkMerge;
|
||||||
|
|
||||||
rootdirOpt =
|
rootdirOpt =
|
||||||
let
|
let
|
||||||
rootFileContent = builtins.readFile rootdir.outPath;
|
rootFileContent = builtins.readFile rootdir.outPath;
|
||||||
@@ -28,21 +33,11 @@
|
|||||||
pkgs.lib.mkIf (rootFileContent != "") rootFileContent;
|
pkgs.lib.mkIf (rootFileContent != "") rootFileContent;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
devenv.shells.mcu = {
|
_module.args.libMy = {
|
||||||
devenv.root = rootdirOpt;
|
withRootdir = c: mkMerge [
|
||||||
|
c
|
||||||
packages = with pkgs; [
|
{ devenv.root = rootdirOpt; }
|
||||||
gnumake
|
|
||||||
cmake
|
|
||||||
gcc-arm-embedded
|
|
||||||
python3
|
|
||||||
picotool
|
|
||||||
openocd-rp2040
|
|
||||||
];
|
];
|
||||||
|
|
||||||
env = {
|
|
||||||
PICO_SDK_PATH = "${pkgs.pico-sdk}/lib/pico-sdk";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
4
mcu/.gdbinit
Normal file
4
mcu/.gdbinit
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
target extended-remote localhost:3333
|
||||||
|
monitor arm semihosting enable
|
||||||
|
monitor reset init
|
||||||
|
monitor debug_level -2
|
1
mcu/.gitignore
vendored
1
mcu/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/build/
|
/build/
|
||||||
|
/build-release/
|
||||||
|
@@ -6,14 +6,14 @@ include(pico_sdk_import.cmake)
|
|||||||
project(test_project C CXX ASM)
|
project(test_project C CXX ASM)
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_C_COMPILER_WORKS ON)
|
|
||||||
set(CMAKE_CXX_COMPILER_WORKS ON)
|
|
||||||
pico_sdk_init()
|
pico_sdk_init()
|
||||||
|
|
||||||
add_executable(qclk
|
add_executable(qclk
|
||||||
main.c
|
main.c
|
||||||
)
|
)
|
||||||
|
|
||||||
pico_enable_stdio_usb(qclk 1)
|
pico_enable_stdio_usb(qclk 1)
|
||||||
pico_enable_stdio_uart(qclk 1)
|
pico_enable_stdio_uart(qclk 0)
|
||||||
|
|
||||||
pico_add_extra_outputs(qclk)
|
pico_add_extra_outputs(qclk)
|
||||||
target_link_libraries(qclk pico_stdlib)
|
target_link_libraries(qclk pico_stdlib hardware_i2c pico_i2c_slave)
|
||||||
|
40
mcu/default.nix
Normal file
40
mcu/default.nix
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
perSystem = { libMy, pkgs, ... }: {
|
||||||
|
devenv.shells.mcu = libMy.withRootdir {
|
||||||
|
packages = with pkgs; [
|
||||||
|
gnumake
|
||||||
|
cmake
|
||||||
|
pkgsCross.arm-embedded.buildPackages.gdb # ARM one is broken
|
||||||
|
gcc-arm-embedded
|
||||||
|
python3
|
||||||
|
picotool
|
||||||
|
openocd-rp2040
|
||||||
|
];
|
||||||
|
|
||||||
|
env = {
|
||||||
|
PICO_SDK_PATH = "${pkgs.pico-sdk}/lib/pico-sdk";
|
||||||
|
};
|
||||||
|
|
||||||
|
scripts = {
|
||||||
|
build.exec = ''
|
||||||
|
cmake -S . -B build -D CMAKE_BUILD_TYPE=Debug -D PICO_STDIO_SEMIHOSTING=1
|
||||||
|
cmake --build build --parallel
|
||||||
|
'';
|
||||||
|
build-rel.exec = ''
|
||||||
|
cmake -S . -B build-release -D CMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build-release --parallel
|
||||||
|
'';
|
||||||
|
clean.exec = ''
|
||||||
|
rm -rf build/ build-release/
|
||||||
|
'';
|
||||||
|
|
||||||
|
ocd.exec = ''
|
||||||
|
openocd -f ${./tigard-swd.cfg} -f target/rp2040.cfg
|
||||||
|
'';
|
||||||
|
gdb.exec = ''
|
||||||
|
arm-none-eabi-gdb -x .gdbinit build/qclk.elf
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
107
mcu/main.c
107
mcu/main.c
@@ -1,23 +1,102 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "pico/stdlib.h"
|
|
||||||
#include "hardware/gpio.h"
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/i2c.h"
|
||||||
|
#include "pico/stdlib.h"
|
||||||
#include "pico/binary_info.h"
|
#include "pico/binary_info.h"
|
||||||
|
#include "pico/i2c_slave.h"
|
||||||
|
|
||||||
const uint LED_PIN = 25;
|
const uint PIN_DIGIT_BASE = 18;
|
||||||
|
const uint PIN_SEGMENT_BASE = 2;
|
||||||
|
|
||||||
int main() {
|
const uint N_DIGITS = 4;
|
||||||
bi_decl(bi_program_description("This is a test binary."));
|
|
||||||
bi_decl(bi_1pin_with_name(LED_PIN, "On-board LED"));
|
|
||||||
|
|
||||||
stdio_init_all();
|
const uint I2C_PIN_SDA = 0;
|
||||||
|
const uint I2C_PIN_SCL = 1;
|
||||||
|
const uint I2C_SLAVE_ADDRESS = 0x17;
|
||||||
|
const uint I2C_RATE = 100 * 1000;
|
||||||
|
|
||||||
gpio_init(LED_PIN);
|
uint8_t digit_states[4] = { 0 };
|
||||||
gpio_set_dir(LED_PIN, GPIO_OUT);
|
static struct {
|
||||||
while (1) {
|
bool digit_written;
|
||||||
gpio_put(LED_PIN, 0);
|
uint digit;
|
||||||
sleep_ms(250);
|
} i2c_context;
|
||||||
gpio_put(LED_PIN, 1);
|
|
||||||
puts("Hello World\n");
|
static void write_digit(uint8_t digit) {
|
||||||
sleep_ms(1000);
|
const uint mask = (0xffffffff << PIN_SEGMENT_BASE) ^ (0xffffffff << PIN_SEGMENT_BASE + 8);
|
||||||
|
gpio_put_masked(mask, (~digit & 0xff) << PIN_SEGMENT_BASE);
|
||||||
|
gpio_put_masked(mask, digit << PIN_SEGMENT_BASE);
|
||||||
|
}
|
||||||
|
static void setup_io() {
|
||||||
|
for (uint i = PIN_DIGIT_BASE; i < PIN_DIGIT_BASE + N_DIGITS; i++) {
|
||||||
|
gpio_init(i);
|
||||||
|
gpio_set_dir(i, GPIO_OUT);
|
||||||
|
gpio_put(i, 0);
|
||||||
|
}
|
||||||
|
for (uint i = PIN_SEGMENT_BASE; i < PIN_SEGMENT_BASE + 8; i++) {
|
||||||
|
gpio_init(i);
|
||||||
|
gpio_set_dir(i, GPIO_OUT);
|
||||||
|
}
|
||||||
|
write_digit(0);
|
||||||
|
}
|
||||||
|
static void display_loop() {
|
||||||
|
for (uint i = 0; i < N_DIGITS; i++) {
|
||||||
|
uint prev = i == 0 ? N_DIGITS - 1 : i - 1;
|
||||||
|
|
||||||
|
gpio_put(PIN_DIGIT_BASE + prev, 0);
|
||||||
|
sleep_us(100);
|
||||||
|
|
||||||
|
write_digit(digit_states[i]);
|
||||||
|
gpio_put(PIN_DIGIT_BASE + i, 1);
|
||||||
|
sleep_ms(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) {
|
||||||
|
switch (event) {
|
||||||
|
case I2C_SLAVE_RECEIVE: // master has written some data
|
||||||
|
if (!i2c_context.digit_written) {
|
||||||
|
// writes always start with the memory address
|
||||||
|
uint8_t addr = i2c_read_byte_raw(i2c);
|
||||||
|
i2c_context.digit = addr < N_DIGITS ? addr : -1;
|
||||||
|
i2c_context.digit_written = true;
|
||||||
|
} else if (i2c_context.digit != -1) {
|
||||||
|
// save into memory
|
||||||
|
digit_states[i2c_context.digit] = i2c_read_byte_raw(i2c);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case I2C_SLAVE_REQUEST: // master is requesting data
|
||||||
|
// load from memory
|
||||||
|
uint8_t data = i2c_context.digit != -1 ? digit_states[i2c_context.digit] : 0;
|
||||||
|
i2c_write_byte_raw(i2c, data);
|
||||||
|
break;
|
||||||
|
case I2C_SLAVE_FINISH: // master has signalled Stop / Restart
|
||||||
|
i2c_context.digit_written = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void setup_i2c() {
|
||||||
|
gpio_init(I2C_PIN_SDA);
|
||||||
|
gpio_set_function(I2C_PIN_SDA, GPIO_FUNC_I2C);
|
||||||
|
gpio_pull_up(I2C_PIN_SDA);
|
||||||
|
|
||||||
|
gpio_init(I2C_PIN_SCL);
|
||||||
|
gpio_set_function(I2C_PIN_SCL, GPIO_FUNC_I2C);
|
||||||
|
gpio_pull_up(I2C_PIN_SCL);
|
||||||
|
|
||||||
|
i2c_init(i2c0, I2C_RATE);
|
||||||
|
// configure I2C0 for slave mode
|
||||||
|
i2c_slave_init(i2c0, I2C_SLAVE_ADDRESS, &i2c_slave_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
bi_decl(bi_program_description("qCLK driver."));
|
||||||
|
|
||||||
|
stdio_init_all();
|
||||||
|
setup_io();
|
||||||
|
setup_i2c();
|
||||||
|
|
||||||
|
while (1) display_loop();
|
||||||
|
}
|
||||||
|
8
mcu/tigard-swd.cfg
Normal file
8
mcu/tigard-swd.cfg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
adapter driver ftdi
|
||||||
|
transport select swd
|
||||||
|
ftdi_vid_pid 0x0403 0x6010
|
||||||
|
ftdi_channel 1
|
||||||
|
adapter speed 2000
|
||||||
|
ftdi_layout_init 0x0028 0x002b
|
||||||
|
ftdi_layout_signal SWD_EN -data 0
|
||||||
|
ftdi_layout_signal nSRST -data 0x0020
|
Reference in New Issue
Block a user