Skip to content
This repository was archived by the owner on Sep 20, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .github/workflows/deploy-production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Deploy to Production

# Deploy only after the tests workflow passes on main.
on:
workflow_run:
workflows: [tests]
types: [completed]
branches: [main]
workflow_dispatch:

concurrency: production_environment

permissions:
contents: read

jobs:
deploy:
if: github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push')
runs-on: ubuntu-latest
environment: production

steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: '8.4'
tools: composer:v2
coverage: none

# Deployer comes from require-dev.
- name: Install Composer dependencies
run: composer install --prefer-dist --no-progress --no-interaction

- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: 'npm'

- name: Create environment file
run: |
cp .env.example .env
php artisan key:generate

# deploy.php runs `npm ci && npm run build` before uploading public/build.
# Pin the revision so the server code matches the assets built here, even
# if main moves while this run is queued.
- name: Deploy to Production
uses: deployphp/action@v1
with:
private-key: ${{ secrets.SSH_PRIVATE_KEY }}
dep: deploy --revision=${{ github.event.workflow_run.head_sha || github.sha }}
env:
DEPLOY_HOSTNAME: ${{ vars.DEPLOY_HOSTNAME }}
DEPLOY_PATH: ${{ vars.DEPLOY_PATH }}
DEPLOY_SSH_PORT: ${{ secrets.DEPLOY_SSH_PORT }}
DEPLOY_USER: ${{ vars.DEPLOY_USER }}
DEPLOY_BRANCH: main
40 changes: 40 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: tests

on:
push:
branches:
- main
pull_request:

permissions:
contents: read

jobs:
ci:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# 8.4 is what php-fpm runs on the server.
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: '8.4'
tools: composer:v2
coverage: none

- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'

# composer setup writes .env, generates a key, migrates sqlite and builds assets.
- name: Setup Application
run: composer setup

- name: Run Tests
run: composer test
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"laravel/tinker": "^3.0"
},
"require-dev": {
"deployer/deployer": "^7.5",
"fakerphp/faker": "^1.23",
"laravel/boost": "^2.2",
"laravel/pail": "^1.2.5",
Expand Down Expand Up @@ -89,4 +90,4 @@
},
"minimum-stability": "stable",
"prefer-stable": true
}
}
56 changes: 55 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

148 changes: 148 additions & 0 deletions deploy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php

namespace Deployer;

require 'recipe/laravel.php';

/*
|--------------------------------------------------------------------------
| Basic Config
|--------------------------------------------------------------------------
*/

// The repository is public, so the server clones it over HTTPS and needs no
// deploy key.
set('repository', 'https://github.com/SourovCodes/Accounts.git');
set('branch', getenv('DEPLOY_BRANCH') ?: 'main');
set('keep_releases', 2);

/*
|--------------------------------------------------------------------------
| Laravel Shared & Writable Paths
|--------------------------------------------------------------------------
*/

set('shared_dirs', [
'storage',
]);

set('shared_files', [
'.env',
]);

set('writable_dirs', [
'storage',
'bootstrap/cache',
]);

set('writable_mode', 'chmod');

// Every class is in the classmap after install, so skip filesystem lookups.
set('composer_options', '--verbose --prefer-dist --no-progress --no-interaction --no-dev --optimize-autoloader --classmap-authoritative');

/*
|--------------------------------------------------------------------------
| Load Environment Variables
|--------------------------------------------------------------------------
*/

$hostname = getenv('DEPLOY_HOSTNAME');
$deployPath = getenv('DEPLOY_PATH');
$sshPort = getenv('DEPLOY_SSH_PORT');

if (! $hostname) {
throw new \RuntimeException('DEPLOY_HOSTNAME environment variable is required');
}

if (! $deployPath) {
throw new \RuntimeException('DEPLOY_PATH environment variable is required');
}

if (! $sshPort) {
throw new \RuntimeException('DEPLOY_SSH_PORT environment variable is required');
}

/*
|--------------------------------------------------------------------------
| Hosts
|--------------------------------------------------------------------------
*/

host($hostname)
->set('remote_user', getenv('DEPLOY_USER') ?: 'sourov')
->set('deploy_path', $deployPath)
->set('http_user', 'www-data')
->set('port', $sshPort);

/*
|--------------------------------------------------------------------------
| Local Asset Build
|--------------------------------------------------------------------------
*/

// The runner builds public/build; the server never installs npm.
task('build:assets', function () {
writeln('📦 Building assets locally...');
runLocally('npm ci');
runLocally('npm run build');
})->desc('Build assets locally');

/*
|--------------------------------------------------------------------------
| Upload Built Assets
|--------------------------------------------------------------------------
*/

task('upload:assets', function () {
writeln('🚀 Uploading built assets...');
$user = get('remote_user');
$hostname = currentHost()->getHostname();
$port = get('port');
$releasePath = get('release_path');
$archive = 'build-assets.tar.gz';

runLocally("tar -czf {$archive} -C public build");
runLocally("scp -P {$port} {$archive} {$user}@{$hostname}:{$releasePath}/");
run("tar -xzf {$releasePath}/{$archive} -C {$releasePath}/public/");
runLocally("rm {$archive}");
run("rm {$releasePath}/{$archive}");
})->desc('Upload built assets');

/*
|--------------------------------------------------------------------------
| Skip npm on Server
|--------------------------------------------------------------------------
*/

task('deploy:npm', function () {
writeln('⏭️ Skipping npm install on server');
});

/*
|--------------------------------------------------------------------------
| Restart Queue Workers
|--------------------------------------------------------------------------
*/

// No queued work ships yet, but the workers are running; queue:restart
// lets one finish its current job, then Supervisor starts it again on the
// new release. The signal travels through the cache, so the deploy user needs
// no rights over the www-data worker processes.
task('queue:restart', function () {
writeln('🔄 Gracefully restarting queue workers...');
run('cd {{release_path}} && php artisan queue:restart');
})->desc('Gracefully restart queue workers');

/*
|--------------------------------------------------------------------------
| Hooks
|--------------------------------------------------------------------------
*/

before('deploy', 'build:assets');

after('deploy:vendors', 'upload:assets');

after('deploy:symlink', 'queue:restart');

after('deploy:failed', 'deploy:unlock');
Loading