8 Commits

Author SHA1 Message Date
f5557a1d99 db: handle database.url_file 2025-05-17 19:17:47 +02:00
de20bad7dd remove conf.py 2025-03-19 18:47:23 +01:00
4bab5e7e21 treewide: fix brother-ql usage 2025-03-19 18:47:16 +01:00
b85a6535fe shell.nix: add python with all packages 2025-03-19 18:14:42 +01:00
22a09b4177 README: add more information 2025-03-19 18:06:40 +01:00
c39b15d1a8 .envrc: init 2025-03-19 17:50:48 +01:00
122ac2ab18 treewide: update everything nix 2025-03-19 17:50:14 +01:00
28228beccd pyproject.toml: remove invalid license
This license field was added without any of the earlier contributors
consent on accident. It is not valid
2025-03-17 21:03:55 +01:00
16 changed files with 176 additions and 442 deletions

3
.envrc

@ -1,2 +1 @@
# devenv needs to know the path to the current working directory to create and manage mutable state
use flake . --no-pure-eval
use flake

4
.gitignore vendored

@ -1,12 +1,8 @@
result
result-*
.venv
.direnv
.devenv
dist
config.ini
test.db
.ruff_cache

@ -2,47 +2,48 @@
EDB-system for PVVVV
## Hva er dette?
Dibbler er et system laget av PVVere for PVVere for å byttelåne både matvarer og godis.
Det er designet for en gammeldags VT terminal, og er laget for å være enkelt både å bruke og å hacke på.
Programmet er skrevet i Python, og bruker en sql database for å lagre data.
Samlespleiseboden er satt opp slik at folk kjøper inn varer, og får dibblerkreditt, og så kan man bruke
denne kreditten til å kjøpe ut andre varer. Det er ikke noen form for authentisering, så hele systemet er basert på tillit.
Det er anbefalt å koble en barkodeleser til systemet for å gjøre det enklere å både legge til og kjøpe varer.
## Kom i gang
Installer python, og lag og aktiver et venv. Installer så avhengighetene med `pip install`.
Deretter kan du kjøre programmet med
```console
python -m dibbler -c example-config.ini create-db
python -m dibbler -c example-config.ini loop
```
## Nix
### Hvordan kjøre
nix run github:Programvareverkstedet/dibbler
### Hvordan utvikle?
python -m venv .venv
source .venv/activate
pip install -e .
cp example-config.ini config.ini
dibbler -c config.ini create-db
dibbler -c config.ini loop
eller hvis du tolererer nix og postgres:
direnv allow # eller bare `nix develop`
devenv up
dibbler create-db
dibbler loop
### Bygge image
### Bygge nytt image
For å bygge et image trenger du en builder som takler å bygge for arkitekturen du skal lage et image for.
_(Eller be til gudene om at cross compile funker)_
(Eller be til gudene om at cross compile funker)
Flaket exposer en modul som autologger inn med en bruker som automatisk kjører dibbler, og setter opp et minimalistisk miljø.
Før du bygger imaget burde du lage en `config.ini` fil lokalt som inneholder instillingene dine. **NB: Denne kommer til å ligge i nix storen.**
Før du bygger imaget burde du kopiere og endre `example-config.ini` lokalt til å inneholde instillingene dine. **NB: Denne kommer til å ligge i nix storen, ikke si noe her som du ikke vil at moren din skal høre.**
Du kan også endre hvilken `config.ini` som blir brukt direkte i pakken eller i modulen.
Du kan også endre hvilken config-fil som blir brukt direkte i pakken eller i modulen.
Se eksempelet for hvordan skrot er satt opp i `flake.nix`
Se eksempelet for hvordan skrot er satt opp i `flake.nix` og `nix/skrott.nix`
### Bygge image for skrot
Skrot har et image definert i flake.nix:
Skrot har et system image definert i `flake.nix`:
1. lag `config.ini` (`cp {example-,}config.ini`)
1. endre `example-config.ini`
2. `nix build .#images.skrot`
3. ???
4. non-profit!
4. non-profit

