|
| 1 | +# GitHub Actions Server Deployment |
| 2 | + |
| 3 | +## What This Automates |
| 4 | + |
| 5 | +Pushing to `main` triggers [.github/workflows/deploy-server.yml](/C:/Users/20562/Desktop/桌面/clipSync/.github/workflows/deploy-server.yml). The workflow: |
| 6 | + |
| 7 | +- Checks out the repo on `ubuntu-latest` |
| 8 | +- Sets up Go from `clipSync-server/go.mod` |
| 9 | +- Runs `go test ./... -v -count=1` in `clipSync-server` |
| 10 | +- Builds a Linux `amd64` server binary with `CGO_ENABLED=1` |
| 11 | +- Packages the binary and `clipSync-server/configs/config.yaml` into `clipsync-server-release-<git-sha>.tar.gz` |
| 12 | +- Uploads the release archive to the deployment host over SSH/SCP |
| 13 | +- Pipes [`scripts/deploy/server-release.sh`](/C:/Users/20562/Desktop/桌面/clipSync/scripts/deploy/server-release.sh) to the remote host and executes it with deployment environment variables |
| 14 | +- Verifies `http://<DEPLOY_HOST>:8081/api/v1/health` from GitHub Actions after the remote deployment finishes |
| 15 | + |
| 16 | +The current production examples are host `8.141.100.238` and live path `/opt/clipSync-server-src`, but the GitHub secrets are the source of truth. |
| 17 | + |
| 18 | +## Required GitHub Secrets |
| 19 | + |
| 20 | +Configure these repository secrets before enabling the workflow: |
| 21 | + |
| 22 | +| Secret | Example | Purpose | |
| 23 | +| --- | --- | --- | |
| 24 | +| `DEPLOY_HOST` | `8.141.100.238` | SSH target used by `scp`, `ssh`, and the final external health check | |
| 25 | +| `DEPLOY_USER` | `root` | SSH user on the deployment host | |
| 26 | +| `DEPLOY_SSH_KEY` | private key contents for the deploy user | Written to `~/.ssh/id_ed25519` inside the workflow | |
| 27 | +| `DEPLOY_PATH` | `/opt/clipSync-server-src` | Live server directory that receives `bin/`, `configs/`, and preserved `data/` | |
| 28 | +| `DEPLOY_SERVICE_NAME` | `clipsync.service` | systemd service restarted during deploy and rollback | |
| 29 | +| `DEPLOY_KNOWN_HOSTS` | output of `ssh-keyscan -H 8.141.100.238` | Host key entry used for strict SSH host verification | |
| 30 | + |
| 31 | +Notes: |
| 32 | + |
| 33 | +- `DEPLOY_SSH_KEY` must match the public key installed for `DEPLOY_USER` on the server. |
| 34 | +- `DEPLOY_KNOWN_HOSTS` is required because the workflow uses `StrictHostKeyChecking=yes`. |
| 35 | +- Keep `DEPLOY_PATH` and `DEPLOY_SERVICE_NAME` aligned with the actual server layout and systemd unit. |
| 36 | +- Optional: `DEPLOY_PUBLIC_HEALTH_URL` can override the final GitHub Actions health-check URL when the public endpoint differs from `http://<DEPLOY_HOST>:8081/api/v1/health`. |
| 37 | + |
| 38 | +## Server Requirements |
| 39 | + |
| 40 | +The workflow assumes the target server already has: |
| 41 | + |
| 42 | +- A Linux environment reachable from GitHub-hosted runners over SSH |
| 43 | +- A systemd service matching `DEPLOY_SERVICE_NAME` |
| 44 | +- `bash`, `tar`, `curl`, and `systemctl` available on the target host |
| 45 | +- A writable deployment directory such as `/opt/clipSync-server-src` |
| 46 | +- Permission for `DEPLOY_USER` to write under `DEPLOY_PATH` |
| 47 | +- Permission for `DEPLOY_USER` to restart `DEPLOY_SERVICE_NAME` |
| 48 | +- The service configured to run the deployed binary from `DEPLOY_PATH/bin/clipsync-server-linux` |
| 49 | +- The service configured so the server can find `configs/config.yaml` after startup |
| 50 | +- The server health endpoint available at `http://127.0.0.1:8081/api/v1/health` |
| 51 | + |
| 52 | +Because the workflow builds on GitHub Actions, the target server does not need a Go toolchain for deployments. |
| 53 | + |
| 54 | +The server process defaults to the relative path `configs/config.yaml`. That means the systemd unit must do one of the following: |
| 55 | + |
| 56 | +- Set `WorkingDirectory=DEPLOY_PATH` so the default relative config path resolves correctly. |
| 57 | +- Export `CLIPSYNC_CONFIG=DEPLOY_PATH/configs/config.yaml` so the binary can start from any working directory. |
| 58 | + |
| 59 | +`ssh` and `scp` are only required on the GitHub Actions runner, not on the deployment host. |
| 60 | + |
| 61 | +## First-Time Setup |
| 62 | + |
| 63 | +1. Confirm the repository version of `clipSync-server/configs/config.yaml` is safe for the deployment environment. |
| 64 | +2. Create the required GitHub repository secrets listed above. |
| 65 | +3. Install the deploy public key for `DEPLOY_USER` on the server. |
| 66 | +4. Capture the server host key for `DEPLOY_KNOWN_HOSTS`. |
| 67 | + |
| 68 | +```bash |
| 69 | +ssh-keyscan -H 8.141.100.238 |
| 70 | +``` |
| 71 | + |
| 72 | +5. Ensure the live directory exists and matches the expected layout. |
| 73 | + |
| 74 | +```bash |
| 75 | +sudo mkdir -p /opt/clipSync-server-src/bin /opt/clipSync-server-src/configs /opt/clipSync-server-src/data |
| 76 | +``` |
| 77 | + |
| 78 | +6. Ensure the systemd service points at the deployed binary and satisfies the config lookup requirement. |
| 79 | + |
| 80 | +Example expectations: |
| 81 | + |
| 82 | +- Binary: `/opt/clipSync-server-src/bin/clipsync-server-linux` |
| 83 | +- Config: `/opt/clipSync-server-src/configs/config.yaml` |
| 84 | +- Data directory: `/opt/clipSync-server-src/data` |
| 85 | +- Either `WorkingDirectory=/opt/clipSync-server-src` or `Environment=CLIPSYNC_CONFIG=/opt/clipSync-server-src/configs/config.yaml` |
| 86 | + |
| 87 | +7. Verify the service can start and answer its local health endpoint before relying on automation. |
| 88 | + |
| 89 | +```bash |
| 90 | +curl --fail http://127.0.0.1:8081/api/v1/health |
| 91 | +``` |
| 92 | + |
| 93 | +8. Merge or push a safe change to `main`, then watch the `Deploy Server` workflow in GitHub Actions. |
| 94 | + |
| 95 | +9. If `8081` is not directly reachable from GitHub-hosted runners, add `DEPLOY_PUBLIC_HEALTH_URL` and point it at the real public health endpoint exposed by your proxy or load balancer. |
| 96 | + |
| 97 | +## Deployment Behavior |
| 98 | + |
| 99 | +The remote deployment script is intentionally narrow and opinionated: |
| 100 | + |
| 101 | +- `data/` is preserved. The script creates `DEPLOY_PATH/data` if needed and never deletes or replaces it. |
| 102 | +- `configs/config.yaml` is overwritten from the repository on every deploy. |
| 103 | +- The live binary path is `DEPLOY_PATH/bin/clipsync-server-linux`. |
| 104 | +- The binary backup path is `DEPLOY_PATH/bin/clipsync-server-linux.prev`. |
| 105 | +- The config backup path is `DEPLOY_PATH/configs/config.yaml.prev`. |
| 106 | +- The uploaded release archive is stored remotely at `/tmp/clipsync-server-release-<git-sha>.tar.gz`. |
| 107 | +- The script extracts into a temporary staging directory under `/tmp/clipsync-release.XXXXXX`. |
| 108 | +- Archive contents are validated before extraction to reject absolute paths, path traversal, and unsupported entry types. |
| 109 | + |
| 110 | +Rollback behavior: |
| 111 | + |
| 112 | +- Before mutating the live deployment, the script backs up the current binary and config if they exist. |
| 113 | +- If an unexpected shell error happens after mutation starts, the script attempts to restore the backups through an `ERR` trap. |
| 114 | +- If `systemctl restart <DEPLOY_SERVICE_NAME>` fails, the script restores backups, restarts the service again, and waits for health recovery. |
| 115 | +- If the post-deploy local health check fails, the same rollback flow runs. |
| 116 | +- Rollback only restores files that actually had prior live versions. On a first deploy, there may be nothing to restore. |
| 117 | +- Rollback covers the deployed binary and `config.yaml` only. It does not roll back SQLite database state under `data/`. |
| 118 | +- Because the server runs embedded migrations on startup, schema changes must remain backward-compatible with the previous binary if you want rollback to be safe. |
| 119 | +- The remote archive is deleted only after a successful deployment. If deployment fails, the archive usually remains in `/tmp/` for inspection. |
| 120 | + |
| 121 | +Health validation happens twice: |
| 122 | + |
| 123 | +- On the server: `server-release.sh` checks `http://127.0.0.1:8081/api/v1/health` up to 10 times with 3-second sleeps. |
| 124 | +- In GitHub Actions: the workflow checks `DEPLOY_PUBLIC_HEALTH_URL` when that optional secret is set; otherwise it falls back to `http://<DEPLOY_HOST>:8081/api/v1/health`, up to 10 times with 5-second sleeps. |
| 125 | + |
| 126 | +## Troubleshooting |
| 127 | + |
| 128 | +### SSH Failures |
| 129 | + |
| 130 | +If the workflow fails during `Prepare SSH`, `Upload release archive`, or `Run remote deployment`: |
| 131 | + |
| 132 | +- Re-check `DEPLOY_HOST`, `DEPLOY_USER`, and `DEPLOY_SSH_KEY`. |
| 133 | +- Rebuild `DEPLOY_KNOWN_HOSTS` from the current server host key. |
| 134 | + |
| 135 | +```bash |
| 136 | +ssh-keyscan -H 8.141.100.238 |
| 137 | +``` |
| 138 | + |
| 139 | +- Confirm the private key in `DEPLOY_SSH_KEY` matches an authorized public key for `DEPLOY_USER`. |
| 140 | +- Confirm the server allows SSH from GitHub-hosted runners and is reachable on port `22`. |
| 141 | +- Test from a trusted machine with strict host checking enabled: |
| 142 | + |
| 143 | +```bash |
| 144 | +ssh -o BatchMode=yes -o StrictHostKeyChecking=yes root@8.141.100.238 |
| 145 | +``` |
| 146 | + |
| 147 | +- If authentication works locally but fails in Actions, re-check line breaks and full key contents in the GitHub secret. |
| 148 | + |
| 149 | +### Health-Check Failures |
| 150 | + |
| 151 | +There are two different health checks, so first identify which one failed: |
| 152 | + |
| 153 | +- Remote script failure means the service did not become healthy on `127.0.0.1:8081`. |
| 154 | +- Final workflow failure means the service may be healthy locally, but `http://<DEPLOY_HOST>:8081/api/v1/health` was not reachable or did not return `"status":"ok"` externally. |
| 155 | +- If `DEPLOY_PUBLIC_HEALTH_URL` is configured, final workflow failure instead refers to that public URL. |
| 156 | + |
| 157 | +Useful checks on the server: |
| 158 | + |
| 159 | +```bash |
| 160 | +systemctl status <DEPLOY_SERVICE_NAME> --no-pager |
| 161 | +journalctl -u <DEPLOY_SERVICE_NAME> -n 100 --no-pager |
| 162 | +curl --fail http://127.0.0.1:8081/api/v1/health |
| 163 | +ls -l /opt/clipSync-server-src/bin/clipsync-server-linux* |
| 164 | +ls -l /opt/clipSync-server-src/configs/config.yaml* |
| 165 | +``` |
| 166 | + |
| 167 | +What to verify: |
| 168 | + |
| 169 | +- The service name in `DEPLOY_SERVICE_NAME` is correct. |
| 170 | +- The deployed config in `/opt/clipSync-server-src/configs/config.yaml` contains production-safe values. |
| 171 | +- The service is actually starting the binary at `/opt/clipSync-server-src/bin/clipsync-server-linux`. |
| 172 | +- Port `8081` is listening and reachable from outside the host if the final GitHub Actions health check is using the default URL. |
| 173 | +- If `DEPLOY_PUBLIC_HEALTH_URL` is configured, verify that public endpoint and any proxy/load-balancer routing in front of it. |
| 174 | +- If rollback ran, check whether `.prev` files were restored and whether the service recovered to the previous version. |
| 175 | + |
| 176 | +## Self-Review Checklist |
| 177 | + |
| 178 | +Before treating this flow as ready: |
| 179 | + |
| 180 | +- Confirm the secrets in GitHub match the real server. |
| 181 | +- Confirm `clipSync-server/configs/config.yaml` is intended to overwrite production on every push to `main`. |
| 182 | +- Confirm `DEPLOY_PUBLIC_HEALTH_URL` is set if the API port is not publicly reachable from GitHub-hosted runners. |
| 183 | +- Confirm the systemd service still uses the same binary path and health endpoint. |
| 184 | +- Confirm operators understand that `data/` is preserved but config is not, and that rollback does not restore SQLite database state. |
0 commit comments