Gitlab GIT-Rust Boilerplate

Initialiseert lokale scripts voor een nieuw GitLab Rust-project.

Gebruik: voer uit vanuit de root van je nieuwe project:

mkdir ~/git/mijn-project && cd ~/git/mijn-project
git init
~/boilerplate-git/new-project.sh

Het script vraagt naar GitLab project path, binary naam, poort en token, en maakt aan: push.sh, bump.sh, release.sh, ctl.sh, .env, .gitignore — geen van deze wordt gecommit.

#!/usr/bin/env bash
# new-project.sh — initialize local project scripts for a GitLab Rust project
#
# Run this from the root of your new project:
#   ~/boilerplate-git/new-project.sh
#
# Creates: push.sh, bump.sh, release.sh, ctl.sh, .env, .gitignore (if absent)
# None of these files are committed to git.

set -euo pipefail

TARGET_DIR="$(pwd)"

echo "=== New project setup ==="
echo "Target: ${TARGET_DIR}"
echo ""

# ── Vragen ────────────────────────────────────────────────────────────────────

read -rp "GitLab project path (bijv. dexter1-dev/myproject): " GITLAB_PATH
if [[ -z "${GITLAB_PATH}" ]]; then
    echo "Fout: GitLab project path mag niet leeg zijn." >&2
    exit 1
fi

# URL-encode slash for API calls (dexter1-dev/myproject → dexter1-dev%2Fmyproject)
GITLAB_PROJECT_ENCODED="${GITLAB_PATH/\//%2F}"
GITLAB_USER="${GITLAB_PATH%%/*}"
GITLAB_REPO="${GITLAB_PATH##*/}"
GITLAB_URL="https://gitlab.com/${GITLAB_PATH}.git"

read -rp "Naam van de hoofd-binary (standaard: ${GITLAB_REPO}): " BINARY_NAME
BINARY_NAME="${BINARY_NAME:-${GITLAB_REPO}}"

read -rp "Naam van de admin-binary (leeg = geen): " ADMIN_NAME

read -rp "Standaard poort (standaard: 3000): " PORT
PORT="${PORT:-3000}"

read -rsp "GitLab token (glpat-...): " GITLAB_TOKEN
echo ""
if [[ -z "${GITLAB_TOKEN}" ]]; then
    echo "Fout: token mag niet leeg zijn." >&2
    exit 1
fi

echo ""

# ── .env ─────────────────────────────────────────────────────────────────────

if [[ -f .env ]]; then
    echo "==> .env bestaat al, wordt overgeslagen."
else
    cat > .env <<EOF
GITLAB_TOKEN=${GITLAB_TOKEN}
EOF
    chmod 600 .env
    echo "==> .env aangemaakt."
fi

# ── .gitignore ────────────────────────────────────────────────────────────────

if [[ ! -f .gitignore ]]; then
    cat > .gitignore <<'EOF'
# Rust build artifacts
/target/

# Local scripts
push.sh
bump.sh
release.sh
ctl.sh

# Local environment (contains tokens)
.env

# Claude Code session data
.claude/

# OS
.DS_Store
*.log
EOF
    echo "==> .gitignore aangemaakt."
else
    for entry in push.sh bump.sh release.sh ctl.sh .env ".claude/"; do
        grep -qxF "${entry}" .gitignore || echo "${entry}" >> .gitignore
    done
    echo "==> .gitignore bijgewerkt."
fi

# ── push.sh ───────────────────────────────────────────────────────────────────

cat > push.sh <<EOF
#!/bin/bash
# Push project naar GitLab: https://gitlab.com/${GITLAB_PATH}
set -euo pipefail

BRANCH="main"
REMOTE="origin"

cd "\$(dirname "\$0")"
[[ -f .env ]] && source .env

REMOTE_URL="\$(git remote get-url origin 2>/dev/null || true)"
if [[ -n "\${GITLAB_TOKEN:-}" ]]; then
    REMOTE_URL="https://${GITLAB_USER}:\${GITLAB_TOKEN}@gitlab.com/${GITLAB_PATH}.git"
fi

echo "=== ${GITLAB_REPO} push naar GitLab ==="

if ! git remote get-url "\$REMOTE" &>/dev/null; then
    echo "Remote '\$REMOTE' niet gevonden. Voeg toe met:"
    echo "  git remote add origin ${GITLAB_URL}"
    exit 1
fi

git add -A

echo ""
echo "--- Wijzigingen ---"
git status --short
echo ""