@ -1,4 +0,0 @@
{ pkgs ? import <nixos-unstable> { } }:
{
dibbler = pkgs.callPackage ./nix/dibbler.nix { };
}

@ -1,12 +1,16 @@
import os
from pathlib import Path
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from dibbler.conf import config
engine = create_engine(
os.environ.get("DIBBLER_DATABASE_URL")
or config.get("database", "url")
)
if (url := config.get("database", "url")) is not None:
database_url = url
elif (url_file := config.get("database", "url_file")) is not None:
with Path(url_file).open() as file:
database_url = file.read().strip()
engine = create_engine(database_url)
Session = sessionmaker(bind=engine)

@ -2,7 +2,7 @@ import os
from PIL import ImageFont
from barcode.writer import ImageWriter, mm2px
from brother_ql.devicedependent import label_type_specs
from brother_ql.labels import ALL_LABELS
def px2mm(px, dpi=300):
@ -12,14 +12,15 @@ def px2mm(px, dpi=300):
class BrotherLabelWriter(ImageWriter):
def __init__(self, typ="62", max_height=350, rot=False, text=None):
super(BrotherLabelWriter, self).__init__()
assert typ in label_type_specs
label = next([l for l in ALL_LABELS if l.identifier == typ])
assert label is not None
self.rot = rot
if self.rot:
self._h, self._w = label_type_specs[typ]["dots_printable"]
self._h, self._w = label.dots_printable
if self._w == 0 or self._w > max_height:
self._w = min(max_height, self._h / 2)
else:
self._w, self._h = label_type_specs[typ]["dots_printable"]
self._w, self._h = label.dots_printable
if self._h == 0 or self._h > max_height:
self._h = min(max_height, self._w / 2)
self._xo = 0.0

@ -2,9 +2,10 @@ import os
import datetime
import barcode
from brother_ql import BrotherQLRaster, create_label
from brother_ql.brother_ql_create import create_label
from brother_ql.raster import BrotherQLRaster
from brother_ql.backends import backend_factory
from brother_ql.devicedependent import label_type_specs
from brother_ql.labels import ALL_LABELS
from PIL import Image, ImageDraw, ImageFont
from .barcode_helpers import BrotherLabelWriter
@ -17,10 +18,11 @@ def print_name_label(
label_type="62",
printer_type="QL-700",
):
label = next([l for l in ALL_LABELS if l.identifier == label_type])
if not rotate:
width, height = label_type_specs[label_type]["dots_printable"]
width, height = label.dots_printable
else:
height, width = label_type_specs[label_type]["dots_printable"]
height, width = label.dots_printable
font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "ChopinScript.ttf")
fs = 2000

