diff --git a/README.md b/README.md index 7007b17..e972c2e 100644 --- a/README.md +++ b/README.md @@ -175,11 +175,11 @@ docker compose build # server image docker build --target client -t tunnel-client . # client image ``` -Release to GitHub + GHCR (multi-arch): `./ship.sh "your message"` (requires `gh` with `write:packages` and git remote `github`). +Release to GitHub + GHCR (multi-arch): `./ship.sh "your message"`. Every run **`docker login ghcr.io`** using **`gh api user`** and **`gh auth token`** (needs **`write:packages`** on that token), then **always** multi-arch **push**. **`git remote`** (default `github`, or `git config ship.remote`) must be **github.com** unless `ship.registryBase` is set. Optional: `ship.registryBase`, `ship.tagServer`, `ship.tagClient`, `ship.platforms`, `ship.buildxBuilder`. ## Binaries and systemd (bare metal) -Build `tunnel-server` / `tunnel-client` with `go build` (or use `./ship.sh`, which writes `dist/`). Use the included systemd units under `systemd/`. +Build `tunnel-server` / `tunnel-client` with `go build`. Use the included systemd units under `systemd/`. **Keys:** Do not reuse the host’s SSH keys or share one key between hosts or tunnels. Generate a dedicated ed25519 key per tunnel (or per host). Add that key’s **public** half to the **tunnel** user’s `authorized_keys` on **ingress.nixc.us** (the reverse tunnel server). @@ -211,10 +211,10 @@ sudo systemctl daemon-reload && sudo systemctl enable --now tunnel-client ### Client on a remote host (clone or copy) -1. Install the binary (from repo clone, `go build`, `dist/tunnel-client` after `./ship.sh`, or raw URL): +1. Install the binary (from repo clone, `go build -o tunnel-client ./cmd/client/`, GHCR image extract, or raw URL): ```bash - sudo cp dist/tunnel-client /usr/local/bin/tunnel-client && sudo chmod +x /usr/local/bin/tunnel-client + sudo cp tunnel-client /usr/local/bin/tunnel-client && sudo chmod +x /usr/local/bin/tunnel-client ``` 2. Copy the systemd unit and create env file: diff --git a/archive/.env.example b/archive/.env.example new file mode 100644 index 0000000..892bef9 --- /dev/null +++ b/archive/.env.example @@ -0,0 +1,5 @@ +# Copy to .env, set values, and never commit .env. +# Used by archive/docker-compose-macmini.yml and archive/docker-compose-logos.yml for tunnel HTTP Basic auth. + +TUNNEL_AUTH_USER= +TUNNEL_AUTH_PASS= diff --git a/archive/docker-compose.production.yml b/archive/docker-compose.production.yml new file mode 100644 index 0000000..97dab45 --- /dev/null +++ b/archive/docker-compose.production.yml @@ -0,0 +1,12 @@ +services: + tunnel-server: + build: + context: . + target: server + image: git.nixc.us/colin/better-argo-tunnels:production + + tunnel-client: + build: + context: . + target: client + image: git.nixc.us/colin/better-argo-tunnels:client-production diff --git a/archive/stack.production.yml b/archive/stack.production.yml new file mode 100644 index 0000000..77c2da6 --- /dev/null +++ b/archive/stack.production.yml @@ -0,0 +1,51 @@ +networks: + traefik: + external: true + +services: + tunnel-server: + image: git.nixc.us/colin/better-argo-tunnels:production + networks: + - traefik + environment: + SSH_PORT: "2222" + PORT_RANGE_START: "10000" + PORT_RANGE_END: "10100" + TRAEFIK_SSH_HOST: "ingress.nixc.us:65522" + TRAEFIK_SSH_USER: "root" + TRAEFIK_SSH_KEY: "/keys/deploy_key" + SWARM_SERVICE_NAME: "better-argo-tunnels_tunnel-server" + TRAEFIK_ENTRYPOINT: "websecure" + TRAEFIK_CERT_RESOLVER: "letsencryptresolver" + HOSTNAME: "{{.Node.Hostname}}" + NODE_ID: "{{.Node.ID}}" + SERVICE_NAME: "{{.Service.Name}}" + TASK_ID: "{{.Task.ID}}" + ENVIRONMENT: "production" + volumes: + # Tunnel user's keys on ingress.nixc.us (clients connect here; authorized_keys = tunnel user's) + - /home/tunnel/.ssh/tunnel_host_key:/keys/host_key:ro + - /home/tunnel/.ssh/authorized_keys:/keys/authorized_keys:ro + - /home/tunnel/.ssh/ca-userkey:/keys/deploy_key:ro + - /home/tunnel/.ssh/ca-userkey-cert.pub:/keys/deploy_key-cert.pub:ro + ports: + - target: 2222 + published: 2222 + protocol: tcp + mode: host + deploy: + replicas: 1 + placement: + constraints: + - node.hostname == ingress.nixc.us + labels: + traefik.enable: "true" + traefik.docker.network: "traefik" + # Dynamic tunnel labels are added at runtime via docker service update. + update_config: + order: stop-first + failure_action: rollback + delay: 0s + parallelism: 1 + restart_policy: + condition: on-failure diff --git a/docker-compose.yml b/docker-compose.yml index 97dab45..65aab13 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,10 +3,10 @@ services: build: context: . target: server - image: git.nixc.us/colin/better-argo-tunnels:production + image: ghcr.io/leopere/better-argo-tunnels:production tunnel-client: build: context: . target: client - image: git.nixc.us/colin/better-argo-tunnels:client-production + image: ghcr.io/leopere/better-argo-tunnels:client-production diff --git a/ship.sh b/ship.sh index eb18d0c..deb5dfb 100755 --- a/ship.sh +++ b/ship.sh @@ -1,17 +1,23 @@ #!/usr/bin/env bash # -# Fixed pipeline (do not reorder or skip steps): +# Fixed pipeline (order is fixed): # 1. git add -A -# 2. git commit (normal if staged changes exist, else --allow-empty with the same message) -# 3. GIT_COMMIT = current HEAD (baked into Go binaries inside each image via Dockerfile ARG) -# 4. docker login ghcr.io -# 5. For each of server + client targets: per-arch buildx --push (linux/amd64, linux/arm64), then imagetools manifest -# 6. git push GIT_REMOTE BRANCH +# 2. git commit (or --allow-empty if nothing staged) +# 3. GIT_COMMIT → Docker --build-arg (baked into binaries) +# 4. docker login ghcr.io — always: gh api user + gh auth token (no other auth path) +# 5. buildx --push per arch + imagetools manifest (server + client) — always, no skips +# 6. git push — always +# +# Configuration (use your machine / repo — no hardcoded owner or registry): +# - Remote to parse for ghcr.io/owner/repo: git config ship.remote (default: github) +# - Optional full registry base (no tag): git config ship.registryBase ghcr.io/org/image +# - Image tags: git config ship.tagServer / ship.tagClient +# - Platforms (comma-separated): git config ship.platforms +# - Buildx builder: git config ship.buildxBuilder (default: orbstack if present, else default) +# +# The push remote URL must be github.com (ssh or https). Owner/repo set GHCR path unless ship.registryBase is set. # # Usage: ./ship.sh -# Requires: git, docker (buildx), gh; remote "github"; gh token with write:packages for GHCR. -# -# Multi-arch: Intel/AMD 64-bit (linux/amd64) and ARM64 (linux/arm64), one manifest tag each for server and client. set -euo pipefail @@ -24,16 +30,6 @@ COMMIT_MSG="$*" ROOT="$(cd "$(dirname "$0")" && pwd)" cd "$ROOT" -# --- release targets (edit only here) --- -GITHUB_OWNER="Leopere" -REGISTRY_BASE="ghcr.io/leopere/better-argo-tunnels" -TAG_SERVER="production" -TAG_CLIENT="client-production" -# Exactly two platforms → one multi-arch manifest per image (amd64 + arm64). -PLATFORMS="linux/amd64,linux/arm64" -GIT_REMOTE="github" -# --- end --- - for cmd in git docker gh; do command -v "$cmd" >/dev/null 2>&1 || { echo "ship: fatal: missing required command: $cmd" >&2 @@ -50,11 +46,49 @@ if [[ -z "$BRANCH" ]]; then echo "ship: fatal: detached HEAD" >&2 exit 1 fi -if ! git remote get-url "$GIT_REMOTE" >/dev/null 2>&1; then - echo "ship: fatal: git remote '$GIT_REMOTE' is not configured" >&2 + +GIT_REMOTE="$(git config ship.remote 2>/dev/null)" || true +GIT_REMOTE="${GIT_REMOTE:-github}" +if ! REMOTE_URL="$(git remote get-url "$GIT_REMOTE" 2>/dev/null)"; then + echo "ship: fatal: git remote '$GIT_REMOTE' not found (set: git config ship.remote )" >&2 exit 1 fi +REMOTE_OWNER="" +REMOTE_REPO="" +if [[ "$REMOTE_URL" =~ ^git@github\.com:([^/]+)/(.+)$ ]]; then + REMOTE_OWNER="${BASH_REMATCH[1]}" + REMOTE_REPO="${BASH_REMATCH[2]%.git}" +elif [[ "$REMOTE_URL" =~ ^https://github\.com/([^/]+)/([^/?#]+) ]]; then + REMOTE_OWNER="${BASH_REMATCH[1]}" + REMOTE_REPO="${BASH_REMATCH[2]%.git}" +else + echo "ship: fatal: remote '$GIT_REMOTE' URL is not github.com: $REMOTE_URL" >&2 + exit 1 +fi + +REGISTRY_BASE="$(git config ship.registryBase 2>/dev/null)" || true +if [[ -z "$REGISTRY_BASE" ]]; then + o_lc="$(echo "$REMOTE_OWNER" | tr '[:upper:]' '[:lower:]')" + r_lc="$(echo "$REMOTE_REPO" | tr '[:upper:]' '[:lower:]')" + REGISTRY_BASE="ghcr.io/${o_lc}/${r_lc}" +fi +REGISTRY_BASE="${REGISTRY_BASE%/}" + +TAG_SERVER="$(git config ship.tagServer 2>/dev/null)" || true +TAG_SERVER="${TAG_SERVER:-production}" +TAG_CLIENT="$(git config ship.tagClient 2>/dev/null)" || true +TAG_CLIENT="${TAG_CLIENT:-client-production}" + +PLATFORMS="$(git config ship.platforms 2>/dev/null)" || true +PLATFORMS="${PLATFORMS:-linux/amd64,linux/arm64}" + +BUILDER_OVERRIDE="$(git config ship.buildxBuilder 2>/dev/null)" || true + +echo "==> ship: remote=$GIT_REMOTE ($REMOTE_OWNER/$REMOTE_REPO)" +echo "==> ship: registry=$REGISTRY_BASE tags=$TAG_SERVER $TAG_CLIENT" +echo "==> ship: platforms=$PLATFORMS" + echo "==> [1/6] git add -A" git add -A @@ -69,17 +103,26 @@ GIT_COMMIT="$(git rev-parse HEAD)" BA=(--build-arg "GIT_COMMIT=${GIT_COMMIT}") echo "==> GIT_COMMIT=${GIT_COMMIT}" -echo "==> [3/6] docker login ghcr.io" +echo "==> [3/6] docker login ghcr.io (gh api user + gh auth token)" +GH_LOGIN="$(gh api user -q .login)" +if [[ -z "${GH_LOGIN}" ]]; then + echo "ship: fatal: gh not logged in" >&2 + exit 1 +fi if ! token="$(gh auth token)" || [[ -z "${token}" ]]; then echo "ship: fatal: gh auth token empty" >&2 exit 1 fi -if ! printf '%s' "$token" | docker login ghcr.io -u "$GITHUB_OWNER" --password-stdin; then - echo "ship: fatal: docker login ghcr.io failed (need: gh auth refresh -h github.com -s write:packages -s read:packages)" >&2 +if ! printf '%s' "$token" | docker login ghcr.io -u "$GH_LOGIN" --password-stdin; then + echo "ship: fatal: docker login ghcr.io failed" >&2 exit 1 fi pick_builder() { + if [[ -n "$BUILDER_OVERRIDE" ]]; then + echo "$BUILDER_OVERRIDE" + return + fi if docker buildx inspect orbstack >/dev/null 2>&1; then echo "orbstack" else @@ -93,7 +136,6 @@ platform_slug() { echo "${p//\//-}" } -# Per-arch push then single multi-arch manifest (orbstack/docker driver compatible). push_multi() { local target="$1" local final_tag="$2" @@ -137,4 +179,4 @@ echo "==> [6/6] git push ${GIT_REMOTE} ${BRANCH}" git push "$GIT_REMOTE" "$BRANCH" echo "ship: ok commit=${GIT_COMMIT}" -echo "ship: images ${SERVER_TAG} ${CLIENT_TAG} (multi-arch: ${PLATFORMS})" +echo "ship: images ${SERVER_TAG} ${CLIENT_TAG}"