From 6addfad663ef011108ac26ff557ce0d7530ffec4 Mon Sep 17 00:00:00 2001 From: seth Date: Fri, 1 Dec 2023 22:45:12 -0500 Subject: [PATCH] feat: add nix flake (#1) Co-authored-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com> --- .env.example | 9 ++ .envrc | 11 +++ .github/workflows/docker.yml | 108 ++++------------------- .github/workflows/nix.yml | 42 +++++++++ .github/workflows/update-lock.yml | 28 ++++++ .github/workflows/upload.yml | 54 ++++++++++++ .gitignore | 5 ++ Dockerfile | 35 -------- flake.lock | 139 ++++++++++++++++++++++++++++++ flake.nix | 31 +++++++ nix/default.nix | 7 ++ nix/derivation.nix | 28 ++++++ nix/dev.nix | 35 ++++++++ nix/docker.nix | 78 +++++++++++++++++ nix/packages.nix | 24 ++++++ 15 files changed, 506 insertions(+), 128 deletions(-) create mode 100644 .env.example create mode 100644 .envrc create mode 100644 .github/workflows/nix.yml create mode 100644 .github/workflows/update-lock.yml create mode 100644 .github/workflows/upload.yml delete mode 100644 Dockerfile create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/default.nix create mode 100644 nix/derivation.nix create mode 100644 nix/dev.nix create mode 100644 nix/docker.nix create mode 100644 nix/packages.nix diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..237c2d9 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +DISCORD_TOKEN= +PAGESPEED_API_KEY= +REDIS_URL= + +GUILD_ID= +ERROR_LOGS_CHANNEL= + +HOST= +PORT= diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..0cfcdcc --- /dev/null +++ b/.envrc @@ -0,0 +1,11 @@ +# only use flake when `nix` is present +if command -v nix &> /dev/null; then + if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs=" + fi + + watch_file ./nix/dev.nix + use flake +fi + +dotenv_if_exists diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e20853d..00b3ebf 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,116 +1,38 @@ name: Docker on: - push: + workflow_call: + pull_request: branches: ["main"] -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -permissions: - contents: read - packages: write - jobs: build: runs-on: ubuntu-latest strategy: matrix: - platform: - - linux/amd64 - # - linux/arm64 + arch: [x86_64, aarch64] steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - if: ${{ matrix.platform != 'linux/amd64' }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v8 - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=raw,value=latest + - name: Setup Nix cache + uses: DeterminateSystems/magic-nix-cache-action@v2 - - name: Build and push by digest - uses: docker/build-push-action@v5 - id: build - with: - context: . - provenance: false - labels: ${{ steps.meta.outputs.labels }} - platforms: ${{ matrix.platform }} - cache-from: type=gha - cache-to: type=gha,mode=max - outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true - - - name: Export digests + - name: Build Docker image + id: image run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" + nix build -L .#container-${{ matrix.arch }} + [ ! -L result ] && exit 1 + echo "path=$(realpath result)" >> "$GITHUB_OUTPUT" - - name: Upload digests + - name: Upload image uses: actions/upload-artifact@v3 with: - name: digests - path: /tmp/digests/* + name: container-${{ matrix.arch }} + path: ${{ steps.image.outputs.path }} if-no-files-found: error retention-days: 1 - - push: - runs-on: ubuntu-latest - needs: - - build - - steps: - - name: Download digests - uses: actions/download-artifact@v3 - with: - name: digests - path: /tmp/digests - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=raw,value=latest - - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) - - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 0000000..4b15353 --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,42 @@ +name: Nix + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v8 + - name: Setup Nix cache + uses: DeterminateSystems/magic-nix-cache-action@v2 + + - name: Build valfisk + run: nix build -L --fallback + + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v8 + - name: Setup Nix cache + uses: DeterminateSystems/magic-nix-cache-action@v2 + + - name: Run checks + run: nix flake check -L --show-trace diff --git a/.github/workflows/update-lock.yml b/.github/workflows/update-lock.yml new file mode 100644 index 0000000..3dbe45c --- /dev/null +++ b/.github/workflows/update-lock.yml @@ -0,0 +1,28 @@ +name: Update flake.lock + +on: + schedule: + - cron: "0 0 * * 0" + workflow_dispatch: + +jobs: + update: + runs-on: ubuntu-latest + + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v6 + + - name: Update lockfile + uses: DeterminateSystems/update-flake-lock@v20 + with: + commit-msg: "chore(flake): update inputs" + pr-title: "chore(flake): update inputs" + token: ${{ github.token }} diff --git a/.github/workflows/upload.yml b/.github/workflows/upload.yml new file mode 100644 index 0000000..d81c062 --- /dev/null +++ b/.github/workflows/upload.yml @@ -0,0 +1,54 @@ +name: Upload to Registry + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + build: + uses: ./.github/workflows/docker.yml + + push: + needs: build + runs-on: ubuntu-latest + + env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + + permissions: + packages: write + + if: github.event_name == 'push' + + steps: + - uses: actions/download-artifacts@v3 + with: + path: images + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ github.token }} + + - name: Push to Registry + env: + TAG: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + run: | + set -eux + + architectures=("x86_64" "aarch64") + for arch in "${architectures[@]}"; do + docker load < images/container-"$arch"/*.tar.gz + docker tag valfisk:latest-"$arch" ${{ env.TAG }}-"$arch" + docker push ${{ env.TAG }}-"$arch" + done + + docker manifest create ${{ env.TAG }} \ + --amend ${{ env.TAG }}-x86_64 \ + --amend ${{ env.TAG }}-aarch64 + + docker manifest push ${{ env.TAG }} diff --git a/.gitignore b/.gitignore index edcb69d..c493577 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,8 @@ # IDEs .vscode/ .idea/ + +# nix stuff +.direnv +result* +repl-result-out* diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 66f3fc2..0000000 --- a/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -FROM rust:1.72-alpine as common-build -ARG TARGETPLATFORM -RUN \ - if [ "$TARGETPLATFORM" = "linux/amd64" ]; then target="x86_64-unknown-linux-musl"; \ - elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then target="aarch64-unknown-linux-musl"; \ - else echo "Unsupported platform ${TARGETPLATFORM}!" && exit 1; \ - fi && \ - echo "$target" > /tmp/rust-target - -RUN apk update && apk upgrade && apk add --no-cache musl-dev -RUN rustup target add "$(cat /tmp/rust-target)" -RUN cargo install cargo-chef --locked - -WORKDIR /build - -FROM common-build AS planner - -COPY . ./ -RUN cargo chef prepare --recipe-path recipe.json - -FROM common-build AS builder -ENV CARGO_BUILD_RUSTFLAGS="-C target-feature=+crt-static" - -COPY --from=planner /build/recipe.json recipe.json -RUN cargo chef cook --recipe-path recipe.json --release --locked --target "$(cat /tmp/rust-target)" - -COPY . ./ -RUN cargo build --release --locked --target "$(cat /tmp/rust-target)" --bin valfisk && \ - mv "./target/$(cat /tmp/rust-target)/release/valfisk" ./valfisk - -FROM gcr.io/distroless/static:latest -COPY --from=builder /build/valfisk /valfisk - -USER nonroot -CMD ["/valfisk"] diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..39396d1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,139 @@ +{ + "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1700029366, + "narHash": "sha256-0URFgoMK5M+xs2hHEGRJN/04Qy/nXrDgftZ7KTx0kA8=", + "owner": "nix-community", + "repo": "fenix", + "rev": "092bd452904e749efa39907aa4a20a42678ac31e", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-root": { + "locked": { + "lastModified": 1692742795, + "narHash": "sha256-f+Y0YhVCIJ06LemO+3Xx00lIcqQxSKJHXT/yk1RTKxw=", + "owner": "srid", + "repo": "flake-root", + "rev": "d9a70d9c7a5fd7f3258ccf48da9335e9b47c3937", + "type": "github" + }, + "original": { + "owner": "srid", + "repo": "flake-root", + "type": "github" + } + }, + "naersk": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1698420672, + "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", + "owner": "nix-community", + "repo": "naersk", + "rev": "aeb58d5e8faead8980a807c840232697982d47b9", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1699781429, + "narHash": "sha256-UYefjidASiLORAjIvVsUHG6WBtRhM67kTjEY4XfZOFs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e44462d6021bfe23dfb24b775cc7c390844f773d", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1698882062, + "narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "8c9fa2545007b49a5db5f650ae91f227672c3877", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "proc-flake": { + "locked": { + "lastModified": 1692742849, + "narHash": "sha256-Nv8SOX+O6twFfPnA9BfubbPLZpqc+UeK6JvIWnWkdb0=", + "owner": "srid", + "repo": "proc-flake", + "rev": "25291b6e3074ad5dd573c1cb7d96110a9591e10f", + "type": "github" + }, + "original": { + "owner": "srid", + "repo": "proc-flake", + "type": "github" + } + }, + "root": { + "inputs": { + "fenix": "fenix", + "flake-root": "flake-root", + "naersk": "naersk", + "nixpkgs": "nixpkgs", + "parts": "parts", + "proc-flake": "proc-flake" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1699996283, + "narHash": "sha256-oj9l5vjhZTUGp8J+6bRfzMIRGsMZvdRQ+hBc6ksZtRU=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "c1e65aa58866cb80849a8d9d1705b537be62db2f", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7278a66 --- /dev/null +++ b/flake.nix @@ -0,0 +1,31 @@ +{ + description = "Next generation Ryanland Discord bot, written in Rust"; + + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + naersk.url = "github:nix-community/naersk"; + naersk.inputs.nixpkgs.follows = "nixpkgs"; + fenix.url = "github:nix-community/fenix"; + fenix.inputs.nixpkgs.follows = "nixpkgs"; + parts.url = "github:hercules-ci/flake-parts"; + parts.inputs.nixpkgs-lib.follows = "nixpkgs"; + proc-flake.url = "github:srid/proc-flake"; + flake-root.url = "github:srid/flake-root"; + }; + + outputs = {parts, ...} @ inputs: + parts.lib.mkFlake {inherit inputs;} { + imports = [ + inputs.proc-flake.flakeModule + inputs.flake-root.flakeModule + ./nix + ]; + + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + }; +} diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000..02c9bce --- /dev/null +++ b/nix/default.nix @@ -0,0 +1,7 @@ +{ + imports = [ + ./dev.nix + ./docker.nix + ./packages.nix + ]; +} diff --git a/nix/derivation.nix b/nix/derivation.nix new file mode 100644 index 0000000..710ce0a --- /dev/null +++ b/nix/derivation.nix @@ -0,0 +1,28 @@ +{ + naersk, + stdenv, + lib, + CoreFoundation, + Security, + SystemConfiguration, + optimizeSize ? false, +}: +naersk.buildPackage { + src = lib.cleanSource ./..; + + nativeBuildInputs = lib.optionals stdenv.hostPlatform.isDarwin [ + CoreFoundation + Security + SystemConfiguration + ]; + + RUSTFLAGS = lib.optionalString optimizeSize " -C codegen-units=1 -C strip=symbols -C opt-level=z"; + + meta = with lib; { + mainProgram = "valfisk"; + description = "Next generation Ryanland Discord bot, written in Rust"; + homepage = "https://github.com/ryanccn/valfisk"; + maintainers = with maintainers; [getchoo ryanccn]; + licenses = licenses.agpl3Only; + }; +} diff --git a/nix/dev.nix b/nix/dev.nix new file mode 100644 index 0000000..5eb2e40 --- /dev/null +++ b/nix/dev.nix @@ -0,0 +1,35 @@ +{ + perSystem = { + lib, + pkgs, + config, + ... + }: { + /* + You can run `daemons` in the devShell to launch these; + `REDIS_URL` should be set to `redis://127.0.0.1` + */ + proc.groups.daemons.processes = { + redis.command = lib.getExe' pkgs.redis "redis-server"; + }; + + devShells = { + default = pkgs.mkShell { + packages = with pkgs; [ + config.formatter + config.proc.groups.daemons.package + + cargo + rustc + clippy + rustfmt + rust-analyzer + ]; + + RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; + }; + }; + + formatter = pkgs.alejandra; + }; +} diff --git a/nix/docker.nix b/nix/docker.nix new file mode 100644 index 0000000..9cbf426 --- /dev/null +++ b/nix/docker.nix @@ -0,0 +1,78 @@ +{inputs, ...}: { + perSystem = { + lib, + pkgs, + system, + config, + inputs', + ... + }: let + crossPkgsFor = lib.fix (finalAttrs: { + "x86_64-linux" = { + "x86_64" = pkgs.pkgsStatic; + "aarch64" = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; + }; + + "aarch64-linux" = { + "x86_64" = pkgs.pkgsCross.musl64; + "aarch64" = pkgs.pkgsStatic; + }; + + "x86_64-darwin" = { + "x86_64" = pkgs.pkgsCross.musl64; + "aarch64" = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; + }; + + "aarch64-darwin" = finalAttrs."x86_64-darwin"; + }); + + valfiskFor = arch: let + target = "${arch}-unknown-linux-musl"; + target' = builtins.replaceStrings ["-"] ["_"] target; + targetUpper = lib.toUpper target'; + + toolchain = with inputs'.fenix.packages; + combine [ + minimal.cargo + minimal.rustc + targets.${target}.latest.rust-std + ]; + + naersk' = inputs.naersk.lib.${system}.override { + cargo = toolchain; + rustc = toolchain; + }; + + valfisk = config.packages.valfisk.override { + naersk = naersk'; + optimizeSize = true; + }; + + inherit (crossPkgsFor.${system}.${arch}.stdenv) cc; + in + lib.getExe ( + valfisk.overrideAttrs (_: + lib.fix (finalAttrs: { + CARGO_BUILD_TARGET = target; + "CC_${target'}" = "${cc}/bin/${cc.targetPrefix}cc"; + "CARGO_TARGET_${targetUpper}_RUSTFLAGS" = "-C target-feature=+crt-static"; + "CARGO_TARGET_${targetUpper}_LINKER" = finalAttrs."CC_${target'}"; + })) + ); + + containerFor = arch: + pkgs.dockerTools.buildImage { + name = "valfisk"; + tag = "latest-${arch}"; + copyToRoot = [pkgs.dockerTools.caCertificates]; + config.Cmd = [(valfiskFor arch)]; + + architecture = crossPkgsFor.${system}.${arch}.go.GOARCH; + }; + in { + legacyPackages = { + container-x86_64 = containerFor "x86_64"; + container-aarch64 = containerFor "aarch64"; + }; + }; +} diff --git a/nix/packages.nix b/nix/packages.nix new file mode 100644 index 0000000..d7e5068 --- /dev/null +++ b/nix/packages.nix @@ -0,0 +1,24 @@ +{inputs, ...}: { + perSystem = { + lib, + pkgs, + system, + config, + ... + }: { + packages = { + valfisk = pkgs.callPackage ./derivation.nix { + naersk = inputs.naersk.lib.${system}; + + inherit + (pkgs.darwin.apple_sdk.frameworks) + CoreFoundation + Security + SystemConfiguration + ; + }; + + default = config.packages.valfisk; + }; + }; +}