diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a6b86df..71e2a5f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,18 +3,18 @@ name: Deploy Resume on: push: branches: [main] + pull_request: + branches: [main] workflow_dispatch: - -env: - # These are GitHub Camo image proxy URLs. - # To obtain them: paste the raw S3 image link into a GitHub Markdown file (README, issue, etc.), - # let GitHub render it, then right-click → "Copy image address". - RESUME_IMAGE_URL: "https://camo.githubusercontent.com/19e0bf2e0d1d9b58f0bd2a4bf66b214f817d5ae7e72ff4e3684b8b15522be23f/68747470733a2f2f64616c746f6e2d63762d6172746966616374732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f696d616765732f63762e706e67" - COVER_LETTER_IMAGE_URL: "https://camo.githubusercontent.com/31c147493e3d013bd48fcbeda87b802e2a8012a64faf6dc324f640ba7eec4f24/68747470733a2f2f64616c746f6e2d63762d6172746966616374732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f696d616765732f636f7665725f6c65747465722e706e67" - jobs: build-and-deploy: runs-on: ubuntu-latest + container: + image: daluce/cv:latest + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} steps: - name: Checkout source @@ -22,29 +22,12 @@ jobs: with: fetch-depth: 0 - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y make texlive-latex-base texlive-latex-extra imagemagick - - name: Build resume run: make all - - name: Upload resume to S3 - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ secrets.AWS_REGION }} - - - name: Copy PDF + images to S3 - run: | - aws s3 cp dist/pdfs/ s3://${{ secrets.AWS_S3_BUCKET }}/pdfs/ --recursive - aws s3 cp dist/images/ s3://${{ secrets.AWS_S3_BUCKET }}/images/ --recursive - - # Purge GitHub's Camo CDN cache so updated resume/cover letter images show up immediately - # Docs: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-anonymized-urls#removing-an-image-from-camos-cache - - name: Clear GitHub Camo cache - run: | - curl -X PURGE "${RESUME_IMAGE_URL}" - curl -X PURGE "${COVER_LETTER_IMAGE_URL}" + - name: Test + run: make test + - name: Upload PDFs and images to S3 + # Only upload on pushes to main branch, not PRs + if: github.event_name == 'push' + run: make release diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..dbb0f20 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,27 @@ +name: Build and Push Docker Image + +on: + push: + branches: + - main + paths: + - 'Dockerfile' + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + push: true + tags: daluce/cv:latest + platforms: linux/amd64 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6529538 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.11-slim + +RUN apt-get update && apt-get install -y \ + imagemagick \ + texlive-latex-base \ + texlive-latex-extra \ + git \ + awscli \ + make \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install cvlint +RUN git clone https://github.com/da-luce/cvlint.git +WORKDIR /cvlint +RUN pip install . diff --git a/Makefile b/Makefile index 7167135..6d7035c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all clean build private cover-letter images help install test +.PHONY: all clean build private cover-letter images help install test release # Default target all: build images @@ -7,75 +7,82 @@ all: build images BUILD_DIR := build DIST_DIR := dist SRC_DIR := src +PDF_DIR := $(DIST_DIR)/pdfs +IMG_DIR := $(DIST_DIR)/images +CV_NAME := dalton_luce_cv +COVER_LETTER_NAME := cover_letter +S3_BUCKET := dalton-cv-artifacts +AWS_REGION := us-east-1 + +# These are GitHub Camo image proxy URLs. +# To obtain them: paste the raw S3 image link into a GitHub Markdown file (README, issue, etc.), +# let GitHub render it, then right-click → "Copy image address". +CV_GITHUB_CACHE_URL:= https://camo.githubusercontent.com/19e0bf2e0d1d9b58f0bd2a4bf66b214f817d5ae7e72ff4e3684b8b15522be23f/68747470733a2f2f64616c746f6e2d63762d6172746966616374732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f696d616765732f63762e706e67 +COVER_GITHUB_CACHE_URL:= https://camo.githubusercontent.com/31c147493e3d013bd48fcbeda87b802e2a8012a64faf6dc324f640ba7eec4f24/68747470733a2f2f64616c746f6e2d63762d6172746966616374732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f696d616765732f636f7665725f6c65747465722e706e67 # Help target help: @echo "Available targets:" - @echo " all - Build CV and cover letter, generate images" - @echo " build - Build CV (public version)" - @echo " private - Build CV (private version)" - @echo " cover-letter - Build cover letter only" - @echo " images - Generate PNG images from PDFs" - @echo " clean - Remove build directory" - @echo " install - Install Python dependencies" - @echo " test - Run tests" + @echo " all - Build CV and cover letter, then generate images" + @echo " build - Build public version of the CV" + @echo " private - Build private version of the CV" + @echo " cover-letter - Build the cover letter PDF" + @echo " images - Generate PNG images from PDFs using ImageMagick" + @echo " clean - Remove all build and distribution artifacts" + @echo " install - Install Python dependencies (not defined here)" + @echo " test - Run cvlint on the generated CV PDF" + @echo " release - Upload PDFs and images to S3, purge GitHub Camo cache" # Create necessary directories -$(DIST_DIR)/pdfs $(DIST_DIR)/images: +$(PDF_DIR) $(IMG_DIR): mkdir -p $@ # Build public CV -build: $(DIST_DIR)/pdfs cover-letter - mkdir -p $(BUILD_DIR) - pdflatex -output-directory=$(BUILD_DIR) -jobname=dalton_luce_cv $(SRC_DIR)/cv.tex - cp $(BUILD_DIR)/dalton_luce_cv.pdf $(DIST_DIR)/pdfs/ +build: $(PDF_DIR) cover-letter + mkdir -p $(PDF_DIR) + pdflatex -output-directory=$(BUILD_DIR) -jobname=$(CV_NAME) $(SRC_DIR)/$(CV_NAME).tex + cp $(BUILD_DIR)/$(CV_NAME).pdf $(PDF_DIR) # Build private CV -private: $(DIST_DIR)/pdfs +private: $(PDF_DIR) mkdir -p $(BUILD_DIR) - pdflatex -output-directory=$(BUILD_DIR) -jobname=dalton_luce_cv '\def\private{} \input{$(SRC_DIR)/cv.tex}' - cp $(BUILD_DIR)/dalton_luce_cv.pdf $(DIST_DIR)/pdfs/ + pdflatex -output-directory=$(BUILD_DIR) -jobname=$(CV_NAME) "\def\\private{} \input{$(SRC_DIR)/$(CV_NAME).tex}" + cp $(BUILD_DIR)/$(CV_NAME).pdf $(PDF_DIR) # Build cover letter -cover-letter: $(DIST_DIR)/pdfs +cover-letter: $(PDF_DIR) mkdir -p $(BUILD_DIR) - pdflatex -output-directory=$(BUILD_DIR) -jobname=cover_letter $(SRC_DIR)/cover_letter.tex - cp $(BUILD_DIR)/cover_letter.pdf $(DIST_DIR)/pdfs/ + pdflatex -output-directory=$(BUILD_DIR) -jobname=cover_letter $(SRC_DIR)/$(COVER_LETTER_NAME).tex + cp $(BUILD_DIR)/$(COVER_LETTER_NAME).pdf $(PDF_DIR) # Generate images from PDFs -# FIXME: currently using deprecated "convert" since ubuntu GitHub runners only have access to IMv6 -images: $(DIST_DIR)/images +images: $(IMG_DIR) @if command -v convert >/dev/null 2>&1; then \ - convert -density 300 $(DIST_DIR)/pdfs/dalton_luce_cv.pdf -quality 100 -flatten -sharpen 0x1.0 $(DIST_DIR)/images/cv.png; \ - convert -density 300 $(DIST_DIR)/pdfs/cover_letter.pdf -quality 100 -flatten -sharpen 0x1.0 $(DIST_DIR)/images/cover_letter.png; \ + magick -density 300 $(DIST_DIR)/pdfs/dalton_luce_cv.pdf -quality 100 -flatten -sharpen 0x1.0 $(IMG_DIR)/$(CV_NAME).png; \ + magick -density 300 $(DIST_DIR)/pdfs/cover_letter.pdf -quality 100 -flatten -sharpen 0x1.0 $(IMG_DIR)/$(COVER_LETTER_NAME).png; \ else \ echo "Imageconvert not found. Please install Imageconvert to generate images."; \ fi # Clean build directory clean: - rm -rf $(BUILD_DIR) - - -# Install Python dependencies in a venv -install: - @if [ ! -d .venv ]; then \ - echo "Creating virtual environment..."; \ - python3 -m venv .venv; \ - fi - @echo "Installing dependencies..." - @. .venv/bin/activate; \ - if [ -f pyproject.toml ]; then \ - pip install --upgrade pip; \ - pip install -e .; \ - else \ - echo "No dependency file found"; \ - fi - @echo "" - @echo "✅ Next steps:" - @echo " source .venv/bin/activate" - @echo " (or run commands with .venv/bin/python, e.g. .venv/bin/python -m pytest)" + rm -rf $(BUILD_DIR) $(DIST_DIR) # Run tests test: - python -m pytest test/test.py + @if ! command -v cvlint >/dev/null 2>&1; then \ + echo "Error: cvlint is not installed or not in PATH."; \ + echo "Please install cvlint by following instructions at https://github.com/da-luce/cvlint"; \ + exit 1; \ + fi + cvlint check $(PDF_DIR)/$(CV_NAME).pdf + +release: + aws s3 cp $(PDF_DIR)/ s3://$(S3_BUCKET)/pdfs/ --recursive --region $(AWS_REGION) + aws s3 cp $(IMG_DIR)/ s3://$(S3_BUCKET)/images/ --recursive --region $(AWS_REGION) + # Purge GitHub Camo image cache, so updated images show up in README immediately + curl -X PURGE "$(CV_GITHUB_CACHE_URL)" + curl -X PURGE "$(COVER_GITHUB_CACHE_URL)" + +enter: + docker run --rm -it --pull always -v "$$(pwd)":/cv -w /cv daluce/cv:latest /bin/bash diff --git a/README.md b/README.md index 93efa20..1e1bd68 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,21 @@ # 📄 CV [![Deploy Resume](https://github.com/da-luce/cv/actions/workflows/deploy.yml/badge.svg)](https://github.com/da-luce/cv/actions/workflows/deploy.yml) +

