|
| 1 | +--- |
| 2 | +name: pb-mapper-client-cli-deploy |
| 3 | +description: Deploy or upgrade `pb-mapper-client-cli` on a remote Linux host through SSH, install it as a managed `systemd` tunnel service, and verify the exposed API path end-to-end. Use when users ask to operationalize a client tunnel for a service key (download locally, upload remotely, run as service, and health-check). |
| 4 | +--- |
| 5 | + |
| 6 | +# Pb Mapper Client Cli Deploy |
| 7 | + |
| 8 | +## Overview |
| 9 | + |
| 10 | +Use this workflow to perform reproducible remote deployment of `pb-mapper-client-cli` with minimal manual edits. |
| 11 | +Execute from the operator machine that has SSH access to the target host. |
| 12 | + |
| 13 | +## Required Inputs |
| 14 | + |
| 15 | +Collect these values first: |
| 16 | + |
| 17 | +- `SSH_TARGET` (example: `ubuntu@sfapi.duckdns.org`) |
| 18 | +- `VERSION` without `v` prefix (example: `0.2.7`) |
| 19 | +- `TARGET_TRIPLE` (example: `x86_64-unknown-linux-musl`) |
| 20 | +- `PB_SERVER` (example: `sfapi.duckdns.org:7666`) |
| 21 | +- `SERVICE_KEY` (example: `sf-backend`) |
| 22 | +- `LOCAL_ADDR` to expose on remote host (example: `127.0.0.1:39080`) |
| 23 | +- `PUBLIC_CHECK_URL` for external validation (example: `https://sfapi.duckdns.org/api/semantic-search?q=test`) |
| 24 | +- optional `MSG_HEADER_KEY` (exactly 32 chars). Leave empty to use default key behavior. |
| 25 | + |
| 26 | +## Deployment Workflow |
| 27 | + |
| 28 | +### 1. Prepare local artifact |
| 29 | + |
| 30 | +Use deterministic file names: |
| 31 | + |
| 32 | +```bash |
| 33 | +export SSH_TARGET="ubuntu@sfapi.duckdns.org" |
| 34 | +export VERSION="0.2.7" |
| 35 | +export TARGET_TRIPLE="x86_64-unknown-linux-musl" |
| 36 | +export PB_SERVER="sfapi.duckdns.org:7666" |
| 37 | +export SERVICE_KEY="sf-backend" |
| 38 | +export LOCAL_ADDR="127.0.0.1:39080" |
| 39 | +export PUBLIC_CHECK_URL="https://sfapi.duckdns.org/api/semantic-search?q=test" |
| 40 | +# Optional. Keep empty to use default header key behavior. |
| 41 | +export MSG_HEADER_KEY="OMbmiQpCTVKqEMzVUzm8YQgmbQR1bbn0" |
| 42 | + |
| 43 | +export ASSET_FILE="pb-mapper-client-cli-v${VERSION}-${TARGET_TRIPLE}.tar.gz" |
| 44 | +export ASSET_URL="https://github.com/acking-you/pb-mapper/releases/download/v${VERSION}/${ASSET_FILE}" |
| 45 | + |
| 46 | +curl -fL "${ASSET_URL}" -o "/tmp/${ASSET_FILE}" |
| 47 | +test -s "/tmp/${ASSET_FILE}" |
| 48 | +``` |
| 49 | + |
| 50 | +### 2. Upload and install binary on remote host |
| 51 | + |
| 52 | +```bash |
| 53 | +scp "/tmp/${ASSET_FILE}" "${SSH_TARGET}:/tmp/${ASSET_FILE}" |
| 54 | + |
| 55 | +ssh "${SSH_TARGET}" "ASSET_FILE='${ASSET_FILE}' sudo bash -s" <<'REMOTE_INSTALL' |
| 56 | +set -euo pipefail |
| 57 | +TMP_DIR="/tmp/pb-mapper-client-cli-install" |
| 58 | +INSTALL_DIR="/opt/pb-mapper-client-cli/current" |
| 59 | +
|
| 60 | +rm -rf "${TMP_DIR}" |
| 61 | +mkdir -p "${TMP_DIR}" |
| 62 | +tar -xzf "/tmp/${ASSET_FILE}" -C "${TMP_DIR}" |
| 63 | +
|
| 64 | +BIN_PATH="$(find "${TMP_DIR}" -type f -name pb-mapper-client-cli | head -n 1)" |
| 65 | +if [ -z "${BIN_PATH}" ]; then |
| 66 | + echo "pb-mapper-client-cli binary not found in archive" >&2 |
| 67 | + exit 1 |
| 68 | +fi |
| 69 | +
|
| 70 | +sudo install -d "${INSTALL_DIR}" |
| 71 | +sudo install -m 0755 "${BIN_PATH}" "${INSTALL_DIR}/pb-mapper-client-cli" |
| 72 | +sudo rm -rf "${TMP_DIR}" "/tmp/${ASSET_FILE}" |
| 73 | +REMOTE_INSTALL |
| 74 | +``` |
| 75 | + |
| 76 | +### 3. Create or update systemd unit |
| 77 | + |
| 78 | +Use a templated service so multiple tunnel instances can coexist: |
| 79 | + |
| 80 | +```bash |
| 81 | +ssh "${SSH_TARGET}" "sudo bash -s" <<'REMOTE_SYSTEMD' |
| 82 | +set -euo pipefail |
| 83 | +SERVICE_TEMPLATE="/etc/systemd/system/pb-mapper-client-cli@.service" |
| 84 | +sudo tee "${SERVICE_TEMPLATE}" >/dev/null <<'UNIT' |
| 85 | +[Unit] |
| 86 | +Description=pb-mapper client tunnel (%i) |
| 87 | +After=network-online.target |
| 88 | +Wants=network-online.target |
| 89 | +
|
| 90 | +[Service] |
| 91 | +Type=simple |
| 92 | +EnvironmentFile=/etc/pb-mapper/client-cli/%i.env |
| 93 | +ExecStart=/opt/pb-mapper-client-cli/current/pb-mapper-client-cli \ |
| 94 | + --pb-mapper-server ${PB_SERVER} \ |
| 95 | + tcp-server \ |
| 96 | + --key ${SERVICE_KEY} \ |
| 97 | + --addr ${LOCAL_ADDR} |
| 98 | +Restart=always |
| 99 | +RestartSec=2 |
| 100 | +
|
| 101 | +[Install] |
| 102 | +WantedBy=multi-user.target |
| 103 | +UNIT |
| 104 | +
|
| 105 | +sudo install -d /etc/pb-mapper/client-cli |
| 106 | +REMOTE_SYSTEMD |
| 107 | +``` |
| 108 | + |
| 109 | +### 4. Write instance env file and start service |
| 110 | + |
| 111 | +`MSG_HEADER_KEY` must be omitted when empty; never write an empty value to env file. |
| 112 | + |
| 113 | +```bash |
| 114 | +export INSTANCE_NAME="${SERVICE_KEY}" |
| 115 | + |
| 116 | +ssh "${SSH_TARGET}" \ |
| 117 | + "INSTANCE_NAME='${INSTANCE_NAME}' PB_SERVER='${PB_SERVER}' SERVICE_KEY='${SERVICE_KEY}' LOCAL_ADDR='${LOCAL_ADDR}' MSG_HEADER_KEY='${MSG_HEADER_KEY}' sudo bash -s" <<'REMOTE_ENV' |
| 118 | +set -euo pipefail |
| 119 | +
|
| 120 | +ENV_FILE="/etc/pb-mapper/client-cli/${INSTANCE_NAME}.env" |
| 121 | +sudo tee "${ENV_FILE}" >/dev/null <<EOF |
| 122 | +PB_SERVER=${PB_SERVER} |
| 123 | +SERVICE_KEY=${SERVICE_KEY} |
| 124 | +LOCAL_ADDR=${LOCAL_ADDR} |
| 125 | +RUST_LOG=info |
| 126 | +PB_MAPPER_KEEP_ALIVE=ON |
| 127 | +EOF |
| 128 | +
|
| 129 | +if [ -n "${MSG_HEADER_KEY}" ]; then |
| 130 | + CLEAN_KEY="$(printf '%s' "${MSG_HEADER_KEY}" | tr -d '\r\n')" |
| 131 | + if [ "${#CLEAN_KEY}" -ne 32 ]; then |
| 132 | + echo "MSG_HEADER_KEY must be exactly 32 characters" >&2 |
| 133 | + exit 1 |
| 134 | + fi |
| 135 | + echo "MSG_HEADER_KEY=${CLEAN_KEY}" | sudo tee -a "${ENV_FILE}" >/dev/null |
| 136 | +fi |
| 137 | +
|
| 138 | +sudo systemctl daemon-reload |
| 139 | +sudo systemctl enable --now "pb-mapper-client-cli@${INSTANCE_NAME}.service" |
| 140 | +REMOTE_ENV |
| 141 | +``` |
| 142 | + |
| 143 | +### 5. Validate tunnel and external path |
| 144 | + |
| 145 | +Run all checks: |
| 146 | + |
| 147 | +```bash |
| 148 | +ssh "${SSH_TARGET}" "sudo systemctl --no-pager --full status pb-mapper-client-cli@${INSTANCE_NAME}.service" |
| 149 | +ssh "${SSH_TARGET}" "curl -fsS 'http://${LOCAL_ADDR}/api/semantic-search?q=test' | jq -e 'type==\"object\" or type==\"array\"' >/dev/null" |
| 150 | +curl -fsS "${PUBLIC_CHECK_URL}" | jq -e 'type==\"object\" or type==\"array\"' >/dev/null |
| 151 | +``` |
| 152 | + |
| 153 | +If `jq` is unavailable, print raw body and verify it is valid JSON manually. |
| 154 | + |
| 155 | +## Troubleshooting Checklist |
| 156 | + |
| 157 | +Use this quick triage when startup or forwarding fails: |
| 158 | + |
| 159 | +- `datalen not valid`: likely `MSG_HEADER_KEY` mismatch or hidden newline; verify both sides use the same 32-byte key. |
| 160 | +- Service restarts immediately: inspect logs with `journalctl -u pb-mapper-client-cli@<instance> -n 200 --no-pager`. |
| 161 | +- Remote port not listening: confirm `LOCAL_ADDR` host/port and no port conflict. |
| 162 | +- Public URL fails but localhost works: investigate reverse proxy (for example, Caddy route/TLS config). |
| 163 | + |
| 164 | +## Safe Update Procedure |
| 165 | + |
| 166 | +Upgrade in place without changing the unit: |
| 167 | + |
| 168 | +1. Download/upload new `vX.Y.Z` artifact. |
| 169 | +2. Re-run install step to overwrite `/opt/pb-mapper-client-cli/current/pb-mapper-client-cli`. |
| 170 | +3. Restart instance: `sudo systemctl restart pb-mapper-client-cli@<instance>.service`. |
| 171 | +4. Re-run validation checks. |
0 commit comments