Compare commits

..

2 commits

Author SHA1 Message Date
0160483e9a
feat: nix flake with OCI image + Forgejo CI
All checks were successful
Build and Push OCI Image / build (pull_request) Successful in 2m49s
Add Nix flake build system with automated container image building:

**Nix Flake (flake.nix)**
- Built with flake-parts for modularity
- `solidhaus` package: builds SvelteKit app with buildNpmPackage
- `solidhaus-image` package: OCI image with nginx serving app
- `push-solidhaus-image` app: pushes to registry.toph.so
- Dev shell with Node.js 22

**OCI Image**
- Based on nixpkgs nginx
- Serves SvelteKit build/ as static SPA
- SPA fallback routing configured
- Security headers (X-Frame-Options, X-Content-Type-Options, X-XSS-Protection)
- Gzip compression for text assets
- 1-year cache for immutable static assets

**Forgejo CI (.forgejo/workflows/build-image.yml)**
- Runs on 'nix' runner (uses nix-runner-image)
- Builds OCI image on every push
- Pushes to registry.toph.so on main branch
- Tags with :latest and :${commit-sha}

**Build commands**
- `nix build .#solidhaus` — build app
- `nix build .#solidhaus-image` — build OCI image
- `nix run .#push-solidhaus-image` — push to registry

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-27 00:00:00 +01:00
bf80685b77
chore: log request #req_20260226_234700_photo
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-26 23:45:59 +01:00
4 changed files with 346 additions and 16 deletions

View file

@ -0,0 +1 @@
{"id":"req_20260226_234700_photo","timestamp":"2026-02-26T23:47:00Z","prompt":"Can we do photo capture with indexeddb and pwa?","session":"feat-photo-capture","branch":"feat/20260226-photo-capture","pr_number":1,"pr_url":"https://git.toph.so/toph/solidhaus/pulls/1","status":"open"}

View file

@ -0,0 +1,45 @@
name: Build and Push OCI Image
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
build:
runs-on: nix
steps:
- name: Checkout code
uses: https://code.forgejo.org/actions/checkout@v4
- name: Build OCI image
run: |
nix build .#solidhaus-image \
--print-build-logs \
--show-trace
- name: Push to registry
if: github.ref == 'refs/heads/main'
run: |
image=$(nix build --no-link --print-out-paths .#solidhaus-image)
skopeo copy \
--dest-tls-verify=false \
"docker-archive:$image" \
"docker://registry.toph.so/solidhaus:latest"
# Also tag with commit SHA
skopeo copy \
--dest-tls-verify=false \
"docker-archive:$image" \
"docker://registry.toph.so/solidhaus:${GITHUB_SHA:0:7}"
- name: Build summary
if: github.ref == 'refs/heads/main'
run: |
echo "### ✅ Image Built and Pushed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Image**: registry.toph.so/solidhaus:latest" >> $GITHUB_STEP_SUMMARY
echo "- **Tag**: ${GITHUB_SHA:0:7}" >> $GITHUB_STEP_SUMMARY
echo "- **Commit**: ${GITHUB_SHA}" >> $GITHUB_STEP_SUMMARY

130
flake.lock generated Normal file
View file

@ -0,0 +1,130 @@
{
"nodes": {
"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"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1771903837,
"narHash": "sha256-sdaqdnsQCv3iifzxwB22tUwN/fSHoN7j2myFW5EIkGk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e764fc9a405871f1f6ca3d1394fb422e0a0c3951",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"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"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1771848320,
"narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2fc6539b481e1d2569f25f8799236694180c0993",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs",
"sd-card": "sd-card"
}
},
"sd-card": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1772146011,
"narHash": "sha256-ZUj4zAAk9shxYOn5jh9Kri4q5O/Aq52OJF43otPMPBE=",
"ref": "refs/heads/main",
"rev": "307eb15035671c58f9145806067030b59c5d7792",
"revCount": 4,
"type": "git",
"url": "ssh://git@git.toph.so/toph/sd-card"
},
"original": {
"type": "git",
"url": "ssh://git@git.toph.so/toph/sd-card"
}
},
"systems": {
"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",
"version": 7
}

