diff --git a/.github/workflows/create-empty-local-db-manifest.yaml b/.github/workflows/create-empty-local-db-manifest.yaml index 8bb6026..6cbc27f 100644 --- a/.github/workflows/create-empty-local-db-manifest.yaml +++ b/.github/workflows/create-empty-local-db-manifest.yaml @@ -3,9 +3,6 @@ name: Create Empty Local DB Manifest env: SPACES_ACCESS_KEY: ${{ secrets.SPACES_ACCESS_KEY }} SPACES_SECRET_KEY: ${{ secrets.SPACES_SECRET_KEY }} - SPACES_REGION: ${{ secrets.SPACES_REGION }} - SPACES_BUCKET: ${{ secrets.SPACES_BUCKET }} - SPACES_ENDPOINT: ${{ secrets.SPACES_ENDPOINT }} on: workflow_dispatch: @@ -30,12 +27,31 @@ jobs: keep-env-derivations = true keep-outputs = true + - name: Load encrypted runtime config + shell: bash + run: | + set -euo pipefail + mkdir -p ~/.ssh + printf '%s\n' "${{ secrets.SSH_KEY }}" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + nix shell nixpkgs#rage -c sh -c 'rage -d -i ~/.ssh/id_ed25519 config/runtime.env.age > /tmp/local-db-runtime.env' + set -a + # shellcheck disable=SC1091 + source /tmp/local-db-runtime.env + set +a + { + echo "DUMP_BASE_URL=$DUMP_BASE_URL" + echo "SPACES_REGION=$SPACES_REGION" + echo "SPACES_BUCKET=$SPACES_BUCKET" + echo "SPACES_ENDPOINT=$SPACES_ENDPOINT" + } >> "$GITHUB_ENV" + - name: Create and upload empty manifest id: create_manifest shell: bash run: | set -euo pipefail - manifest_url="$(nix run .#local-db-create-empty-manifest)" + manifest_url="$(nix run .#local-db-create-empty-manifest | tail -n 1)" echo "manifest_url=$manifest_url" >>"$GITHUB_OUTPUT" - name: Summarize manifest URL diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index f060ba1..da6f436 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -3,6 +3,16 @@ name: Deploy on: workflow_dispatch: inputs: + target_ref: + description: Git ref to deploy from + required: true + default: main + type: string + allow_non_main: + description: Allow deployment from a non-main ref + required: true + default: false + type: boolean settings_url: description: Settings YAML URL to deploy required: true @@ -18,22 +28,31 @@ permissions: env: HYPER_RPC_API_TOKEN: ${{ secrets.HYPER_RPC_API_TOKEN }} - DUMP_BASE_URL: ${{ secrets.DUMP_BASE_URL }} + PUBLIC_WALLETCONNECT_PROJECT_ID: "00000000000000000000000000000000" SPACES_ACCESS_KEY: ${{ secrets.SPACES_ACCESS_KEY }} SPACES_SECRET_KEY: ${{ secrets.SPACES_SECRET_KEY }} - SPACES_REGION: ${{ secrets.SPACES_REGION }} - SPACES_BUCKET: ${{ secrets.SPACES_BUCKET }} - SPACES_ENDPOINT: ${{ secrets.SPACES_ENDPOINT }} + TAILSCALE_HOSTNAME: local-db-remote + TARGET_REF: ${{ inputs.target_ref }} jobs: deploy: runs-on: ubuntu-latest steps: + - name: Validate target ref + shell: bash + run: | + set -euo pipefail + if [ "$TARGET_REF" != "main" ] && [ "${{ inputs.allow_non_main }}" != "true" ]; then + echo "Set allow_non_main=true to deploy from a non-main ref." >&2 + exit 1 + fi + - name: Free disk space uses: jlumbroso/free-disk-space@v1.3.1 - uses: actions/checkout@v4 with: + ref: ${{ env.TARGET_REF }} submodules: recursive fetch-depth: 0 @@ -54,17 +73,40 @@ jobs: printf '%s\n' "${{ secrets.SSH_KEY }}" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 + - name: Load encrypted runtime config + shell: bash + run: | + set -euo pipefail + nix shell nixpkgs#rage -c sh -c 'rage -d -i ~/.ssh/id_ed25519 config/runtime.env.age > /tmp/local-db-runtime.env' + set -a + # shellcheck disable=SC1091 + source /tmp/local-db-runtime.env + set +a + { + echo "DUMP_BASE_URL=$DUMP_BASE_URL" + echo "SPACES_REGION=$SPACES_REGION" + echo "SPACES_BUCKET=$SPACES_BUCKET" + echo "SPACES_ENDPOINT=$SPACES_ENDPOINT" + } >> "$GITHUB_ENV" + + - name: Connect runner to Tailscale + uses: tailscale/github-action@v4 + with: + oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} + oauth-secret: ${{ secrets.TS_OAUTH_SECRET }} + tags: tag:ci + - name: Prepare raindex submodule run: ./prep.sh - name: Build local CLI artifact run: nix run .#build-raindex-cli - - name: Resolve host IP + - name: Resolve host over Tailscale shell: bash run: | set -euo pipefail - host_ip="$(nix run .#resolve-ip)" + host_ip="$(tailscale ip -4 "$TAILSCALE_HOSTNAME")" echo "::add-mask::$host_ip" echo "HOST_IP=$host_ip" >> "$GITHUB_ENV" @@ -75,6 +117,8 @@ jobs: ssh-keyscan -H "$HOST_IP" >> ~/.ssh/known_hosts - name: Deploy NixOS configuration + env: + DEPLOY_HOST: ${{ env.HOST_IP }} run: nix run .#deploy-nixos - name: Upload runtime assets and restart service @@ -124,6 +168,7 @@ jobs: { echo "## Local DB Remote Deployment" echo + echo "- Target ref: $TARGET_REF" echo "- Host IP: $HOST_IP" echo "- Settings URL: ${{ inputs.settings_url }}" echo "- Timer: local-db-sync.timer" diff --git a/.github/workflows/provision-host.yaml b/.github/workflows/provision-host.yaml index 12afb45..fe5c073 100644 --- a/.github/workflows/provision-host.yaml +++ b/.github/workflows/provision-host.yaml @@ -3,6 +3,16 @@ name: Provision Host on: workflow_dispatch: inputs: + target_ref: + description: Git ref to provision from + required: true + default: main + type: string + allow_non_main: + description: Allow provisioning from a non-main ref + required: true + default: false + type: boolean bootstrap_nixos: description: Bootstrap NixOS onto the provisioned droplet required: true @@ -22,13 +32,14 @@ jobs: runs-on: ubuntu-latest env: TF_VAR_do_token: ${{ secrets.DO_TOKEN }} + TARGET_REF: ${{ inputs.target_ref }} steps: - - name: Require main branch + - name: Validate target ref shell: bash run: | set -euo pipefail - if [ "${GITHUB_REF_NAME}" != "main" ]; then - echo "This workflow must be run from the main branch." >&2 + if [ "$TARGET_REF" != "main" ] && [ "${{ inputs.allow_non_main }}" != "true" ]; then + echo "Set allow_non_main=true to provision from a non-main ref." >&2 exit 1 fi @@ -37,7 +48,7 @@ jobs: - uses: actions/checkout@v4 with: - ref: main + ref: ${{ env.TARGET_REF }} submodules: recursive fetch-depth: 0 @@ -76,7 +87,7 @@ jobs: git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add infra/terraform.tfstate.age infra/terraform.tfvars.age git commit -m "chore: update local-db remote terraform state" - git push origin HEAD:main + git push origin HEAD:"$TARGET_REF" - name: Resolve host IP id: resolve_ip @@ -100,6 +111,7 @@ jobs: { echo "## Host Provisioning" echo + echo "- Target ref: $TARGET_REF" echo "- Host IP: ${{ steps.resolve_ip.outputs.host_ip }}" echo "- Bootstrap NixOS: ${{ inputs.bootstrap_nixos }}" echo "- Terraform apply ran successfully" diff --git a/config/runtime.env.age b/config/runtime.env.age new file mode 100644 index 0000000..dd29cb9 Binary files /dev/null and b/config/runtime.env.age differ diff --git a/config/runtime.env.example b/config/runtime.env.example new file mode 100644 index 0000000..f7dd0af --- /dev/null +++ b/config/runtime.env.example @@ -0,0 +1,4 @@ +DUMP_BASE_URL= +SPACES_REGION= +SPACES_BUCKET= +SPACES_ENDPOINT= diff --git a/config/secrets.nix b/config/secrets.nix new file mode 100644 index 0000000..f9a43a4 --- /dev/null +++ b/config/secrets.nix @@ -0,0 +1,3 @@ +{ + "runtime.env.age".publicKeys = (import ../keys.nix).roles.infra; +} diff --git a/deploy.nix b/deploy.nix index 6a2eb9a..f450f16 100644 --- a/deploy.nix +++ b/deploy.nix @@ -22,8 +22,13 @@ in { infraPkgs.buildInputs ++ [ deploy-rs.packages.${localSystem}.deploy-rs ]; deployPreamble = '' - ${infraPkgs.resolveIp} - export DEPLOY_HOST="$host_ip" + ${infraPkgs.parseIdentity} + if [ -n "''${DEPLOY_HOST:-}" ]; then + host_ip="$DEPLOY_HOST" + else + ${infraPkgs.resolveIp} + export DEPLOY_HOST="$host_ip" + fi export NIX_SSHOPTS="-i $identity" ssh_flag="--ssh-opts=-i $identity" ''; diff --git a/flake.lock b/flake.lock index f7f4e4f..accdccc 100644 --- a/flake.lock +++ b/flake.lock @@ -412,6 +412,22 @@ "type": "github" } }, + "nixpkgs-tailscale": { + "locked": { + "lastModified": 1776329215, + "narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b86751bc4085f48661017fa226dee99fab6c651b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs_2": { "locked": { "lastModified": 1761672384, @@ -544,6 +560,7 @@ "rainix", "nixpkgs" ], + "nixpkgs-tailscale": "nixpkgs-tailscale", "ragenix": "ragenix", "rainix": "rainix" } diff --git a/flake.nix b/flake.nix index 1d61907..d151cc4 100644 --- a/flake.nix +++ b/flake.nix @@ -5,6 +5,7 @@ rainix.url = "github:rainlanguage/rainix"; flake-utils.url = "github:numtide/flake-utils"; nixpkgs.follows = "rainix/nixpkgs"; + nixpkgs-tailscale.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; ragenix.url = "github:yaxitech/ragenix"; deploy-rs.url = "github:serokell/deploy-rs"; @@ -16,32 +17,21 @@ nixos-anywhere.inputs.nixpkgs.follows = "rainix/nixpkgs"; }; - outputs = { - self, - flake-utils, - rainix, - nixpkgs, - ragenix, - deploy-rs, - disko, - nixos-anywhere, - ... - }: - let - deploySystem = "x86_64-linux"; + outputs = { self, flake-utils, rainix, nixpkgs, nixpkgs-tailscale, ragenix + , deploy-rs, disko, nixos-anywhere, ... }: + let deploySystem = "x86_64-linux"; in { nixosConfigurations.local-db-remote = - rainix.inputs.nixpkgs.lib.nixosSystem { + let tailscalePkgs = import nixpkgs-tailscale { system = deploySystem; }; + in rainix.inputs.nixpkgs.lib.nixosSystem { system = deploySystem; - specialArgs = { inherit self; }; - modules = [ - disko.nixosModules.disko - ./os.nix - ]; + specialArgs = { inherit self tailscalePkgs; }; + modules = [ disko.nixosModules.disko ./os.nix ]; }; deploy = (import ./deploy.nix { inherit deploy-rs self; }).config; - checks.${deploySystem} = deploy-rs.lib.${deploySystem}.deployChecks self.deploy; + checks.${deploySystem} = + deploy-rs.lib.${deploySystem}.deployChecks self.deploy; } // flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { @@ -54,9 +44,8 @@ rainixPkgs = rainix.packages.${system}; addBuildInputs = shell: extraInputs: - shell.overrideAttrs (old: { - buildInputs = (old.buildInputs or [ ]) ++ extraInputs; - }); + shell.overrideAttrs + (old: { buildInputs = (old.buildInputs or [ ]) ++ extraInputs; }); repoRootSetup = '' repo_root="''${LOCAL_DB_REPO_ROOT:-$(pwd -P)}" @@ -104,6 +93,82 @@ exit 1 fi } + + resolve_manifest_publish_target() { + local urls first_url + + urls="$("$cli_bin" local-db manifest-urls --settings-yaml "$settings_yaml")" + first_url="" + + while IFS= read -r url; do + if [ -n "$url" ]; then + first_url="$url" + break + fi + done <<<"$urls" + + if [ -z "$first_url" ]; then + echo "❌ Expected at least one local-db remote manifest URL" + exit 1 + fi + + manifest_url="$first_url" + manifest_dir_url="''${manifest_url%/*}" + + if [ "$manifest_dir_url" = "$manifest_url" ]; then + echo "❌ Manifest URL does not contain a publish directory: $manifest_url" + exit 1 + fi + } + + url_host() { + local without_scheme="$1" + without_scheme="''${without_scheme#http://}" + without_scheme="''${without_scheme#https://}" + printf '%s\n' "''${without_scheme%%/*}" + } + + url_path() { + local without_scheme="$1" + without_scheme="''${without_scheme#http://}" + without_scheme="''${without_scheme#https://}" + + case "$without_scheme" in + */*) printf '/%s\n' "''${without_scheme#*/}" ;; + *) printf '\n' ;; + esac + } + + object_key_from_url() { + local url="$1" + local host path endpoint_host + + host="$(url_host "$url")" + path="$(url_path "$url")" + endpoint_host="$(url_host "$SPACES_ENDPOINT")" + + case "$host" in + "$SPACES_BUCKET.$endpoint_host") + printf '%s\n' "''${path#/}" + ;; + "$endpoint_host") + case "$path" in + "/$SPACES_BUCKET/"*) + printf '%s\n' "''${path#/"$SPACES_BUCKET"/}" + ;; + *) + echo "Manifest URL path is not inside bucket $SPACES_BUCKET: $url" >&2 + exit 1 + ;; + esac + ;; + *) + echo "Manifest URL host does not match bucket endpoint: $url" >&2 + exit 1 + ;; + esac + } + ''; buildRaindexCliCommand = pkgs.writeShellApplication { @@ -126,6 +191,11 @@ export LIBRARY_PATH="${pkgs.gmp}/lib''${LIBRARY_PATH:+:$LIBRARY_PATH}" export PKG_CONFIG_PATH="${pkgs.gmp.dev}/lib/pkgconfig''${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" export RUSTFLAGS="-L native=${pkgs.gmp}/lib ''${RUSTFLAGS:-}" + export OPENSSL_DIR="${pkgs.openssl.dev}" + export OPENSSL_LIB_DIR="${pkgs.openssl.out}/lib" + export OPENSSL_INCLUDE_DIR="${pkgs.openssl.dev}/include" + COMMIT_SHA="$(git -C "$raindex_root" rev-parse HEAD)" + export COMMIT_SHA target_triple="$(rustc -vV | sed -n 's/^host: //p')" @@ -138,11 +208,11 @@ ;; esac - binary_source="$raindex_root/target/$target_triple/release/rain_orderbook_cli" + binary_source="$raindex_root/target/$target_triple/release/raindex_cli" binary_output="$repo_root/rain-orderbook-cli" echo "Building local raindex CLI artifact..." - cargo build --release --manifest-path "$raindex_manifest" -p rain_orderbook_cli --target "$target_triple" + cargo build --release --manifest-path "$raindex_manifest" -p raindex_cli --target "$target_triple" cp "$binary_source" "$binary_output" chmod 755 "$binary_output" @@ -154,10 +224,7 @@ localDbSyncCommand = pkgs.writeShellApplication { name = "local-db-sync"; - runtimeInputs = with pkgs; [ - coreutils - curl - ]; + runtimeInputs = with pkgs; [ coreutils curl ]; text = '' set -euo pipefail ${raindexSetup} @@ -168,7 +235,6 @@ require_var SETTINGS_YAML_URL require_var HYPER_RPC_API_TOKEN - require_var DUMP_BASE_URL cli_bin="$repo_root/rain-orderbook-cli" if [ ! -x "$cli_bin" ]; then @@ -181,12 +247,13 @@ echo "🌐 Fetching settings YAML from $SETTINGS_YAML_URL" settings_yaml="$(curl -fsSL "$SETTINGS_YAML_URL")" + resolve_manifest_publish_target echo "🚀 Running local-db sync via $cli_bin" "$cli_bin" local-db sync \ --settings-yaml "$settings_yaml" \ --api-token "$HYPER_RPC_API_TOKEN" \ - --release-base-url "$DUMP_BASE_URL" \ + --release-base-url "$manifest_dir_url" \ --out-root "$out_root" \ --debug-status ''; @@ -194,11 +261,7 @@ localDbUploadCommand = pkgs.writeShellApplication { name = "local-db-upload"; - runtimeInputs = with pkgs; [ - awscli2 - coreutils - findutils - ]; + runtimeInputs = with pkgs; [ awscli2 coreutils curl findutils ]; text = '' set -euo pipefail ${repoRootSetup} @@ -207,6 +270,7 @@ load_env_file + require_var SETTINGS_YAML_URL require_var SPACES_ACCESS_KEY require_var SPACES_SECRET_KEY require_var SPACES_REGION @@ -218,34 +282,56 @@ export AWS_DEFAULT_REGION="$SPACES_REGION" local_dir="$repo_root/local-db" + cli_bin="$repo_root/rain-orderbook-cli" + + if [ ! -x "$cli_bin" ]; then + echo "❌ Local CLI artifact missing at $cli_bin" + echo " Run: nix run .#build-raindex-cli" + exit 1 + fi if [ ! -d "$local_dir" ]; then echo "❌ Local DB directory not found at $local_dir" exit 1 fi - echo "🚀 Uploading dump files and manifest from $local_dir to Spaces bucket: $SPACES_BUCKET" - echo " Using endpoint: $SPACES_ENDPOINT" - echo + echo "🌐 Fetching settings YAML from $SETTINGS_YAML_URL" + settings_yaml="$(curl -fsSL "$SETTINGS_YAML_URL")" + resolve_manifest_publish_target + manifest_object_key="$(object_key_from_url "$manifest_url")" + publish_prefix_key="''${manifest_object_key%/*}" - if [ -f "$local_dir/manifest.yaml" ]; then - echo "📄 Uploading manifest.yaml..." - aws s3 cp "$local_dir/manifest.yaml" "s3://$SPACES_BUCKET/manifest.yaml" \ - --endpoint-url "$SPACES_ENDPOINT" \ - --acl public-read \ - --content-type "text/yaml" + if [ "$publish_prefix_key" = "$manifest_object_key" ]; then + publish_prefix_key="" fi - echo "🗂️ Uploading SQL dump files (flattened to bucket root)..." + echo "🚀 Uploading dump files and manifest from $local_dir to Spaces bucket: $SPACES_BUCKET" + echo " Manifest URL: $manifest_url" + echo + + echo "🗂️ Uploading SQL dump files..." find "$local_dir" -type f -iname "*-0x*.sql.gz" | while IFS= read -r file; do filename="$(basename "$file")" + if [ -n "$publish_prefix_key" ]; then + object_key="$publish_prefix_key/$filename" + else + object_key="$filename" + fi echo "→ Uploading: $filename" - aws s3 cp "$file" "s3://$SPACES_BUCKET/$filename" \ + aws s3 cp "$file" "s3://$SPACES_BUCKET/$object_key" \ --endpoint-url "$SPACES_ENDPOINT" \ --acl public-read \ --content-type "application/gzip" done + if [ -f "$local_dir/manifest.yaml" ]; then + echo "📄 Uploading manifest.yaml to $manifest_url..." + aws s3 cp "$local_dir/manifest.yaml" "s3://$SPACES_BUCKET/$manifest_object_key" \ + --endpoint-url "$SPACES_ENDPOINT" \ + --acl public-read \ + --content-type "text/yaml" + fi + echo echo "✅ Upload complete!" ''; @@ -253,11 +339,7 @@ localDbCreateEmptyManifestCommand = pkgs.writeShellApplication { name = "local-db-create-empty-manifest"; - runtimeInputs = with pkgs; [ - awscli2 - coreutils - gnused - ]; + runtimeInputs = with pkgs; [ awscli2 coreutils gnused ]; text = '' set -euo pipefail ${raindexSetup} @@ -332,28 +414,24 @@ localDbRemoteRunner = pkgs.writeShellApplication { name = "local-db-remote-run"; - runtimeInputs = with pkgs; [ - awscli2 - coreutils - curl - findutils - ]; - text = builtins.readFile ./nixos/local-db-remote-run.sh; + runtimeInputs = with pkgs; [ awscli2 coreutils curl findutils ]; + text = '' + ${builtins.readFile ./nixos/local-db-remote-run.sh} + ''; }; - infraPkgs = import ./infra { - inherit pkgs ragenix rainix system; - }; + infraPkgs = import ./infra { inherit pkgs ragenix rainix system; }; - deployPkgs = (import ./deploy.nix { inherit deploy-rs self; }).wrappers { - inherit pkgs infraPkgs; - localSystem = system; - }; + deployPkgs = + (import ./deploy.nix { inherit deploy-rs self; }).wrappers { + inherit pkgs infraPkgs; + localSystem = system; + }; bootstrapNixos = rainix.mkTask.${system} { name = "bootstrap-nixos"; - additionalBuildInputs = - infraPkgs.buildInputs ++ [ nixos-anywhere.packages.${system}.default ]; + additionalBuildInputs = infraPkgs.buildInputs + ++ [ nixos-anywhere.packages.${system}.default ]; body = '' ${infraPkgs.resolveIp} ssh_opts="-o StrictHostKeyChecking=no -o ConnectTimeout=5 -i $identity" diff --git a/infra/terraform.tfstate.age b/infra/terraform.tfstate.age new file mode 100644 index 0000000..89726fc Binary files /dev/null and b/infra/terraform.tfstate.age differ diff --git a/infra/terraform.tfvars.age b/infra/terraform.tfvars.age new file mode 100644 index 0000000..04440ca Binary files /dev/null and b/infra/terraform.tfvars.age differ diff --git a/infra/terraform.tfvars.example b/infra/terraform.tfvars.example index 841ba90..0ee92e4 100644 --- a/infra/terraform.tfvars.example +++ b/infra/terraform.tfvars.example @@ -4,6 +4,11 @@ # `do_token` is intentionally omitted from this example so CI can supply it # through `TF_VAR_do_token` without being overridden by a checked-in file. # +# Optional overrides (current deployment defaults shown): +# ssh_key_name = "github_do" +# region = "lon1" +# droplet_size = "s-2vcpu-4gb" +# # Narrow this to trusted admin IPs once you have them. Leaving the default keeps # SSH reachable from anywhere while the rest of the droplet stays closed. # ssh_allowed_cidrs = ["203.0.113.10/32"] diff --git a/infra/variables.tf b/infra/variables.tf index 0559c6c..7fcfe93 100644 --- a/infra/variables.tf +++ b/infra/variables.tf @@ -7,7 +7,7 @@ variable "do_token" { variable "ssh_key_name" { description = "Name of the SSH key in DigitalOcean to add to the droplet" type = string - default = "st0x-op" + default = "github_do" } variable "region" { @@ -19,7 +19,7 @@ variable "region" { variable "droplet_size" { description = "Droplet size slug" type = string - default = "s-1vcpu-1gb" + default = "s-2vcpu-4gb" } variable "ssh_allowed_cidrs" { diff --git a/keys.nix b/keys.nix index 07833ab..bba7014 100644 --- a/keys.nix +++ b/keys.nix @@ -1,17 +1,13 @@ rec { keys = { - st0x-op = - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPZ56nOYbGDd0ZfbqxeY7AbvaQGQrHnlC80ccpRGpCoj"; - ci = - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTd2zKSwHgWegi290EiK5nYp1Wp4+x2fDYqFxbd0WLN"; + github_do = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIMYFMrh1LLoYCZhir9LA8FpbwKOpWrZ3gpZ5VvFT5Bu github_do"; arda = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAyTREGZCOzMsl7N9dp1saN/t7DCs7YesusVUKApMJ78"; - sid = - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPl3/6RlR6Rvz0ZRyZukzFtt4zUYNz5OVuTsajJl7V3n"; }; roles = with keys; { - infra = [ st0x-op ci sid ]; - ssh = [ st0x-op ci arda sid ]; + infra = [ github_do arda ]; + ssh = [ github_do arda ]; }; } diff --git a/lib/raindex b/lib/raindex index e686b4d..9b33a3c 160000 --- a/lib/raindex +++ b/lib/raindex @@ -1 +1 @@ -Subproject commit e686b4d353bfa271b1eef99c2569426eaa075b9b +Subproject commit 9b33a3c4eca44726b4602f1a2d4046982584ef93 diff --git a/nixos/local-db-remote-run.sh b/nixos/local-db-remote-run.sh index 1b62381..ee4f990 100644 --- a/nixos/local-db-remote-run.sh +++ b/nixos/local-db-remote-run.sh @@ -8,6 +8,81 @@ require_var() { fi } +resolve_manifest_publish_target() { + local urls first_url + + urls="$("$cli_bin" local-db manifest-urls --settings-yaml "$settings_yaml")" + first_url="" + + while IFS= read -r url; do + if [ -n "$url" ]; then + first_url="$url" + break + fi + done <<<"$urls" + + if [ -z "$first_url" ]; then + echo "Expected at least one local-db remote manifest URL" >&2 + exit 1 + fi + + manifest_url="$first_url" + manifest_dir_url="${manifest_url%/*}" + + if [ "$manifest_dir_url" = "$manifest_url" ]; then + echo "Manifest URL does not contain a publish directory: $manifest_url" >&2 + exit 1 + fi +} + +url_host() { + local without_scheme="$1" + without_scheme="${without_scheme#http://}" + without_scheme="${without_scheme#https://}" + printf '%s\n' "${without_scheme%%/*}" +} + +url_path() { + local without_scheme="$1" + without_scheme="${without_scheme#http://}" + without_scheme="${without_scheme#https://}" + + case "$without_scheme" in + */*) printf '/%s\n' "${without_scheme#*/}" ;; + *) printf '\n' ;; + esac +} + +object_key_from_url() { + local url="$1" + local host path endpoint_host + + host="$(url_host "$url")" + path="$(url_path "$url")" + endpoint_host="$(url_host "$SPACES_ENDPOINT")" + + case "$host" in + "$SPACES_BUCKET.$endpoint_host") + printf '%s\n' "${path#/}" + ;; + "$endpoint_host") + case "$path" in + "/$SPACES_BUCKET/"*) + printf '%s\n' "${path#/"$SPACES_BUCKET"/}" + ;; + *) + echo "Manifest URL path is not inside bucket $SPACES_BUCKET: $url" >&2 + exit 1 + ;; + esac + ;; + *) + echo "Manifest URL host does not match bucket endpoint: $url" >&2 + exit 1 + ;; + esac +} + cli_bin="/var/lib/local-db-remote/bin/rain-orderbook-cli" state_root="/var/lib/local-db-remote/work" out_root="$state_root/local-db" @@ -20,7 +95,6 @@ trap cleanup EXIT require_var SETTINGS_YAML_URL require_var HYPER_RPC_API_TOKEN -require_var DUMP_BASE_URL require_var SPACES_ACCESS_KEY require_var SPACES_SECRET_KEY require_var SPACES_REGION @@ -36,16 +110,26 @@ export AWS_ACCESS_KEY_ID="$SPACES_ACCESS_KEY" export AWS_SECRET_ACCESS_KEY="$SPACES_SECRET_KEY" export AWS_DEFAULT_REGION="$SPACES_REGION" +manifest_url="" +manifest_dir_url="" + mkdir -p "$state_root" echo "Fetching settings YAML from $SETTINGS_YAML_URL" settings_yaml="$(curl -fsSL "$SETTINGS_YAML_URL")" +resolve_manifest_publish_target +manifest_object_key="$(object_key_from_url "$manifest_url")" +publish_prefix_key="${manifest_object_key%/*}" + +if [ "$publish_prefix_key" = "$manifest_object_key" ]; then + publish_prefix_key="" +fi echo "Running local-db sync via $cli_bin" "$cli_bin" local-db sync \ --settings-yaml "$settings_yaml" \ --api-token "$HYPER_RPC_API_TOKEN" \ - --release-base-url "$DUMP_BASE_URL" \ + --release-base-url "$manifest_dir_url" \ --out-root "$out_root" \ --debug-status @@ -54,22 +138,27 @@ if [ ! -d "$out_root" ]; then exit 1 fi -if [ -f "$out_root/manifest.yaml" ]; then - echo "Uploading manifest.yaml" - aws s3 cp "$out_root/manifest.yaml" "s3://$SPACES_BUCKET/manifest.yaml" \ - --endpoint-url "$SPACES_ENDPOINT" \ - --acl public-read \ - --content-type "text/yaml" -fi - echo "Uploading SQL dump files" find "$out_root" -type f -iname "*-0x*.sql.gz" | while IFS= read -r file; do filename="$(basename "$file")" + if [ -n "$publish_prefix_key" ]; then + object_key="$publish_prefix_key/$filename" + else + object_key="$filename" + fi echo "Uploading $filename" - aws s3 cp "$file" "s3://$SPACES_BUCKET/$filename" \ + aws s3 cp "$file" "s3://$SPACES_BUCKET/$object_key" \ --endpoint-url "$SPACES_ENDPOINT" \ --acl public-read \ --content-type "application/gzip" done +if [ -f "$out_root/manifest.yaml" ]; then + echo "Uploading manifest.yaml to $manifest_url" + aws s3 cp "$out_root/manifest.yaml" "s3://$SPACES_BUCKET/$manifest_object_key" \ + --endpoint-url "$SPACES_ENDPOINT" \ + --acl public-read \ + --content-type "text/yaml" +fi + echo "Local DB remote sync completed" diff --git a/os.nix b/os.nix index bcc1054..a426775 100644 --- a/os.nix +++ b/os.nix @@ -1,4 +1,4 @@ -{ lib, modulesPath, pkgs, self, ... }: +{ lib, modulesPath, pkgs, self, tailscalePkgs, ... }: let inherit (import ./keys.nix) roles; @@ -18,16 +18,6 @@ in { networking.useDHCP = lib.mkForce false; services = { - cloud-init = { - enable = true; - network.enable = true; - settings = { - datasource_list = [ "ConfigDrive" "Digitalocean" ]; - datasource.ConfigDrive = { }; - datasource.Digitalocean = { }; - }; - }; - openssh = { enable = true; settings = { @@ -50,6 +40,12 @@ in { mode = "aggressive"; }; }; + + tailscale = { + enable = true; + openFirewall = true; + package = tailscalePkgs.tailscale; + }; }; users.users.root.openssh.authorizedKeys.keys = roles.ssh; @@ -57,6 +53,7 @@ in { networking.firewall = { enable = true; allowedTCPPorts = [ 22 ]; + trustedInterfaces = [ "tailscale0" ]; }; nix = { @@ -72,6 +69,16 @@ in { }; }; + programs.nix-ld = { + enable = true; + libraries = with pkgs; [ + openssl + sqlite + stdenv.cc.cc + zlib + ]; + }; + systemd.tmpfiles.rules = [ "d /etc/local-db-remote 0750 root root -" "d /var/lib/local-db-remote 0755 root root -" @@ -120,6 +127,7 @@ in { git htop jq + tailscalePkgs.tailscale zellij ];