@ -1,6 +1,4 @@
import argparse
import sys
import os
from dibbler.conf import config
@ -12,7 +10,6 @@ parser.add_argument(
help="Path to the config file",
type=str,
required=False,
default=os.environ.get("DIBBLER_CONFIG_FILE", None)
)
subparsers = parser.add_subparsers(
@ -27,8 +24,6 @@ subparsers.add_parser("slabbedasker", help="Find out who is slabbedasker")
def main():
args = parser.parse_args()
if args.config is None:
print("ERROR: no config was provided", file=sys.stderr)
config.read(args.config)
if args.subcommand == "loop":

@ -1,20 +1,18 @@
[general]
; quit_allowed = false
; stop_allowed = false
quit_allowed = true ; not for prod
stop_allowed = true ; not for prod
quit_allowed = true
stop_allowed = false
show_tracebacks = true
input_encoding = 'utf8'
[database]
; url = postgresql://dibbler:hunter2@127.0.0.1/pvvvv
url = sqlite:///test.db ; devenv will override this to postgres using DIBBLER_DATABASE_URL
; url = postgresql://robertem@127.0.0.1/pvvvv
url = sqlite:///test.db
[limits]
low_credit_warning_limit = -100
user_recent_transaction_limit = 100
# See https://pypi.org/project/brother_ql_next/ for label types
# See https://pypi.org/project/brother_ql/ for label types
# Set rotate to False for endless labels
[printer]
label_type = "62"

249
flake.lock generated

@ -1,93 +1,5 @@
{
"nodes": {
"cachix": {
"inputs": {
"devenv": [
"devenv"
],
"flake-compat": [
"devenv"
],
"git-hooks": [
"devenv"
],
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1728672398,
"narHash": "sha256-KxuGSoVUFnQLB2ZcYODW7AVPAh9JqRlD5BrfsC/Q4qs=",
"owner": "cachix",
"repo": "cachix",
"rev": "aac51f698309fd0f381149214b7eee213c66ef0a",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "latest",
"repo": "cachix",
"type": "github"
}
},
"devenv": {
"inputs": {
"cachix": "cachix",
"flake-compat": "flake-compat",
"git-hooks": "git-hooks",
"nix": "nix",
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1731619804,
"narHash": "sha256-wyxFaVooL8SzvQNpolpx32X+GoBPnCAg9E0i/Ekn3FU=",
"owner": "cachix",
"repo": "devenv",
"rev": "87edaaf1dddf17fe16eabab3c8edaf7cca2c3bc2",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"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-utils": {
"inputs": {
"systems": "systems"
@ -101,117 +13,17 @@
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": [
"devenv"
],
"gitignore": "gitignore",
"nixpkgs": [
"devenv",
"nixpkgs"
],
"nixpkgs-stable": [
"devenv"
]
},
"locked": {
"lastModified": 1730302582,
"narHash": "sha256-W1MIJpADXQCgosJZT8qBYLRuZls2KSiKdpnTVdKBuvU=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "af8a16fe5c264f5e9e18bcee2859b40a656876cf",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"devenv",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"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": {
"flake-compat": [
"devenv"
],
"flake-parts": "flake-parts",
"libgit2": "libgit2",
"nixpkgs": "nixpkgs_2",
"nixpkgs-23-11": [
"devenv"
],
"nixpkgs-regression": [
"devenv"
],
"pre-commit-hooks": [
"devenv"
]
},
"locked": {
"lastModified": 1727438425,
"narHash": "sha256-X8ES7I1cfNhR9oKp06F6ir4Np70WGZU5sfCOuNBEwMg=",
"owner": "domenkozar",
"repo": "nix",
"rev": "f6c5ae4c1b2e411e6b1e6a8181cc84363d6a7546",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.24",
"repo": "nix",
"type": "github"
"id": "flake-utils",
"type": "indirect"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1730531603,
"narHash": "sha256-Dqg6si5CqIzm87sp57j5nTaeBbWhHFaVyG7V6L8k3lY=",
"lastModified": 1742288794,
"narHash": "sha256-Txwa5uO+qpQXrNG4eumPSD+hHzzYi/CdaM80M9XRLCo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7ffd9ae656aec493492b44d0ddfb28e79a1ea25d",
"rev": "b6eaf97c6960d97350c584de1b6dcff03c9daf42",
"type": "github"
},
"original": {
@ -221,59 +33,10 @@
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1717432640,
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1716977621,
"narHash": "sha256-Q1UQzYcMJH4RscmpTkjlgqQDX5yi1tZL0O345Ri6vXQ=",
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "4267e705586473d3e5c8d50299e71503f16a6fb6",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1731611831,
"narHash": "sha256-R51rOqkWMfubBkZ9BY4Y1VaRoeqEBshlfQ8mMH5RjqI=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "cea28c811faadb50bee00d433bbf2fea845a43e4",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable-small",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_4"
"nixpkgs": "nixpkgs"
}
},
"systems": {

151
flake.nix

@ -1,128 +1,65 @@
{
description = "Dibbler samspleisebod";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable-small";
flake-utils.url = "github:numtide/flake-utils";
devenv.url = "github:cachix/devenv";
};
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixConfig = {
extra-trusted-public-keys = [
"devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="
outputs = { self, nixpkgs, flake-utils }: let
inherit (nixpkgs) lib;
systems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
extra-substituters = [
"https://devenv.cachix.org"
];
};
outputs = { self, ... } @ inputs:
inputs.flake-utils.lib.eachDefaultSystem (system: let
pkgs = inputs.nixpkgs.legacyPackages.${system};
inherit (pkgs) lib;
in {
packages = {
forAllSystems = f: lib.genAttrs systems (system: let
pkgs = nixpkgs.legacyPackages.${system};
in f system pkgs);
in {
packages = forAllSystems (system: pkgs: {
default = self.packages.${system}.dibbler;
dibbler = pkgs.callPackage ./nix/dibbler.nix {
python3Packages = pkgs.python312Packages;
};
skrot = self.nixosConfigurations.skrot.config.system.build.sdImage;
});
dibbler = pkgs.python311Packages.callPackage ./nix/dibbler.nix { };
skrot-vm = self.nixosConfigurations.skrot.config.system.build.vm;
apps = forAllSystems (system: pkgs: {
default = self.apps.${system}.dibbler;
dibbler = flake-utils.lib.mkApp {
drv = self.packages.${system}.dibbler;
};
});
# devenv cruft
devenv-up = self.devShells.${system}.default.config.procfileScript;
devenv-test = self.devShells.${system}.default.config.test;
};
devShells = {
default = self.devShells.${system}.dibbler;
dibbler = inputs.devenv.lib.mkShell {
inherit inputs pkgs;
modules = [({ config, ... }: {
# https://devenv.sh/reference/options/
enterShell = ''
if [[ ! -f config.ini ]]; then
cp -v example-config.ini config.ini
fi
export REPO_ROOT=$(realpath .) # used by mkPythonEditablePackage
export DIBBLER_CONFIG_FILE=$(realpath config.ini)
export DIBBLER_DATABASE_URL=postgresql://dibbler:hunter2@/dibbler?host=${config.env.PGHOST}
'';
packages = [
/* self.packages.${system}.dibbler */
(pkgs.python311Packages.mkPythonEditablePackage {
inherit (self.packages.${system}.dibbler)
pname version
build-system dependencies;
scripts = (lib.importTOML ./pyproject.toml).project.scripts;
root = "$REPO_ROOT";
})
pkgs.python311Packages.black
pkgs.ruff
];
services.postgres = {
enable = true;
initialDatabases = [
{
name = "dibbler";
user = "dibbler";
pass = "hunter2";
}
];
};
})];
overlays = {
default = self.overlays.dibbler;
dibbler = final: prev: {
inherit (self.packages.${prev.system}) dibbler;
};
};
})
devShells = forAllSystems (system: pkgs: {
default = self.devShells.${system}.dibbler;
dibbler = pkgs.callPackage ./nix/shell.nix {
python = pkgs.python312;
};
});
//
{
# Note: using the module requires that you have applied the
# overlay first
# Note: using the module requires that you have applied the overlay first
nixosModules.default = import ./nix/module.nix;
images.skrot = self.nixosConfigurations.skrot.config.system.build.sdImage;
nixosConfigurations.skrot = inputs.nixpkgs.lib.nixosSystem {
nixosConfigurations.skrot = nixpkgs.lib.nixosSystem (rec {
system = "aarch64-linux";
pkgs = import nixpkgs {
inherit system;
overlays = [ self.overlays.dibbler ];
};
modules = [
(inputs.nixpkgs + "/nixos/modules/installer/sd-card/sd-image-aarch64.nix")
(nixpkgs + "/nixos/modules/installer/sd-card/sd-image-aarch64.nix")
self.nixosModules.default
({...}: {
system.stateVersion = "22.05";
networking = {
hostName = "skrot";
domain = "pvv.ntnu.no";
nameservers = [ "129.241.0.200" "129.241.0.201" ];
defaultGateway = "129.241.210.129";
interfaces.eth0 = {
useDHCP = false;
ipv4.addresses = [{
address = "129.241.210.235";
prefixLength = 25;
}];
};
};
# services.resolved.enable = true;
# systemd.network.enable = true;
# systemd.network.networks."30-network" = {
# matchConfig.Name = "*";
# DHCP = "no";
# address = [ "129.241.210.235/25" ];
# gateway = [ "129.241.210.129" ];
# };
})
./nix/skrott.nix
];
};
});
};
}

@ -1,25 +1,21 @@
{ lib
, python3Packages
, fetchFromGitHub
, buildPythonApplication
, setuptools
, brother-ql
, matplotlib
, psycopg2
, python-barcode
, sqlalchemy
}:
buildPythonApplication {
python3Packages.buildPythonApplication {
pname = "dibbler";
version = "0.0.0";
pyproject = true;
version = "unstable";
src = lib.cleanSource ../.;
build-system = [ setuptools ];
dependencies = [
# we override pname to satisfy mkPythonEditablePackage
(brother-ql.overridePythonAttrs { pname = "brother-ql-next"; })
format = "pyproject";
# brother-ql is breaky breaky
# https://github.com/NixOS/nixpkgs/issues/285234
dontCheckRuntimeDeps = true;
nativeBuildInputs = with python3Packages; [ setuptools ];
propagatedBuildInputs = with python3Packages; [
brother-ql
matplotlib
psycopg2
python-barcode

@ -2,15 +2,20 @@
cfg = config.services.dibbler;
in {
options.services.dibbler = {
enable = lib.mkEnableOption "dibbler, the little kiosk computer";
package = lib.mkPackageOption pkgs "dibbler" { };
config = lib.mkOption {
default = ../conf.py;
type = lib.types.path;
description = "Path to the configuration file.";
default = ../example-config.ini;
};
};
config = let
screen = "${pkgs.screen}/bin/screen";
in {
in lib.mkIf cfg.enable {
boot = {
consoleLogLevel = 0;
enableContainers = false;
@ -23,10 +28,7 @@ in {
group = "dibbler";
extraGroups = [ "lp" ];
isNormalUser = true;
shell = (
(pkgs.writeShellScriptBin "login-shell" "${screen} -x dibbler")
// {shellPath = "/bin/login-shell";}
);
shell = ((pkgs.writeShellScriptBin "login-shell" "${screen} -x dibbler") // {shellPath = "/bin/login-shell";});
};
};
@ -72,7 +74,7 @@ in {
console.keyMap = "no";
programs.command-not-found.enable = false;
i18n.supportedLocales = [ "en_US.UTF-8/UTF-8" ];
environment.noXlibs = true;
# environment.noXlibs = true;
documentation = {
info.enable = false;

18
nix/shell.nix Normal file

@ -0,0 +1,18 @@
{
mkShell,
python,
ruff,
}:
mkShell {
packages = [
ruff
(python.withPackages (ps: with ps; [
brother-ql
matplotlib
psycopg2
python-barcode
sqlalchemy
]))
];
}

27
nix/skrott.nix Normal file

@ -0,0 +1,27 @@
{...}: {
system.stateVersion = "25.05";
services.dibbler.enable = true;
networking = {
hostName = "skrot";
domain = "pvv.ntnu.no";
nameservers = [ "129.241.0.200" "129.241.0.201" ];
defaultGateway = "129.241.210.129";
interfaces.eth0 = {
useDHCP = false;
ipv4.addresses = [{
address = "129.241.210.235";
prefixLength = 25;
}];
};
};
# services.resolved.enable = true;
# systemd.network.enable = true;
# systemd.network.networks."30-network" = {
# matchConfig.Name = "*";
# DHCP = "no";
# address = [ "129.241.210.235/25" ];
# gateway = [ "129.241.210.129" ];
# };
}

@ -8,13 +8,12 @@ authors = []
description = "EDB-system for PVV"
readme = "README.md"
requires-python = ">=3.11"
license = {text = "BSD-3-Clause"}
classifiers = [
"Programming Language :: Python :: 3",
]
dependencies = [
"SQLAlchemy >= 2.0, <2.1",
"brother_ql_next",
"brother-ql",
"matplotlib",
"psycopg2 >= 2.8, <2.10",
"python-barcode",