From 8cfc7463c60944e4a5d4f40f9f016545542b7275 Mon Sep 17 00:00:00 2001 From: yukkop Date: Mon, 7 Sep 2026 10:59:36 +0000 Subject: [PATCH] ci!: deploy workflow --- .gitea/workflows/deploy-neuro.yaml | 62 ++++++++++++++++++ nixos/module/hectic/service/dify.nix | 94 ++++++++++++++++++++++++++++ nixos/system/neuro/neuro.nix | 8 +++ package/deploy/deploy.sh | 6 +- sus/neuro.yaml | 6 +- 5 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 .gitea/workflows/deploy-neuro.yaml create mode 100644 nixos/module/hectic/service/dify.nix diff --git a/.gitea/workflows/deploy-neuro.yaml b/.gitea/workflows/deploy-neuro.yaml new file mode 100644 index 00000000..d1b16dc7 --- /dev/null +++ b/.gitea/workflows/deploy-neuro.yaml @@ -0,0 +1,62 @@ +--- +# yamllint disable rule:line-length +name: deploy neuro + +on: # yamllint disable-line rule:truthy + workflow_dispatch: + +concurrency: + group: deploy-neuro + cancel-in-progress: false + +jobs: + deploy: + name: deploy neuro from performance node + if: ${{ gitea.ref == 'refs/heads/master' }} + runs-on: gross-nix-x86-perf + timeout-minutes: 30 + env: + NIX_CONFIG: | + extra-substituters = https://cache.nixos.org https://cache.hectic-lab.com/hectic + extra-trusted-public-keys = hectic:KMQsKow4SoA9K2vOJlOljmx7/Zpf91Yy+5qEtxDDCzA= + DEPLOY_REVISION: ${{ gitea.sha }} + steps: + - name: Checkout dispatched revision + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af6832 # v4.2.2 + with: + ref: ${{ gitea.sha }} + persist-credentials: false + + - name: Show runner and cache configuration + run: | + set -eu + uname -a + nix --version + nix config show substituters + nix config show trusted-public-keys + + - name: Deploy neuro + env: + NEURO_SSH_PRIVATE_KEY: ${{ secrets.NEURO_SSH_PRIVATE_KEY }} + NEURO_SSH_KNOWN_HOSTS: ${{ secrets.NEURO_SSH_KNOWN_HOSTS }} + run: | + set -eu + test -n "$NEURO_SSH_PRIVATE_KEY" + test -n "$NEURO_SSH_KNOWN_HOSTS" + ssh_home=$(mktemp -d) + trap 'rm -rf "$ssh_home"' EXIT + install -d -m 700 "$ssh_home/.ssh" + printf '%s\n' "$NEURO_SSH_PRIVATE_KEY" > "$ssh_home/.ssh/id_ed25519" + printf '%s\n' "$NEURO_SSH_KNOWN_HOSTS" > "$ssh_home/.ssh/known_hosts" + chmod 600 "$ssh_home/.ssh/id_ed25519" "$ssh_home/.ssh/known_hosts" + export HOME="$ssh_home" + export NIX_SSHOPTS="-o BatchMode=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$HOME/.ssh/known_hosts -o IdentitiesOnly=yes -i $HOME/.ssh/id_ed25519" + ssh -o BatchMode=yes -o ConnectTimeout=10 \ + -o StrictHostKeyChecking=yes \ + -o UserKnownHostsFile="$HOME/.ssh/known_hosts" \ + -o IdentitiesOnly=yes \ + -i "$HOME/.ssh/id_ed25519" \ + neuro true + # Deploy tool intentionally uses requested GitHub flake; revision matches checkout. + nix run --refresh "github:hectic-lab/util.nix/${DEPLOY_REVISION}#deploy" -- \ + push -- --flake '.#neuro|x86_64-linux' --target-host neuro --use-remote-sudo diff --git a/nixos/module/hectic/service/dify.nix b/nixos/module/hectic/service/dify.nix new file mode 100644 index 00000000..8090d8e7 --- /dev/null +++ b/nixos/module/hectic/service/dify.nix @@ -0,0 +1,94 @@ +{ ... }: { + lib, + config, + pkgs, + ... +}: let + cfg = config.hectic.services.dify; + + difySource = pkgs.fetchFromGitHub { + owner = "langgenius"; + repo = "dify"; + rev = "00e578606715a9da34488608edee8c68d4ef4893"; + hash = "sha256-kLxMdmt1FtOl39C9SS7ZGY6C78e3hPiVI5Q3EdbkMPU="; + }; + + composeOverride = pkgs.writeText "dify-compose.override.yaml" '' + services: + nginx: + ports: !override + - "127.0.0.1:${toString cfg.port}:80" + plugin_daemon: + ports: !override + - "127.0.0.1:${toString cfg.pluginPort}:5003" + ''; +in { + options.hectic.services.dify = { + enable = lib.mkEnableOption "Dify self-hosted AI platform"; + + dataDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/dify"; + description = "Persistent directory for Dify compose state and volumes."; + }; + + environmentFile = lib.mkOption { + type = lib.types.path; + description = '' + Environment file for Dify. Keep secrets here, including SECRET_KEY, + DB_PASSWORD, REDIS_PASSWORD, and plugin daemon credentials. + ''; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 80; + description = "Loopback HTTP port forwarded to Dify through SSH."; + }; + + pluginPort = lib.mkOption { + type = lib.types.port; + default = 5003; + description = "Loopback plugin daemon port forwarded through SSH when needed."; + }; + + composeProfiles = lib.mkOption { + type = lib.types.str; + default = "weaviate,postgresql,collaboration"; + description = "Dify Docker Compose profiles to start."; + }; + }; + + config = lib.mkIf cfg.enable { + virtualisation.docker.enable = true; + + systemd.services.dify = { + description = "Dify Docker Compose stack"; + wantedBy = [ "multi-user.target" ]; + wants = [ "docker.service" ]; + after = [ "docker.service" ]; + requires = [ "docker.service" ]; + restartIfChanged = true; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + WorkingDirectory = cfg.dataDir; + ExecStartPre = pkgs.writeShellScript "dify-prepare" '' + set -eu + install -d -m 0750 ${lib.escapeShellArg cfg.dataDir} + cp -R ${difySource}/docker/. ${lib.escapeShellArg cfg.dataDir}/ + install -m 0600 ${lib.escapeShellArg cfg.environmentFile} ${lib.escapeShellArg cfg.dataDir}/.env + ''; + ExecStart = "${pkgs.docker-compose}/bin/docker-compose --project-directory ${cfg.dataDir} --file ${cfg.dataDir}/docker-compose.yaml --file ${composeOverride} --env-file ${cfg.dataDir}/.env up --detach"; + ExecStop = "${pkgs.docker-compose}/bin/docker-compose --project-directory ${cfg.dataDir} --file ${cfg.dataDir}/docker-compose.yaml --file ${composeOverride} --env-file ${cfg.dataDir}/.env down"; + }; + environment = { + COMPOSE_PROFILES = cfg.composeProfiles; + }; + }; + + systemd.tmpfiles.rules = [ + "d ${cfg.dataDir} 0750 root root -" + ]; + }; +} diff --git a/nixos/system/neuro/neuro.nix b/nixos/system/neuro/neuro.nix index e94b0d72..3ca3c0d0 100644 --- a/nixos/system/neuro/neuro.nix +++ b/nixos/system/neuro/neuro.nix @@ -194,6 +194,13 @@ in { openFirewall = false; }; + hectic.services.dify = { + enable = true; + environmentFile = config.sops.secrets."dify/environment".path; + port = 8080; + pluginPort = 5003; + }; + networking = { networkmanager.enable = true; useDHCP = lib.mkDefault true; @@ -229,6 +236,7 @@ in { # group = "turnserver"; # mode = "0400"; #}; + secrets."dify/environment" = {}; }; boot.loader.systemd-boot.enable = true; diff --git a/package/deploy/deploy.sh b/package/deploy/deploy.sh index 4990a12c..5650058c 100644 --- a/package/deploy/deploy.sh +++ b/package/deploy/deploy.sh @@ -7,8 +7,12 @@ HECTIC_NAMESPACE="deploy" # ssh that not saves the host in ~/.ssh/know_hosts puressh() { + local known_hosts="${HECTIC_DEPLOY_KNOWN_HOSTS:-$HOME/.ssh/known_hosts}" + local identity_file="${HECTIC_DEPLOY_IDENTITY_FILE:-$HOME/.ssh/id_ed25519}" # shellcheck disable=SC2068 - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $@ + ssh -o BatchMode=yes -o StrictHostKeyChecking=yes \ + -o UserKnownHostsFile="$known_hosts" -o IdentitiesOnly=yes \ + -i "$identity_file" $@ } # echo | find_older_gen(gen) diff --git a/sus/neuro.yaml b/sus/neuro.yaml index ebf1e020..138161a5 100644 --- a/sus/neuro.yaml +++ b/sus/neuro.yaml @@ -2,6 +2,8 @@ init-postgresql: ENC[AES256_GCM,data:JUoZk/A3b3xCDWPKt08Uy7PjSmBd3BQqn+uymiBKTA7 matrix: secrets: ENC[AES256_GCM,data:vL5opJTCzQMQwhxGZm1a2K4lTFySYHAj92EW4JQSKpt3yWQAu3QHLHdt9YGOvb9/c/0APouoc+NeYWKNC5oYcih2H8z4wGfYPImn+bH7OCzr/fAGNxgOlG27cWRbkzkIpbNE7qtll0GPpEL+BsdAklzbZcgMp9s2LvQKYEovrWronBksuTI5sbOtZtoAY40yuYmnuH/Sjp+6az9KlX3fuCrb/HF6UUsPoRz98cYVPcpGiNImb64WwPGUkvDemDzKSLHB9ClUMLWuWJHumameMAKE236bz+Jqlrz5rVhbL9E1oadyw1JgQWlgeA3axKw3Ju+sThalwxQXiW3pZJPGFAmwOoNY6LiH4WBkXnfPlTY7m5f/+6FjFALUFTKHWr+iZ3S1ykUrwJGxCsK6ARBUH7U1CY6fU+fLRxjuyDogGpe7O+TcntxCKHqB/UpNjX1BDbpakOoa,iv:JR2pVrakbhDakRQDC2dfdtB1C2HneJcAtFuHClTw5gU=,tag:OYQYO22o6eKpyMrfZ/VmvA==,type:str] turn-secret: ENC[AES256_GCM,data:d2KxHlbeE1dehmbWy9KLKCT6UuC3bu7LaOeEQTIRTSk/etMpNL9gv2rHBmKQ/35en30PIjsAbQr3nMHLuI6uDA==,iv:Cjyb6LfdVbmlKqO16Cn7jSEy2j/7+xAz1y9UJjcpiuo=,tag:pX6RdGMxWLdP5QzqmS4tow==,type:str] +dify: + environment: ENC[AES256_GCM,data:MPK6v5qeNve6sZo/OVRlPJ52+m+vHd/L96BUUX18h7JO9ErJT5kAMbu86pp3WC3g8j1MOEOvt6ccciR2wmaerzABDR+2KANRKJeDii4BWKbBssFLwRMXKn8qPlDxenTE08UsieDEyW+iF+797+CniMGPpQuynSbhV5/7QWjT1fkOImbmqslNpo2x4QgQ0gLpZCAXHMpotDlrr25MgP9AHWBBjI42o2cFz8Rfz1gBb7opu6lhbmGwt+4VL8itxmlKxPgt5iyCrL9s9TR9Kyz1dPZQzrCWcqepD8IbjyeMi3F/RGEBKsll1NM=,iv:YvSK40NV95/xoEbVERDdT3LfY2mpvguvceKvxzcqBE4=,tag:UYGTojOoh0SYe2QmOx8Avw==,type:str] sops: age: - recipient: age1r25zdeqq8nac6dgca9en28r57ffyz9u9d8z5yc25gc8xqz747vaqmdtk0h @@ -22,7 +24,7 @@ sops: YWNZWGhKNnF6VUNsYnB6UlZFeis0dGsKVFEdSbZdnJhFrUTFrrXza43GcoCk/6m2 2qQyFPc/cQQ2xB7ygLfNDK4xKDkvEbKmmwAIttnDbX13qIWWxvblZw== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-02T20:48:15Z" - mac: ENC[AES256_GCM,data:PYuxQRG0VIq6XmsdiH6JeC9XPPE084/keH7j/Os9qCtBZRP2b4d8yUcIFPYXpP3G9uLS55YvAOWMw9zlxpG2QM/TNwRxJ/3kziaA9sB6uA3o1DDxfhwZFiRcB5sGPB5Sh8bHYMMdk/s3KcipJ7FdSYrNn+/dGguDGAlSyruEMME=,iv:MJlGblf/VHvAr3jGRVvt0Nvr2hHRK724PiZ0wqdBtDY=,tag:Pg5mbqLG/nlvuS7Yse9iFg==,type:str] + lastmodified: "2026-09-06T21:54:19Z" + mac: ENC[AES256_GCM,data:9Z7r0jp6hB54cO1/plMLx6Dmj3MDLTbnocQGv22g+HO/dF/rwxPXW+FOhamGjOTSyncAX6h9Pg2oIGVeFbKwP0dgoYloi5eq3XOsVdn2obNn7/1PNSGmqTqw1y1N8p29f/Iv+rRl9itofE974TvpCG2Jydz1HhQ8yx/YPDC5tTY=,iv:v1DDKxJX8R+vPCXO8jCxZcbgIRizxVUY3DPUmeaWKs0=,tag:vXYmW7kbXFYuHekGEzgmoA==,type:str] unencrypted_suffix: _unencrypted version: 3.10.2