diff options
Diffstat (limited to 'mod')
32 files changed, 0 insertions, 1682 deletions
diff --git a/mod/code/cses/.envrc b/mod/code/cses/.envrc deleted file mode 100644 index 1d953f4..0000000 --- a/mod/code/cses/.envrc +++ /dev/null @@ -1 +0,0 @@ -use nix diff --git a/mod/code/cses/default.nix b/mod/code/cses/default.nix deleted file mode 100644 index 6538eb8..0000000 --- a/mod/code/cses/default.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ pkgs ? import <nixpkgs> { }, ... }: - -pkgs.mkShell { - packages = with pkgs; [ - clang-tools - (writeShellScriptBin "cpprun" '' - TEMP=".tmp.cpp" - g++ -std=c++20 -O3 ./problems/"$1.cpp" -o $TEMP - ./$TEMP - rm $TEMP - '') - ]; -} diff --git a/mod/code/cses/problems/1068.cpp b/mod/code/cses/problems/1068.cpp deleted file mode 100644 index 8d05936..0000000 --- a/mod/code/cses/problems/1068.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include <iostream> - -int main(int argc, char *argv[]) { - unsigned long long n; - std::cin >> n; - - while (n != 1) { - std::cout << n << " "; - - if (n % 2 == 0) - n /= 2; - else - n = (n * 3) + 1; - } - - std::cout << 1; -} diff --git a/mod/code/cses/problems/1069.cpp b/mod/code/cses/problems/1069.cpp deleted file mode 100644 index 232d2f7..0000000 --- a/mod/code/cses/problems/1069.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include <iostream> - -int main() { - std::string input; - std::cin >> input; - - auto max = 0; - auto curr = '-'; - auto count = 0; - - for (auto const ch : input) { - if (curr != ch) { - curr = ch; - count = 0; - } - - count += 1; - max = std::max(max, count); - } - - std::cout << max; - return 0; -} diff --git a/mod/code/cses/problems/1070.cpp b/mod/code/cses/problems/1070.cpp deleted file mode 100644 index 131a644..0000000 --- a/mod/code/cses/problems/1070.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include <iostream> - -int main() { - int n; - std::cin >> n; - - if (n == 1) - std::cout << 1; - else if (n < 4) - std::cout << "NO SOLUTION"; - else { - for (auto i = 2; i <= n; i += 2) std::cout << i << " "; - for (auto i = 1; i <= n; i += 2) std::cout << i << " "; - } -} diff --git a/mod/code/cses/problems/1071.cpp b/mod/code/cses/problems/1071.cpp deleted file mode 100644 index 778cc86..0000000 --- a/mod/code/cses/problems/1071.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include <iostream> -#include <tuple> -#include <vector> - -using ull = unsigned long long; - -int main() { - int n; - std::cin >> n; - - while (n--) { - ull x, y; - std::cin >> y >> x; - - auto area = y * y; - auto perimeter = y + y + 1; - auto max = area + perimeter; - } -} diff --git a/mod/code/cses/problems/1083.cpp b/mod/code/cses/problems/1083.cpp deleted file mode 100644 index a3cd253..0000000 --- a/mod/code/cses/problems/1083.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include <iostream> -#include <set> - -using ull = unsigned long long; - -int main(int argc, char *argv[]) { - ull n; - std::cin >> n; - - auto set = std::set<ull>{}; - - std::string numstr; - while (std::getline(std::cin, numstr, ' ')) set.insert(std::stoi(numstr)); - - for (auto i = 1; i <= n; i++) { - if (set.contains(i)) continue; - std::cout << i; - break; - } -} diff --git a/mod/code/cses/problems/1094.cpp b/mod/code/cses/problems/1094.cpp deleted file mode 100644 index 56f3886..0000000 --- a/mod/code/cses/problems/1094.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include <iostream> -#include <vector> - -using ull = unsigned long long; - -int main() { - ull n; - auto arr = std::vector<ull>{}; - - std::cin >> n; - - std::string type; - while (std::cin >> type) arr.push_back(std::stoi(type)); - - ull count = 0; - auto last = arr[0]; - - for (auto const num : arr) { - if (num > last) { - last = num; - continue; - } - count += last - num; - } - - std::cout << count; -} diff --git a/mod/misc/cv/cv.typ b/mod/misc/cv/cv.typ deleted file mode 100644 index 1120e39..0000000 --- a/mod/misc/cv/cv.typ +++ /dev/null @@ -1,250 +0,0 @@ -#let resume( - author: "", - email: "", - github: "", - personal-site: "", - accent-color: "#000000", - font: "New Computer Modern", - body, -) = { - set document(author: author, title: author) - set text( - // LaTeX style font - font: font, - size: 10pt, - lang: "en", - ligatures: false - ) - - set page( - margin: (0.5in), - paper: "us-letter", - ) - - show link: underline - show link: set text( - fill: rgb(accent-color), - ) - - show heading.where(level: 2): it => [ - #pad(top: 0pt, bottom: -10pt, [#smallcaps(it.body)]) - #line(length: 100%, stroke: 1pt) - ] - - // Accent Color Styling - show heading: set text( - fill: rgb(accent-color), - ) - - show heading.where(level: 1): it => [ - #set align(left) - #set text( - weight: 700, - size: 20pt, - ) - #pad(it.body) - ] - - [= #(author)] - - let contact-item(value, prefix: "", link-type: "") = { - if value != "" { - if link-type != "" { - link(link-type + value)[#(prefix + value)] - } else { - value - } - } - } - - // Personal Info - pad( - top: 0.25em, - align(left)[ - #{ - let items = ( - contact-item(email, link-type: "mailto:"), - contact-item(github, link-type: "https://"), - contact-item(personal-site, link-type: "https://"), - ) - items.filter(x => x != none).join(" | ") - } - ], - ) - - set par(justify: true) - - body -} - -#let generic-two-by-two( - top-left: "", - top-right: "", - bottom-left: "", - bottom-right: "", -) = { - [ - #top-left #h(1fr) #top-right \ - #bottom-left #h(1fr) #bottom-right - ] -} - -#let dates-helper( - start-date: "", - end-date: "", -) = { - start-date + " " + $dash.em$ + " " + end-date -} - -#let edu( - institution: "", - dates: "", - degree: "", - location: "", -) = { - generic-two-by-two( - top-left: strong(institution), - top-right: location, - bottom-left: emph(degree), - bottom-right: emph(dates), - ) -} - -#let work( - title: "", - dates: "", - company: "", - location: "", -) = { - generic-two-by-two( - top-left: strong(title), - top-right: dates, - bottom-left: company, - bottom-right: emph(location), - ) -} - -#show: resume.with( - author: "Kleidi Bujari", - email: "mail@4kb.net", - github: "github.com/kbujari", - personal-site: "4kb.net", -) - -== Education - -#edu( - institution: "Toronto Metropolitan University", - dates: dates-helper(start-date: "Sep 2020", end-date: "Apr 2025"), - location: "Ontario, Canada", - degree: "Bachelor's of Engineering, Computer Engineering", -) - -- *Relevant Coursework*: - Data Structures, Embedded Programming, Compilers, Digital Systems, Computer Networks -- *Extracurriculars*: - Member of student robotics design team, - teaching assistant for micro-processor courses. - -== Experience - -#work( - company: "Toronto Metropolitan University", - title: "Graduate Research Assistant", - dates: dates-helper(start-date: "May 2024", end-date: "Sep 2024"), - location: "Toronto, Canada", -) - -- Implemented transformations for hundreds of media files, - using ffmpeg and unix primitives to parallelize workload. -- Designed frontend with AstroJS to generate only static HTML, - ensuring compatibility with many hosting providers. - -#work( - company: "Canadian Broadcasting Corporation", - title: "Network Engineering Intern", - dates: dates-helper(start-date: "May 2023", end-date: "Apr 2024"), - location: "Toronto, Canada", -) - -- Designed custom PXE-boot implementation for hundreds of devices using NetBox, - eliminating manual configuration. -- Configured Hyper-Converged Proxmox cluster for 2024 Olympics, - saving \$250k+ with reused hardware and open software. -- Deployed vendor-agnostic routing observability from scratch, - with Prometheus metrics and Grafana dashboards. -- Mentored junior application developers in modern C++23 programming, - aiding in performance design and memory safety. - -#work( - company: "WSP Canada", - title: "Student Engineer", - dates: dates-helper(start-date: "May", end-date: "Aug") + ", 2021, 2022", - location: "Toronto, Canada", -) - -- Contributed to subway car control systems with modern C++20, - replacing legacy code with newer STL functions. -- Extended internal distributed filesystem with support for deduplication and compression, - reclaiming 30% of storage. -- Validated new electrical designs for power consumption, - cost efficiency, and viability with existing systems. -- Participated in reviewing and adjusting large scale electrical and structural engineering designs. - -== Projects - -*Custom Linux Distribution* --- -Designed entire Linux distribution used for hosting production servers, -daily desktop use, and embedded programming on a Raspberry Pi. -Using NixOS, it supports enabling only required functionality at build time. -Features systemd, -an in-memory root filesystem, -ZFS persistent storage with backups, -and extremely hardened networking. - -*ICER Compressor* --- -Image compression library written in Rust, -designed for deep-space communication. -Hand tuned for speed and portability by using only integer arithmetic, -no heap allocations, and no standard library by default. -Achieves practically instant compressions, even on microprocessors. - -*Kubernetes Cluster* --- -Bare-metal compute cluster managed with GitOps to be completely reproducible. -Uses Cilium CNI for fast eBPF networking and BGP load balancing, -FluxCD for cluster management, -and CEPH distributed storage for stateful workloads. -Running Prometheus, Loki and AlertManager for complete observability. - -*Mirrorlist Generator* --- -Fetches Arch Linux package mirrors, -filtering them based on user parameters. -Sorts and outputs formatted data compliant with the pacman package manager. -Heavily outperforms default Python implementation. - -*Toronto Metropolitan Robotics* --- -Member of university design team working on space focused automated robotics. -Designed custom STM32 hardware with various interfaces (SPI, I2C, etc.) for controlling motor functions on robot. -Worked alongside various subteams to deliver a competition ready autonomous system. - -== Skills - -- *Languages*: #( - "Rust", - "C++", - "Nix", - "Haskell", - "TypeScript", - "Lua", - "Python", - ).join(", ") - -- *Technologies*: #( - "Linux", - "Compilers", - "Virtualisation", - "Terraform", - "Ansible", - "Computer Networks", - "Frontend (Svelte, Astro)" - ).join(", ") diff --git a/mod/misc/cv/default.nix b/mod/misc/cv/default.nix deleted file mode 100644 index b663475..0000000 --- a/mod/misc/cv/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{ pkgs, ... }: - -pkgs.runCommand "cv" { } '' - mkdir -p $out/share - ${pkgs.typst}/bin/typst compile ${./cv.typ} $out/share/cv.pdf -'' diff --git a/mod/nix/readTree/default.nix b/mod/nix/readTree/default.nix deleted file mode 100644 index 4a745ce..0000000 --- a/mod/nix/readTree/default.nix +++ /dev/null @@ -1,326 +0,0 @@ -# Copyright (c) 2019 Vincent Ambo -# Copyright (c) 2020-2021 The TVL Authors -# SPDX-License-Identifier: MIT -# -# Provides a function to automatically read a filesystem structure -# into a Nix attribute set. -# -# Called with an attribute set taking the following arguments: -# -# path: Path to a directory from which to start reading the tree. -# -# args: Argument set to pass to each imported file. -# -# filter: Function to filter `args` based on the tree location. This should -# be a function of the form `args -> location -> args`, where the -# location is a list of strings representing the path components of -# the current readTree target. Optional. -{ ... }: - -let - inherit (builtins) - attrNames - concatMap - concatStringsSep - elem - elemAt - filter - hasAttr - head - isAttrs - listToAttrs - map - match - readDir - substring; - - argsWithPath = args: parts: - let meta.locatedAt = parts; - in meta // (if isAttrs args then args else args meta); - - readDirVisible = path: - let - children = readDir path; - # skip hidden files, except for those that contain special instructions to readTree - isVisible = f: f == ".skip-subtree" || f == ".skip-tree" || (substring 0 1 f) != "."; - names = filter isVisible (attrNames children); - in - listToAttrs (map - (name: { - inherit name; - value = children.${name}; - }) - names); - - # Create a mark containing the location of this attribute and - # a list of all child attribute names added by readTree. - marker = parts: children: { - __readTree = parts; - __readTreeChildren = builtins.attrNames children; - }; - - # Create a label from a target's tree location. - mkLabel = target: - let label = concatStringsSep "/" target.__readTree; - in if target ? __subtarget - then "${label}:${target.__subtarget}" - else label; - - # Merge two attribute sets, but place attributes in `passthru` via - # `overrideAttrs` for derivation targets that support it. - merge = a: b: - if a ? overrideAttrs - then - a.overrideAttrs - (prev: { - passthru = (prev.passthru or { }) // b; - }) - else a // b; - - # Import a file and enforce our calling convention - importFile = args: scopedArgs: path: parts: filter: - let - importedFile = - if scopedArgs != { } && builtins ? scopedImport # For tvix - then builtins.scopedImport scopedArgs path - else import path; - pathType = builtins.typeOf importedFile; - in - if pathType != "lambda" - then throw "readTree: trying to import ${toString path}, but it’s a ${pathType}, you need to make it a function like { depot, pkgs, ... }" - else importedFile (filter parts (argsWithPath args parts)); - - nixFileName = file: - let res = match "(.*)\\.nix" file; - in if res == null then null else head res; - - # Internal implementation of readTree, which handles things like the - # skipping of trees and subtrees. - # - # This method returns an attribute sets with either of two shapes: - # - # { ok = ...; } # a tree was read successfully - # { skip = true; } # a tree was skipped - # - # The higher-level `readTree` method assembles the final attribute - # set out of these results at the top-level, and the internal - # `children` implementation unwraps and processes nested trees. - readTreeImpl = { args, initPath, rootDir, parts, argsFilter, scopedArgs }: - let - dir = readDirVisible initPath; - - # Determine whether any part of this tree should be skipped. - # - # Adding a `.skip-subtree` file will still allow the import of - # the current node's "default.nix" file, but stop recursion - # there. - # - # Adding a `.skip-tree` file will completely ignore the folder - # in which this file is located. - skipTree = hasAttr ".skip-tree" dir; - skipSubtree = skipTree || hasAttr ".skip-subtree" dir; - - joinChild = c: initPath + ("/" + c); - - self = - if rootDir - then { __readTree = [ ]; } - else importFile args scopedArgs initPath parts argsFilter; - - # Import subdirectories of the current one, unless any skip - # instructions exist. - # - # This file can optionally contain information on why the tree - # should be ignored, but its content is not inspected by - # readTree - filterDir = f: dir."${f}" == "directory"; - filteredChildren = map - (c: { - name = c; - value = readTreeImpl { - inherit argsFilter scopedArgs; - args = args; - initPath = (joinChild c); - rootDir = false; - parts = (parts ++ [ c ]); - }; - }) - (filter filterDir (attrNames dir)); - - # Remove skipped children from the final set, and unwrap the - # result set. - children = - if skipSubtree then [ ] - else map ({ name, value }: { inherit name; value = value.ok; }) (filter (child: child.value ? ok) filteredChildren); - - # Import Nix files - nixFiles = - if skipSubtree then [ ] - else filter (f: f != null) (map nixFileName (attrNames dir)); - nixChildren = map - (c: - let - p = joinChild (c + ".nix"); - childParts = parts ++ [ c ]; - imported = importFile args scopedArgs p childParts argsFilter; - in - { - name = c; - value = - if isAttrs imported - then merge imported (marker childParts { }) - else imported; - }) - nixFiles; - - nodeValue = if dir ? "default.nix" then self else { }; - - allChildren = listToAttrs ( - if dir ? "default.nix" - then children - else nixChildren ++ children - ); - - in - if skipTree - then { skip = true; } - else { - ok = - if isAttrs nodeValue - then merge nodeValue (allChildren // (marker parts allChildren)) - else nodeValue; - }; - - # Top-level implementation of readTree itself. - readTree = args: - let - tree = readTreeImpl args; - in - if tree ? skip - then throw "Top-level folder has a .skip-tree marker and could not be read by readTree!" - else tree.ok; - - # Helper function to fetch subtargets from a target. This is a - # temporary helper to warn on the use of the `meta.targets` - # attribute, which is deprecated in favour of `meta.ci.targets`. - subtargets = node: - let targets = (node.meta.targets or [ ]) ++ (node.meta.ci.targets or [ ]); - in if node ? meta.targets then - builtins.trace '' - [1;31mWarning: The meta.targets attribute is deprecated. - - Please move the subtargets of //${mkLabel node} to the - meta.ci.targets attribute. - [0m - '' - targets else targets; - - # Function which can be used to find all readTree targets within an - # attribute set. - # - # This function will gather physical targets, that is targets which - # correspond directly to a location in the repository, as well as - # subtargets (specified in the meta.ci.targets attribute of a node). - # - # This can be used to discover targets for inclusion in CI - # pipelines. - # - # Called with the arguments: - # - # eligible: Function to determine whether the given derivation - # should be included in the build. - gather = eligible: node: - if node ? __readTree then - # Include the node itself if it is eligible. - (if eligible node then [ node ] else [ ]) - # Include eligible children of the node - ++ concatMap (gather eligible) (map (attr: node."${attr}") node.__readTreeChildren) - # Include specified sub-targets of the node - ++ filter eligible (map - (k: (node."${k}" or { }) // { - # Keep the same tree location, but explicitly mark this - # node as a subtarget. - __readTree = node.__readTree; - __readTreeChildren = [ ]; - __subtarget = k; - }) - (subtargets node)) - else [ ]; - - # Determine whether a given value is a derivation. - # Copied from nixpkgs/lib for cases where lib is not available yet. - isDerivation = x: isAttrs x && x ? type && x.type == "derivation"; -in -{ - inherit gather mkLabel; - - __functor = _: - { path - , args - , filter ? (_parts: x: x) - , scopedArgs ? { } - }: - readTree { - inherit args scopedArgs; - argsFilter = filter; - initPath = path; - rootDir = true; - parts = [ ]; - }; - - # In addition to readTree itself, some functionality is exposed that - # is useful for users of readTree. - - # Create a readTree filter disallowing access to the specified - # top-level folder in the repository, except for specific exceptions - # specified by their (full) paths. - # - # Called with the arguments: - # - # folder: Name of the restricted top-level folder (e.g. 'experimental') - # - # exceptions: List of readTree parts (e.g. [ [ "services" "some-app" ] ]), - # which should be able to access the restricted folder. - # - # reason: Textual explanation for the restriction (included in errors) - restrictFolder = { folder, exceptions ? [ ], reason }: parts: args: - if (elemAt parts 0) == folder || elem parts exceptions - then args - else args // { - depot = args.depot // { - "${folder}" = throw '' - Access to targets under //${folder} is not permitted from - other repository paths. Specific exceptions are configured - at the top-level. - - ${reason} - At location: ${builtins.concatStringsSep "." parts} - ''; - }; - }; - - # This definition of fix is identical to <nixpkgs>.lib.fix, but is - # provided here for cases where readTree is used before nixpkgs can - # be imported. - # - # It is often required to create the args attribute set. - fix = f: let x = f x; in x; - - # Takes an attribute set and adds a meta.ci.targets attribute to it - # which contains all direct children of the attribute set which are - # derivations. - # - # Type: attrs -> attrs - drvTargets = attrs: - attrs // { - # preserve .meta from original attrs - meta = (attrs.meta or { }) // { - # preserve .meta.ci (except .targets) from original attrs - ci = (attrs.meta.ci or { }) // { - targets = builtins.filter - (x: isDerivation attrs."${x}") - (builtins.attrNames attrs); - }; - }; - }; -} diff --git a/mod/tools/perf-flamegraph.nix b/mod/tools/perf-flamegraph.nix deleted file mode 100644 index b472b74..0000000 --- a/mod/tools/perf-flamegraph.nix +++ /dev/null @@ -1,12 +0,0 @@ -# Script that collects perf timing for the execution of a command and writes a -# flamegraph to stdout -{ pkgs, ... }: - -pkgs.writeShellScriptBin "perf-flamegraph" '' - set -euo pipefail - - ${pkgs.linuxPackages.perf}/bin/perf record -g --call-graph dwarf -F max "$@" - ${pkgs.linuxPackages.perf}/bin/perf script \ - | ${pkgs.flamegraph}/bin/stackcollapse-perf.pl \ - | ${pkgs.flamegraph}/bin/flamegraph.pl -'' diff --git a/mod/tools/typst/default.nix b/mod/tools/typst/default.nix deleted file mode 100644 index 1871cc6..0000000 --- a/mod/tools/typst/default.nix +++ /dev/null @@ -1,34 +0,0 @@ -{ pkgs, ... }: -let - version = "0.0.0"; - - depotPackages = pkgs.stdenvNoCC.mkDerivation { - pname = "depot-typst-packages"; - inherit version; - src = ./packages; - phases = [ "installPhase" ]; - - installPhase = '' - for pkg in $src/*; do - name=$(basename $pkg) - mkdir -p $out/share/typst/packages/depot/$name/${version} - cp -rv $pkg/* $out/share/typst/packages/depot/$name/${version}/ - done - ''; - }; -in -{ - inherit depotPackages; - - writeEnv = pkgs.mkShellNoCC { - packages = [ - pkgs.typst - pkgs.typstyle - pkgs.tinymist - depotPackages - ]; - - shellHook = "export TYPST_PACKAGE_PATH=${depotPackages}/share/typst"; - # shellHook = "alias typst='XDG_DATA_HOME=${depotPackages}/share typst'"; - }; -} diff --git a/mod/tools/typst/main.typ b/mod/tools/typst/main.typ deleted file mode 100644 index c6be2b4..0000000 --- a/mod/tools/typst/main.typ +++ /dev/null @@ -1 +0,0 @@ -#import "@depot/tmu:0.0.0": * diff --git a/mod/tools/typst/packages/tmu/template.typ b/mod/tools/typst/packages/tmu/template.typ deleted file mode 100644 index 721e7b6..0000000 --- a/mod/tools/typst/packages/tmu/template.typ +++ /dev/null @@ -1,37 +0,0 @@ -#let report( - title: [], - course: [], - semester: [], - authors: (), - doc, -) = { - set page( - paper: "us-letter", - margin: 1.5in, - numbering: "1", - header: align(horizon, strong(course + h(1fr) + semester)), - ) - - set align(center) - text(18pt, strong(title)) - linebreak() - text(12pt, "Toronto Metropolitan University") - - v(24pt) - - grid( - columns: (1fr,) * calc.min(authors.len(), 4), - row-gutter: 12pt, - ..authors.map(a => [ - #a.name \ - #a.id \ - #link("mailto:" + a.email) - ]) - ) - - set align(left) - set par(justify: true) - v(24pt) - - doc -} diff --git a/mod/tools/typst/packages/tmu/typst.toml b/mod/tools/typst/packages/tmu/typst.toml deleted file mode 100644 index caf96d7..0000000 --- a/mod/tools/typst/packages/tmu/typst.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "tmu" -version = "0.0.0" -entrypoint = "template.typ" -authors = ["Kleidi Bujari"] -license = "MIT" -description = "TMU engineering report template" diff --git a/mod/users/kle/default.nix b/mod/users/kle/default.nix deleted file mode 100644 index fca1ec7..0000000 --- a/mod/users/kle/default.nix +++ /dev/null @@ -1,56 +0,0 @@ -{ pkgs, depot, ... }: -let - inherit (builtins) - attrValues - ; - - inherit (depot.tools) - perf-flamegraph - ; - - - gitKeys = builtins.fetchurl { - url = "https://github.com/kbujari.keys"; - sha256 = "1kskbiyqvjz1wsmcrgh9v0iryf33y70zk503z0m96wmzdjllmc94"; - }; - - keys = { - t480 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEOw8YEbHsKy38JHp9W1wcxxZgWCDgnabOXccZUN5ddd"; - t1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP7T2uWJFUu8aFZZgQusGKyEMocb2pKbHLDad2eIJus9"; - }; -in -{ - inherit keys; - inherit gitKeys; - - nixos = { - initialHashedPassword = "$6$R4dDhaftX.vapGMd$.An36hlp3DXfkIC7bPZ0MDPo6Zvpk8JRrhy2LES.lZZj6JDa74oJkcMW3DCsIySvLJxOPXSShos0TpgJ/w0fH/"; - isNormalUser = true; - shell = pkgs.fish; - home = "/persist/usr/kle"; - createHome = true; - extraGroups = [ "wheel" "users" "networkmanager" "video" "corectrl" ]; - packages = with pkgs; [ - btop - curl - fzf - guvcview - jq - lynx - neovim - perf-flamegraph - ranger - ripgrep - rsync - sshfs - tree - zip - - # nix debugging - nixd - nixpkgs-fmt - ]; - - openssh.authorizedKeys.keys = attrValues keys; - }; -} diff --git a/mod/web/blog/.gitignore b/mod/web/blog/.gitignore deleted file mode 100644 index decc3f8..0000000 --- a/mod/web/blog/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -public/ -nohup.out diff --git a/mod/web/blog/config.toml b/mod/web/blog/config.toml deleted file mode 100644 index 69c389b..0000000 --- a/mod/web/blog/config.toml +++ /dev/null @@ -1,8 +0,0 @@ -base_url = "https://4kb.net" -build_search_index = false -compile_sass = false -generate_feeds = true -minify_html = true - -[markdown] -highlight_code = false diff --git a/mod/web/blog/content/posts/_index.md b/mod/web/blog/content/posts/_index.md deleted file mode 100644 index 9efc62e..0000000 --- a/mod/web/blog/content/posts/_index.md +++ /dev/null @@ -1,6 +0,0 @@ -+++ -title = "Posts" -sort_by = "date" -page_template = "post.html" -redirect_to="/" -+++ diff --git a/mod/web/blog/content/posts/deterministic-hostnames.md b/mod/web/blog/content/posts/deterministic-hostnames.md deleted file mode 100644 index 43dc7d8..0000000 --- a/mod/web/blog/content/posts/deterministic-hostnames.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: "Deterministic and unique network hostnames" -date: "2024-11-10" ---- - -As part of building out a Kubernetes cluster, I wanted to build and distribute a -single OS image to create stateless worker nodes. Using network booting, and -some clever tricks to differentiate nodes, we can create a scaleable and -efficient farm of workers for a cluster that don't even need disks. - -The idea came from a plan to build a cluster using the -[compute blade](https://computeblade.com/), and a few Raspberry Pi SBCs I -already own. Running the cluster from an SD card is not recommended due to the -not-so-great reliability of the flash used by most manufacturers, so I wanted to -try PXE booting each Pi to save money rather than purchasing an SSD for each -one. The compute blades do support an NVMe disk, but I plan to use those for a -storage cluster later, so they need to remain empty. - -## Base image - -Alpine Linux has been my preferred server OS for a long time. It provides a very -lightweight base system, and bundles an excellent bootstrapping system, -[apkovl](https://wiki.alpinelinux.org/wiki/Alpine_local_backup), that allows the -user to save a set of customisations to an system as an overlay to a stock -Alpine live image. In other words, we can create our image once, save the -changes as an `apkovl.tar.gz` file, and apply the same changes to a base system -on boot. This file can even be provided as a -[kernel parameter](https://wiki.alpinelinux.org/wiki/PXE_boot#Guide_to_options) -and will be fetched from a remote webserver automatically! - -Since the image and configuration will be shipped to the node via the network, -an added benefit of using Alpine is its tiny space consumption. I'm not using -enough nodes for this to really matter, but it's a cool optimization regardless. - -## Differentiating the nodes - -One of the main goals of this project is that there should be no persistent -storage required outside the boot image itself. Since every node will download -and generate the same root file-system on startup, the first problem that arises -is how the nodes will identify themselves both on the network and the cluster, -given that it's not possible to name them ahead of time. In other words, any -given node has to generate a unique hostname that won't collide with other -workers, and that will be the same each time that node boots. - -Since these nodes will not have a predefined name, we have to rely on -characteristics of the hardware to differentiate each one. The hardware MAC -address is perfect for this, since it's unique to to each node and will not be -wiped away after the node reboots. On a system like Linux that exposes its -hardware through a _sysfs_, we can find a file containing the address at -`/sys/class/net/eth0/address`. I don't really like the idea of attaching the -literal MAC address of the node to its network hostname, since it's a security -risk, and a bit too verbose. Instead, we can transform it into something safer -using a `sha1sum`, which is already present on our Alpine base system: - -```console -sha1sum /sys/class/net/eth0/address | head -c 6 | awk '{print "worker-" $0}' -``` - -### Applying the new name - -Ideally, the node should apply its generated hostname before reaching out for an -address over DHCP or joining the cluster. We can make sure it happens before any -traffic is sent out by adding a `pre-up` command to the right interface in -`/etc/network/interfaces`: - -``` - -... - -auto eth0 -iface eth0 inet dhcp - pre-up sha1sum /sys/class/net/eth0/address | head -c 6 | awk '{print "worker-" $0}' > /etc/hostname - -... -``` - -The VM I tested with looks outputs `worker-e2fae8`. Pretty clean result, and if -you want to know the physical node that maps to each hostname, you can take note -of the MAC address beforehand and generate the same hash on another computer to -match them up. diff --git a/mod/web/blog/content/posts/nohup.md b/mod/web/blog/content/posts/nohup.md deleted file mode 100644 index 64f7983..0000000 --- a/mod/web/blog/content/posts/nohup.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: "Spawning background processes" -date: "2024-11-17" ---- - -Working in a terminal, -I often pair my editor with a background process watching files. -Before reaching for terminal multiplexers, -see if you can get away with simple tty job control. -Spawn the background process, -still attached to the terminal instance: - -``` -program args & -``` - -Also redirect its output to a file, -for when the process writes to the tty from the background: - -``` -nohup program args & -``` - -Extra reading: - -- <https://jvns.ca/blog/2024/07/03/reasons-to-use-job-control/> diff --git a/mod/web/blog/content/posts/stateless-compute-networks.md b/mod/web/blog/content/posts/stateless-compute-networks.md deleted file mode 100644 index bac9e5d..0000000 --- a/mod/web/blog/content/posts/stateless-compute-networks.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: "On stateless compute networks" -date: "2024-11-10" -draft: true ---- - -On the topic of distributed systems and clustering, -I am quite invested in the idea of compute nodes that rely entirely on the network for configuration. -Arbitrary nodes can join a pre-existing cluster, -offering their CPU time and memory for computation without relying on any pre-existing configuration on the node itself. -In other words, any computer could pick up work, -only needing power and a network connection to the cluster. - -Perhaps this eventually leads into a "self-healing" cluster where only one node is manually bootstrapped, -which then serves a _configuration endpoint_ for other stateless nodes to reach out to for their instructions, -which they will then also serve once they are themselves ready. - -Early revisions of these notes mention Kubernetes, -but I am also trying to achieve similar results with NixOS on a custom project. -In any case, these are my ever-updating notes towards a general implementation of a stateless distributed systems architecture. - -## Self healing cluster - -Assuming control of an external DHCP server, -a self healing Kubernetes cluster would be feasible, -with the PXE boot artifacts supplied by the cluster itself. -That is, as long as one node is running the pod hosting the artifacts on a given endpoint, -other nodes can boot those artifacts and join the cluster, -thereby being able to host the artifacts as well. - -## Configuration endpoint - -The nodes shouldn't require a disk installed to be able to join the network. -Rather, the lofty goal of zero-configuration compute nodes passes the job of node initialization to the supporting network. -This is accomplished with PXE boot instructions supplied over DHCP. - -I delegate the following tasks to a single node in the subnet: - -- Gateway: Optional outbound connections if required -- DHCP server: Cluster IPAM -- TFTP and HTTP server: Serves iPXE firmware and kernel/initrd artifacts diff --git a/mod/web/blog/content/posts/vim-compilers.md b/mod/web/blog/content/posts/vim-compilers.md deleted file mode 100644 index f735228..0000000 --- a/mod/web/blog/content/posts/vim-compilers.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: "Neovim's built-in compilers" -date: "2025-01-17" ---- - -Today I learned that neovim's `:make` comes with many -[backends](https://neovim.io/doc/user/quickfix.html#_6.-selecting-a-compiler) -already configured. -This works for checking c/cpp and python files, among other, but I was -most interested to see [Typst] and [Pandoc] listed as well. - -I was looking to add this functionality with a plugin or implementing it -manually, -where a document can be compiled on the fly from the editor. -After setting `:compiler pandoc`, -generating a pdf is done with `:make pdf`, -with other pandoc options just appended afterwards if needed. - -[typst]: https://typst.app -[pandoc]: https://github.com/jgm/pandoc diff --git a/mod/web/blog/default.nix b/mod/web/blog/default.nix deleted file mode 100644 index 1f91a68..0000000 --- a/mod/web/blog/default.nix +++ /dev/null @@ -1,30 +0,0 @@ -{ pkgs, depot, ... }: -let - inherit (pkgs) - symlinkJoin - ; - - posts = pkgs.stdenvNoCC.mkDerivation { - pname = "4kb.net"; - version = "1.0"; - src = ./.; - - nativeBuildInputs = with pkgs; [ - zola - ]; - - buildPhase = "zola build"; - installPhase = '' - mkdir -p $out - cp -r public/* $out/ - ''; - }; - -in -symlinkJoin { - name = "site"; - paths = [ - posts - depot.misc.cv - ]; -} diff --git a/mod/web/blog/templates/404.html b/mod/web/blog/templates/404.html deleted file mode 100644 index a4669df..0000000 --- a/mod/web/blog/templates/404.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -<head> - <title>Error 404!</title> -</head> - -404! -{% endblock content %} diff --git a/mod/web/blog/templates/base.html b/mod/web/blog/templates/base.html deleted file mode 100644 index 5a91df5..0000000 --- a/mod/web/blog/templates/base.html +++ /dev/null @@ -1,88 +0,0 @@ -<!doctype html> -<html lang="en"> - <head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>{% block title %}4kb.net{% endblock title %}</title> - </head> - <style> - :root { - --bg: #181616; - --fg: #c5c9c5; - --link: #76946a; - --linkhover: #98bb6c; - } - - html { - scroll-behavior: smooth; - color-scheme: dark; - } - - body { - font-family: serif; - padding: 0 0.75em; - max-width: 42em; - margin: auto; - background: var(--bg); - color: var(--fg); - } - - p { - line-height: 1.3em; - } - - th, - td { - padding: 0.2em 0.4em; - border: thin solid; - } - - table { - border: thin solid; - border-collapse: collapse; - } - - a, - a:link { - color: var(--link); - } - - a:hover { - color: var(--linkhover); - } - - article img { - display: block; - margin: 0 auto; - max-width: 80%; - height: auto; - object-fit: contain; - } - - pre { - padding: 1em; - overflow-x: scroll; - } - </style> - <body> - <header - style=" - margin: 3em 0 3em 0; - display: flex; - gap: 8px; - flex-direction: column; - font-family: monospace; - " - > - <span - >Kleidi Bujari <<a href="mailto:mail@4kb.net">mail@4kb.net</a>></span - > - <nav style="display: flex; gap: 8px"> - <span><a href="/">4kb.net</a> |</span> - <a target="_blank" href="https://github.com/kbujari">git</a> - <a href="/atom.xml">rss</a> - </nav> - </header> - <main>{% block content %} {% endblock %}</main> - </body> -</html> diff --git a/mod/web/blog/templates/index.html b/mod/web/blog/templates/index.html deleted file mode 100644 index 6e8a2af..0000000 --- a/mod/web/blog/templates/index.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "base.html" %} {% block content %} - -<div> - {% set section = get_section(path="posts/_index.md") %} - - <ul style="list-style: none; padding-left: 0"> - {% for page in section.pages %} - <li style="display: flex; gap: 1em; margin-bottom: 0.3em"> - <span sty>{{ page.date }}</span> - <a href="{{ page.permalink | safe }}">{{ page.title }}</a> - </li> - {% endfor %} - </ul> - - {% endblock content %} -</div> diff --git a/mod/web/blog/templates/post.html b/mod/web/blog/templates/post.html deleted file mode 100644 index b819520..0000000 --- a/mod/web/blog/templates/post.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "base.html" %} - -{% block title%} - {{ page.title }} - {{ super() }} -{% endblock title %} - -{% block content %} - -<h1 style="text-align: center">{{ page.title }}</h1> -<article> - {{ page.content | safe }} -</article> - -{% endblock content %} diff --git a/mod/web/resistors/default.nix b/mod/web/resistors/default.nix deleted file mode 100644 index 5fca68b..0000000 --- a/mod/web/resistors/default.nix +++ /dev/null @@ -1,12 +0,0 @@ -{ pkgs, ... }: - -pkgs.stdenvNoCC.mkDerivation { - pname = "resistors"; - version = "0.0"; - src = ./.; - - buildInputs = [ pkgs.nodePackages.prettier ]; - - phases = [ "installPhase" ]; - installPhase = "mkdir -p $out; cp -r $src/*.{css,html} $out/"; -} diff --git a/mod/web/resistors/index.html b/mod/web/resistors/index.html deleted file mode 100644 index 0683f49..0000000 --- a/mod/web/resistors/index.html +++ /dev/null @@ -1,339 +0,0 @@ -<!doctype html> -<head> - <meta charset="utf-8" /> - <title>rs-calc</title> - <link rel="stylesheet" href="styles.css" /> - <meta name="viewport" content="initial-scale=1.0" /> -</head> - -<body> - <h1>rs-calc</h1> - - <input id="resistor-query" placeholder="e.g. 3.3K" /> - <label class="resistor-query-label" for="resistor-query">Ω</label> - - <div id="resistor-result" style="display: none"> - <h2>four bands</h2> - <div class="resistor-diagram resistor-diagram-4"> - <div id="resistor-stripe-4-1" class="resistor-stripe"></div> - <div id="resistor-stripe-4-2" class="resistor-stripe"></div> - <div id="resistor-stripe-4-3" class="resistor-stripe"></div> - <div id="resistor-stripe-4-4" class="resistor-stripe"> - <br /> - </div> - <div - class="resistor-stripe-tolerance resistor-stripe" - style="color: #000; background-color: #cfb53b" - > - gold<br />±5% - </div> - </div> - <h2>five bands</h2> - <div class="resistor-diagram resistor-diagram-5"> - <div id="resistor-stripe-5-1" class="resistor-stripe"></div> - <div id="resistor-stripe-5-2" class="resistor-stripe"></div> - <div id="resistor-stripe-5-3" class="resistor-stripe"></div> - <div id="resistor-stripe-5-4" class="resistor-stripe"></div> - <div - class="resistor-stripe-tolerance resistor-stripe" - style="color: #fff; background-color: #964b00" - > - brown<br />±1% - </div> - </div> - <h2>surface mount</h2> - <div id="resistor-smt-3" class="resistor-smt"></div> - </div> - - <footer> - <p> - forked from - <a href="https://github.com/joewalnes/resisto.rs/" target="_blank">this</a - >, with removed analytics, cleaned up code. - <a href="https://github.com/kleidib" target="_blank">my github</a> - </p> - </footer> -</body> - -<script> - // Resistor calculator. - // -Joe Walnes - // See resistors-test.html - - var resistors = {}; - - // These hex codes came from - // http://en.wikipedia.org/wiki/Electronic_color_code - resistors.digitsToColors = { - "-2": { hex: "#c0c0c0", label: "#000", name: "silver", multiplier: "0.01" }, - "-1": { hex: "#cfb53b", label: "#000", name: "gold", multiplier: "0.1" }, - 0: { hex: "#000000", label: "#fff", name: "black", figure: "0" }, - 1: { - hex: "#964b00", - label: "#fff", - name: "brown", - figure: "1", - multiplier: "10", - }, - 2: { - hex: "#ff0000", - label: "#fff", - name: "red", - figure: "2", - multiplier: "100", - }, - 3: { - hex: "#ffa500", - label: "#000", - name: "orange", - figure: "3", - multiplier: "1K", - }, - 4: { - hex: "#ffff00", - label: "#000", - name: "yellow", - figure: "4", - multiplier: "10K", - }, - 5: { - hex: "#9acd32", - label: "#000", - name: "green", - figure: "5", - multiplier: "100K", - }, - 6: { - hex: "#6495ed", - label: "#000", - name: "blue", - figure: "6", - multiplier: "1M", - }, - 7: { - hex: "#ee82ee", - label: "#000", - name: "purple", - figure: "7", - multiplier: "10M", - }, - 8: { - hex: "#a0a0a0", - label: "#000", - name: "gray", - figure: "8", - multiplier: "100M", - }, - 9: { - hex: "#ffffff", - label: "#000", - name: "white", - figure: "9", - multiplier: "1000M", - }, - }; - - resistors.query = function (input) { - input = input - .replace(/ +?/g, "") - .replace(/ohm[s]?/, "") - .replace(/\u2126/, "") - .replace(/\.$/, ""); - - var value = this.parseValue(input); - - if (value !== null) { - value = this.roundToSignificantPlaces(value, 3); - - if ((value <= 99900000000 && value >= 1) || value === 0) { - var colors5 = this.numberTo5ColorDigits(value); - var colors4 = this.numberTo4ColorDigits(value); - var smt3 = value < 10 ? colors4[0].toString() : colors4.join(""); - var self = this; - - return { - value: value, - smt3: smt3, - //smt4: smt4, - //smtEia96: smtEia96, - formatted: this.formatValue(value), - colors5: colors5.map(function (d) { - return self.digitsToColors[d]; - }), - colors4: colors4.map(function (d) { - return self.digitsToColors[d]; - }), - }; - } - } - - return null; - }; - - /** - * Given ohm rating as string (e.g. 3.2m or 3M2), return - * integer value (e.g. 3200000). - */ - resistors.parseValue = function (input) { - var multiplier = 1; - var match; - - if ((match = input.match(/^(\d+)(\.(\d+))?([km])?$/i))) { - // e.g. 123, 1.23, 1M, 1.23M - var unit = match[4]; - if (unit) { - if (unit == "k" || unit == "K") { - multiplier = 1000; - } - if (unit == "m" || unit == "M") { - multiplier = 1000000; - } - } - - return (match[1] + "." + (match[3] || 0)) * multiplier; - } else if ((match = input.match(/^(\d+)([km])(\d+)$/i))) { - // e.g. 12K3 - var unit = match[2]; - if (unit) { - if (unit == "k" || unit == "K") { - multiplier = 1000; - } - if (unit == "m" || unit == "M") { - multiplier = 1000000; - } - } - return (match[1] + "." + (match[3] || 0)) * multiplier; - } else { - return null; - } - }; - - /** - * Round a value to significant places. - * e.g. (123456789, 3) -> 123000000) - * (0.0045678, 3) -> 0.00457) - */ - resistors.roundToSignificantPlaces = function (value, significant) { - if (!value) { - return 0; - } - var nearest = Math.pow( - 10, - Math.floor(Math.log(Math.abs(value)) / Math.log(10)) - (significant - 1), - ); - return Math.round(value / nearest) * nearest; - }; - - /** - * Given ohm rating as integer (e.g. 470000), return - * array of color digits (e.g. 4, 7, 0, 3). See digitsTo_Colors. - */ - resistors.numberTo5ColorDigits = function (value) { - if (!value) { - return [0, 0, 0, 0]; // Special case - } - - var precision = 5; - var digits = ( - Math.floor(value * 100 * Math.pow(10, precision)) / - Math.pow(10, precision) - ).toString(); - function getDigit(digits, i) { - var d = parseInt(digits[i]); - return isNaN(d) ? 0 : d; - } - return [ - getDigit(digits, 0), - getDigit(digits, 1), - getDigit(digits, 2), - digits.length - 5, - ]; - }; - - /** - * Given ohm rating as integer (e.g. 470000), return - * array of color digits (e.g. 4, 7, 0, 3). See digitsTo_Colors. - */ - resistors.numberTo4ColorDigits = function (value) { - if (!value) { - return [0, 0, 0]; // Special case - } - - var precision = 5; - var digits = ( - Math.floor(value * 100 * Math.pow(10, precision)) / - Math.pow(10, precision) - ).toString(); - function getDigit(digits, i) { - var d = parseInt(digits[i]); - return isNaN(d) ? 0 : d; - } - return [getDigit(digits, 0), getDigit(digits, 1), digits.length - 4]; - }; - - /** - * Given a numeric value, format it like '3.2M' etc. - */ - resistors.formatValue = function (value) { - if (value >= 1000000) { - return value / 1000000 + "M"; - } else if (value >= 1000) { - return value / 1000 + "K"; - } else { - return value.toString(); - } - }; -</script> -<script> - var queryEl = document.getElementById("resistor-query"), - resultEl = document.getElementById("resistor-result"); - - function showStripes(colors, prefix) { - for (var i = 0; i < colors.length; i++) { - var color = colors[i]; - var stripeEl = document.getElementById( - "resistor-stripe-" + prefix + "-" + (i + 1), - ); - stripeEl.style.backgroundColor = color.hex; - stripeEl.style.color = color.label; - var text = color.name + "<br>"; - - if (i == colors.length - 1) { - if (color.multiplier !== undefined) { - text += "×" + color.multiplier; - } else { - text += " "; - } - } else { - text += color.figure; - } - stripeEl.innerHTML = text; - } - } - - queryEl.onchange = queryEl.onkeyup = function () { - location.hash = encodeURIComponent(queryEl.value); - var result = resistors.query(queryEl.value); - - if (result) { - resultEl.style.display = "block"; - showStripes(result.colors4, "4"); - showStripes(result.colors5, "5"); - document.getElementById("resistor-smt-3").innerHTML = result.smt3; - } else { - resultEl.style.display = "none"; - } - }; - - if (location.hash) { - queryEl.value = decodeURIComponent(location.hash.substring(1)); - queryEl.onchange(); - } - - window.onhashchange = function () { - queryEl.value = decodeURIComponent(location.hash.substring(1)); - queryEl.onchange(); - }; - - queryEl.focus(); -</script> diff --git a/mod/web/resistors/styles.css b/mod/web/resistors/styles.css deleted file mode 100644 index e2823e4..0000000 --- a/mod/web/resistors/styles.css +++ /dev/null @@ -1,127 +0,0 @@ -* { - /*font-family: 'firacode';*/ - text-rendering: optimizeLegibility; -} - -body { - margin: 10px 20px; - color: #cccccc; - background: #252525; - background-attachment: fixed !important; -} - -h1 { - font-weight: normal; - font-size: 50px; - margin: -10px 0 10px 0; -} - -h2 { - font-weight: normal; - font-size: 15px; -} - -strong { - font-weight: normal; -} - -footer { - position: absolute; - font-size: 12px; - color: #555; - bottom: 0; - left: 0; - right: 0; - padding: 20px; -} - -a { - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -footer a { - color: #775; -} - -#resistor-query { - font-size: 20px; - border: 1px solid #999; - color: #fff; - background-color: #444; - outline: none; - width: 180px; -} - -.resistor-stripe { - width: 80px; - height: 150px; - display: inline-block; - padding: 4px 4px; -} - -.resistor-smt { - display: inline-block; - padding: 0px 10px; - font-size: 35px; - background-color: #000; - border-left: 15px solid silver; - border-right: 15px solid silver; - border-top: 1px solid silver; - border-bottom: 1px solid silver; - margin-right: 10px; -} - -@media print, screen and (max-width: 520px) { - .resistor-stripe { - width: 55px; - font-size: 80%; - height: 100px; - } - .resistor-smt { - font-size: 25px; - border-left: 12px solid silver; - border-right: 12px solid silver; - } -} - -@media print, screen and (max-width: 410px) { - .resistor-stripe { - width: 35px; - font-size: 55%; - height: 70px; - } - .resistor-smt { - font-size: 18px; - border-left: 8px solid silver; - border-right: 8px solid silver; - } -} - -@media print, screen and (max-width: 300px) { - .resistor-stripe { - width: 25px; - font-size: 50%; - height: 60px; - padding: 2px; - } - .resistor-smt { - font-size: 13px; - border-left: 6px solid silver; - border-right: 6px solid silver; - padding: 2px 4px; - } -} - -@media print, screen and (max-height: 660px) { - .resistor-stripe { - height: 55px; - } - footer { - position: inherit; - padding: 10px 0; - } -} |
