Files
HickLab3D_Dashboard/.gitea/workflows/deploy.yml
T
Hickmeister 53ca438b0e
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 27s
Share Laravel package caches with production deploy
2026-10-11 16:53:29 +01:00

288 lines
11 KiB
YAML

name: Deploy HickLab3D Dashboard
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
env:
PROJECT_NAME: TechOdyssey_Designs_Dashboard
DEPLOY_ROOT: /mnt/www-live
REQUIRED_PHP_VERSION: "8.4.0"
PHP_CMD: /opt/hicklab3d-toolchain/bin/php
COMPOSER_CMD: /opt/hicklab3d-toolchain/bin/composer
jobs:
deploy-production:
name: Deploy to production
runs-on: [swarm, linux, arm64]
if: github.ref == 'refs/heads/main'
steps:
- name: Prepare runner toolchain
run: |
PHP_BIN="$(command -v php84)"
test -x "$PHP_BIN"
command -v git
command -v rsync
command -v curl
command -v node
command -v npm
mkdir -p /opt/hicklab3d-toolchain/bin
ln -sfn "$PHP_BIN" "$PHP_CMD"
curl -fsSL https://getcomposer.org/installer -o /tmp/hicklab3d-composer-setup.php
"$PHP_CMD" /tmp/hicklab3d-composer-setup.php --install-dir=/opt/hicklab3d-toolchain/bin --filename=composer.phar
printf '%s\n' '#!/bin/sh' 'exec /opt/hicklab3d-toolchain/bin/php /opt/hicklab3d-toolchain/bin/composer.phar "$@"' > "$COMPOSER_CMD"
chmod +x "$COMPOSER_CMD"
- name: Verify runner toolchain
run: |
"$PHP_CMD" -v
"$PHP_CMD" -r '
$required = getenv("REQUIRED_PHP_VERSION");
if (version_compare(PHP_VERSION, $required, "<")) {
fwrite(STDERR, "PHP ".PHP_VERSION." is too old. Required: ".$required." or newer.\n");
exit(1);
}
$extensions = ["bcmath", "ctype", "curl", "dom", "fileinfo", "iconv", "mbstring", "openssl", "pdo", "pdo_mysql", "pdo_sqlite", "session", "sqlite3", "tokenizer", "xml", "xmlwriter", "zip"];
$missing = array_values(array_filter($extensions, fn ($extension) => ! extension_loaded($extension)));
if ($missing !== []) {
fwrite(STDERR, "Missing required PHP extensions: ".implode(", ", $missing)."\n");
exit(1);
}
'
node -e "const [major, minor] = process.versions.node.split('.').map(Number); const ok = (major === 20 && minor >= 19) || (major === 22 && minor >= 12) || major > 22; if (!ok) { console.error('Node '+process.versions.node+' is not supported. Vite requires ^20.19.0 or >=22.12.0.'); process.exit(1); }"
"$COMPOSER_CMD" --version
npm --version
- name: Clone main
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
REPOSITORY: ${{ github.repository }}
run: |
test -n "$GITEA_TOKEN"
git -c credential.helper='!f() { echo "username=oauth2"; echo "password=$GITEA_TOKEN"; }; f' clone --depth 1 --single-branch --branch main "https://git.hickmeister.uk/${REPOSITORY}.git" src
- name: Install dependencies and verify app
working-directory: src
run: |
"$COMPOSER_CMD" install --no-interaction --prefer-dist --no-progress
npm ci
cp .env.example .env
"$PHP_CMD" artisan key:generate --ansi
npm run build
test -f public/build/manifest.json
"$PHP_CMD" vendor/bin/phpunit
ls -la public/build
- name: Check existing production environment
env:
DEPLOY_PATH: ${{ env.DEPLOY_ROOT }}/${{ env.PROJECT_NAME }}
run: |
test -d "$DEPLOY_PATH"
test -f "$DEPLOY_PATH/.env"
test -f "$DEPLOY_PATH/.htaccess"
grep -q '^APP_KEY=base64:' "$DEPLOY_PATH/.env"
- name: Deploy application to live directory
env:
DEPLOY_PATH: ${{ env.DEPLOY_ROOT }}/${{ env.PROJECT_NAME }}
run: |
set +e
rsync -rlz --delete --no-perms --no-owner --no-group --omit-dir-times --no-times --inplace \
--exclude '.env' \
--exclude '.htaccess' \
--exclude '.git/' \
--exclude '.gitea/' \
--exclude 'node_modules/' \
--exclude 'vendor/' \
--exclude 'tests/' \
--exclude '.phpunit.cache/' \
--exclude '.phpunit.result.cache' \
--exclude 'storage/' \
--exclude 'bootstrap/cache/' \
--exclude 'logs/' \
--exclude 'public/storage' \
src/ "$DEPLOY_PATH/"
status=$?
set -e
if [ "$status" -ne 0 ]; then
if [ "$status" -eq 23 ] || [ "$status" -eq 24 ]; then
echo "rsync reported partial transfer (likely timestamps/attrs not permitted); continuing."
else
exit "$status"
fi
fi
- name: Prepare Laravel directories
env:
DEPLOY_PATH: ${{ env.DEPLOY_ROOT }}/${{ env.PROJECT_NAME }}
RUNTIME_PATH: /tmp/${{ env.PROJECT_NAME }}
run: |
mkdir -p \
"$DEPLOY_PATH/storage/app/public" \
"$DEPLOY_PATH/storage/framework/cache/data" \
"$DEPLOY_PATH/storage/framework/sessions" \
"$DEPLOY_PATH/storage/framework/testing" \
"$DEPLOY_PATH/storage/framework/views" \
"$DEPLOY_PATH/storage/logs" \
"$DEPLOY_PATH/bootstrap/cache" \
"$RUNTIME_PATH/views" \
"$RUNTIME_PATH/logs" \
"$RUNTIME_PATH/cache"
- name: Verify deployed Laravel files
working-directory: ${{ env.DEPLOY_ROOT }}/${{ env.PROJECT_NAME }}
run: |
for path in \
artisan \
public/index.php \
public/build/manifest.json \
routes/web.php \
resources/views/dashboard.blade.php \
app/Services/Etsy.php \
app/Services/Bambuddy.php \
app/Services/Parcel2Go.php \
app/Services/DryerClient.php
do
if [ ! -f "$path" ]; then
echo "Missing deployed Laravel file: $(pwd)/$path" >&2
exit 1
fi
done
- name: Require Laravel environment file
working-directory: ${{ env.DEPLOY_ROOT }}/${{ env.PROJECT_NAME }}
run: |
if [ ! -f ".env" ]; then
echo "Missing .env at $(pwd)/.env. Create the server-side Laravel environment file before deploying." >&2
exit 1
fi
- name: Ensure writable runtime paths
working-directory: ${{ env.DEPLOY_ROOT }}/${{ env.PROJECT_NAME }}
env:
RUNTIME_PATH: /tmp/${{ env.PROJECT_NAME }}
run: |
mkdir -p "$RUNTIME_PATH/views" "$RUNTIME_PATH/logs" "$RUNTIME_PATH/cache"
chmod 0777 "$RUNTIME_PATH" "$RUNTIME_PATH/views" "$RUNTIME_PATH/logs" "$RUNTIME_PATH/cache"
if grep -q '^VIEW_COMPILED_PATH=' .env; then
sed -i "s|^VIEW_COMPILED_PATH=.*|VIEW_COMPILED_PATH=/tmp/TechOdyssey_Designs_Dashboard.views|" .env
else
echo "VIEW_COMPILED_PATH=/tmp/TechOdyssey_Designs_Dashboard.views" >> .env
fi
if grep -q '^LOG_PATH=' .env; then
sed -i "s|^LOG_PATH=.*|LOG_PATH=/tmp/TechOdyssey_Designs_Dashboard.laravel.log|" .env
else
echo "LOG_PATH=/tmp/TechOdyssey_Designs_Dashboard.laravel.log" >> .env
fi
for key_path in \
"APP_SERVICES_CACHE=storage/framework/cache/data/services.php" \
"APP_PACKAGES_CACHE=storage/framework/cache/data/packages.php" \
"APP_CONFIG_CACHE=/tmp/TechOdyssey_Designs_Dashboard.config.php" \
"APP_ROUTES_CACHE=/tmp/TechOdyssey_Designs_Dashboard.routes.php" \
"APP_EVENTS_CACHE=/tmp/TechOdyssey_Designs_Dashboard.events.php"
do
key="${key_path%%=*}"
value="${key_path#*=}"
if grep -q "^${key}=" .env; then
sed -i "s|^${key}=.*|${key}=${value}|" .env
else
echo "${key}=${value}" >> .env
fi
done
- name: Install production PHP dependencies
working-directory: ${{ env.DEPLOY_ROOT }}/${{ env.PROJECT_NAME }}
run: |
rm -f \
storage/framework/cache/data/packages.php \
storage/framework/cache/data/services.php
"$COMPOSER_CMD" install --no-dev --no-interaction --prefer-dist --optimize-autoloader
rm -f \
storage/framework/cache/data/packages.php \
storage/framework/cache/data/services.php
"$PHP_CMD" artisan package:discover --ansi --no-interaction
- name: Clear compiled Laravel files
working-directory: ${{ env.DEPLOY_ROOT }}/${{ env.PROJECT_NAME }}
run: |
rm -f bootstrap/cache/config.php bootstrap/cache/routes-*.php bootstrap/cache/events.php bootstrap/cache/packages.php bootstrap/cache/services.php
"$PHP_CMD" artisan config:clear
"$PHP_CMD" artisan route:clear
"$PHP_CMD" artisan event:clear
"$PHP_CMD" artisan view:clear
- name: Ensure application key exists
working-directory: ${{ env.DEPLOY_ROOT }}/${{ env.PROJECT_NAME }}
run: |
grep -q '^APP_KEY=base64:' .env
- name: Verify database target
working-directory: ${{ env.DEPLOY_ROOT }}/${{ env.PROJECT_NAME }}
run: |
"$PHP_CMD" <<'PHP'
$env = [];
foreach (file(".env", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
$line = trim($line);
if ($line === "" || str_starts_with($line, "#") || ! str_contains($line, "=")) {
continue;
}
[$key, $value] = explode("=", $line, 2);
$env[$key] = trim($value, "\"'");
}
$connection = $env["DB_CONNECTION"] ?? "";
if (! in_array($connection, ["mysql", "mariadb"], true)) {
fwrite(STDERR, "DB_CONNECTION must be mysql or mariadb for this deployment. Current value: ".$connection."\n");
exit(1);
}
$host = $env["DB_HOST"] ?? "";
$port = (int) ($env["DB_PORT"] ?? 3306);
$database = $env["DB_DATABASE"] ?? "";
if ($host === "") {
fwrite(STDERR, "DB_HOST is missing from .env. Set DB_HOST to the MySQL server IP or hostname.\n");
exit(1);
}
if ($database === "") {
fwrite(STDERR, "DB_DATABASE is missing from .env.\n");
exit(1);
}
$socket = @fsockopen($host, $port, $errno, $errstr, 5);
if (! $socket) {
fwrite(STDERR, "Cannot reach MySQL at DB_HOST=".$host." DB_PORT=".$port.": ".$errstr." (".$errno.")\n");
exit(1);
}
fclose($socket);
echo "Database target reachable at DB_HOST=".$host." DB_PORT=".$port." DB_DATABASE=".$database."\n";
PHP
- name: Run Laravel migrations
working-directory: ${{ env.DEPLOY_ROOT }}/${{ env.PROJECT_NAME }}
run: |
"$PHP_CMD" artisan migrate --force
- name: Optimise Laravel
working-directory: ${{ env.DEPLOY_ROOT }}/${{ env.PROJECT_NAME }}
run: |
rm -f public/hot
rm -rf build
cp -a public/build build
rm -rf public/storage
ln -s ../storage/app/public public/storage
"$PHP_CMD" artisan config:clear
"$PHP_CMD" artisan route:clear
"$PHP_CMD" artisan view:clear