if git diff --cached --quiet; then
    echo "Geen wijzigingen om te committen."
else
    MSG="\${1:-"update \$(date '+%Y-%m-%d %H:%M')"}"
    git commit -m "\$MSG"
fi

echo "Pushen naar \$REMOTE/\$BRANCH ..."
git push -u "\${REMOTE_URL:-\$REMOTE}" "\$BRANCH"

echo ""
echo "Klaar! Zie: https://gitlab.com/${GITLAB_PATH}"
EOF
chmod +x push.sh
echo "==> push.sh aangemaakt."

# ── bump.sh ───────────────────────────────────────────────────────────────────

cat > bump.sh <<'BUMP'
#!/usr/bin/env bash
# bump.sh — increase version and commit
#
# Usage:
#   ./bump.sh            # patch  (0.1.0 → 0.1.1)
#   ./bump.sh minor      # minor  (0.1.1 → 0.2.0)
#   ./bump.sh major      # major  (0.2.0 → 1.0.0)
#   ./bump.sh 1.2.3      # exact version
set -euo pipefail

cd "$(dirname "$0")"

BUMP="${1:-patch}"

CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*= *"\(.*\)"/\1/')
IFS='.' read -r MAJOR MINOR PATCH <<< "${CURRENT}"

case "${BUMP}" in
  major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
  minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
  patch) PATCH=$((PATCH + 1)) ;;
  [0-9]*.[0-9]*.[0-9]*) MAJOR=""; MINOR=""; PATCH=""; VERSION="${BUMP}" ;;
  *)
    echo "Usage: $0 [patch|minor|major|x.y.z]" >&2
    exit 1
    ;;
esac

VERSION="${VERSION:-${MAJOR}.${MINOR}.${PATCH}}"

if [[ "${VERSION}" == "${CURRENT}" ]]; then
  echo "Version is already ${CURRENT}, nothing to do." >&2
  exit 1
fi

echo "==> ${CURRENT} -> ${VERSION}"
sed -i "0,/^version = \"${CURRENT}\"/s//version = \"${VERSION}\"/" Cargo.toml

if [[ -f CHANGELOG.md ]] && grep -q '^\#\# \[Unreleased\]' CHANGELOG.md; then
  TODAY=$(date '+%Y-%m-%d')
  sed -i "0,/^## \[Unreleased\].*/s//## [${VERSION}] - ${TODAY}/" CHANGELOG.md
  sed -i "/^## \[Unreleased\].*/d" CHANGELOG.md
  echo "==> CHANGELOG.md updated: [Unreleased] → [${VERSION}] - ${TODAY}"
fi

cargo build --release

git add Cargo.toml Cargo.lock CHANGELOG.md
git commit -m "Bump version to ${VERSION}"

echo "==> Done. Use ./push.sh and then ./release.sh to tag and publish."
BUMP
chmod +x bump.sh
echo "==> bump.sh aangemaakt."

# ── release.sh ────────────────────────────────────────────────────────────────

ADMIN_BLOCK=""
ADMIN_UPLOAD_BLOCK=""
ADMIN_LINK_BLOCK=""

if [[ -n "${ADMIN_NAME}" ]]; then
    ADMIN_BLOCK='
ADMIN_BINARY_NAME="'"${ADMIN_NAME}"'-linux-x86_64"
ADMIN_BINARY_PATH="target/release/'"${ADMIN_NAME}"'"'

    ADMIN_UPLOAD_BLOCK='
echo "==> Admin binary uploaden (\${ADMIN_BINARY_NAME})..."
HTTP=$(curl -s -o /dev/null -w "%{http_code}" \
  --header "PRIVATE-TOKEN: \${GITLAB_TOKEN}" \
  --upload-file "\${ADMIN_BINARY_PATH}" \
  "https://gitlab.com/api/v4/projects/\${PROJECT}/packages/generic/'"${BINARY_NAME}"'/\${VERSION}/\${ADMIN_BINARY_NAME}")
[[ "\${HTTP}" == "201" || "\${HTTP}" == "200" ]] || { echo "Fout admin upload (HTTP \${HTTP})." >&2; exit 1; }
echo "   Admin binary geupload (HTTP \${HTTP})."'

    ADMIN_LINK_BLOCK='        ,
        {
          \"name\": \"\${ADMIN_BINARY_NAME}\",
          \"url\": \"https://gitlab.com/api/v4/projects/\${PROJECT}/packages/generic/'"${BINARY_NAME}"'/\${VERSION}/\${ADMIN_BINARY_NAME}\",
          \"link_type\": \"package\"
        }'
