Add secrets configuration and deployment scripts

- Added GitHub Actions secrets configuration for deployment
- Updated next.config.ts with environment variables
- Modified package.json for production deployment
- Added robots.ts and sitemap.ts configuration
- Created deployment scripts directory
This commit is contained in:
Derek Slenk 2025-06-29 17:42:11 -04:00
parent cece15bcb6
commit 626219620b
6 changed files with 171 additions and 67 deletions

View file

@ -1,8 +1,5 @@
# Sample workflow for building and deploying a Next.js site to GitHub Pages # Workflow for building and deploying a Next.js site to AWS S3
# name: Deploy Next.js site to S3
# To get started with Next.js see: https://nextjs.org/docs/getting-started
#
name: Deploy Next.js site to Pages
on: on:
# Runs on pushes targeting the default branch # Runs on pushes targeting the default branch
@ -12,82 +9,61 @@ on:
# Allows you to run this workflow manually from the Actions tab # Allows you to run this workflow manually from the Actions tab
workflow_dispatch: workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency: concurrency:
group: "pages" group: "s3-deployment"
cancel-in-progress: false cancel-in-progress: false
jobs: jobs:
# Build job build-and-deploy:
build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Detect package manager
id: detect-package-manager - name: Setup Node.js
run: |
if [ -f "${{ github.workspace }}/yarn.lock" ]; then
echo "manager=yarn" >> $GITHUB_OUTPUT
echo "command=install" >> $GITHUB_OUTPUT
echo "runner=yarn" >> $GITHUB_OUTPUT
exit 0
elif [ -f "${{ github.workspace }}/package.json" ]; then
echo "manager=npm" >> $GITHUB_OUTPUT
echo "command=ci" >> $GITHUB_OUTPUT
echo "runner=npx --no-install" >> $GITHUB_OUTPUT
exit 0
else
echo "Unable to determine package manager"
exit 1
fi
- name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: "20" node-version: "20"
cache: ${{ steps.detect-package-manager.outputs.manager }} cache: 'npm'
- name: Setup Pages
uses: actions/configure-pages@v5 - name: Create .env.local file
with: run: |
# Automatically inject basePath in your Next.js configuration file and disable echo "YOUTUBE_API_KEY=${{ secrets.YOUTUBE_API_KEY }}" > .env.local
# server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized). echo "S3_BUCKET_NAME=${{ secrets.S3_BUCKET_NAME }}" >> .env.local
#
# You may remove this line if you want to manage the configuration yourself. - name: Install dependencies
static_site_generator: next run: npm ci
- name: Restore cache
- name: Restore Next.js cache
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: | path: |
.next/cache .next/cache
# Generate a new cache whenever packages or source files change. key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: | restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}- ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- name: Install dependencies
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
- name: Build with Next.js
run: ${{ steps.detect-package-manager.outputs.runner }} next build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./out
# Deployment job - name: Build Next.js site
# deploy: env:
# environment: YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY }}
# name: github-pages S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
# url: ${{ steps.deployment.outputs.page_url }} run: npm run build:static
# runs-on: ubuntu-latest
# needs: build - name: Configure AWS credentials
# steps: uses: aws-actions/configure-aws-credentials@v4
# - name: Deploy to GitHub Pages with:
# id: deployment role-to-assume: arn:aws:iam::499518182498:role/cheatingchelsea-github-deployment
# uses: actions/deploy-pages@v4 aws-region: us-east-2
- name: Deploy to S3
run: |
aws s3 sync out/ s3://${{ secrets.S3_BUCKET_NAME }} --delete --no-cli-pager
- name: Output deployment URL
run: |
echo "🎉 Deployment successful!"
echo "Your site is available at: http://${{ secrets.S3_BUCKET_NAME }}.s3-website-us-east-2.amazonaws.com"

View file

@ -2,6 +2,9 @@ import type {NextConfig} from 'next';
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ /* config options here */
output: 'export',
trailingSlash: true,
skipTrailingSlashRedirect: true,
typescript: { typescript: {
ignoreBuildErrors: true, ignoreBuildErrors: true,
}, },

View file