View PDF        - + View PNG

+

-My [Curriculum Vitae](https://en.wikipedia.org/wiki/Curriculum_vitae) (CV) and cover letter template, written in $\LaTeX$ ([learn more](https://www.latex-project.org/)). All artifacts (PDFs and preview images) are automatically built using [GitHub Actions](https://github.com/features/actions) and uploaded to an [AWS S3](https://aws.amazon.com/s3/) bucket. A [Terraform](https://developer.hashicorp.com/terraform) configuration and [instructions](#deploying-artifacts-to-aws) are included below if you want to set it up yourself. +My [Curriculum Vitae](https://en.wikipedia.org/wiki/Curriculum_vitae) and cover letter template, written in $\LaTeX$. All artifacts (PDFs and preview images) are automatically built using GitHub Actions and uploaded to an AWS S3 bucket. A Terraform configuration and [instructions](#deploying-artifacts-to-aws) are included below if you want a similar settup for your own resume. Is it overengineered? _For most, probably yes._
Is it perfect for automation enthusiasts? _Absolutely._
@@ -23,7 +25,7 @@ Maybe I can convince you to become one too—_[here's why](#why-this-setup)._ ### Curriculum Vitae   [![PDF](https://img.shields.io/badge/PDF-blue?style=flat)](https://dalton-cv-artifacts.s3.us-east-1.amazonaws.com/pdfs/dalton_luce_cv.pdf) -![CV Image](https://dalton-cv-artifacts.s3.us-east-1.amazonaws.com/images/cv.png) +![CV Image](https://dalton-cv-artifacts.s3.us-east-1.amazonaws.com/images/dalton_luce_cv.png) ### Cover Letter   [![PDF](https://img.shields.io/badge/PDF-blue?style=flat)](https://dalton-cv-artifacts.s3.us-east-1.amazonaws.com/pdfs/cover_letter.pdf) @@ -31,37 +33,20 @@ Maybe I can convince you to become one too—_[here's why](#why-this-setup)._ ## Building -> [!NOTE] -> Requires `pdflatex` and `imagemagick` (for image generation). On macOS, you can install them with: -> -> ```shell -> brew install basictex imagemagick -> ``` -> -> Then reload your terminal to ensure the binaries are in your PATH. - -1. Install Python dependencies for testing: +1. Enter the development container (this requires Docker) ```shell - make install + make enter ``` - ```shell - source .venv/bin/activate - ```` - -2. Build all outputs +2. Build and test all outputs ```shell - # Build everything (CV + cover letter + images) - make all + make all && make test ``` To view other available targets, run `make help`. -> [!NOTE] -> The Makefile builds PDFs and images locally. PDFs and images are uploaded to an [S3](https://aws.amazon.com/s3/) bucket via a [GitHub Action](./.github/workflows/deploy.yml) for easy access. Storage cost is extremely low: for a 2 MB resume and cover letter, it would be just over a cent for three **years**. - ## Deploying Artifacts to AWS Follow these steps to set up your AWS account and configure GitHub Actions for automatic CV deployment. @@ -99,24 +84,17 @@ Follow these steps to set up your AWS account and configure GitHub Actions for a 5. See it in action - Once the secrets are added, the provided [workflow](./.github/workflows/deploy.yml) will automatically build your CV PDFs and preview images, then upload them to the configured S3 bucket whenever you push changes to the repository. - -> **Note:** S3 storage is extremely cheap—effectively pennies for years of CV and cover letter artifacts. + Once the secrets are added, the provided [workflow](./.github/workflows/deploy.yml) will automatically build your CV PDFs and preview images, then upload them to the configured S3 bucket whenever you push changes to the repository. + You can also run `make release` to upload artifacts to S3. ## Why This Setup? -| Component | Why? | -| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Git** | Tracks every change in fine detail—much more granular than cloud-hosted solutions like Google Drive. Supports collaboration, safe experimentation, and integrates seamlessly with automation workflows. | -| **LaTeX** | Produces professional, highly customizable PDFs. Standard in math and engineering fields, and easy to track with Git. | -| **AWS S3** | Cheap, reliable hosting. Stores the latest PDFs and images outside the repo (avoiding repo bloat) and provides a stable URL accessible by anyone on the internet. | -| **Terraform** | Makes AWS setup reproducible and version-controlled. Anyone (including future you) can recreate the environment easily. | - -## To be Implemented - -* [ ] Improve testing and add to `deploy.yml` action -* [x] Add Terraform template for AWS setup -* [x] Add details on AWS usage in README -* [x] GitHub action to only generate new PDFs/Images on `main` to reduce repo size -* [x] A more standard build process that works on any OS (✅ Makefile added) -* [x] Migrate from requirements.txt to pyproject.toml for Python dependencies +| Component | Why? | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [**Git**](https://git-scm.com/) | Tracks every change in fine detail—much more granular than cloud-hosted solutions like Google Drive. Supports collaboration, safe experimentation, and integrates seamlessly with automation workflows. | +| [**LaTeX**](https://www.latex-project.org/) | Produces professional, highly customizable PDFs. Standard in math and engineering fields, and easy to track with Git. | +| [**AWS S3**](https://aws.amazon.com/s3/) | Cheap (pennies for 2 MB of assets) and reliable hosting. Stores the latest PDFs and images outside the repo (avoiding repo bloat) and provides a stable URL accessible by anyone on the internet. | +| [**Terraform**](https://developer.hashicorp.com/terraform) | Makes AWS setup reproducible and version-controlled. Anyone (including future you) can recreate the environment easily. | +| [**GitHub Actions**](https://github.com/features/actions) | Automates the build, test, and deployment workflow on every push. Ensures your CV and cover letter are always up-to-date without manual intervention, seamlessly integrating with Git and AWS S3. | +| [**cvlint**](https://github.com/da-luce/cvlint) | Automatically checks your CV for formatting and content issues. | +| [**Docker**](https://www.docker.com/) | Encapsulates all dependencies and tools needed to build your CV into one portable image, guaranteeing the build runs exactly the same anywhere. | diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index d3a6669..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,32 +0,0 @@ -[build-system] -requires = ["setuptools>=45", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "cv-tools" -version = "1.0.0" -description = "Tools for building and testing LaTeX CV" -authors = [{ name = "Dalton Luce", email = "your.email@example.com" }] -readme = "README.md" -license = { text = "MIT" } -requires-python = ">=3.8" -dependencies = ["pypdf==6.0.0", "pyspellchecker==0.7.1", "pytest==8.4.0"] - -[project.optional-dependencies] -dev = ["black", "flake8", "mypy"] - -[tool.pytest.ini_options] -testpaths = ["test"] -python_files = ["test_*.py"] -python_classes = ["Test*"] -python_functions = ["test_*"] - -[tool.black] -line-length = 88 -target-version = ['py38'] - -[tool.mypy] -python_version = "3.8" -warn_return_any = true -warn_unused_configs = true -disallow_untyped_defs = true diff --git a/src/cv.tex b/src/dalton_luce_cv.tex similarity index 98% rename from src/cv.tex rename to src/dalton_luce_cv.tex index acb03fe..4eaa901 100644 --- a/src/cv.tex +++ b/src/dalton_luce_cv.tex @@ -44,11 +44,16 @@ \usepackage{titlesec} \usepackage[usenames,dvipsnames]{color} \usepackage{enumitem} -\usepackage[hidelinks]{hyperref} \usepackage{multicol} \usepackage{tabularx} \usepackage[T1]{fontenc} \input{glyphtounicode} +\usepackage[ + hidelinks, + % This adds metda data fields to the PDF + pdfauthor={Dalton Luce}, + pdftitle={Dalton Luce Resume}, +]{hyperref} %------------------------- % Page Layout diff --git a/test/test.py b/test/test.py deleted file mode 100644 index d03ee4d..0000000 --- a/test/test.py +++ /dev/null @@ -1,47 +0,0 @@ -from pypdf import PdfReader -from spellchecker import SpellChecker -from pathlib import Path -import re -import pytest - -# ---------- Configuration ---------- -PDF_PATH = Path("dist/pdfs/dalton_luce_cv.pdf") -TEX_PATH = Path("src/cv.tex") - -# Custom words to ignore in spellcheck -CUSTOM_WORDS = { - "tailwind", "github", "lambda", "s3", "msk", "step", "functions", - "ci/cd", "terraform", "cloudformation", "clearcase", "node.js", - "ros", "verilog", "groovy", "java", "c++", "javascript", "typescript", - "svelte", "docker", "jenkins", "grafana", "opencv", "autobike", - "linkedin", "daltonluce.com", "nix", "ntp", "ieee", "hkn", "cv", -} - -# ---------- PDF Tests ---------- -def test_pdf_exists(): - assert PDF_PATH.is_file(), f"CV PDF not found at {PDF_PATH}" - -def test_pdf_page_count(): - pdf = PdfReader(PDF_PATH) - assert len(pdf.pages) <= 1, f"CV is {len(pdf.pages)} pages (should be ≤1 page)" - -# ---------- LaTeX Spellcheck ---------- -def test_tex_spellcheck(): - assert TEX_PATH.is_file(), f".tex file not found at {TEX_PATH}" - - with open(TEX_PATH, "r", encoding="utf-8") as f: - tex_content = f.read() - - # Extract words from LaTeX content (remove commands, comments, etc.) - # Remove LaTeX comments - tex_content = re.sub(r'%.*$', '', tex_content, flags=re.MULTILINE) - # Remove LaTeX commands - tex_content = re.sub(r'\\[a-zA-Z]+\*?(\[[^\]]*\])?(\{[^}]*\})*', '', tex_content) - # Remove special characters and extract words - words = re.findall(r'\b[a-zA-Z]+\b', tex_content.lower()) - - spell = SpellChecker() - spell.word_frequency.load_words(CUSTOM_WORDS) - - misspelled = spell.unknown(words) - assert not misspelled, f"Spelling errors found: {misspelled}"