fi

cat > release.sh <<EOF
#!/usr/bin/env bash
set -euo pipefail

GITLAB_TOKEN="\${GITLAB_TOKEN:?Set GITLAB_TOKEN as environment variable or add to .env}"
PROJECT="${GITLAB_PROJECT_ENCODED}"
BINARY_NAME="${BINARY_NAME}-linux-x86_64"
BINARY_PATH="target/release/${BINARY_NAME}"${ADMIN_BLOCK}

cd "\$(dirname "\$0")"
[[ -f .env ]] && source .env

REPLACE=""
[[ "\${1:-}" == "--replace" ]] && REPLACE=1

GIT_REMOTE="https://${GITLAB_USER}:\${GITLAB_TOKEN}@gitlab.com/${GITLAB_PATH}.git"

VERSION=\$(grep '^version' Cargo.toml | head -1 | sed 's/.*= *\"\(.*\)\"/\1/')
TAG="v\${VERSION}"

echo "==> Releasing \${TAG}\${REPLACE:+ (replace)}"

if [[ "\${REPLACE}" == "1" ]]; then
  echo "==> Removing existing release..."
  HTTP=\$(curl -s -o /dev/null -w "%{http_code}" \\
    --header "PRIVATE-TOKEN: \${GITLAB_TOKEN}" \\
    --request DELETE \\
    "https://gitlab.com/api/v4/projects/\${PROJECT}/releases/\${TAG}")
  [[ "\${HTTP}" == "200" ]] && echo "   Release removed." || echo "   Release not found (HTTP \${HTTP}), continuing."

  echo "==> Removing and recreating tag..."
  git tag -d "\${TAG}" 2>/dev/null || true
  HTTP=\$(curl -s -o /dev/null -w "%{http_code}" \\
    --header "PRIVATE-TOKEN: \${GITLAB_TOKEN}" \\
    --request DELETE \\
    "https://gitlab.com/api/v4/projects/\${PROJECT}/repository/tags/\${TAG}")
  [[ "\${HTTP}" == "200" ]] && echo "   Remote tag removed." || echo "   Remote tag not found (HTTP \${HTTP})."
fi

echo "==> Building (release)..."
cargo build --release

if ! git tag -l | grep -q "^\${TAG}\$"; then
  git tag "\${TAG}"
fi

echo "==> Pushing tag..."
git push "\${GIT_REMOTE}" "\${TAG}" \${REPLACE:+--force}

echo "==> Uploading binary (\${BINARY_NAME})..."
HTTP=\$(curl -s -o /dev/null -w "%{http_code}" \\
  --header "PRIVATE-TOKEN: \${GITLAB_TOKEN}" \\
  --upload-file "\${BINARY_PATH}" \\
  "https://gitlab.com/api/v4/projects/\${PROJECT}/packages/generic/${BINARY_NAME}/\${VERSION}/\${BINARY_NAME}")
[[ "\${HTTP}" == "201" || "\${HTTP}" == "200" ]] || { echo "Binary upload error (HTTP \${HTTP})." >&2; exit 1; }
echo "   Binary uploaded."
${ADMIN_UPLOAD_BLOCK}

if [[ -f CHANGELOG.md ]]; then
  DESCRIPTION=\$(awk "/^\#\# \[?\${VERSION}\]?/{found=1; next} found && /^\#\# /{exit} found{print}" CHANGELOG.md)
else
  DESCRIPTION="Release \${TAG}."
fi

echo "==> Creating GitLab release..."
RESULT=\$(curl -s -w "\n%{http_code}" \\
  --header "PRIVATE-TOKEN: \${GITLAB_TOKEN}" \\
  --header "Content-Type: application/json" \\
  --request POST \\
  --data "{
    \"name\": \"${BINARY_NAME} \${TAG}\",
    \"tag_name\": \"\${TAG}\",
    \"description\": \$(echo "\${DESCRIPTION}" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'),
    \"assets\": {
      \"links\": [
        {
          \"name\": \"\${BINARY_NAME}\",
          \"url\": \"https://gitlab.com/api/v4/projects/\${PROJECT}/packages/generic/${BINARY_NAME}/\${VERSION}/\${BINARY_NAME}\",
          \"link_type\": \"package\"
        }${ADMIN_LINK_BLOCK}
      ]
    }
  }" \\
  "https://gitlab.com/api/v4/projects/\${PROJECT}/releases")

