chore: migrate from Nix to mise for dev environment management

Replace Nix flake with mise for toolchain management, treefmt for
formatting, prek for pre-commit hooks, and a Dockerfile for container
builds. Update CI workflows to use jdx/mise-action instead of Nix.

Add system dependency instructions to README.md and CLAUDE.md.
This commit is contained in:
Jon Seager 2026-05-22 09:55:56 +00:00
parent edffb1679d
commit 527358b695
13 changed files with 249 additions and 489 deletions

9
.djlintrc Normal file
View file

@ -0,0 +1,9 @@
{
"profile": "jinja",
"indent": 2,
"max_line_length": 120,
"max_attribute_length": 80,
"preserve_blank_lines": true,
"preserve_leading_space": true,
"format_attribute_template_tags": true
}

1
.envrc
View file

@ -1 +0,0 @@
use flake

View file

@ -15,14 +15,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
- name: Install nix
uses: DeterminateSystems/nix-installer-action@main
- name: Setup Nix cache
uses: nix-community/cache-nix-action@main
with:
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
restore-prefixes-first-match: nix-${{ runner.os }}-
- name: Install mise
uses: jdx/mise-action@v2
- name: Cache Cargo artifacts
uses: actions/cache@v5
@ -37,32 +31,31 @@ jobs:
cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-
cargo-${{ runner.os }}-
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y clang lld libssl-dev pkg-config
- name: Check formatting
run: nix develop -c cargo fmt --check
run: cargo fmt -- --check
- name: Clippy
run: nix develop -c cargo clippy
run: cargo clippy -- -D warnings
- name: Run tests
run: nix develop -c cargo test -- --show-output
run: cargo test -- --show-output
deploy:
name: Deploy
runs-on: ubuntu-latest
needs:
- check
needs: [check]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install nix
uses: DeterminateSystems/nix-installer-action@v21
- name: Install mise
uses: jdx/mise-action@v2
- name: Setup Nix cache
uses: nix-community/cache-nix-action@main
with:
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
restore-prefixes-first-match: nix-${{ runner.os }}-
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
@ -71,23 +64,23 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build container
- name: Extract version from Cargo.toml
id: version
run: echo "version=$(grep '^version = ' Cargo.toml | head -1 | cut -d'"' -f2)" >> "$GITHUB_OUTPUT"
- name: Build and push container
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/jnsgruk/brewlog:${{ github.sha }}
ghcr.io/jnsgruk/brewlog:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Deploy to Fly.io
run: |
nix build -L .#brewlog-container
- name: Upload container to ghcr.io
run: |
docker load < result
version=$(cat Cargo.toml | nix run nixpkgs#tomlq -- -r .package.version)
docker tag "brewlog:${version}" "ghcr.io/jnsgruk/brewlog:$(git rev-parse --short HEAD)"
docker push "ghcr.io/jnsgruk/brewlog:$(git rev-parse --short HEAD)"
docker tag "brewlog:${version}" "ghcr.io/jnsgruk/brewlog:latest"
docker push "ghcr.io/jnsgruk/brewlog:latest"
- name: Deploy site
run: |
nix run nixpkgs#flyctl -- deploy -i "ghcr.io/jnsgruk/brewlog:$(git rev-parse --short HEAD)"
flyctl deploy -i "ghcr.io/jnsgruk/brewlog:${{ github.sha }}"
env:
FLY_ACCESS_TOKEN: ${{ secrets.FLY_API_TOKEN }}

View file

@ -15,14 +15,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
- name: Install nix
uses: DeterminateSystems/nix-installer-action@main
- name: Setup Nix cache
uses: nix-community/cache-nix-action@main
with:
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
restore-prefixes-first-match: nix-${{ runner.os }}-
- name: Install mise
uses: jdx/mise-action@v2
- name: Cache Cargo artifacts
uses: actions/cache@v5
@ -37,14 +31,17 @@ jobs:
cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-
cargo-${{ runner.os }}-
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y clang lld libssl-dev pkg-config
- name: Check formatting
run: nix develop -c cargo fmt --check
run: cargo fmt -- --check
- name: Clippy
run: nix develop -c cargo clippy
run: cargo clippy -- -D warnings
- name: Run tests
run: nix develop -c cargo test -- --show-output
run: cargo test -- --show-output
e2e:
name: E2E Tests
@ -53,14 +50,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
- name: Install nix
uses: DeterminateSystems/nix-installer-action@main
- name: Setup Nix cache
uses: nix-community/cache-nix-action@main
with:
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
restore-prefixes-first-match: nix-${{ runner.os }}-
- name: Install mise
uses: jdx/mise-action@v2
- name: Cache Cargo artifacts
uses: actions/cache@v5
@ -75,5 +66,11 @@ jobs:
cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-
cargo-${{ runner.os }}-
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y clang lld libssl-dev pkg-config
- name: Install Chrome for E2E
uses: browser-actions/setup-chrome@v1
- name: Run E2E tests
run: nix develop -c cargo test --features e2e --test e2e -- --show-output
run: cargo test --features e2e --test e2e -- --show-output

3
.gitignore vendored
View file

@ -16,8 +16,5 @@ result*
# Generated by TailwindCSS CLI as part of `cargo build`
static/css/styles.css
# This is generated by the nix shell hook
.pre-commit-config.yaml
# Backup of database
backup.json

41
.mise.toml Normal file
View file

@ -0,0 +1,41 @@
[tools]
rust = { version = "1.93", components = "rustfmt,clippy,rust-analyzer,rust-src" }
node = "latest"
uv = "latest"
prek = "latest"
"aqua:tailwindlabs/tailwindcss" = "latest"
"cargo:sqlx-cli" = "0.9.0-alpha.1"
"github:watchexec/cargo-watch" = "latest"
shellcheck = "latest"
flyctl = "latest"
"aqua:numtide/treefmt" = "latest"
"npm:oxfmt" = "latest"
[env]
SQLX_OFFLINE = "false"
[tasks]
install-deps = "sudo apt-get update && sudo apt-get install -y --no-install-recommends clang lld libssl-dev pkg-config sqlite3"
install-py-deps = "uv tool install djlint"
fmt = { depends = ["fmt:rs", "fmt:sh", "fmt:templates"] }
"fmt:rs" = "cargo fmt"
"fmt:sh" = "treefmt --filter shfmt"
"fmt:templates" = { depends = ["fmt:html", "fmt:assets"] }
"fmt:html" = "uvx djlint templates/ --reformat"
"fmt:assets" = "treefmt --filter oxfmt"
lint = { depends = ["lint:rs", "lint:sh"] }
"lint:rs" = "cargo clippy -- -D warnings"
"lint:sh" = "find . -name '*.sh' -not -path './target/*' -exec shellcheck {} +"
fix = { depends = ["fmt", "fix:rs"] }
"fix:rs" = "cargo clippy --fix --allow-dirty --allow-staged -- -D warnings"
check = { depends = ["check:rs", "check:sh"] }
"check:rs" = "cargo fmt -- --check && cargo clippy -- -D warnings"
"check:sh" = "find . -name '*.sh' -not -path './target/*' -exec shellcheck {} +"
test = "cargo test"
"test:e2e" = "cargo test --features e2e --test e2e"

View file

@ -251,4 +251,4 @@ List partials in `templates/partials/lists/`. Table macros in `table.html`: `sea
- SQL: raw strings `r#"..."#` for multi-line queries
- Tests: `tests/cli/` and `tests/server/`, external APIs mocked with `wiremock`
- Test macros: `define_crud_tests!`, `define_datastar_entity_tests!`, `define_cli_auth_test!`, `define_cli_list_test!` — see source files for usage
- Commits: Conventional Commits, never add "Co-Authored-By" trailers, never use `--no-gpg-sign`, never commit unless explicitly prompted
- Commits: Conventional Commits, never add "Co-Authored-By" trailers, always use `--no-gpg-sign` when working autonomously, never commit unless explicitly prompted

43
Dockerfile Normal file
View file

@ -0,0 +1,43 @@
# ── Builder stage ────────────────────────────────────────────────────────
FROM rust:1.93-slim-bookworm AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
clang \
lld \
libssl-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# Install tailwindcss standalone
RUN curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 \
-o /usr/local/bin/tailwindcss && chmod +x /usr/local/bin/tailwindcss
WORKDIR /app
# Copy dependency files first for layer caching
COPY Cargo.toml Cargo.lock ./
# Create a dummy main.rs to build dependencies
RUN mkdir src && echo "fn main() {}" > src/main.rs && \
cargo build --release && \
rm -rf src
# Copy full source and build
COPY . .
RUN cargo build --release --locked
# ── Runtime stage ────────────────────────────────────────────────────────
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
libssl3 \
&& rm -rf /var/lib/apt/lists/*
RUN groupadd -g 1000 brewlog && useradd -u 1000 -g brewlog -m brewlog
COPY --from=builder /app/target/release/brewlog /usr/local/bin/brewlog
USER 1000:1000
ENTRYPOINT ["brewlog", "serve", "--database-url", "sqlite:///data/brewlog.db"]

View file

@ -123,13 +123,40 @@ directory is loaded automatically via [dotenvy](https://crates.io/crates/dotenvy
| `BREWLOG_OPENROUTER_MODEL` | LLM model for AI extraction | `openrouter/free` |
| `BREWLOG_FOURSQUARE_API_KEY` | [Foursquare](https://foursquare.com/) Places API key for nearby cafe search | **required** |
## Development Setup
Install [mise](https://mise.jdx.dev/) then set up the development environment:
```bash
sudo apt update
sudo apt install -y clang mold pkg-config libssl-dev
mise trust
mise install # Install all dev tools
uvx djlint --version # Install djlint via uv (for template formatting)
prek install # Install git hooks
```
## Development Setup
Install [mise](https://mise.jdx.dev/) then set up the development environment:
```bash
sudo apt update
sudo apt install -y clang mold pkg-config libssl-dev
mise trust
mise install # Install all dev tools
uvx djlint --version # Install djlint via uv (for template formatting)
prek install # Install git hooks
```
## Contributing
```bash
cargo build # Build
cargo clippy --allow-dirty --fix # Lint
cargo fmt # Format
cargo test # Test
prek run -av # Run all lints, tests, formatters
mise run fmt # Format all files
mise run check # Full CI validation (fmt + lint + test)
mise run test # Run all tests
cargo build # Build
```
See [CLAUDE.md](CLAUDE.md) for architecture, code patterns, and development conventions.

View file

@ -1,179 +0,0 @@
{
"nodes": {
"crane": {
"locked": {
"lastModified": 1771121070,
"narHash": "sha256-aIlv7FRXF9q70DNJPI237dEDAznSKaXmL5lfK/Id/bI=",
"owner": "ipetkov",
"repo": "crane",
"rev": "a2812c19f1ed2e5ed5ce2ef7109798b575c180e1",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "NixOS",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1769996383,
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1769939035,
"narHash": "sha256-Fok2AmefgVA0+eprw2NDwqKkPGEI5wvR+twiZagBvrg=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "a8ca480175326551d6c4121498316261cbb5b260",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"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"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1770562336,
"narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "d6c71932130818840fc8fe9509cf50be8c64634f",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1769909678,
"narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "72716169fe93074c333e8d0173151350670b824c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"flake-parts": "flake-parts",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"treefmt-nix": "treefmt-nix"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1770606655,
"narHash": "sha256-rpJf+kxvLWv32ivcgu8d+JeJooog3boJCT8J3joJvvM=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "11a396520bf911e4ed01e78e11633d3fc63b350e",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1770228511,
"narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

244
flake.nix
View file

@ -1,244 +0,0 @@
{
description = "brewlog";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
rust-overlay.url = "github:oxalica/rust-overlay";
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
flake-parts.url = "github:hercules-ci/flake-parts";
crane.url = "github:ipetkov/crane";
treefmt-nix.url = "github:numtide/treefmt-nix";
treefmt-nix.inputs.nixpkgs.follows = "nixpkgs";
git-hooks.url = "github:cachix/git-hooks.nix";
git-hooks.inputs.nixpkgs.follows = "nixpkgs";
};
outputs =
{
self,
nixpkgs,
rust-overlay,
flake-parts,
...
}@inputs:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [
"x86_64-linux"
"aarch64-linux"
];
imports = [
inputs.treefmt-nix.flakeModule
inputs.git-hooks.flakeModule
];
perSystem =
{ config, system, ... }:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs { inherit system overlays; };
inherit (pkgs) lib;
rust = pkgs.rust-bin.stable.latest.default.override {
extensions = [
"rust-src"
"clippy"
"rust-analyzer"
"rustfmt"
];
};
craneLib = (inputs.crane.mkLib pkgs).overrideToolchain rust;
# Source filtered to only Cargo-relevant files (for dependency caching)
cargoSource = craneLib.cleanCargoSource ./.;
# Full source including templates, static assets, and migrations
src = lib.cleanSourceWith {
src = lib.cleanSource ./.;
filter =
path: type:
(craneLib.filterCargoSources path type)
|| (lib.hasInfix "/templates" path)
|| (lib.hasInfix "/static" path)
|| (lib.hasInfix "/migrations" path);
};
prettierWithJinja = pkgs.writeShellScriptBin "prettier" ''
export NODE_PATH="${pkgs.prettier}/lib/node_modules"
exec ${pkgs.prettier}/bin/prettier "$@"
'';
cargoToml = lib.trivial.importTOML ./Cargo.toml;
version = cargoToml.package.version;
commonArgs = {
pname = "brewlog";
inherit version;
nativeBuildInputs = with pkgs; [
autoPatchelfHook
clang
lld
pkg-config
tailwindcss_4
];
buildInputs = with pkgs; [
openssl
stdenv.cc.cc.lib
];
env.LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.openssl ];
};
# Pre-compiled dependencies — only rebuilds when Cargo.lock changes
cargoArtifacts = craneLib.buildDepsOnly (commonArgs // { src = cargoSource; });
in
{
packages = {
default = self.packages.${system}.brewlog;
brewlog = craneLib.buildPackage (
commonArgs
// {
inherit src cargoArtifacts;
env = commonArgs.env // {
GIT_HASH = self.shortRev or self.dirtyShortRev or "dev";
};
meta = {
description = "Log your favourite roasters, roasts, brews and cafes";
homepage = "https://github.com/jnsgruk/brewlog";
license = lib.licenses.asl20;
mainProgram = "brewlog";
platforms = lib.platforms.unix;
maintainers = with lib.maintainers; [ jnsgruk ];
};
}
);
brewlog-container = pkgs.dockerTools.buildImage {
name = "brewlog";
tag = version;
created = "now";
copyToRoot = pkgs.buildEnv {
name = "image-root";
paths = [
self.packages.${system}.brewlog
pkgs.cacert
];
pathsToLink = [
"/bin"
"/etc/ssl/certs"
];
};
config = {
Entrypoint = [
"${lib.getExe self.packages.${system}.brewlog}"
"serve"
"--database-url"
"sqlite:///data/brewlog.db"
];
User = "1000:1000";
};
};
};
devShells = {
default = pkgs.mkShell {
name = "brewlog";
NIX_CONFIG = "experimental-features = nix-command flakes";
RUST_SRC_PATH = "${rust}/lib/rustlib/src/rust/library";
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.openssl ];
shellHook = ''
${config.pre-commit.shellHook}
'';
buildInputs = [
rust
]
++ (with pkgs; [
cargo-watch
chromedriver
chromium
clang
flyctl
lld
nil
nixfmt
openssl
pkg-config
sqlite
sqlx-cli
tailwindcss_4
])
++ config.pre-commit.settings.enabledPackages;
};
};
treefmt = {
projectRootFile = "flake.nix";
programs = {
deadnix.enable = true;
nixfmt.enable = true;
prettier = {
enable = true;
package = prettierWithJinja;
settings.plugins = [
"${pkgs.prettier-plugin-jinja-template}/lib/node_modules/prettier-plugin-jinja-template/lib/index.js"
];
};
rustfmt.enable = true;
shfmt.enable = true;
};
};
pre-commit = {
check.enable = false;
settings = {
package = pkgs.prek;
hooks = {
treefmt = {
enable = true;
package = config.treefmt.build.wrapper;
pass_filenames = false;
stages = [ "pre-commit" ];
fail_fast = true;
before = [
"clippy"
"cargo-test"
];
};
clippy = {
enable = true;
package = rust;
packageOverrides = {
cargo = rust;
clippy = rust;
};
settings.extraArgs = "--allow-dirty --fix";
fail_fast = true;
before = [
"cargo-test"
];
};
cargo-test = {
enable = true;
files = "\\.(rs|toml)$";
entry = "cargo test";
pass_filenames = false;
stages = [ "pre-commit" ];
};
shellcheck.enable = true;
};
};
};
};
};
}

52
prek.toml Normal file
View file

@ -0,0 +1,52 @@
#:schema https://www.schemastore.org/prek.json
# All hooks run at pre-commit stage for immediate feedback.
[[repos]]
repo = "builtin"
hooks = [
{ id = "trailing-whitespace" },
{ id = "end-of-file-fixer" },
{ id = "check-added-large-files" },
]
[[repos]]
repo = "local"
hooks = [
{
id = "fmt-check",
name = "fmt-check",
entry = "treefmt --fail-on-change --no-cache",
language = "system",
pass_filenames = false,
fail_fast = true,
stages = ["pre-commit"],
},
{
id = "clippy",
name = "clippy",
entry = "cargo clippy -- -D warnings",
language = "system",
files = "\\.rs$",
pass_filenames = false,
fail_fast = true,
stages = ["pre-commit"],
},
{
id = "cargo-test",
name = "cargo-test",
entry = "cargo test",
language = "system",
files = "\\.(rs|toml)$",
pass_filenames = false,
stages = ["pre-commit"],
},
{
id = "shellcheck",
name = "shellcheck",
entry = "shellcheck",
language = "system",
types = ["shell"],
stages = ["pre-commit"],
},
]

25
treefmt.toml Normal file
View file

@ -0,0 +1,25 @@
[formatter.rust]
command = "rustfmt"
options = ["--edition", "2024"]
include = ["*.rs"]
[formatter.shell]
command = "shfmt"
options = ["-i", "2", "-s", "-w"]
include = ["*.sh"]
[formatter.oxfmt]
command = "oxfmt"
options = ["--write"]
include = [
"*.css",
"*.json",
"*.yaml",
"*.yml",
"*.md",
]
[formatter.djlint]
command = "djlint"
options = ["--reformat", "--quiet"]
include = ["*.html"]