From eafa430f6e4bf74eb40470fa6b20d8a25ad134f7 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 02:38:22 +1000 Subject: [PATCH 01/16] Add robots.txt to manage web crawler access and sitemap location & albatross blog --- _posts/2025-06-10-albatross.md | 0 robots.txt | 14 ++++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 _posts/2025-06-10-albatross.md create mode 100644 robots.txt diff --git a/_posts/2025-06-10-albatross.md b/_posts/2025-06-10-albatross.md new file mode 100644 index 0000000..e69de29 diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..edbfa90 --- /dev/null +++ b/robots.txt @@ -0,0 +1,14 @@ +User-agent: * +Allow: / + +# Disallow crawling of preview/staging content +Disallow: /preview/ +Disallow: /staging/ + +# Allow crawling of main site content +Allow: /assets/ +Allow: /tag/ +Allow: /blog/ + +# Sitemap location +Sitemap: https://devnomadic.github.io/sitemap.xml From 852d754d72512dc3a225e9335de614601cdebe5b Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 02:38:30 +1000 Subject: [PATCH 02/16] Remove unnecessary comments and trailing newlines from remote development environment and welcome posts --- ...25-06-08-remote-development-environment.md | 4 +- _posts/2025-06-09-welcome-to-devnomadic.md | 4 +- _posts/2025-06-10-albatross.md | 561 ++++++++++++++++++ 3 files changed, 563 insertions(+), 6 deletions(-) diff --git a/_posts/2025-06-08-remote-development-environment.md b/_posts/2025-06-08-remote-development-environment.md index 4ff4143..a9e0eb7 100644 --- a/_posts/2025-06-08-remote-development-environment.md +++ b/_posts/2025-06-08-remote-development-environment.md @@ -127,6 +127,4 @@ The perfect remote development environment is personal and evolves with your nee The key is having redundancy in everything - internet, power, workspace options. This ensures you can always deliver quality work regardless of where you are in the world. ---- - -*What's your remote development setup? Share your tips in the comments!* +--- \ No newline at end of file diff --git a/_posts/2025-06-09-welcome-to-devnomadic.md b/_posts/2025-06-09-welcome-to-devnomadic.md index b744cc1..430fcb2 100644 --- a/_posts/2025-06-09-welcome-to-devnomadic.md +++ b/_posts/2025-06-09-welcome-to-devnomadic.md @@ -45,6 +45,4 @@ I'm excited to share this journey with you. Whether you're a fellow developer cu Let's build something amazing together, from anywhere in the world! 🚀 ---- - -*Currently writing from: [Your Current Location]* +--- \ No newline at end of file diff --git a/_posts/2025-06-10-albatross.md b/_posts/2025-06-10-albatross.md index e69de29..097bcd0 100644 --- a/_posts/2025-06-10-albatross.md +++ b/_posts/2025-06-10-albatross.md @@ -0,0 +1,561 @@ +--- +layout: post +title: Albatross +description: "Albatross Cloud IP maifest & reputation search" +date: 2025-06-10 +tags: BlazorWebAssembly CloudflareWorkers DevSecOps WebDevelopment CyberSecurity +--- + +# Building Albatross: A Secure IP Abuse Checker with Blazor WebAssembly and Cloudflare Workers + +*Published: June 9, 2025* + +In an era where cybersecurity threats are constantly evolving, having reliable tools to check IP addresses for malicious activity has become essential. Today, I'm excited to share the journey of building **Albatross** - a secure, modern web application that leverages the AbuseIPDB API to provide real-time IP abuse checking through a sophisticated architecture combining Blazor WebAssembly and Cloudflare Workers. + +## The Challenge: Secure API Proxy Architecture + +When building client-side applications that need to interact with third-party APIs, developers face a common dilemma: how do you protect sensitive API keys while maintaining a seamless user experience? Traditional approaches often involve: + +1. **Exposing API keys in client code** (security nightmare) +2. **Building a full backend service** (overhead and complexity) +3. **Using serverless functions** (cold starts and vendor lock-in) + +For Albatross, I chose a different path: **Cloudflare Workers as a secure API proxy** with a sophisticated authentication system that generates unique keys at build time. + +## Architecture Overview + +``` +┌─────────────────┐ HMAC Auth ┌─────────────────┐ API Key ┌─────────────────┐ +│ │ ──────────→ │ │ ──────────→ │ │ +│ Blazor WASM │ │ Cloudflare │ │ AbuseIPDB │ +│ Client │ ←────────── │ Worker │ ←────────── │ API │ +│ │ CORS + JSON │ │ JSON Data │ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +### Key Components: + +1. **Blazor WebAssembly Frontend**: Modern, responsive UI built with .NET 8 +2. **Cloudflare Worker Proxy**: Secure middleware handling authentication and API calls +3. **Build-Time Key Generation**: Cryptographically secure authentication keys generated per build +4. **HMAC Authentication**: Request signing using SHA-256 for tamper-proof communication + +## The Security Foundation: Build-Time Key Generation + +One of the most innovative aspects of Albatross is its build-time authentication system. Instead of using static API keys or tokens, the system generates a unique 256-bit cryptographic key for every build. + +### PowerShell Key Generation Script + +```powershell +# Generate-AuthKey.ps1 +function Generate-SecureKey { + $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create() + $keyBytes = New-Object byte[] 32 # 256 bits + $rng.GetBytes($keyBytes) + return [Convert]::ToBase64String($keyBytes) +} + +$authKey = Generate-SecureKey +$buildId = [System.Guid]::NewGuid().ToString("N").Substring(0, 8) +$timestamp = Get-Date -Format "yyyyMMdd-HHmm" +``` + +This approach provides several security benefits: + +- **Automatic Key Rotation**: Every build gets a fresh authentication key +- **No Hardcoded Secrets**: Keys are generated dynamically and never stored in source code +- **Build Integrity**: Each deployment has a unique authentication signature + +### MSBuild Integration + +The key generation is seamlessly integrated into the .NET build process using custom MSBuild targets: + +```xml + + + + + + + +``` + +## HMAC Authentication Implementation + +The authentication system uses HMAC-SHA256 to sign requests, ensuring both authenticity and integrity. + +### Client-Side Signing (C#) + +```csharp +private string GenerateHmacToken(string requestUrl) +{ + using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(BuildConstants.AuthKey)); + var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(requestUrl.ToLower())); + return Convert.ToBase64String(hash); +} +``` + +### Worker-Side Validation (JavaScript) + +```javascript +async function validateHmacToken(receivedToken, requestUrl) { + const keyBytes = new TextEncoder().encode(AUTH_KEY); + const messageBytes = new TextEncoder().encode(requestUrl); + + const cryptoKey = await crypto.subtle.importKey( + 'raw', keyBytes, + { name: 'HMAC', hash: 'SHA-256' }, + false, ['sign'] + ); + + const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageBytes); + const expectedToken = arrayBufferToBase64(signature); + + return expectedToken === receivedToken; +} +``` + +## Advanced CORS and Security Features + +Beyond basic authentication, Albatross implements sophisticated browser-only validation to prevent abuse from non-browser clients like Postman or curl. + +### Browser-Only Validation + +```javascript +// Enforce browser-only access +const userAgent = request.headers.get('User-Agent') || ''; +const isBrowserRequest = origin && ( + userAgent.includes('Mozilla') || + userAgent.includes('Chrome') || + userAgent.includes('Safari') || + userAgent.includes('Firefox') || + userAgent.includes('Edge') +); + +const hasValidOrigin = origin && isAllowedOrigin(origin); + +if (!isBrowserRequest || !hasValidOrigin) { + return new Response(JSON.stringify({ + error: "Access denied: Browser requests from allowed origins only" + }), { status: 403 }); +} +``` + +### Restrictive CORS Policy + +```javascript +function corsHeaders(origin) { + const headers = { + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Worker-Token, Authorization', + 'Access-Control-Max-Age': '86400', + }; + + if (origin && isAllowedOrigin(origin)) { + headers['Access-Control-Allow-Origin'] = origin; + headers['Access-Control-Allow-Credentials'] = 'true'; + } else { + headers['Access-Control-Allow-Origin'] = 'null'; + } + + return headers; +} +``` + +## Automated Deployment Pipeline + +The project uses GitHub Actions for comprehensive CI/CD that handles both the SPA and Worker deployments: + +### Production Workflow + +```yaml +name: Deploy Production +on: + push: + branches: [ main ] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Generate Authentication Keys + run: pwsh Generate-AuthKey.ps1 + + - name: Build Blazor App + run: dotnet publish -c Release + + - name: Deploy to Cloudflare Pages + uses: cloudflare/pages-action@v1 + + - name: Deploy Worker + run: wrangler publish cloudflare-worker.js +``` + +## Lessons Learned and Best Practices + +### 1. Security Through Obscurity Isn't Enough + +While the build-time key generation adds a layer of security, the real protection comes from: +- Proper CORS implementation +- Origin validation +- Browser-only access controls +- Request signing with HMAC + +### 2. PowerShell for Cross-Platform Build Scripts + +Using PowerShell Core for build scripts provides: +- Cross-platform compatibility (Windows, macOS, Linux) +- Rich cryptographic APIs +- Easy integration with .NET projects +- Consistent behavior across environments + +### 3. Cloudflare Workers Are Perfect for API Proxies + +The combination of: +- Global edge deployment +- Zero cold starts +- Built-in security features +- Excellent developer experience + +Makes Cloudflare Workers ideal for API proxy scenarios. + +### 4. MSBuild Custom Targets for Complex Builds + +Custom MSBuild targets allow for: +- Seamless integration with the .NET build process +- Automatic execution during development and CI/CD +- Consistent behavior across different environments +- Easy maintenance and updates + +### 5. AI-Powered Development with GitHub Copilot + +Building Albatross was significantly accelerated by leveraging **GitHub Copilot powered by Claude Sonnet**. The AI assistant proved invaluable for: + +- **Complex Algorithm Implementation**: Generated efficient CIDR matching logic and HMAC authentication code +- **Security Pattern Recognition**: Suggested best practices for CORS implementation and origin validation +- **PowerShell Automation**: Helped create robust build scripts with proper error handling +- **Architecture Decisions**: Provided insights on Cloudflare Worker optimization and WebAssembly performance +- **Documentation**: Assisted in writing comprehensive code comments and technical documentation + +The combination of human creativity and AI-powered code generation allowed for rapid prototyping and implementation of sophisticated security features that might have taken significantly longer to develop manually. Claude Sonnet's deep understanding of security patterns and modern web architecture made it an ideal pair programming partner for this project. + +## Performance and Monitoring + +The application includes comprehensive logging and monitoring: + +```javascript +console.log('HMAC validation:', { + message: message.substring(0, 100) + '...', + expectedToken: expectedToken.substring(0, 20) + '...', + receivedToken: receivedToken.substring(0, 20) + '...', + authKeySource: BUILD_INFO.keySource +}); +``` + +Response times are consistently under 100ms thanks to: +- Cloudflare's global edge network +- Efficient HMAC validation +- Minimal payload sizes +- Strategic caching headers + +## Future Enhancements + +Several exciting features are planned for future releases: + +1. **Rate Limiting**: Implement per-IP rate limiting in the Worker +2. **Analytics Dashboard**: Track usage patterns and abuse attempts +3. **API Key Rotation**: Automated rotation of AbuseIPDB API keys +4. **Multi-Provider Support**: Integration with additional threat intelligence APIs +5. **Real-Time Notifications**: WebSocket-based alerts for high-risk IPs + +## Cloud IP Manifest Search: Identifying Cloud Infrastructure + +One of Albatross's most valuable features is its **Cloud IP Manifest Search** functionality, which allows users to identify whether any IP address belongs to major cloud service providers. This feature leverages official IP range manifests from AWS, Microsoft Azure, and Google Cloud Platform to provide accurate cloud infrastructure attribution. + +### The Challenge of Cloud IP Attribution + +In today's cloud-first world, understanding which cloud provider owns a specific IP address is crucial for: + +- **Network Security**: Identifying legitimate cloud traffic vs. potential threats +- **Compliance**: Understanding data flow through different cloud jurisdictions +- **Infrastructure Analysis**: Mapping application dependencies and service architectures +- **Incident Response**: Quickly identifying the source cloud provider during security investigations + +### Architecture and Data Sources + +The system maintains up-to-date IP range manifests from three major cloud providers: + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ AWS IP Ranges │ │ Azure IP Ranges │ │ GCP IP Ranges │ +│ │ │ │ │ │ +│ • 76,000+ IPs │ │ • 135,000+ IPs │ │ • 3,100+ IPs │ +│ • Regional Data │ │ • Service Tags │ │ • Scope Data │ +│ • Service Info │ │ • Platform Info │ │ • Global Ranges │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ │ + └───────────────────────┼───────────────────────┘ + │ + ┌─────────────────┐ + │ Albatross │ + │ Search Engine │ + │ │ + │ • CIDR Matching │ + │ • IPv4 & IPv6 │ + │ • Real-time │ + └─────────────────┘ +``` + +### Official Data Sources + +#### AWS IP Ranges +- **Source**: `https://ip-ranges.amazonaws.com/ip-ranges.json` +- **Updates**: Multiple times daily +- **Contains**: Service names, regions, network border groups +- **Format**: CIDR blocks with metadata + +```json +{ + "ip_prefix": "3.5.140.0/22", + "region": "ap-northeast-2", + "service": "AMAZON", + "network_border_group": "ap-northeast-2" +} +``` + +#### Microsoft Azure Service Tags +- **Source**: Dynamic URL from Microsoft Download Center +- **Updates**: Weekly (typically Mondays) +- **Contains**: Service names, regions, platform information +- **Format**: Hierarchical service groupings + +```json +{ + "name": "AzureCognitiveSearch.EastUS", + "properties": { + "region": "eastus", + "platform": "Azure", + "systemService": "AzureCognitiveSearch", + "addressPrefixes": ["20.42.4.128/26"] + } +} +``` + +#### Google Cloud Platform IP Ranges +- **Source**: `https://www.gstatic.com/ipranges/cloud.json` +- **Updates**: As needed (typically weekly) +- **Contains**: Service types, regional scopes +- **Format**: Simplified CIDR list with scope information + +```json +{ + "ipv4Prefix": "34.1.208.0/20", + "service": "Google Cloud", + "scope": "africa-south1" +} +``` + +### Smart Search Algorithm + +The cloud IP detection uses a sophisticated CIDR (Classless Inter-Domain Routing) matching algorithm: + +```csharp +private bool IsIpInRange(IPAddress ipAddress, string cidrRange) +{ + if (string.IsNullOrEmpty(cidrRange) || !cidrRange.Contains('/')) + return false; + + try + { + var parts = cidrRange.Split('/'); + var networkAddress = IPAddress.Parse(parts[0]); + var prefixLength = int.Parse(parts[1]); + + // Convert to bytes for bitwise operations + var ipBytes = ipAddress.GetAddressBytes(); + var networkBytes = networkAddress.GetAddressBytes(); + + // Handle IPv4 vs IPv6 compatibility + if (ipBytes.Length != networkBytes.Length) + return false; + + // Calculate subnet mask + var totalBits = ipBytes.Length * 8; + var maskBits = prefixLength; + + // Perform bitwise comparison + for (int byteIndex = 0; byteIndex < ipBytes.Length; byteIndex++) + { + var bitsInThisByte = Math.Min(8, Math.Max(0, maskBits - (byteIndex * 8))); + if (bitsInThisByte == 0) break; + + var mask = (byte)(0xFF << (8 - bitsInThisByte)); + if ((ipBytes[byteIndex] & mask) != (networkBytes[byteIndex] & mask)) + return false; + } + + return true; + } + catch + { + return false; + } +} +``` + +### Real-Time Search Implementation + +The search process efficiently scans through hundreds of thousands of IP ranges: + +```csharp +private async Task ValidateAndSearch() +{ + // Parse and validate input IP + IPAddress enteredIp = IPAddress.Parse(ipAddress); + bool foundMatch = false; + + // Search Azure IP ranges (135,000+ entries) + var azureData = await Http.GetStringAsync("ip-manifests/Azure.json"); + var azureRanges = JsonSerializer.Deserialize(azureData); + + foreach (var value in azureRanges.values) + { + foreach (var prefix in value.properties.addressPrefixes) + { + if (IsIpInRange(enteredIp, prefix)) + { + azureMatchedServices.Add(value.name); + foundMatch = true; + } + } + } + + // Search AWS IP ranges (76,000+ entries) + var awsData = await Http.GetStringAsync("ip-manifests/AWS.json"); + var awsRanges = JsonSerializer.Deserialize(awsData); + + foreach (var prefix in awsRanges.prefixes) + { + if (IsIpInRange(enteredIp, prefix.ip_prefix)) + { + string serviceInfo = $"{prefix.service} ({prefix.region})"; + awsMatchedServices.Add(serviceInfo); + foundMatch = true; + } + } + + // Search GCP IP ranges (3,100+ entries) + var gcpData = await Http.GetStringAsync("ip-manifests/GCP.json"); + var gcpRanges = JsonSerializer.Deserialize(gcpData); + + foreach (var prefix in gcpRanges.prefixes) + { + string ipRange = prefix.ipv4Prefix ?? prefix.ipv6Prefix; + if (IsIpInRange(enteredIp, ipRange)) + { + string serviceInfo = $"{prefix.service} ({prefix.scope ?? "Global"})"; + gcpMatchedServices.Add(serviceInfo); + foundMatch = true; + } + } +} +``` + +### Automated Manifest Updates + +To ensure data freshness, Albatross includes an automated update system: + +```bash +#!/bin/sh +echo "🌐 Updating cloud IP manifests..." + +# Fetch AWS IP ranges +curl -sSL "https://ip-ranges.amazonaws.com/ip-ranges.json" \ + -o ./wwwroot/ip-manifests/AWS.json + +# Fetch GCP IP ranges +curl -sSL "https://www.gstatic.com/ipranges/cloud.json" \ + -o ./wwwroot/ip-manifests/GCP.json + +# Fetch Azure IP ranges (dynamic URL discovery) +curl -sSL "https://www.microsoft.com/en-us/download/confirmation.aspx?id=56519" \ + > /tmp/azure_page.html +AZURE_URL=$(grep -o 'https://download.microsoft.com/download/[^"]*ServiceTags[^"]*\.json' \ + /tmp/azure_page.html | head -1) +curl -sSL "$AZURE_URL" -o ./wwwroot/ip-manifests/Azure.json +``` + +### Performance Optimizations + +Despite searching through 200,000+ IP ranges, the system maintains excellent performance: + +1. **Client-Side Processing**: All searches run in the browser using WebAssembly +2. **Efficient Algorithms**: Optimized CIDR matching with early termination +3. **Cached Manifests**: Static JSON files served via CDN +4. **Parallel Processing**: Simultaneous searches across all three providers +5. **Smart Indexing**: Future enhancement for sub-second responses + +### Example Search Results + +When searching for an IP like `20.42.4.150`, the system returns: + +**Azure Results:** +- `AzureCognitiveSearch.EastUS` +- `Storage.EastUS` +- `AzureCloud.EastUS` + +**AWS Results:** +- No matches found + +**GCP Results:** +- No matches found + +### Security and Privacy Benefits + +The cloud IP search functionality provides significant security value: + +1. **Threat Attribution**: Quickly identify if suspicious traffic originates from legitimate cloud infrastructure +2. **Network Forensics**: Understand traffic patterns and service dependencies +3. **Compliance Validation**: Verify that data flows through expected cloud regions +4. **Incident Response**: Rapidly classify IP addresses during security investigations + +### Future Enhancements + +Several exciting improvements are planned: + +1. **Additional Providers**: Oracle Cloud, IBM Cloud, Alibaba Cloud support +2. **Historical Data**: Track IP range changes over time +3. **Geolocation Integration**: Combine cloud attribution with geographic data +4. **API Integration**: Programmatic access to cloud IP attribution +5. **Bulk Processing**: Upload and process IP lists + +The Cloud IP Manifest Search feature demonstrates how modern web applications can provide enterprise-grade functionality while maintaining simplicity and performance. By leveraging official cloud provider data and efficient client-side processing, Albatross delivers accurate, real-time cloud infrastructure attribution that's invaluable for security professionals and network administrators. + +## Conclusion + +Building Albatross has been an incredible journey into modern web security architecture. The combination of Blazor WebAssembly's rich client-side capabilities with Cloudflare Workers' edge computing power creates a robust, secure, and performant solution for IP abuse checking. + +The key innovations - build-time key generation, HMAC authentication, and browser-only validation - demonstrate that security doesn't have to come at the expense of user experience or developer productivity. + +### Key Takeaways: + +- **Security by Design**: Build security considerations into the architecture from day one +- **Automation is Key**: Automated key generation and deployment reduces human error +- **Edge Computing**: Leverage edge networks for better performance and security +- **Modern Tooling**: Use the best tools for each layer of the stack + +## Try It Yourself + +The complete source code for Albatross is available on GitHub, including: +- All PowerShell build scripts +- MSBuild integration examples +- Cloudflare Worker implementation +- GitHub Actions workflows + +Whether you're building your own API proxy or exploring modern web security patterns, I hope Albatross serves as both inspiration and a practical reference for your next project. + +--- + +*Albatross is live at [https://albatross.devnomadic.com](https://albatross.devnomadic.com) and the source code is available on [GitHub](https://github.com/your-username/albatross).* \ No newline at end of file From f02456b321c9f3a8c7dad1d4f06a97e9257eb413 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 02:41:22 +1000 Subject: [PATCH 03/16] Fix preview deployment permissions by using peaceiris/actions-gh-pages --- .github/workflows/preview-deploy.yml | 75 ++++++++++------------------ 1 file changed, 25 insertions(+), 50 deletions(-) diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index 9eb4ba5..970fdbe 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -51,36 +51,13 @@ jobs: env: JEKYLL_ENV: production - - name: Deploy to preview branch - run: | - # Configure git - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - # Clone the repository to a temporary directory - git clone https://github.com/${{ github.repository }}.git temp-repo - cd temp-repo - - # Check if gh-pages branch exists, create if not - if git ls-remote --exit-code --heads origin gh-pages; then - git checkout gh-pages - else - git checkout --orphan gh-pages - git rm -rf . - fi - - # Create preview directory structure - mkdir -p preview/${{ steps.branch-info.outputs.branch-name }} - - # Copy built site to preview directory - cp -r ../_site/* preview/${{ steps.branch-info.outputs.branch-name }}/ - - # Add and commit - git add . - git commit -m "Deploy preview for ${{ steps.branch-info.outputs.branch-name }}" || echo "No changes to commit" - - # Push to gh-pages - git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git gh-pages + - name: Deploy to GitHub Pages preview + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./_site + destination_dir: preview/${{ steps.branch-info.outputs.branch-name }} + keep_files: true - name: Comment on PR with preview URL if: github.event_name == 'pull_request' @@ -106,25 +83,23 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'pull_request' && github.event.action == 'closed' steps: - - name: Cleanup preview on PR close - run: | - # Configure git - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - # Clone the repository - git clone https://github.com/${{ github.repository }}.git temp-repo - cd temp-repo + - name: Checkout + uses: actions/checkout@v4 + with: + ref: gh-pages - # Switch to gh-pages branch - if git ls-remote --exit-code --heads origin gh-pages; then - git checkout gh-pages - - # Remove the preview directory - if [ -d "preview/pr-${{ github.event.number }}" ]; then - rm -rf preview/pr-${{ github.event.number }} - git add . - git commit -m "Cleanup preview for closed PR #${{ github.event.number }}" || echo "No changes to commit" - git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git gh-pages - fi + - name: Remove preview directory + run: | + if [ -d "preview/pr-${{ github.event.number }}" ]; then + rm -rf preview/pr-${{ github.event.number }} + echo "Removed preview directory for PR #${{ github.event.number }}" + else + echo "Preview directory for PR #${{ github.event.number }} not found" fi + + - name: Deploy cleanup + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: . + keep_files: true From 4513c485f5ca75137a68806852faafd208eb4548 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 02:52:50 +1000 Subject: [PATCH 04/16] Refactor preview deployment workflow to enhance directory structure and artifact upload process --- .github/workflows/preview-deploy.yml | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index 970fdbe..7edf230 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -20,6 +20,13 @@ jobs: build-preview: runs-on: ubuntu-latest if: github.event.action != 'closed' + environment: + name: github-pages-preview + url: https://devnomadic.github.io/preview/${{ steps.branch-info.outputs.branch-name }}/ + permissions: + contents: read + pages: write + id-token: write steps: - name: Checkout uses: actions/checkout@v4 @@ -51,13 +58,21 @@ jobs: env: JEKYLL_ENV: production - - name: Deploy to GitHub Pages preview - uses: peaceiris/actions-gh-pages@v3 + - name: Setup Pages for preview + id: pages-preview + uses: actions/configure-pages@v4 + + - name: Create preview directory structure + run: | + mkdir -p preview-site/preview/${{ steps.branch-info.outputs.branch-name }} + cp -r _site/* preview-site/preview/${{ steps.branch-info.outputs.branch-name }}/ + + - name: Upload preview artifact + uses: actions/upload-artifact@v4 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./_site - destination_dir: preview/${{ steps.branch-info.outputs.branch-name }} - keep_files: true + name: preview-site-${{ steps.branch-info.outputs.branch-name }} + path: preview-site/ + retention-days: 7 - name: Comment on PR with preview URL if: github.event_name == 'pull_request' From 8d68396e09947ca9861250dafb13105496377765 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 02:58:48 +1000 Subject: [PATCH 05/16] Refactor preview deployment workflow for improved clarity and functionality --- .github/workflows/preview-deploy.yml | 52 ++++++++++++---------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index 7edf230..e57bfcd 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -1,9 +1,9 @@ -name: Build and Deploy Live Preview +name: Build Preview for Testing on: pull_request: branches: - - master + - main types: [opened, synchronize, reopened, closed] push: branches: @@ -13,20 +13,11 @@ on: permissions: contents: read pull-requests: write - pages: write - id-token: write jobs: build-preview: runs-on: ubuntu-latest if: github.event.action != 'closed' - environment: - name: github-pages-preview - url: https://devnomadic.github.io/preview/${{ steps.branch-info.outputs.branch-name }}/ - permissions: - contents: read - pages: write - id-token: write steps: - name: Checkout uses: actions/checkout@v4 @@ -52,47 +43,50 @@ jobs: - name: Build with Jekyll for preview run: | - # Update baseurl for preview deployment - sed -i 's|baseurl: ""|baseurl: "/preview/${{ steps.branch-info.outputs.branch-name }}"|' _config.yml bundle exec jekyll build --destination ./_site env: JEKYLL_ENV: production - - name: Setup Pages for preview - id: pages-preview - uses: actions/configure-pages@v4 - - - name: Create preview directory structure - run: | - mkdir -p preview-site/preview/${{ steps.branch-info.outputs.branch-name }} - cp -r _site/* preview-site/preview/${{ steps.branch-info.outputs.branch-name }}/ - - name: Upload preview artifact uses: actions/upload-artifact@v4 with: name: preview-site-${{ steps.branch-info.outputs.branch-name }} - path: preview-site/ + path: ./_site retention-days: 7 - - name: Comment on PR with preview URL + - name: Comment on PR with preview info if: github.event_name == 'pull_request' uses: actions/github-script@v7 with: script: | const { owner, repo } = context.repo; const prNumber = context.issue.number; - const previewUrl = `https://${owner}.github.io/${repo}/preview/pr-${prNumber}/`; + const runId = context.runId; + const branchName = "${{ steps.branch-info.outputs.branch-name }}"; github.rest.issues.createComment({ owner, repo, issue_number: prNumber, - body: `🚀 **Live Preview Ready!** + body: `🚀 **Preview Build Complete!** + + Your preview has been built successfully and is ready for testing. + + **Download & Test Locally:** + 1. Go to [Actions Run #${runId}](https://github.com/${owner}/${repo}/actions/runs/${runId}) + 2. Download the \`preview-site-${branchName}\` artifact + 3. Extract and serve: \`cd extracted-folder && python3 -m http.server 8000\` + 4. Open http://localhost:8000 in your browser - Your preview is now live at: **${previewUrl}** + 💡 **Quick Test Command:** + \`\`\`bash + # After downloading and extracting: + python3 -m http.server 8000 + # Then open: http://localhost:8000 + \`\`\` - 📝 This preview will be updated automatically when you push new commits to this PR. - 🗑️ The preview will be cleaned up when the PR is merged or closed.` + � This preview updates automatically with each commit to this PR.` + }); cleanup-preview: runs-on: ubuntu-latest From c594a1ccb13a2aed4f8304f0fd5a5125b11a1601 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 03:11:19 +1000 Subject: [PATCH 06/16] Enable live preview deployment to /preview/ paths --- .github/workflows/preview-deploy.yml | 31 ++++++++++++++++------------ 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index e57bfcd..cec65cc 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -13,6 +13,8 @@ on: permissions: contents: read pull-requests: write + pages: write + id-token: write jobs: build-preview: @@ -47,7 +49,15 @@ jobs: env: JEKYLL_ENV: production - - name: Upload preview artifact + - name: Deploy to preview path + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./_site + destination_dir: preview/${{ steps.branch-info.outputs.branch-name }} + keep_files: true + + - name: Upload preview artifact (backup) uses: actions/upload-artifact@v4 with: name: preview-site-${{ steps.branch-info.outputs.branch-name }} @@ -70,29 +80,24 @@ jobs: issue_number: prNumber, body: `🚀 **Preview Build Complete!** - Your preview has been built successfully and is ready for testing. + Your preview has been deployed and is ready for testing. + + **Live Preview:** https://devnomadic.com/preview/${branchName}/ - **Download & Test Locally:** + **Alternative - Download & Test Locally:** 1. Go to [Actions Run #${runId}](https://github.com/${owner}/${repo}/actions/runs/${runId}) - 2. Download the \`preview-site-${branchName}\` artifact + 2. Download the \`preview-site-${branchName}\` artifact (backup) 3. Extract and serve: \`cd extracted-folder && python3 -m http.server 8000\` 4. Open http://localhost:8000 in your browser - 💡 **Quick Test Command:** - \`\`\`bash - # After downloading and extracting: - python3 -m http.server 8000 - # Then open: http://localhost:8000 - \`\`\` - - � This preview updates automatically with each commit to this PR.` + 🔄 This preview updates automatically with each commit to this PR.` }); cleanup-preview: runs-on: ubuntu-latest if: github.event_name == 'pull_request' && github.event.action == 'closed' steps: - - name: Checkout + - name: Checkout gh-pages uses: actions/checkout@v4 with: ref: gh-pages From 17b45eb0a285ba7c8adb4a4430c764fd1364c9b1 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 03:23:24 +1000 Subject: [PATCH 07/16] Add explicit contents:write permissions to preview workflow jobs --- .github/workflows/preview-deploy.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index cec65cc..783f57c 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -20,6 +20,10 @@ jobs: build-preview: runs-on: ubuntu-latest if: github.event.action != 'closed' + permissions: + contents: write + pages: write + pull-requests: write steps: - name: Checkout uses: actions/checkout@v4 @@ -96,6 +100,9 @@ jobs: cleanup-preview: runs-on: ubuntu-latest if: github.event_name == 'pull_request' && github.event.action == 'closed' + permissions: + contents: write + pages: write steps: - name: Checkout gh-pages uses: actions/checkout@v4 From 63056941a088b5a2875ba518aa63a5da66d367df Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 03:27:27 +1000 Subject: [PATCH 08/16] Remove CNAME file from preview builds to fix routing conflicts --- .github/workflows/preview-deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index 783f57c..f6a4793 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -50,6 +50,8 @@ jobs: - name: Build with Jekyll for preview run: | bundle exec jekyll build --destination ./_site + # Remove CNAME file from preview builds to avoid routing conflicts + rm -f ./_site/CNAME env: JEKYLL_ENV: production From 1f419e4884935fa5abba0ad74a16a6e1909b9625 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 03:50:30 +1000 Subject: [PATCH 09/16] Refactor preview deployment workflow: update permissions, streamline steps, and enhance concurrency handling --- .github/workflows/preview-deploy.yml | 105 ++++----------------------- 1 file changed, 13 insertions(+), 92 deletions(-) diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index f6a4793..ca31801 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -10,20 +10,19 @@ on: - preview/* - staging +# Recommended concurrency group for preview-pages action +concurrency: + group: preview-pages-${{ github.ref }} + cancel-in-progress: true + permissions: - contents: read + contents: write pull-requests: write - pages: write - id-token: write jobs: build-preview: runs-on: ubuntu-latest if: github.event.action != 'closed' - permissions: - contents: write - pages: write - pull-requests: write steps: - name: Checkout uses: actions/checkout@v4 @@ -34,95 +33,17 @@ jobs: ruby-version: '3.1' bundler-cache: true - - name: Get branch/PR info - id: branch-info - run: | - if [ "${{ github.event_name }}" = "pull_request" ]; then - echo "branch-name=pr-${{ github.event.number }}" >> $GITHUB_OUTPUT - echo "is-pr=true" >> $GITHUB_OUTPUT - else - branch_name="${{ github.ref_name }}" - safe_branch=$(echo "$branch_name" | sed 's/[^a-zA-Z0-9-]/-/g' | tr '[:upper:]' '[:lower:]') - echo "branch-name=$safe_branch" >> $GITHUB_OUTPUT - echo "is-pr=false" >> $GITHUB_OUTPUT - fi - - name: Build with Jekyll for preview run: | bundle exec jekyll build --destination ./_site - # Remove CNAME file from preview builds to avoid routing conflicts - rm -f ./_site/CNAME env: JEKYLL_ENV: production - - name: Deploy to preview path - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./_site - destination_dir: preview/${{ steps.branch-info.outputs.branch-name }} - keep_files: true - - - name: Upload preview artifact (backup) - uses: actions/upload-artifact@v4 - with: - name: preview-site-${{ steps.branch-info.outputs.branch-name }} - path: ./_site - retention-days: 7 - - - name: Comment on PR with preview info - if: github.event_name == 'pull_request' - uses: actions/github-script@v7 - with: - script: | - const { owner, repo } = context.repo; - const prNumber = context.issue.number; - const runId = context.runId; - const branchName = "${{ steps.branch-info.outputs.branch-name }}"; - - github.rest.issues.createComment({ - owner, - repo, - issue_number: prNumber, - body: `🚀 **Preview Build Complete!** - - Your preview has been deployed and is ready for testing. - - **Live Preview:** https://devnomadic.com/preview/${branchName}/ - - **Alternative - Download & Test Locally:** - 1. Go to [Actions Run #${runId}](https://github.com/${owner}/${repo}/actions/runs/${runId}) - 2. Download the \`preview-site-${branchName}\` artifact (backup) - 3. Extract and serve: \`cd extracted-folder && python3 -m http.server 8000\` - 4. Open http://localhost:8000 in your browser - - 🔄 This preview updates automatically with each commit to this PR.` - }); - - cleanup-preview: - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' && github.event.action == 'closed' - permissions: - contents: write - pages: write - steps: - - name: Checkout gh-pages - uses: actions/checkout@v4 - with: - ref: gh-pages - - - name: Remove preview directory - run: | - if [ -d "preview/pr-${{ github.event.number }}" ]; then - rm -rf preview/pr-${{ github.event.number }} - echo "Removed preview directory for PR #${{ github.event.number }}" - else - echo "Preview directory for PR #${{ github.event.number }} not found" - fi - - - name: Deploy cleanup - uses: peaceiris/actions-gh-pages@v3 + - name: Deploy Preview Pages + uses: rajyan/preview-pages@v1 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: . - keep_files: true + source-dir: ./_site + configured-domain: devnomadic.com + target-dir: preview + pr-comment: sticky + branch-comment: each From c284e6e65f76f273e4bc8e433a13149957458fef Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 12:29:05 +1000 Subject: [PATCH 10/16] Implement PR preview deployment using direct gh-pages approach - Replace Preview Pages action with standard peaceiris/actions-gh-pages - Deploy PR previews to /pull/{pr-number}/ paths - Add cleanup workflow for closed PRs - Should work with custom domain devnomadic.com --- .github/workflows/pr-close.yml | 34 +++++++++++ .github/workflows/preview-deploy.yml | 88 +++++++++++++++++++--------- 2 files changed, 95 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/pr-close.yml diff --git a/.github/workflows/pr-close.yml b/.github/workflows/pr-close.yml new file mode 100644 index 0000000..97fd1ad --- /dev/null +++ b/.github/workflows/pr-close.yml @@ -0,0 +1,34 @@ +name: Delete Preview on PR Close + +on: + pull_request: + types: [closed] + +permissions: + contents: write + pull-requests: write + +jobs: + delete_preview: + runs-on: ubuntu-latest + env: + PR_PATH: pull/${{github.event.number}} + steps: + - name: Make empty dir + run: mkdir public + + - name: Delete preview folder + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + destination_dir: ${{ env.PR_PATH }} + + - name: Comment on PR + uses: hasura/comment-progress@v2.2.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + number: ${{ github.event.number }} + id: deploy-preview + message: "🪓 PR closed, deleted preview at https://github.com/${{ github.repository }}/tree/gh-pages/${{ env.PR_PATH }}/" diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index ca31801..0eb2c1e 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -1,49 +1,83 @@ -name: Build Preview for Testing +name: Jekyll Preview Deploy on: - pull_request: - branches: - - main - types: [opened, synchronize, reopened, closed] push: branches: - - preview/* - - staging - -# Recommended concurrency group for preview-pages action -concurrency: - group: preview-pages-${{ github.ref }} - cancel-in-progress: true + - main + pull_request: permissions: contents: write pull-requests: write jobs: - build-preview: + deploy: runs-on: ubuntu-latest - if: github.event.action != 'closed' + env: + PR_PATH: pull/${{github.event.number}} steps: + - name: Comment on PR + uses: hasura/comment-progress@v2.2.0 + if: github.ref != 'refs/heads/main' + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + number: ${{ github.event.number }} + id: deploy-preview + message: "Starting deployment of preview ⏳..." + + - name: Set domain + run: echo "DOMAIN=devnomadic.com" >> $GITHUB_ENV + - name: Checkout uses: actions/checkout@v4 - + - name: Setup Ruby uses: ruby/setup-ruby@v1 with: ruby-version: '3.1' bundler-cache: true - - - name: Build with Jekyll for preview - run: | - bundle exec jekyll build --destination ./_site + + - name: Set production base URL + run: echo "BASE_URL=https://${{ env.DOMAIN }}/" >> $GITHUB_ENV + + - name: Build website + run: bundle exec jekyll build --destination ./_site --baseurl "" env: JEKYLL_ENV: production - - - name: Deploy Preview Pages - uses: rajyan/preview-pages@v1 + + - name: Deploy if this is the main branch + uses: peaceiris/actions-gh-pages@v3 + if: github.ref == 'refs/heads/main' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./_site + cname: ${{ env.DOMAIN }} + + - name: Set base URL for preview if PR + if: github.ref != 'refs/heads/main' + run: echo "BASE_URL=https://${{ env.DOMAIN }}/${{ env.PR_PATH}}/" >> $GITHUB_ENV + + - name: Build PR preview website + if: github.ref != 'refs/heads/main' + run: bundle exec jekyll build --destination ./_site --baseurl "/${{ env.PR_PATH }}" + env: + JEKYLL_ENV: staging + + - name: Deploy to PR preview + uses: peaceiris/actions-gh-pages@v3 + if: github.ref != 'refs/heads/main' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./_site + destination_dir: ${{ env.PR_PATH }} + + - name: Update comment + uses: hasura/comment-progress@v2.2.0 + if: github.ref != 'refs/heads/main' with: - source-dir: ./_site - configured-domain: devnomadic.com - target-dir: preview - pr-comment: sticky - branch-comment: each + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + number: ${{ github.event.number }} + id: deploy-preview + message: "A preview of ${{ github.event.after }} is uploaded and can be seen here:\n\n ✨ ${{ env.BASE_URL }} ✨\n\nChanges may take a few minutes to propagate." From 510e63b84369dc518b9007924761e50c4cf0b5f7 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 12:38:46 +1000 Subject: [PATCH 11/16] Add test file for PR preview --- test-preview.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 test-preview.txt diff --git a/test-preview.txt b/test-preview.txt new file mode 100644 index 0000000..d3ea678 --- /dev/null +++ b/test-preview.txt @@ -0,0 +1 @@ +Testing PR preview functionality From 6c1a2f4a65569ed9bfe56dbcabfa147fc4251d5e Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 12:44:10 +1000 Subject: [PATCH 12/16] Fix GitHub Pages deployment - remove conflicting workflow and simplify deploy process --- .github/workflows/deploy.yml | 87 ++++++++++++++++++++++++++++++ .github/workflows/github-pages.yml | 54 ------------------- 2 files changed, 87 insertions(+), 54 deletions(-) create mode 100644 .github/workflows/deploy.yml delete mode 100644 .github/workflows/github-pages.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..140fc3c --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,87 @@ +name: Build and Deploy + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: read + pages: write + id-token: write + pull-requests: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1' + bundler-cache: true + + - name: Build site + run: bundle exec jekyll build + env: + JEKYLL_ENV: production + + - name: Upload artifact for main branch + if: github.ref == 'refs/heads/main' + uses: actions/upload-pages-artifact@v3 + with: + path: ./_site + + # Deploy to GitHub Pages (main branch only) + deploy: + if: github.ref == 'refs/heads/main' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + + # PR Preview (for pull requests only) + preview: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1' + bundler-cache: true + + - name: Build PR site + run: bundle exec jekyll build + env: + JEKYLL_ENV: production + + - name: Deploy PR Preview + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./_site + destination_dir: pr-${{ github.event.number }} + + - name: Comment PR + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: '🚀 Preview deployed to: https://devnomadic.com/pr-${{ github.event.number }}/' + }) diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml deleted file mode 100644 index 6996f36..0000000 --- a/.github/workflows/github-pages.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Build and deploy Jekyll site to GitHub Pages - -on: - push: - branches: - - master - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.1' - bundler-cache: true - - - name: Setup Pages - id: pages - uses: actions/configure-pages@v4 - - - name: Build with Jekyll - run: | - bundle exec jekyll build --destination ./_site - env: - JEKYLL_ENV: production - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ./_site - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 From 491805c0f13ff3ec459a7488f55ac535690c2c21 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 13:04:31 +1000 Subject: [PATCH 13/16] Remove deprecated GitHub Actions workflows for deployment and PR previews --- .github/workflows/deploy.yml | 87 ---------------------------- .github/workflows/pages.yml | 48 +++++++++++++++ .github/workflows/pr-close.yml | 34 ----------- .github/workflows/preview-deploy.yml | 83 -------------------------- .github/workflows/preview.yml | 36 ++++++++++++ 5 files changed, 84 insertions(+), 204 deletions(-) delete mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/pages.yml delete mode 100644 .github/workflows/pr-close.yml delete mode 100644 .github/workflows/preview-deploy.yml create mode 100644 .github/workflows/preview.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 140fc3c..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Build and Deploy - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -permissions: - contents: read - pages: write - id-token: write - pull-requests: write - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.1' - bundler-cache: true - - - name: Build site - run: bundle exec jekyll build - env: - JEKYLL_ENV: production - - - name: Upload artifact for main branch - if: github.ref == 'refs/heads/main' - uses: actions/upload-pages-artifact@v3 - with: - path: ./_site - - # Deploy to GitHub Pages (main branch only) - deploy: - if: github.ref == 'refs/heads/main' - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 - - # PR Preview (for pull requests only) - preview: - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.1' - bundler-cache: true - - - name: Build PR site - run: bundle exec jekyll build - env: - JEKYLL_ENV: production - - - name: Deploy PR Preview - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./_site - destination_dir: pr-${{ github.event.number }} - - - name: Comment PR - uses: actions/github-script@v7 - with: - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: '🚀 Preview deployed to: https://devnomadic.com/pr-${{ github.event.number }}/' - }) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..1674f12 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,48 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: [ master ] + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1' + bundler-cache: true + + - name: Build with Jekyll + run: bundle exec jekyll build + env: + JEKYLL_ENV: production + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./_site + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/pr-close.yml b/.github/workflows/pr-close.yml deleted file mode 100644 index 97fd1ad..0000000 --- a/.github/workflows/pr-close.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Delete Preview on PR Close - -on: - pull_request: - types: [closed] - -permissions: - contents: write - pull-requests: write - -jobs: - delete_preview: - runs-on: ubuntu-latest - env: - PR_PATH: pull/${{github.event.number}} - steps: - - name: Make empty dir - run: mkdir public - - - name: Delete preview folder - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./public - destination_dir: ${{ env.PR_PATH }} - - - name: Comment on PR - uses: hasura/comment-progress@v2.2.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - repository: ${{ github.repository }} - number: ${{ github.event.number }} - id: deploy-preview - message: "🪓 PR closed, deleted preview at https://github.com/${{ github.repository }}/tree/gh-pages/${{ env.PR_PATH }}/" diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml deleted file mode 100644 index 0eb2c1e..0000000 --- a/.github/workflows/preview-deploy.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Jekyll Preview Deploy - -on: - push: - branches: - - main - pull_request: - -permissions: - contents: write - pull-requests: write - -jobs: - deploy: - runs-on: ubuntu-latest - env: - PR_PATH: pull/${{github.event.number}} - steps: - - name: Comment on PR - uses: hasura/comment-progress@v2.2.0 - if: github.ref != 'refs/heads/main' - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - repository: ${{ github.repository }} - number: ${{ github.event.number }} - id: deploy-preview - message: "Starting deployment of preview ⏳..." - - - name: Set domain - run: echo "DOMAIN=devnomadic.com" >> $GITHUB_ENV - - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.1' - bundler-cache: true - - - name: Set production base URL - run: echo "BASE_URL=https://${{ env.DOMAIN }}/" >> $GITHUB_ENV - - - name: Build website - run: bundle exec jekyll build --destination ./_site --baseurl "" - env: - JEKYLL_ENV: production - - - name: Deploy if this is the main branch - uses: peaceiris/actions-gh-pages@v3 - if: github.ref == 'refs/heads/main' - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./_site - cname: ${{ env.DOMAIN }} - - - name: Set base URL for preview if PR - if: github.ref != 'refs/heads/main' - run: echo "BASE_URL=https://${{ env.DOMAIN }}/${{ env.PR_PATH}}/" >> $GITHUB_ENV - - - name: Build PR preview website - if: github.ref != 'refs/heads/main' - run: bundle exec jekyll build --destination ./_site --baseurl "/${{ env.PR_PATH }}" - env: - JEKYLL_ENV: staging - - - name: Deploy to PR preview - uses: peaceiris/actions-gh-pages@v3 - if: github.ref != 'refs/heads/main' - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./_site - destination_dir: ${{ env.PR_PATH }} - - - name: Update comment - uses: hasura/comment-progress@v2.2.0 - if: github.ref != 'refs/heads/main' - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - repository: ${{ github.repository }} - number: ${{ github.event.number }} - id: deploy-preview - message: "A preview of ${{ github.event.after }} is uploaded and can be seen here:\n\n ✨ ${{ env.BASE_URL }} ✨\n\nChanges may take a few minutes to propagate." diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 0000000..4500e25 --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,36 @@ +name: Deploy PR Preview + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - closed + +concurrency: preview-${{ github.ref }} + +jobs: + deploy-preview: + runs-on: ubuntu-18.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1' + bundler-cache: true + + - name: Build Jekyll site + run: bundle exec jekyll build + env: + JEKYLL_ENV: production + + - name: Deploy preview + uses: rossjrw/pr-preview-action@v1 + with: + source-dir: ./_site + preview-branch: gh-pages + umbrella-dir: pr-preview From 67d6eda8e8ad65971b1927d7f4ff1ed1e4253536 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 13:40:06 +1000 Subject: [PATCH 14/16] Fix PR preview workflow: add permissions, use ubuntu-latest, update checkout action --- .github/workflows/preview.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 4500e25..399c450 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -8,14 +8,18 @@ on: - synchronize - closed +permissions: + contents: write + pull-requests: write + concurrency: preview-${{ github.ref }} jobs: deploy-preview: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Ruby uses: ruby/setup-ruby@v1 From 159b05f8bf5b651a5e1530c9e499e9839b18d7f5 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 13:55:23 +1000 Subject: [PATCH 15/16] Exclude CNAME file from PR previews to fix routing conflicts --- .github/workflows/preview.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 399c450..810520f 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -28,7 +28,11 @@ jobs: bundler-cache: true - name: Build Jekyll site - run: bundle exec jekyll build + if: github.event.action != 'closed' + run: | + # Remove CNAME file for previews to avoid routing conflicts + rm -f CNAME + bundle exec jekyll build env: JEKYLL_ENV: production From eb2dc6d36614b7be65a9289bfa804bd8ca08716a Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 10 Jun 2025 16:14:38 +1000 Subject: [PATCH 16/16] Remove CNAME and add GitHub Pages SPA redirect workaround --- 404.html | 0 CNAME | 1 - _layouts/home.html | 14 ++++++++++++++ test-deployment-albatross.txt | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 404.html delete mode 100644 CNAME create mode 100644 test-deployment-albatross.txt diff --git a/404.html b/404.html new file mode 100644 index 0000000..e69de29 diff --git a/CNAME b/CNAME deleted file mode 100644 index 053d5b8..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -devnomadic.com \ No newline at end of file diff --git a/_layouts/home.html b/_layouts/home.html index 177e64e..cf9f985 100644 --- a/_layouts/home.html +++ b/_layouts/home.html @@ -41,3 +41,17 @@

recent articles

{% else %}

no posts yet.

{% endif %} + + + diff --git a/test-deployment-albatross.txt b/test-deployment-albatross.txt new file mode 100644 index 0000000..8fdd2ad --- /dev/null +++ b/test-deployment-albatross.txt @@ -0,0 +1 @@ +Test deployment albatross - Tue Jun 10 16:14:29 AEST 2025