HTTP=\$(echo "\${RESULT}" | tail -1)
BODY=\$(echo "\${RESULT}" | head -n -1)

if [[ "\${HTTP}" == "201" ]]; then
  echo "==> Release created: https://gitlab.com/${GITLAB_PATH}/-/releases/\${TAG}"
else
  echo "Error creating release (HTTP \${HTTP}):" >&2
  echo "\${BODY}" >&2
  exit 1
fi
EOF
chmod +x release.sh
echo "==> release.sh aangemaakt."

# ── ctl.sh ────────────────────────────────────────────────────────────────────

cat > ctl.sh <<EOF
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
BINARY="\$SCRIPT_DIR/target/release/${BINARY_NAME}"

MDS_DIR="\${MDS_DIR:-\$SCRIPT_DIR/demo}"
MDS_PORT="\${MDS_PORT:-${PORT}}"
MDS_LOGFILE="\${MDS_LOGFILE:-\$SCRIPT_DIR/${BINARY_NAME}.log}"
MDS_ACCESS_LOG="\${MDS_ACCESS_LOG:-\$SCRIPT_DIR/access.log}"
MDS_CONFIG="\${MDS_CONFIG:-}"

cmd_stop() {
    if pgrep -f "${BINARY_NAME}.*--port \${MDS_PORT}" > /dev/null; then
        pkill -f "${BINARY_NAME}.*--port \${MDS_PORT}"
        echo "${BINARY_NAME} stopped."
    else
        echo "${BINARY_NAME} was not running."
    fi
}

cmd_build() {
    echo "Building..."
    cd "\$SCRIPT_DIR"
    cargo build --release
    [[ "\${1:-}" == "--start" ]] && cmd_start
}

cmd_start() {
    if [[ ! -x "\$BINARY" ]]; then
        echo "Error: \$BINARY not found. Use './ctl.sh build --start'." >&2
        exit 1
    fi
    if pgrep -f "${BINARY_NAME}.*--port \${MDS_PORT}" > /dev/null; then
        pkill -f "${BINARY_NAME}.*--port \${MDS_PORT}"
        sleep 1
    fi
    nohup "\$BINARY" \\
        --dir "\$MDS_DIR" \\
        --port "\$MDS_PORT" \\
        --access-log "\$MDS_ACCESS_LOG" \\
        \${MDS_CONFIG:+--config "\$MDS_CONFIG"} \\
        >> "\$MDS_LOGFILE" 2>&1 &
    sleep 1
    if pgrep -f "${BINARY_NAME}.*--port \${MDS_PORT}" > /dev/null; then
        echo "${BINARY_NAME} started : http://0.0.0.0:\${MDS_PORT}"
    else
        echo "Error: server did not start. Check \$MDS_LOGFILE" >&2
        exit 1
    fi
}

cmd_restart() { cmd_stop; sleep 1; cmd_start; }

cmd_status() {
    PID=\$(pgrep -f "${BINARY_NAME}.*--port \${MDS_PORT}" || true)
    if [[ -n "\${PID}" ]]; then
        echo "${BINARY_NAME} running (pid=\${PID}) on http://0.0.0.0:\${MDS_PORT}"
    else
        echo "${BINARY_NAME} is not running."
    fi
}

case "\${1:-}" in
    start)   cmd_start ;;
    stop)    cmd_stop ;;
    restart) cmd_restart ;;
    status)  cmd_status ;;
    build)   cmd_build "\${2:-}" ;;
    *)
        echo "Usage: \$0 {start|stop|restart|status|build} [--start]"
        exit 1 ;;
esac
EOF
chmod +x ctl.sh
echo "==> ctl.sh aangemaakt."

# ── git remote ────────────────────────────────────────────────────────────────

if git rev-parse --git-dir > /dev/null 2>&1; then
    if ! git remote get-url origin &>/dev/null; then
        git remote add origin "${GITLAB_URL}"
        echo "==> Git remote 'origin' ingesteld op ${GITLAB_URL}"
    else
        echo "==> Git remote bestaat al: $(git remote get-url origin)"
    fi
else
    echo "==> Geen git repo gevonden. Initialiseer met: git init"
fi

echo ""
echo "Klaar! Bestanden aangemaakt in ${TARGET_DIR}:"
echo "  push.sh, bump.sh, release.sh, ctl.sh, .env, .gitignore"
echo ""
echo "Volgende stappen:"
echo "  1. git init (als nog niet gedaan)"
echo "  2. ./ctl.sh build --start"
echo "  3. ./push.sh 'first commit'"