diff --git a/README.md b/README.md index afd7bbb..9cd32be 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Reusable Forgejo/Gitea actions for toph's infrastructure. ### `deploy-site` -Deploy a static site to production. +Deploy a static site to production via S3 and Nomad. **Usage:** @@ -29,20 +29,228 @@ jobs: uses: https://git.toph.so/toph/ci-actions/deploy-site@main with: site-name: mysite + traefik-rule: Host(`mysite.example.com`) source-dir: dist # optional, defaults to current dir + env: + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} ``` -**What it does:** -- Packages your site -- Uploads to deployment server -- Triggers Nomad deployment -- Site becomes available at `https://{site-name}.toph.so` +**Inputs:** +- `site-name` (required): Site identifier used as Nomad service name +- `traefik-rule` (required): Traefik routing rule (see examples below) +- `source-dir` (optional): Directory with built files, defaults to `.` +- `s3-endpoint` (optional): S3 endpoint, defaults to `https://s3.toph.so` -**No configuration needed** - infrastructure handles: -- Docker images -- Resource limits -- Domains & SSL -- Load balancing +**Environment variables:** +- `S3_ACCESS_KEY`: S3 access key (set via Forgejo secrets) +- `S3_SECRET_KEY`: S3 secret key (set via Forgejo secrets) + +**What it does:** +1. Packages the site directory as a tarball +2. Uploads to S3 at `s3://artifacts/.tar.gz` +3. Sets public-read ACL on the artifact +4. Dispatches Nomad job to deploy the site +5. Site becomes available via the specified Traefik rule with Let's Encrypt SSL + +**Infrastructure handles:** +- Docker containers (static-web-server) +- Resource limits (100 CPU, 64MB RAM) +- Traefik routing & Let's Encrypt SSL +- Automatic restarts + +## Traefik Rule Examples + +**Single domain:** +```yaml +traefik-rule: Host(`example.com`) +``` + +**Multiple domains (with www):** +```yaml +traefik-rule: Host(`example.com`) || Host(`www.example.com`) +``` + +**Subdomain:** +```yaml +traefik-rule: Host(`blog.example.com`) +``` + +**toph.so domain:** +```yaml +traefik-rule: Host(`mysite.toph.so`) +``` + +**Path-based routing:** +```yaml +traefik-rule: Host(`example.com`) && PathPrefix(`/docs`) +``` + +## Setup + +### 1. Deploy S3 service (SeaweedFS) + +```bash +cd /srv/infra +bosun dispatch s3 +``` + +### 2. Create artifacts bucket + +```bash +# Configure AWS CLI +export AWS_ACCESS_KEY_ID= +export AWS_SECRET_ACCESS_KEY= +export AWS_ENDPOINT_URL=https://s3.toph.so +export AWS_EC2_METADATA_DISABLED=true + +# Create bucket +aws s3 mb s3://artifacts + +# Set public-read policy for the bucket +aws s3api put-bucket-acl --bucket artifacts --acl public-read +``` + +### 3. Deploy static-site parameterized job + +```bash +cd /srv/infra +bosun dispatch static-site-dispatch +``` + +### 4. Add Forgejo secrets + +In your repository settings (or organization settings for global secrets): +- `S3_ACCESS_KEY`: The access key from S3 credentials +- `S3_SECRET_KEY`: The secret key from S3 credentials + +## Examples + +### Simple HTML site + +```yaml +name: Deploy +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + + - uses: https://git.toph.so/toph/ci-actions/deploy-site@main + with: + site-name: mysite + traefik-rule: Host(`mysite.toph.so`) + source-dir: . # HTML files in repo root + env: + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} +``` + +### Node.js/Vite site with custom domain + +```yaml +name: Deploy +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install & build + run: | + npm ci + npm run build + + - uses: https://git.toph.so/toph/ci-actions/deploy-site@main + with: + site-name: myapp + traefik-rule: Host(`app.example.com`) || Host(`www.app.example.com`) + source-dir: dist + env: + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} +``` + +### Hugo site + +```yaml +name: Deploy +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + with: + submodules: true # For Hugo themes + + - name: Setup Hugo + run: | + wget https://github.com/gohugoio/hugo/releases/download/v0.121.0/hugo_extended_0.121.0_linux-amd64.tar.gz + tar xzf hugo_extended_0.121.0_linux-amd64.tar.gz + sudo mv hugo /usr/local/bin/ + + - name: Build + run: hugo --minify + + - uses: https://git.toph.so/toph/ci-actions/deploy-site@main + with: + site-name: myblog + traefik-rule: Host(`blog.example.com`) + source-dir: public + env: + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} +``` + +## S3 Access + +- **API endpoint**: https://s3.toph.so +- **Console**: https://s3-console.toph.so (restricted to Tailscale) +- **Credentials**: Stored in Nomad variables at `nomad/jobs/s3` +- **Backend**: SeaweedFS (S3-compatible) + +## Troubleshooting + +### AWS CLI not found +The action automatically installs AWS CLI if not present on the runner. + +### Access denied during upload +Check that: +1. `S3_ACCESS_KEY` and `S3_SECRET_KEY` are set in Forgejo secrets +2. The credentials match those in the S3 Nomad job config +3. S3 service is running: `nomad job status s3` + +### Nomad dispatch fails +Ensure the `static-site` parameterized job is running: +```bash +nomad job status static-site +# If not found: +bosun dispatch static-site-dispatch +``` + +### Site not accessible +Check: +1. Nomad allocation is running: `nomad job status static-site` +2. Traefik has picked up the service: check Traefik dashboard +3. DNS resolves: `dig .toph.so` +4. Certificate is valid: `curl -v https://.toph.so` ## Development diff --git a/deploy-site/action.yaml b/deploy-site/action.yaml index 488e3d1..ef02f9d 100644 --- a/deploy-site/action.yaml +++ b/deploy-site/action.yaml @@ -1,9 +1,13 @@ name: Deploy Site -description: Deploy static site via Nomad dispatch +description: Deploy static site via S3 and Nomad dispatch inputs: site-name: - description: 'Site identifier' + description: 'Site identifier (used as service name in Nomad)' + required: true + + traefik-rule: + description: 'Traefik routing rule (e.g., Host(`example.com`) or Host(`example.com`) || Host(`www.example.com`))' required: true source-dir: @@ -11,35 +15,57 @@ inputs: required: false default: '.' + s3-endpoint: + description: 'S3 endpoint' + required: false + default: 'https://s3.toph.so' + runs: using: composite steps: - - name: Package artifact + - name: Install AWS CLI + shell: bash + run: | + if ! command -v aws &> /dev/null; then + curl -sL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "/tmp/awscliv2.zip" + unzip -q /tmp/awscliv2.zip -d /tmp + sudo /tmp/aws/install + fi + + - name: Package and upload to S3 shell: bash run: | cd "${{ inputs.source-dir }}" - tar czf /tmp/${{ inputs.site-name }}.tar.gz . + ARTIFACT_NAME="${{ github.sha }}.tar.gz" + tar czf "/tmp/${ARTIFACT_NAME}" . - - name: Upload artifact - shell: bash - run: | - ARTIFACT_URL="http://artifacts.toph.so/${{ github.sha }}.tar.gz" + # Configure AWS CLI for S3 + export AWS_ACCESS_KEY_ID="${{ env.S3_ACCESS_KEY }}" + export AWS_SECRET_ACCESS_KEY="${{ env.S3_SECRET_KEY }}" + export AWS_ENDPOINT_URL="${{ inputs.s3-endpoint }}" + export AWS_EC2_METADATA_DISABLED=true - curl -X PUT \ - --data-binary @/tmp/${{ inputs.site-name }}.tar.gz \ - "$ARTIFACT_URL" + # Upload to S3 + aws s3 cp "/tmp/${ARTIFACT_NAME}" "s3://artifacts/${ARTIFACT_NAME}" - echo "artifact_url=$ARTIFACT_URL" >> $GITHUB_OUTPUT + # Make publicly readable + aws s3api put-object-acl \ + --bucket artifacts \ + --key "${ARTIFACT_NAME}" \ + --acl public-read + + echo "📦 Artifact uploaded: ${{ inputs.s3-endpoint }}/artifacts/${ARTIFACT_NAME}" - name: Deploy via Nomad shell: bash run: | nomad job dispatch static-site \ -meta site_name=${{ inputs.site-name }} \ - -meta artifact_url=http://artifacts.toph.so/${{ github.sha }}.tar.gz + -meta traefik_rule="${{ inputs.traefik-rule }}" \ + -meta artifact_url=${{ inputs.s3-endpoint }}/artifacts/${{ github.sha }}.tar.gz - - name: Summary + - name: Deployment summary shell: bash run: | echo "✅ Deployed ${{ inputs.site-name }}" - echo "🌐 https://${{ inputs.site-name }}.toph.so" + echo "📋 Traefik rule: ${{ inputs.traefik-rule }}"