Skip to content

avi892nash/devshram

Repository files navigation

Devshram

A website of free tools and blogs.

Personal portfolio + blog built with Next.js 15 (App Router, SSR, standalone output), React 19, TypeScript, Tailwind CSS, and Framer Motion. Releases ship as a self-installing tarball with a systemd service and a GitHub-poll auto-updater.


Quick start

npm ci
npm run dev   # http://localhost:3000

Production build:

npm run build && npm start

Tech stack

Layer Choice
Framework Next.js 15 (App Router, SSR, output: 'standalone')
UI React 19, Tailwind CSS, Framer Motion
Language TypeScript 5
Hosting Self-hosted SSR behind Cloudflare (app assets served from origin); project showcase content on S3 + CloudFront at assets.devshram.com/projects/*
Releases semantic-release on main
Distribution Tarball with systemd installer

Project conventions

File naming

  • React components: PascalCase.tsx (UserProfile.tsx)
  • Pages: page.tsx (App Router)
  • Utilities: camelCase.ts
  • Type-only files: types.ts or ComponentName.types.ts

Component shape

interface ComponentProps {
  // typed props, no `any`
}

export default function Component({ prop }: ComponentProps) {
  return <div>...</div>;
}

Import order

  1. React
  2. Next.js
  3. Third-party
  4. Local (@/components, @/utils, ...)

TypeScript

  • Define interface for object shapes; type for unions/primitives.
  • Strict null checks; no any.
  • Export types that callers may reuse.

Styling

  • Tailwind first; custom CSS only when Tailwind can't express it.
  • Mobile-first responsive (sm:, md:, lg:).
  • Never pass z-index as a prop — express layering via component structure and Tailwind z-* classes.

Accessibility

  • Semantic HTML, ARIA labels where needed.
  • Keyboard reachable.
  • Color contrast: WCAG AA minimum.

State

  • React hooks for local state.
  • Shared state lives in src/state/; follow existing patterns.

Performance

  • Use next/image for images.
  • Code-split heavy components; the optimizePackageImports hint in next.config.ts already tree-shakes Framer Motion per-import.

Animations

The portfolio uses Framer Motion. Reusable wrappers live in src/components/:

Component Purpose
AnimatedSection Scroll-triggered enter animation. Props: direction (up/down/left/right), delay, duration, distance.
AnimatedText Three modes via type: fade, typewriter, slide.
AnimatedCard Entrance + hover lift/scale.
AnimatedLoader Rotating spinner; size and color configurable.

Patterns

  • useInView with once: true so sections animate only on first scroll into view.
  • Prefer transforms (GPU-accelerated) over layout-changing properties.
  • Stagger children with small delay increments to guide attention.

Examples

<AnimatedSection delay={0.2} direction="up">
  <YourContent />
</AnimatedSection>

<AnimatedText text="Hello World" type="typewriter" delay={0.5} />

<AnimatedCard delay={0.3} hover>
  <CardContent />
</AnimatedCard>

Releases

semantic-release on push to main analyzes commit messages, bumps version, updates CHANGELOG.md, creates a git tag, and creates a GitHub release with the SSR tarball asset attached.

Configured in .releaserc.json. The release flow lives in .github/workflows/release.yml.

Conventional Commits

The version bump is determined by commit type:

Type Bump Example
fix: patch (0.1.0 → 0.1.1) fix(auth): resolve login timeout
feat: minor (0.1.0 → 0.2.0) feat: add dark mode toggle
feat!: / BREAKING CHANGE: major feat(api)!: change response format
chore:, docs:, style:, refactor:, test:, ci: none chore: bump deps

Commits are validated by commitlint via Husky. Use npm run commit for an interactive prompt:

git add .
npm run commit

What ships per release

  • GitHub release: devshram-ssr-v<X.Y.Z>.tar.gz asset (self-installing — see Deployment).
  • Bundled static assets: the tarball ships .next/static/ and public/ alongside the standalone server, which serves them at the origin. Cloudflare proxies the origin and caches at the edge — no separate CDN host, no upload step. Hashed .next/static/* filenames mean no cache invalidation is needed across versions.

Deployment

Each release publishes a self-installing tarball as a GitHub release asset. Install on any systemd Linux (Debian, Ubuntu, Fedora, RHEL, Arch, openSUSE…) with one script. The installer registers a systemd service plus a self-update timer that polls GitHub releases every 5 minutes and applies new versions automatically.

Requirements

  • Linux with systemd
  • Node.js 20+ in $PATH. Debian 12 ships Node 18 — install from NodeSource first.
  • curl, tar, flock (standard on every supported distro)
  • 512 MB RAM minimum (1 GB recommended)

Install

One-liner — installs the latest release:

curl -fsSL https://raw.githubusercontent.com/avi892nash/devshram/main/packaging/install-latest.sh | sudo bash

The bootstrap script queries https://api.github.com/repos/avi892nash/devshram/releases/latest, downloads the tarball, extracts it, and runs the embedded install.sh. Source: packaging/install-latest.sh.

You only run this once. After the first install, the devshram-update.timer systemd unit polls GitHub every 5 minutes and applies new releases automatically. Don't re-run this script to upgrade — see Auto-update below.

If you'd rather not pipe curl into a shell, do it manually — pick a version from the releases page and:

VERSION=0.0.18
curl -fsSLO "https://github.com/avi892nash/devshram/releases/download/v${VERSION}/devshram-ssr-v${VERSION}.tar.gz"
tar -xzf "devshram-ssr-v${VERSION}.tar.gz"
sudo bash devshram/install.sh

Either way, after install:

  • Service: devshram.service (logs via journalctl -u devshram)
  • Auto-update timer: devshram-update.timer (polls every 5 min; logs via journalctl -u devshram-update)
  • Server: http://localhost:8888

What gets installed

Path Purpose
/usr/lib/devshram/ Standalone Next.js server + minimal node_modules
/etc/devshram/.env Editable config (PORT, NODE_ENV, HOSTNAME)
/var/lib/devshram/installed-tag Currently-installed release tag (read by the updater)
/etc/systemd/system/devshram*.service Service + updater oneshot
/etc/systemd/system/devshram-update.timer 5-minute poll timer
/usr/local/bin/devshram-update Updater script

Configuration

Edit /etc/devshram/.env and restart the service:

sudo systemctl restart devshram

Defaults:

NODE_ENV=production
PORT=8888
HOSTNAME=0.0.0.0

Auto-update

Enabled by default. The timer fires 2 min after boot and every 5 min after, queries /repos/avi892nash/devshram/releases/latest, and runs install.sh --upgrade if a newer tag exists.

# Disable
sudo systemctl disable --now devshram-update.timer

# Force a check now
sudo systemctl start devshram-update.service
sudo journalctl -u devshram-update -n 30

The updater is unauthenticated — GitHub's 60 req/h IP limit covers a 5-minute timer on a single host.

Uninstall

sudo bash devshram/uninstall.sh           # keeps /etc/devshram and /var/lib/devshram
sudo bash devshram/uninstall.sh --purge   # removes config, state, and the system user

Troubleshooting

# Service status & logs
sudo systemctl status devshram
sudo journalctl -u devshram -f

# Updater logs
sudo journalctl -u devshram-update -n 50

# Currently-installed tag
sudo cat /var/lib/devshram/installed-tag

# Next scheduled update poll
systemctl list-timers devshram-update.timer

Port already in use — edit /etc/devshram/.env, change PORT=8888, sudo systemctl restart devshram.

Updater stuck on an old version — check journalctl -u devshram-update -n 100 for HTTP errors (rate limits, network). Force: sudo systemctl start devshram-update.service.


CI / workflows

File Trigger What it does
.github/workflows/release.yml Push to main Compile check → semantic-release (tag, CHANGELOG, GitHub release) → production build → tarball → upload tarball asset to the release
.github/workflows/smoke-test.yml PR to main + after release.yml on main Matrix install of the tarball (Node 20, 22, 24) — sudo bash install.sh, HTTP probe, re-run installer for idempotency, --purge
.github/workflows/pr-test.yml PR to main Build, lint, type-check, dev-server route smoke (/, /about-me/, /projects/, …)

smoke-test.yml actually installs the tarball on the runner via sudo bash install.sh, hits the service over HTTP, re-runs the installer to verify idempotency, then --purges. On PRs it gates the merge; after a push to main it gates whether the just-cut release is healthy.

The packaging scripts that get tested live in packaging/:

  • install.sh / uninstall.sh — root-elevating, idempotent, both runnable standalone (shipped inside the tarball).
  • install-latest.sh — bootstrap for first-time installs. Curl-pipeable; queries the GitHub API, fetches the latest tarball, runs install.sh.
  • build-tarball.sh — produces devshram-ssr-v<X>.tar.gz. Honors SKIP_BUILD=1 so CI can reuse a build it already ran.
  • bin/devshram-update — the bash GitHub poller dropped at /usr/local/bin/devshram-update.
  • systemd/*.{service,timer} — installed at /etc/systemd/system/.

Adding a portfolio project

The portfolio takes structured project metadata (title, description, tech, category, theme, links, image). To extract that from a free-form description, paste the prompt below into any AI assistant along with your project info.

Required JSON shape

{
  "title": "Project name (concise, clear)",
  "description": "One-sentence description (max 100 chars)",
  "technologies": ["Array", "of", "technologies"],
  "category": "complete-app | small-project | blog | tool",
  "theme": "purple | green | brown | dark",
  "featured": true,
  "liveLink": "https://assets.devshram.com/projects/{slug}/index.html",
  "githubLink": "https://github.com/...",
  "figmaLink": "(optional)",
  "cachedLink": "(optional)",
  "image": "https://assets.devshram.com/projects/{slug}/main.png"
}

Field rules

  • categorycomplete-app for full apps, small-project for prototypes, blog for articles, tool for utilities/CLIs.
  • themepurple (AI/ML), green (productivity, devtools), brown (general web), dark (portfolio/meta).
  • featuredtrue only for standout work.
  • liveLink / image{slug} must match the GitHub workflow YAML filename for that project.
  • Omit cachedLink, githubLink, figmaLink when not applicable — don't pass empty strings.

Prompt template

I need to add a project to my portfolio. Analyze the following and produce
JSON matching the schema below. Respond with ONLY the JSON.

[PASTE PROJECT INFO — repo URL, README, description, or any combination]

Schema:
{
  "title": "...",
  "description": "max 100 chars",
  "technologies": ["..."],
  "category": "complete-app | small-project | blog | tool",
  "theme": "purple | green | brown | dark",
  "featured": true | false,
  "liveLink": "https://assets.devshram.com/projects/{slug}/index.html",
  "githubLink": "(optional)",
  "figmaLink": "(optional)",
  "cachedLink": "(optional)",
  "image": "https://assets.devshram.com/projects/{slug}/main.png"
}

About

A website of free tools and blogs.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors