Commit
Add Docker image, compose file and deploy script for code.rasmusj.com
commit
dfe884e
4 changed files with +265 and −0
Jump to a changed file
- DEPLOY.md +135 −0
- Dockerfile +32 −0
- deploy.sh +64 −0
- docker-compose.yml +34 −0
added DEPLOY.md +135 −0
| @@ -0,0 +1,135 @@ | ||
| 1 | +# profileShare juurutamine, code.rasmusj.com | |
| 2 | + | |
| 3 | +Eesmärk: rasmusj.com saab näidata privaatsete repode koodi ilma ühtegi repo | |
| 4 | +avalikuks tegemata. Portfooliolehel on kaks linki, üks kogu hetktõmmisele ja | |
| 5 | +üks iga projekti juures otse selle repo juurde. | |
| 6 | + | |
| 7 | +Kolm sammu, millest ainult esimest ja kolmandat pead ise tegema. | |
| 8 | + | |
| 9 | +--- | |
| 10 | + | |
| 11 | +## 1. DNS ja GitHubi app (10 min, sinu teha) | |
| 12 | + | |
| 13 | +**DNS.** Lisa A-kirje `code.rasmusj.com` -> `YOUR_SERVER_IP`, sama IP mis | |
| 14 | +ülejäänud. Oota, kuni `nslookup code.rasmusj.com` vastab. | |
| 15 | + | |
| 16 | +**GitHubi app.** Sul on app juba olemas ja töötab lokaalselt. Vaja on ainult | |
| 17 | +kolm aadressi ümber suunata, sest need peavad olema samal origin'il kui | |
| 18 | +`BASE_URL`. Ava github.com/settings/apps, vali oma app ja muuda: | |
| 19 | + | |
| 20 | +| Väli | Uus väärtus | | |
| 21 | +|---|---| | |
| 22 | +| Homepage URL | `https://code.rasmusj.com` | | |
| 23 | +| Callback URL | `https://code.rasmusj.com/auth/github/callback` | | |
| 24 | +| Setup URL | `https://code.rasmusj.com/github/installed` | | |
| 25 | + | |
| 26 | +Klienditunnust ja saladust vahetada ei ole vaja, need jäävad samaks. | |
| 27 | + | |
| 28 | +**Kontrolli, et app on paigaldatud kõigile portfoolio repodele.** Ava | |
| 29 | +github.com/settings/installations, vali app, "Configure", ja veendu, et | |
| 30 | +valitud on `splitapp`, `roundtable`, `voicetask`, `allyouknow`, | |
| 31 | +`countrysense` ja `profileshare`. Kui mõni puudub, lisa. | |
| 32 | + | |
| 33 | +--- | |
| 34 | + | |
| 35 | +## 2. Server (esmakordselt, minu ette valmistatud) | |
| 36 | + | |
| 37 | +**Caddy plokk.** Lisa `/opt/caddy/Caddyfile` faili ja lae Caddy uuesti | |
| 38 | +(`docker exec caddy caddy reload --config /etc/caddy/Caddyfile`): | |
| 39 | + | |
| 40 | +``` | |
| 41 | +code.rasmusj.com { | |
| 42 | + encode zstd gzip | |
| 43 | + reverse_proxy profileshare:3000 | |
| 44 | +} | |
| 45 | +``` | |
| 46 | + | |
| 47 | +**.env serveris.** See fail elab ainult serveris, `deploy.sh` ei kirjuta seda | |
| 48 | +kunagi üle. Loo see kord: | |
| 49 | + | |
| 50 | +```bash | |
| 51 | +mkdir -p /opt/projects/profileshare | |
| 52 | +cat > /opt/projects/profileshare/.env <<'EOF' | |
| 53 | +GITHUB_CLIENT_ID=... | |
| 54 | +GITHUB_CLIENT_SECRET=... | |
| 55 | +GITHUB_APP_SLUG=... | |
| 56 | +NODE_ENV=production | |
| 57 | +SESSION_SECRET=... | |
| 58 | +BASE_URL=https://code.rasmusj.com | |
| 59 | +PORT=3000 | |
| 60 | +DATABASE_PATH=data/profileshare.db | |
| 61 | +EOF | |
| 62 | +chmod 600 /opt/projects/profileshare/.env | |
| 63 | +``` | |
| 64 | + | |
| 65 | +Kolm esimest väärtust kopeeri oma lokaalsest `.env` failist. **`SESSION_SECRET` | |
| 66 | +genereeri uus**, sest serveri andmebaas on uus ja lokaalse seansisaladuse | |
| 67 | +kaasa vedamine ei anna midagi peale ühe lisakoha, kust see lekkida saab: | |
| 68 | + | |
| 69 | +```bash | |
| 70 | +node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))" | |
| 71 | +``` | |
| 72 | + | |
| 73 | +Lokaalset `data/profileshare.db` faili serverisse ei kopeerita. Selles on | |
| 74 | +neli aegunud hetktõmmist ja mõtet neid kaasa tuua ei ole. | |
| 75 | + | |
| 76 | +**Juuruta:** | |
| 77 | + | |
| 78 | +```bash | |
| 79 | +./deploy.sh | |
| 80 | +``` | |
| 81 | + | |
| 82 | +Skript jooksutab enne saatmist testid, keeldub tööst kui `.env` puudub, ja | |
| 83 | +ootab lõpus, kuni tervisekontroll roheliseks läheb. | |
| 84 | + | |
| 85 | +--- | |
| 86 | + | |
| 87 | +## 3. Hetktõmmis ja lingid portfooliolehele (5 min, sinu teha) | |
| 88 | + | |
| 89 | +1. Ava `https://code.rasmusj.com`, logi GitHubiga sisse. | |
| 90 | +2. Vali kõik kuus repot: `splitapp`, `roundtable`, `voicetask`, `allyouknow`, | |
| 91 | + `countrysense`, `profileshare`. | |
| 92 | +3. Vali kehtivusaeg. **Võta 365 päeva.** Praktikakohta otsid mitu kuud ja | |
| 93 | + kirjades olev link peab veel siis töötama, kui keegi selle kolm nädalat | |
| 94 | + hiljem avab. | |
| 95 | +4. Kopeeri loodud link. See on kujul `https://code.rasmusj.com/s/<shareId>`. | |
| 96 | +5. Saada see link mulle, siis panen portfooliolehele paika. Või tee ise: | |
| 97 | + `portfolio/lib/content.ts`, väli `codeAccess.url`. | |
| 98 | + | |
| 99 | +**Repo ID-d.** Kolm on juba teada, need on GitHubi püsivad numbrid ja neid ei | |
| 100 | +pea uuesti otsima: | |
| 101 | + | |
| 102 | +| Projekt | repoId | | |
| 103 | +|---|---| | |
| 104 | +| roundtable | `1296615860` | | |
| 105 | +| countrysense | `1299668802` | | |
| 106 | +| voicetask | `1309047169` | | |
| 107 | + | |
| 108 | +Puudu on `splitapp`, `allyouknow` ja `profileshare`. Need saab kätte nii: ava | |
| 109 | +hetktõmmises see repo ja vaata aadressi lõppu, `/repositories/<number>`. | |
| 110 | + | |
| 111 | +--- | |
| 112 | + | |
| 113 | +## Kui link aegub | |
| 114 | + | |
| 115 | +Tee töölaual uus hetktõmmis ja asenda portfoolios **üks rida**, | |
| 116 | +`codeAccess.url`. Repo ID-d on püsivad ja neid ei pea puutuma, sest | |
| 117 | +projektilingid ehitatakse kokku funktsiooniga `repoUrl()`. | |
| 118 | + | |
| 119 | +Kui link on aegunud ja uut ei ole, kaovad koodilingid lehelt ise ära ja tekst | |
| 120 | +läheb tagasi variandile "küsi ja saadan lingi". Katkist linki leht kunagi ei | |
| 121 | +näita. | |
| 122 | + | |
| 123 | +## Kust mida vaadata | |
| 124 | + | |
| 125 | +```bash | |
| 126 | +docker logs -f profileshare # logid | |
| 127 | +docker inspect -f '{{.State.Health.Status}}' profileshare | |
| 128 | +docker exec profileshare ls -la /app/data # andmebaas on siin | |
| 129 | +``` | |
| 130 | + | |
| 131 | +Varundus on ühe faili kopeerimine: | |
| 132 | + | |
| 133 | +```bash | |
| 134 | +docker cp profileshare:/app/data/profileshare.db ./profileshare-backup.db | |
| 135 | +``` |
added Dockerfile +32 −0
| @@ -0,0 +1,32 @@ | ||
| 1 | +# syntax=docker/dockerfile:1 | |
| 2 | + | |
| 3 | +# Node 24 because the app stores everything through `node:sqlite`, which is | |
| 4 | +# built in from 22 and stable from 24. That is also why there is no build stage | |
| 5 | +# and no compiler in this image: every dependency is plain JavaScript, so there | |
| 6 | +# is no native module to compile and nothing to bundle. | |
| 7 | +FROM node:24-bookworm-slim | |
| 8 | +WORKDIR /app | |
| 9 | + | |
| 10 | +ENV NODE_ENV=production | |
| 11 | +ENV PORT=3000 | |
| 12 | + | |
| 13 | +COPY package*.json ./ | |
| 14 | +RUN npm ci --omit=dev | |
| 15 | + | |
| 16 | +COPY src ./src | |
| 17 | + | |
| 18 | +# The SQLite file lives in a named volume mounted here. It has to belong to the | |
| 19 | +# `node` user, because the container does not run as root. | |
| 20 | +RUN mkdir -p /app/data && chown -R node:node /app/data | |
| 21 | + | |
| 22 | +USER node | |
| 23 | + | |
| 24 | +EXPOSE 3000 | |
| 25 | + | |
| 26 | +# Node 24 has global fetch, so the slim image needs no curl for this. The root | |
| 27 | +# path renders the sign-in page and touches the database, so a 200 here means | |
| 28 | +# both the server and its storage are alive. | |
| 29 | +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ | |
| 30 | + CMD node -e "fetch('http://127.0.0.1:3000/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" | |
| 31 | + | |
| 32 | +CMD ["npm", "start"] |
added deploy.sh +64 −0
| @@ -0,0 +1,64 @@ | ||
| 1 | +#!/usr/bin/env bash | |
| 2 | +# One-command redeploy for code.rasmusj.com. | |
| 3 | +# | |
| 4 | +# Ships the source to the VPS, rebuilds the image there and restarts the | |
| 5 | +# container. Run from Git Bash on Windows: ./deploy.sh | |
| 6 | +# | |
| 7 | +# Two things this script deliberately never touches: | |
| 8 | +# | |
| 9 | +# .env lives on the server only. It holds the GitHub App secret and the | |
| 10 | +# session secret, and overwriting it from here would eventually | |
| 11 | +# push a stale copy over a good one. | |
| 12 | +# Caddyfile the shared reverse-proxy config at /opt/caddy. First-time setup | |
| 13 | +# is done once by hand, so a routine redeploy cannot take other | |
| 14 | +# sites down with it. | |
| 15 | +# | |
| 16 | +# The SQLite database is in a named Docker volume, so it survives every rebuild. | |
| 17 | +set -euo pipefail | |
| 18 | + | |
| 19 | +VPS=user@host | |
| 20 | +DEST=/opt/projects/profileshare | |
| 21 | + | |
| 22 | +cd "$(dirname "$0")" | |
| 23 | + | |
| 24 | +echo "==> Running the test suite before shipping anything..." | |
| 25 | +npm test | |
| 26 | + | |
| 27 | +echo "==> Ensuring VPS project dir exists..." | |
| 28 | +ssh "$VPS" "mkdir -p '$DEST'" | |
| 29 | + | |
| 30 | +echo "==> Checking that .env exists on the server..." | |
| 31 | +if ! ssh "$VPS" "test -f '$DEST/.env'"; then | |
| 32 | + echo "" | |
| 33 | + echo " $DEST/.env is missing. Create it on the server first, see DEPLOY.md." | |
| 34 | + echo " Nothing has been changed." | |
| 35 | + exit 1 | |
| 36 | +fi | |
| 37 | + | |
| 38 | +echo "==> Uploading source (node_modules, data and .env stay out of it)..." | |
| 39 | +tar --exclude=node_modules --exclude=data --exclude=.env --exclude=.git \ | |
| 40 | + --exclude=coverage -czf - . | ssh "$VPS" " | |
| 41 | + rm -rf '$DEST/src.tmp' && | |
| 42 | + mkdir -p '$DEST/src.tmp' && | |
| 43 | + tar -C '$DEST/src.tmp' -xzf - && | |
| 44 | + cp '$DEST/.env' '$DEST/src.tmp/.env' && | |
| 45 | + rm -rf '$DEST/app' && | |
| 46 | + mv '$DEST/src.tmp' '$DEST/app' | |
| 47 | +" | |
| 48 | + | |
| 49 | +echo "==> Rebuilding and restarting the container..." | |
| 50 | +ssh "$VPS" "cd '$DEST/app' && docker compose up -d --build" | |
| 51 | + | |
| 52 | +echo "==> Waiting for the health check to go green..." | |
| 53 | +ssh "$VPS" " | |
| 54 | + for i in \$(seq 1 20); do | |
| 55 | + state=\$(docker inspect -f '{{.State.Health.Status}}' profileshare 2>/dev/null || echo starting) | |
| 56 | + [ \"\$state\" = healthy ] && echo ' healthy' && exit 0 | |
| 57 | + sleep 3 | |
| 58 | + done | |
| 59 | + echo ' still not healthy, check: docker logs profileshare' | |
| 60 | + exit 1 | |
| 61 | +" | |
| 62 | + | |
| 63 | +echo "" | |
| 64 | +echo "==> Done. Live at https://code.rasmusj.com" |
added docker-compose.yml +34 −0
| @@ -0,0 +1,34 @@ | ||
| 1 | +# code.rasmusj.com, profileShare. | |
| 2 | +# | |
| 3 | +# Same shape as the other projects on this host: Caddy already runs on the | |
| 4 | +# external `web` network and terminates TLS, so nothing here is published to | |
| 5 | +# the host directly. The container is only reachable through the proxy. | |
| 6 | +# | |
| 7 | +# The domain lives in the Caddyfile alone. Changing it never touches this file. | |
| 8 | + | |
| 9 | +services: | |
| 10 | + profileshare: | |
| 11 | + build: . | |
| 12 | + container_name: profileshare | |
| 13 | + restart: unless-stopped | |
| 14 | + env_file: .env | |
| 15 | + volumes: | |
| 16 | + # The snapshots and the owner session live in one SQLite file. Backing up | |
| 17 | + # is copying the volume; there is nothing else to keep. | |
| 18 | + - profileshare-data:/app/data | |
| 19 | + networks: | |
| 20 | + - web | |
| 21 | + expose: | |
| 22 | + - "3000" | |
| 23 | + logging: | |
| 24 | + driver: json-file | |
| 25 | + options: | |
| 26 | + max-size: "10m" | |
| 27 | + max-file: "3" | |
| 28 | + | |
| 29 | +volumes: | |
| 30 | + profileshare-data: | |
| 31 | + | |
| 32 | +networks: | |
| 33 | + web: | |
| 34 | + external: true |