@ -7,9 +7,12 @@
"genkit:dev": "genkit start -- tsx src/ai/dev.ts", "genkit:dev": "genkit start -- tsx src/ai/dev.ts",
"genkit:watch": "genkit start -- tsx --watch src/ai/dev.ts", "genkit:watch": "genkit start -- tsx --watch src/ai/dev.ts",
"build": "next build", "build": "next build",
"build:static": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit",
"deploy:s3": "npm run build:static && aws s3 sync out/ s3://$S3_BUCKET_NAME --delete --no-cli-pager",
"create-s3-bucket": "node scripts/create-s3-bucket.js"
}, },
"dependencies": { "dependencies": {
"@genkit-ai/googleai": "^1.13.0", "@genkit-ai/googleai": "^1.13.0",

118
scripts/create-s3-bucket.js Executable file
View file

@ -0,0 +1,118 @@
#!/usr/bin/env node
const { execSync } = require('child_process');
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function askQuestion(question) {
return new Promise((resolve) => {
rl.question(question, (answer) => {
resolve(answer);
});
});
}
async function main() {
try {
console.log('🚀 Setting up S3 bucket for static website hosting\n');
// Get bucket name
const bucketName = await askQuestion('Enter your S3 bucket name (must be globally unique): ');
if (!bucketName) {
console.error('❌ Bucket name is required');
process.exit(1);
}
// Get AWS region
const region = await askQuestion('Enter AWS region (default: us-east-1): ') || 'us-east-1';
console.log(`\n📦 Creating S3 bucket: ${bucketName} in region: ${region}`);
// Create bucket
try {
if (region === 'us-east-1') {
execSync(`aws s3 mb s3://${bucketName} --no-cli-pager`, { stdio: 'inherit' });
} else {
execSync(`aws s3 mb s3://${bucketName} --region ${region} --no-cli-pager`, { stdio: 'inherit' });
}
console.log('✅ Bucket created successfully');
} catch (error) {
console.error('❌ Failed to create bucket. It might already exist or you might not have permissions.');
console.error('Error:', error.message);
}
// Enable static website hosting
console.log('\n🌐 Enabling static website hosting...');
try {
execSync(`aws s3 website s3://${bucketName} --index-document index.html --error-document error.html --no-cli-pager`, { stdio: 'inherit' });
console.log('✅ Static website hosting enabled');
} catch (error) {
console.error('❌ Failed to enable static website hosting');
console.error('Error:', error.message);
}
// Create bucket policy for public read access
const bucketPolicy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": `arn:aws:s3:::${bucketName}/*`
}
]
};
// Write policy to temporary file
require('fs').writeFileSync('/tmp/bucket-policy.json', JSON.stringify(bucketPolicy, null, 2));
console.log('\n🔓 Setting bucket policy for public read access...');
try {
execSync(`aws s3api put-bucket-policy --bucket ${bucketName} --policy file:///tmp/bucket-policy.json --no-cli-pager`, { stdio: 'inherit' });
console.log('✅ Bucket policy applied');
} catch (error) {
console.error('❌ Failed to apply bucket policy');
console.error('Error:', error.message);
}
// Disable block public access
console.log('\n🔐 Configuring public access settings...');
try {
execSync(`aws s3api put-public-access-block --bucket ${bucketName} --public-access-block-configuration "BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false" --no-cli-pager`, { stdio: 'inherit' });
console.log('✅ Public access configured');
} catch (error) {
console.error('❌ Failed to configure public access');
console.error('Error:', error.message);
}
// Get website URL
const websiteUrl = `http://${bucketName}.s3-website-${region}.amazonaws.com`;
console.log(`\n🎉 Setup complete!`);
console.log(`\n📋 Next steps:`);
console.log(`1. Set your bucket name as an environment variable:`);
console.log(` export S3_BUCKET_NAME=${bucketName}`);
console.log(`\n2. Deploy your site:`);
console.log(` npm run deploy:s3`);
console.log(`\n3. Your website will be available at:`);
console.log(` ${websiteUrl}`);
console.log(`\n💡 Pro tip: Add S3_BUCKET_NAME=${bucketName} to your .env.local file`);
// Clean up
require('fs').unlinkSync('/tmp/bucket-policy.json');
} catch (error) {
console.error('❌ An error occurred:', error.message);
} finally {
rl.close();
}
}
main();

View file

@ -1,5 +1,7 @@
import type { MetadataRoute } from 'next'; import type { MetadataRoute } from 'next';
export const dynamic = 'force-static';
export default function robots(): MetadataRoute.Robots { export default function robots(): MetadataRoute.Robots {
const baseUrl = 'https://cheatingchelsea.com'; const baseUrl = 'https://cheatingchelsea.com';
return { return {

View file

@ -1,5 +1,7 @@
import type { MetadataRoute } from 'next'; import type { MetadataRoute } from 'next';
export const dynamic = 'force-static';
export default function sitemap(): MetadataRoute.Sitemap { export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://cheatingchelsea.com'; const baseUrl = 'https://cheatingchelsea.com';