#!/bin/bash # Laravel Base Setup Script # Run this after post-install.sh to configure auth, API, and base structure # Usage: ./scripts/laravel-setup.sh set -e echo "==========================================" echo "Laravel Base Setup" echo "==========================================" # Check if we're in a Laravel project if [ ! -f "artisan" ]; then echo "Error: Not in a Laravel project directory" echo "Run this from your Laravel project root (src/)" exit 1 fi # ============================================ # AUTH SCAFFOLDING (Blade-focused, no JS frameworks) # ============================================ echo "" echo "============================================" echo "AUTHENTICATION SETUP (Blade-based)" echo "============================================" echo "" echo "Choose authentication scaffolding:" echo "" echo "1) Laravel Breeze + Blade (Recommended)" echo " - Simple, server-side rendered" echo " - Login, register, password reset, email verification" echo " - Tailwind CSS styling" echo "" echo "2) Laravel Breeze + Livewire" echo " - Reactive components without writing JavaScript" echo " - Same features as Blade with dynamic updates" echo "" echo "3) Laravel Breeze API only" echo " - Headless API authentication" echo " - For when you build your own Blade views" echo "" echo "4) Laravel Jetstream + Livewire (Full-featured)" echo " - Profile management, 2FA, API tokens" echo " - Optional teams feature" echo " - Best for SaaS applications" echo "" echo "5) None (Manual setup later)" echo "" read -p "Enter choice [1-5]: " AUTH_CHOICE case $AUTH_CHOICE in 1) echo "" echo "Installing Breeze with Blade..." composer require laravel/breeze --dev php artisan breeze:install blade php artisan migrate npm install && npm run build echo "Breeze (Blade) installed successfully!" ;; 2) echo "" echo "Installing Breeze with Livewire..." composer require laravel/breeze --dev php artisan breeze:install livewire php artisan migrate npm install && npm run build echo "Breeze (Livewire) installed successfully!" ;; 3) echo "" echo "Installing Breeze API only..." composer require laravel/breeze --dev php artisan breeze:install api php artisan migrate echo "Breeze (API) installed successfully!" echo "Build your own Blade views for the frontend." ;; 4) echo "" read -p "Enable Teams feature? [y/N]: " ENABLE_TEAMS composer require laravel/jetstream TEAMS_FLAG="" if [[ "$ENABLE_TEAMS" =~ ^[Yy]$ ]]; then TEAMS_FLAG="--teams" fi php artisan jetstream:install livewire $TEAMS_FLAG php artisan migrate npm install && npm run build echo "Jetstream (Livewire) installed successfully!" ;; 5) echo "Skipping auth scaffolding." ;; esac # ============================================ # TESTING (PEST) # ============================================ echo "" echo "============================================" echo "TESTING FRAMEWORK (Pest)" echo "============================================" echo "" echo "Pest is a testing framework with elegant syntax:" echo " - Clean, readable test syntax" echo " - Built on top of PHPUnit" echo " - Great for unit & feature tests" echo "" read -p "Install Pest testing framework? [Y/n]: " INSTALL_PEST if [[ ! "$INSTALL_PEST" =~ ^[Nn]$ ]]; then echo "" echo "Installing Pest..." composer require pestphp/pest --dev --with-all-dependencies composer require pestphp/pest-plugin-laravel --dev # Initialize Pest php artisan pest:install # Configure phpunit.xml for SQLite in-memory if [ -f "phpunit.xml" ]; then # Add SQLite in-memory for testing sed -i 's|||' phpunit.xml sed -i 's|||' phpunit.xml fi # Create tests directory structure mkdir -p tests/Feature mkdir -p tests/Unit mkdir -p tests/Modules # Create Pest.php helper with useful traits cat > tests/Pest.php << 'PEST' in('Feature', 'Modules'); uses(Tests\TestCase::class)->in('Unit'); /* |-------------------------------------------------------------------------- | Database Refresh |-------------------------------------------------------------------------- | Uses LazilyRefreshDatabase for faster tests - only migrates when needed. */ uses(LazilyRefreshDatabase::class)->in('Feature', 'Modules'); /* |-------------------------------------------------------------------------- | Expectations |-------------------------------------------------------------------------- */ expect()->extend('toBeOne', function () { return $this->toBe(1); }); /* |-------------------------------------------------------------------------- | Functions |-------------------------------------------------------------------------- */ function createUser(array $attributes = []): \App\Models\User { return \App\Models\User::factory()->create($attributes); } function createAdmin(array $attributes = []): \App\Models\User { $user = createUser($attributes); $user->assignRole('admin'); return $user; } PEST # Create example feature test cat > tests/Feature/ExampleTest.php << 'TEST' get('/'); $response->assertStatus(200); }); it('redirects guests from admin to login', function () { $response = $this->get('/admin'); $response->assertRedirect('/admin/login'); }); TEST # Create example unit test cat > tests/Unit/ExampleTest.php << 'TEST' toBeTrue(); expect(1 + 1)->toBe(2); expect(['a', 'b', 'c'])->toContain('b'); }); it('can use Laravel helpers', function () { expect(config('app.name'))->toBeString(); expect(app())->toBeInstanceOf(\Illuminate\Foundation\Application::class); }); TEST # Create module test helper cat > tests/Modules/.gitkeep << 'GITKEEP' # Module tests go here # Create subdirectories per module, e.g., tests/Modules/Inventory/ GITKEEP # Create test helper for modules cat > tests/TestHelpers.php << 'HELPER' create(); foreach ($permissions as $permission) { $user->givePermissionTo($permission); } return $user; } /** * Create a user with module access. */ protected function userWithModuleAccess(string $moduleSlug): \App\Models\User { return $this->userWithPermissions([ "{$moduleSlug}.view", "{$moduleSlug}.create", "{$moduleSlug}.edit", "{$moduleSlug}.delete", ]); } /** * Assert model was audited. */ protected function assertAudited($model, string $event = 'created'): void { $this->assertDatabaseHas('audits', [ 'auditable_type' => get_class($model), 'auditable_id' => $model->id, 'event' => $event, ]); } } HELPER echo "" echo "Pest installed!" echo "Run tests with: php artisan test" echo "Or: ./vendor/bin/pest" fi # ============================================ # FILAMENT ADMIN PANEL # ============================================ echo "" echo "============================================" echo "ADMIN PANEL (Filament)" echo "============================================" echo "" echo "Filament provides a full-featured admin panel with:" echo " - User management (list, create, edit, delete)" echo " - Role & permission management" echo " - Dashboard widgets" echo " - Form & table builders" echo "" read -p "Install Filament Admin Panel? [Y/n]: " INSTALL_FILAMENT if [[ ! "$INSTALL_FILAMENT" =~ ^[Nn]$ ]]; then echo "" echo "Installing Filament..." composer require filament/filament:"^3.2" -W php artisan filament:install --panels echo "" echo "Creating admin user..." php artisan make:filament-user # Create UserResource for user management echo "" echo "Creating User management resource..." php artisan make:filament-resource User --generate echo "" echo "Filament installed successfully!" echo "Admin panel available at: /admin" # Install Site Settings echo "" echo "============================================" echo "SITE SETTINGS (Appearance)" echo "============================================" echo "" echo "Site settings allow you to manage:" echo " - Logo and favicon" echo " - Color scheme" echo " - Site name" echo "" read -p "Install site settings? [Y/n]: " INSTALL_SETTINGS if [[ ! "$INSTALL_SETTINGS" =~ ^[Nn]$ ]]; then echo "" echo "Installing spatie/laravel-settings..." composer require spatie/laravel-settings composer require filament/spatie-laravel-settings-plugin:"^3.2" php artisan vendor:publish --provider="Spatie\LaravelSettings\LaravelSettingsServiceProvider" --tag="migrations" php artisan migrate # Create Settings directory mkdir -p app/Settings # Create SiteSettings class cat > app/Settings/SiteSettings.php << 'SETTINGS' database/settings/$(date +%Y_%m_%d_%H%M%S)_create_site_settings.php << 'MIGRATION' migrator->add('site.site_name', config('app.name', 'Laravel')); $this->migrator->add('site.logo', null); $this->migrator->add('site.favicon', null); $this->migrator->add('site.primary_color', '#3b82f6'); $this->migrator->add('site.secondary_color', '#64748b'); $this->migrator->add('site.dark_mode', false); $this->migrator->add('site.footer_text', null); } }; MIGRATION # Run settings migration php artisan migrate # Create Filament Settings Page mkdir -p app/Filament/Pages cat > app/Filament/Pages/ManageSiteSettings.php << 'PAGE' schema([ Forms\Components\Section::make('General') ->description('Basic site information') ->schema([ Forms\Components\TextInput::make('site_name') ->label('Site Name') ->required() ->maxLength(255), Forms\Components\FileUpload::make('logo') ->label('Logo') ->image() ->directory('site') ->visibility('public') ->imageResizeMode('contain') ->imageCropAspectRatio('16:9') ->imageResizeTargetWidth('400') ->helperText('Recommended: 400x100px or similar aspect ratio'), Forms\Components\FileUpload::make('favicon') ->label('Favicon') ->image() ->directory('site') ->visibility('public') ->imageResizeTargetWidth('32') ->imageResizeTargetHeight('32') ->helperText('Will be resized to 32x32px'), ]), Forms\Components\Section::make('Appearance') ->description('Customize the look and feel') ->schema([ Forms\Components\ColorPicker::make('primary_color') ->label('Primary Color') ->required(), Forms\Components\ColorPicker::make('secondary_color') ->label('Secondary Color') ->required(), Forms\Components\Toggle::make('dark_mode') ->label('Enable Dark Mode') ->helperText('Allow users to switch to dark mode'), ]), Forms\Components\Section::make('Footer') ->schema([ Forms\Components\Textarea::make('footer_text') ->label('Footer Text') ->rows(2) ->placeholder('© 2024 Your Company. All rights reserved.'), ]), ]); } } PAGE # Create helper function cat > app/Helpers/site.php << 'HELPER' {$key} ?? $default; } } if (!function_exists('site_logo')) { function site_logo(): ?string { $logo = site_settings('logo'); return $logo ? asset('storage/' . $logo) : null; } } if (!function_exists('site_favicon')) { function site_favicon(): ?string { $favicon = site_settings('favicon'); return $favicon ? asset('storage/' . $favicon) : null; } } if (!function_exists('site_name')) { function site_name(): string { return site_settings('site_name', config('app.name')); } } if (!function_exists('primary_color')) { function primary_color(): string { return site_settings('primary_color', '#3b82f6'); } } if (!function_exists('secondary_color')) { function secondary_color(): string { return site_settings('secondary_color', '#64748b'); } } HELPER # Register helper in composer.json autoload php -r " \$composer = json_decode(file_get_contents('composer.json'), true); \$composer['autoload']['files'] = \$composer['autoload']['files'] ?? []; if (!in_array('app/Helpers/site.php', \$composer['autoload']['files'])) { \$composer['autoload']['files'][] = 'app/Helpers/site.php'; } file_put_contents('composer.json', json_encode(\$composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); " composer dump-autoload # Create Blade component mkdir -p app/View/Components cat > app/View/Components/SiteHead.php << 'COMPONENT' resources/views/components/site-head.blade.php << 'BLADE' {{-- Site Head Component - Include in your --}} {{ $title ? $title . ' - ' . site_name() : site_name() }} @if(site_favicon()) @endif BLADE echo "" echo "Site settings installed!" echo "Access at: /admin → Settings → Appearance" fi # Install Audit Trail echo "" echo "============================================" echo "AUDIT TRAIL" echo "============================================" echo "" echo "Audit trail tracks all changes to your data:" echo " - Who made the change" echo " - What was changed (old → new values)" echo " - When it happened" echo " - Which module/model" echo "" read -p "Install audit trail system? [Y/n]: " INSTALL_AUDIT if [[ ! "$INSTALL_AUDIT" =~ ^[Nn]$ ]]; then echo "" echo "Installing owen-it/laravel-auditing..." composer require owen-it/laravel-auditing php artisan vendor:publish --provider="OwenIt\Auditing\AuditingServiceProvider" --tag="config" php artisan vendor:publish --provider="OwenIt\Auditing\AuditingServiceProvider" --tag="migrations" php artisan migrate # Install Filament Auditing plugin echo "" echo "Installing Filament audit trail UI..." composer require tapp/filament-auditing:"^3.0" php artisan vendor:publish --tag="filament-auditing-config" # Create base Auditable trait for modules mkdir -p app/Traits cat > app/Traits/ModuleAuditable.php << 'TRAIT' getModuleName(), ]; } /** * Attributes to include in the audit. * Override in your model to customize. */ // protected $auditInclude = []; /** * Attributes to exclude from the audit. * Override in your model to customize. */ // protected $auditExclude = []; /** * Audit strategy: 'all', 'include', 'exclude' * Set via module config or model property. */ public function getAuditStrategy(): string { $moduleName = $this->getModuleName(); $configKey = strtolower(preg_replace('/(?getModuleName(); $configKey = strtolower(preg_replace('/(?createToken('token-name')" fi # ============================================ # STORAGE LINK # ============================================ echo "" echo "Creating storage symlink..." php artisan storage:link # ============================================ # BASE MIDDLEWARE # ============================================ echo "" echo "============================================" echo "ADDITIONAL SETUP" echo "============================================" # Force HTTPS in production read -p "Add ForceHttps middleware for production? [Y/n]: " ADD_HTTPS if [[ ! "$ADD_HTTPS" =~ ^[Nn]$ ]]; then mkdir -p app/Http/Middleware cat > app/Http/Middleware/ForceHttps.php << 'MIDDLEWARE' environment('production') && !$request->secure()) { return redirect()->secure($request->getRequestUri()); } return $next($request); } } MIDDLEWARE echo "ForceHttps middleware created at app/Http/Middleware/ForceHttps.php" echo "Register in bootstrap/app.php or routes to enable." fi # Security Headers Middleware read -p "Add SecurityHeaders middleware? [Y/n]: " ADD_SECURITY if [[ ! "$ADD_SECURITY" =~ ^[Nn]$ ]]; then cat > app/Http/Middleware/SecurityHeaders.php << 'MIDDLEWARE' headers->set('X-Frame-Options', 'SAMEORIGIN'); $response->headers->set('X-Content-Type-Options', 'nosniff'); $response->headers->set('X-XSS-Protection', '1; mode=block'); $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin'); $response->headers->set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()'); if (app()->environment('production')) { $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); } return $response; } } MIDDLEWARE echo "SecurityHeaders middleware created at app/Http/Middleware/SecurityHeaders.php" fi # ============================================ # MODULE SYSTEM SETUP # ============================================ echo "" echo "============================================" echo "MODULE SYSTEM" echo "============================================" echo "" echo "The modular architecture allows you to organize features" echo "into self-contained modules with their own admin panels." echo "" read -p "Install module system (spatie/laravel-permission + make:module command)? [Y/n]: " INSTALL_MODULES if [[ ! "$INSTALL_MODULES" =~ ^[Nn]$ ]]; then echo "" echo "Installing Spatie Permission for role-based access..." composer require spatie/laravel-permission php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" php artisan migrate echo "" echo "Setting up module system..." # Create Modules directory mkdir -p app/Modules # Copy ModuleServiceProvider if [ -f "../src/app/Providers/ModuleServiceProvider.php.stub" ]; then cp ../src/app/Providers/ModuleServiceProvider.php.stub app/Providers/ModuleServiceProvider.php else # Create inline if stub not found cat > app/Providers/ModuleServiceProvider.php << 'PROVIDER' app->register($providerClass); } } } public function boot(): void { $modulesPath = app_path('Modules'); if (!File::isDirectory($modulesPath)) return; foreach (File::directories($modulesPath) as $modulePath) { $moduleName = basename($modulePath); // Load routes $webRoutes = "{$modulePath}/Routes/web.php"; $apiRoutes = "{$modulePath}/Routes/api.php"; if (File::exists($webRoutes)) $this->loadRoutesFrom($webRoutes); if (File::exists($apiRoutes)) $this->loadRoutesFrom($apiRoutes); // Load views $viewsPath = "{$modulePath}/Resources/views"; if (File::isDirectory($viewsPath)) { $slug = strtolower(preg_replace('/(?loadViewsFrom($viewsPath, $slug); } // Load migrations $migrationsPath = "{$modulePath}/Database/Migrations"; if (File::isDirectory($migrationsPath)) { $this->loadMigrationsFrom($migrationsPath); } } } } PROVIDER fi # Copy MakeModule command mkdir -p app/Console/Commands if [ -f "../src/app/Console/Commands/MakeModule.php.stub" ]; then cp ../src/app/Console/Commands/MakeModule.php.stub app/Console/Commands/MakeModule.php fi # Register ModuleServiceProvider in bootstrap/providers.php if [ -f "bootstrap/providers.php" ]; then if ! grep -q "ModuleServiceProvider" bootstrap/providers.php; then sed -i 's/];/ App\\Providers\\ModuleServiceProvider::class,\n];/' bootstrap/providers.php fi fi # Create base permission seeder cat > database/seeders/PermissionSeeder.php << 'SEEDER' 'admin', 'guard_name' => 'web']); $userRole = Role::firstOrCreate(['name' => 'user', 'guard_name' => 'web']); // Load permissions from all modules $modulesPath = app_path('Modules'); if (File::isDirectory($modulesPath)) { foreach (File::directories($modulesPath) as $modulePath) { $permissionsFile = "{$modulePath}/Permissions.php"; if (File::exists($permissionsFile)) { $permissions = require $permissionsFile; foreach ($permissions as $name => $description) { Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web']); } // Give admin all module permissions $adminRole->givePermissionTo(array_keys($permissions)); } } } } } SEEDER echo "" echo "Module system installed!" echo "" echo "Create modules with: php artisan make:module ModuleName" echo "Options:" echo " --model=Product Create a model with Filament resource" echo " --api Include API routes" echo " --no-filament Skip Filament integration" fi # ============================================ # CACHE CONFIGURATION # ============================================ echo "" echo "Clearing and caching configuration..." php artisan config:clear php artisan cache:clear php artisan view:clear php artisan route:clear # ============================================ # FINAL OUTPUT # ============================================ echo "" echo "==========================================" echo "Laravel Base Setup Complete!" echo "==========================================" echo "" echo "What was configured:" echo " ✓ Authentication scaffolding (based on selection)" echo " ✓ Filament Admin Panel (if selected)" echo " ✓ Sanctum API authentication" echo " ✓ Storage symlink" echo " ✓ Security middleware" echo " ✓ Module system (if selected)" echo "" echo "Next steps:" echo " 1. Review and customize routes in routes/" echo " 2. Add middleware to bootstrap/app.php if needed" echo " 3. Create your first module: php artisan make:module YourModule" echo " 4. Start building your application!" echo "" echo "Useful commands:" echo " make up - Start development server" echo " make test - Run tests" echo " make lint - Fix code style" echo " make fresh - Reset database with seeders" echo " php artisan make:module Name - Create a new module" echo ""