186
flake.nix
View file

@ -1,26 +1,180 @@
{ {
description = "solidhaus dev shell"; description = "SolidHaus Local-first household inventory app";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
flake-utils.url = "github:numtide/flake-utils"; flake-parts.url = "github:hercules-ci/flake-parts";
forgejo-workflow.url = "path:/nix/store/1kpd2vzj87rlraw81p4iy4ldw4dm8g6z-forgejo-workflow-setup-flake/bin"; sd-card.url = "git+ssh://git@git.toph.so/toph/sd-card";
};
outputs = inputs @ {
flake-parts,
nixpkgs,
sd-card,
...
}:
flake-parts.lib.mkFlake {inherit inputs;} {
systems = ["x86_64-linux" "aarch64-linux"];
perSystem = {
config,
pkgs,
system,
...
}: let
nodejs = pkgs.nodejs_22;
buildNpmPackage = pkgs.buildNpmPackage.override {
nodejs = nodejs;
};
in {
packages = {
# Build the SvelteKit app
solidhaus = buildNpmPackage {
pname = "solidhaus";
version = "0.0.1";
src = ./.;
npmDepsHash = "sha256-d7k2YpmFt/Ba0j0SrhgoHQdhYjxHO46BliOzyecZgbY=";
buildPhase = ''
npm run build
'';
installPhase = ''
mkdir -p $out
cp -r build/* $out/
'';
meta = {
description = "Local-first household inventory app with barcode scanning";
homepage = "https://git.toph.so/toph/solidhaus";
};
};
# OCI image with nginx serving the built app
solidhaus-image = pkgs.dockerTools.buildLayeredImage {
name = "registry.toph.so/solidhaus";
tag = "latest";
contents = with pkgs; [
fakeNss
nginx
];
config = {
Cmd = [
"${pkgs.nginx}/bin/nginx"
"-c"
"/etc/nginx/nginx.conf"
"-g"
"daemon off;"
];
ExposedPorts = {
"80/tcp" = {};
};
WorkingDir = "/usr/share/nginx/html";
};
extraCommands = ''
# Create directory structure
mkdir -p var/log/nginx
mkdir -p var/cache/nginx
mkdir -p tmp
mkdir -p etc/nginx
# Copy built app
mkdir -p usr/share/nginx/html
cp -r ${config.packages.solidhaus}/* usr/share/nginx/html/
# Create nginx config
cat > etc/nginx/nginx.conf <<'EOF'
user nobody nobody;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include ${pkgs.nginx}/conf/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
sendfile on;
keepalive_timeout 65;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml+rss;
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}
EOF
'';
};
# nix run .#dev — start SvelteKit dev server with annotation proxy
dev = sd-card.lib.${system}.mkDevApp {
inherit pkgs;
devCmd = "${nodejs}/bin/npm run dev";
devPort = 5173; # Vite default port
};
default = config.packages.solidhaus;
};
apps = {
# Push image to registry
push-solidhaus-image = {
type = "app";
program = pkgs.lib.getExe (pkgs.writeShellApplication {
name = "push-solidhaus-image";
runtimeInputs = [pkgs.skopeo];
text = ''
image=$(nix build --no-link --print-out-paths .#solidhaus-image)
skopeo copy \
--insecure-policy \
"docker-archive:$image" \
"docker://registry.toph.so/solidhaus:latest"
'';
});
};
}; };
outputs = { self, nixpkgs, flake-utils, forgejo-workflow }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
packages = packages = sd-card.packages.${system}.tools # tea, jq, nodejs, playwright
# Forgejo workflow tools: tea, jq, yq, git, curl
forgejo-workflow.packages.${system}.tools
++ (with pkgs; [ ++ (with pkgs; [
# add your project packages here nodejs
nodePackages.npm
]); ]);
}; };
} };
); };
} }