| 1 |
# NestEggCode — a lightweight, self-hosted code browser |
| 2 |
|
| 3 |
A tiny, dependency-free PHP application that gives you a **GitHub-style browse-only view of your code**, hosted on your own domain. Upload files (or whole folders, or a `.zip`) into named repositories from a simple admin panel, and share clean, readable URLs where visitors can browse the file tree, read syntax-highlighted source, and see your `README.md` rendered on the repo's front page. |
| 4 |
|
| 5 |
It is **not** a version-control system. Think of it as a personal, self-hosted showcase for code you want to publish and link to — without a GitHub account, without Git, and without giving anyone else control over where your code lives. |
| 6 |
|
| 7 |
--- |
| 8 |
|
| 9 |
## Table of contents |
| 10 |
|
| 11 |
- [Features](#features) |
| 12 |
- [What makes it different from GitHub](#what-makes-it-different-from-github) |
| 13 |
- [Limitations](#limitations) |
| 14 |
- [Server requirements](#server-requirements) |
| 15 |
- [Installation](#installation) |
| 16 |
- [1. Get the files onto the server](#1-get-the-files-onto-the-server) |
| 17 |
- [2. Create the database](#2-create-the-database) |
| 18 |
- [3. Configure `config.php`](#3-configure-configphp) |
| 19 |
- [4. Set your admin password](#4-set-your-admin-password) |
| 20 |
- [5. Point the router at the right base path](#5-point-the-router-at-the-right-base-path) |
| 21 |
- [6. Set the public origin (recommended)](#6-set-the-public-origin-recommended) |
| 22 |
- [7. Adjust upload limits (optional)](#7-adjust-upload-limits-optional) |
| 23 |
- [8. Log in and create your first repository](#8-log-in-and-create-your-first-repository) |
| 24 |
- [Project layout](#project-layout) |
| 25 |
- [Security notes](#security-notes) |
| 26 |
|
| 27 |
--- |
| 28 |
|
| 29 |
## Features |
| 30 |
|
| 31 |
**Browsing (public, no login required)** |
| 32 |
|
| 33 |
- **Repository index** at the site root listing every repository, with an auto-detected or manually set language badge and description. |
| 34 |
- **GitHub-style file tree** with breadcrumbs, folder navigation, and persistent (even empty) folders. |
| 35 |
- **Syntax-highlighted source view** for a broad set of languages (PHP, JavaScript/TypeScript, Python, Java, C/C++, Go, Ruby, SQL, Bash, CSS, HTML, JSON, Markdown, and more) via a small built-in, dependency-free highlighter. |
| 36 |
- **Rendered `README.md`** on the repository root, using a built-in GitHub-Flavored-Markdown renderer (headings, tables, task lists, fenced code, images, links, blockquotes, and more) — no external Markdown library required. |
| 37 |
- **Image rendering** for PNG, JPG, GIF, SVG, WebP, BMP, ICO, and AVIF, with a **Rendered / Source toggle** for SVGs. |
| 38 |
- **Interactive 3D viewer** for **STL** (binary + ASCII), **3MF**, **OBJ**, **PLY**, and **G-code** — orbit / zoom / pan a model right in the browser (G-code renders as a layer-coloured toolpath). Powered by a small built-in WebGL viewer with **no external 3D library** — 3MF archives are even unzipped in-browser via the native `DecompressionStream`. Text-based formats (OBJ, ASCII PLY, G-code) also keep a **Rendered / Source toggle**. |
| 39 |
- **Raw view and Download** buttons for every file (`/raw/` served with correct MIME type and `X-Content-Type-Options: nosniff`). |
| 40 |
- **Client-side file search** in the file view's sidebar tree. |
| 41 |
- **Automatic language detection** — a repo's dominant language is inferred from its files (documentation, license, and image files are ignored), or you can set the label manually. |
| 42 |
- **Generated social preview cards** — every repository gets its own 1200×630 OpenGraph image (name, slug, description, language, file count and size) drawn server-side with GD and cached on disk, so links unfurl with a proper card on Slack, Facebook, X, LinkedIn and iMessage. The image URL carries a content hash, which is what makes crawlers pick up a card after the repo changes. |
| 43 |
|
| 44 |
**Admin (single user, password-protected)** |
| 45 |
|
| 46 |
- **Create / edit / delete repositories**, each with a slug, name, description, and optional language label. |
| 47 |
- **Slug renaming** that moves the on-disk files and rewrites database paths in a single transaction. |
| 48 |
- **Drag-and-drop uploads** of individual files *or* whole folders (directory structure preserved via the browser's directory-upload API). |
| 49 |
- **Zip import** — upload a single `.zip` and it is unpacked into the repository, recreating its folder structure. A single wrapping top-level folder (e.g. `myrepo-main/`) is stripped automatically, and macOS/Windows junk (`__MACOSX/`, `.DS_Store`, `Thumbs.db`) is dropped. |
| 50 |
- **Manual folder creation**, including nested paths (`src/utils/helpers`). |
| 51 |
- **Per-file and per-folder deletion** (folder deletion cascades to everything inside it). |
| 52 |
- **Re-upload replaces** a same-named file in place. |
| 53 |
|
| 54 |
--- |
| 55 |
|
| 56 |
## What makes it different from GitHub |
| 57 |
|
| 58 |
| | This app | GitHub | |
| 59 |
|---|---|---| |
| 60 |
| **Purpose** | Publish & browse code you upload | Full source-control platform | |
| 61 |
| **Version control** | None — files are just stored and shown | Full Git history, branches, diffs | |
| 62 |
| **Where it runs** | Your own server / shared hosting | GitHub's cloud | |
| 63 |
| **Accounts** | One admin (you); everyone else is a read-only visitor | Multi-user, orgs, teams | |
| 64 |
| **Collaboration** | None (no PRs, issues, forks, comments) | PRs, issues, reviews, CI/CD | |
| 65 |
| **Dependencies** | Zero — plain PHP + MySQL, no Composer/npm | N/A (hosted service) | |
| 66 |
| **Data ownership** | 100% yours, on your host | Hosted by GitHub | |
| 67 |
| **Setup** | Copy files, create a DB, edit one config file | Sign up | |
| 68 |
| **Cost** | Whatever your hosting costs | Free / paid tiers | |
| 69 |
|
| 70 |
**In short:** GitHub is a collaboration and version-control platform. This is a self-contained, self-hosted *publisher* — the smallest thing that gives your code a clean, GitHub-like public face on a domain you control, with no third parties and no dependencies to keep up to date. |
| 71 |
|
| 72 |
--- |
| 73 |
|
| 74 |
## Limitations |
| 75 |
|
| 76 |
Be aware of what this project intentionally does **not** do: |
| 77 |
|
| 78 |
- **No version control.** No commits, branches, history, or diffs. Uploading a file simply overwrites the previous one. |
| 79 |
- **No collaboration features.** No issues, pull requests, forks, stars, comments, or multiple accounts. |
| 80 |
- **Single admin user.** Credentials live in `config.php`; there is no user-management UI or registration. |
| 81 |
- **No Git integration.** You cannot `git push` to it. Content is added through the web upload forms (files, folders, or a `.zip`). |
| 82 |
- **Browse-only for the public.** Visitors can read and download, but never edit. |
| 83 |
- **Basic syntax highlighting.** The highlighter is a compact, regex-based tokenizer, not a full language parser — it colors one line at a time and covers common cases rather than every edge case. |
| 84 |
- **Simplified Markdown.** The README renderer covers the common Markdown a README uses, but is not a full CommonMark implementation. |
| 85 |
- **File-size / count caps.** Uploads are bounded per file, per request, and per zip (see [upload limits](#6-adjust-upload-limits-optional)); large archives are cut off once the total uncompressed size ceiling is reached. |
| 86 |
- **No search across repositories.** File search is client-side and scoped to a single file view's tree. |
| 87 |
- **3D viewer scope.** The built-in 3D viewer covers STL, 3MF, OBJ, PLY, and G-code and needs a **WebGL-capable browser**. It does *not* handle glTF/GLB, STEP, FBX, or binary G-code (`.bgcode`); materials/textures and mesh transforms are ignored (geometry is shown with flat shading). Very large models are bounded by the upload size cap. |
| 88 |
|
| 89 |
--- |
| 90 |
|
| 91 |
## Server requirements |
| 92 |
|
| 93 |
- **PHP 7.4 or newer** (uses typed function signatures, `??`, arrow-ish syntax, etc.). PHP 8.x recommended. |
| 94 |
- **MySQL / MariaDB** with the **PDO MySQL** extension enabled. |
| 95 |
- **Apache with `mod_rewrite`** (an `.htaccess` front-controller rule ships with the app). Other web servers work too, but you'll need to translate the rewrite rule (route all non-file, non-directory requests to `index.php`). |
| 96 |
- **PHP `zip` extension** — *optional*, required only for the "Upload a zip" feature. Everything else works without it. |
| 97 |
- **PHP `gd` extension with FreeType support** — *optional*, required only for the generated social preview cards. Without it, pages simply advertise no `og:image` and everything else works unchanged. |
| 98 |
- Standard PHP file/session functions (`session`, `fopen`/`fwrite`, `move_uploaded_file`, `password_hash`/`password_verify`, `random_bytes`). |
| 99 |
- A writable **`uploads/`** directory. |
| 100 |
- **No extra PHP extension for 3D viewing** — STL/3MF/OBJ/PLY/G-code are parsed and rendered entirely client-side in a WebGL-capable browser; the server just stores and serves the raw file. |
| 101 |
|
| 102 |
Works out of the box on typical **cPanel shared hosting** and locally with **MAMP / XAMPP / WAMP**. |
| 103 |
|
| 104 |
--- |
| 105 |
|
| 106 |
## Installation |
| 107 |
|
| 108 |
### Easiest: the guided installer (recommended) |
| 109 |
|
| 110 |
Upload the project to your server, then visit **`https://your-site/install.php`** in a browser (or `.../pgh5/install.php` in a subfolder). The installer: |
| 111 |
|
| 112 |
1. Runs **server checks** (PHP version, database driver, writable folders) and explains how to fix anything that fails. |
| 113 |
2. Asks for your **database** and **admin** details, then **tests the connection**, **creates the tables**, writes `config.php`, and matches the `.htaccess` URL settings — all for you. |
| 114 |
3. Shows a **post-launch checklist** to confirm everything works. |
| 115 |
|
| 116 |
When you're done, the admin dashboard shows a banner offering to **delete `install.php`** (only after you tick a box confirming your site works — it never deletes anything on its own). **Remove `install.php` once the site is live** — leaving it in place is a security risk. |
| 117 |
|
| 118 |
Prefer to do it by hand? Follow the manual steps below instead. |
| 119 |
|
| 120 |
### Manual installation |
| 121 |
|
| 122 |
### 1. Get the files onto the server |
| 123 |
|
| 124 |
Copy the project into your web root (or a subfolder of it). For example: |
| 125 |
|
| 126 |
- **Production (domain root):** upload everything into `public_html/`. |
| 127 |
- **Local MAMP:** drop the folder into `htdocs/` (e.g. `htdocs/pgh5`). |
| 128 |
|
| 129 |
### 2. Create the database |
| 130 |
|
| 131 |
Create an empty MySQL database and a user with access to it (in cPanel: *MySQL Databases*; locally: phpMyAdmin). Then import the schema: |
| 132 |
|
| 133 |
```bash |
| 134 |
mysql -u YOUR_USER -p YOUR_DB < schema.sql |
| 135 |
``` |
| 136 |
|
| 137 |
…or import `schema.sql` through phpMyAdmin. It creates three tables: `repositories`, `files`, and `folders`. |
| 138 |
|
| 139 |
### 3. Configure `config.php` |
| 140 |
|
| 141 |
Open `config.php` and set your real values: |
| 142 |
|
| 143 |
```php |
| 144 |
define('DB_HOST', 'localhost'); |
| 145 |
define('DB_NAME', 'your_database_name'); |
| 146 |
define('DB_USER', 'your_database_user'); |
| 147 |
define('DB_PASS', 'your_database_password'); |
| 148 |
``` |
| 149 |
|
| 150 |
> ⚠️ **Keep `config.php` out of version control** and out of public reach. It holds your database credentials and admin password hash. The bundled `.htaccess` already denies direct web access to `config.php` and `schema.sql`, but you should still never commit real secrets to a public repo. Consider adding `config.php`, `uploads/`, and any `*.zip` to a `.gitignore` before pushing this project to GitHub. |
| 151 |
|
| 152 |
### 4. Set your admin password |
| 153 |
|
| 154 |
The admin password is stored as a **bcrypt hash**, never in plaintext. Generate one: |
| 155 |
|
| 156 |
```bash |
| 157 |
php -r "echo password_hash('your-strong-password-here', PASSWORD_DEFAULT), PHP_EOL;" |
| 158 |
``` |
| 159 |
|
| 160 |
Paste the result into `config.php` and pick a username: |
| 161 |
|
| 162 |
```php |
| 163 |
define('ADMIN_USER', 'your-admin-username'); |
| 164 |
define('ADMIN_PASS_HASH', '$2y$12$...the-hash-you-generated...'); |
| 165 |
``` |
| 166 |
|
| 167 |
> The shipped default hash is for the password `changeme` — **change it before going live.** |
| 168 |
|
| 169 |
### 5. Point the router at the right base path |
| 170 |
|
| 171 |
`BASE_PATH` is the URL prefix the app is served under. |
| 172 |
|
| 173 |
- **Domain root:** `define('BASE_PATH', '');` |
| 174 |
- **Subfolder (e.g. local `htdocs/pgh5`):** `define('BASE_PATH', '/pgh5');` |
| 175 |
|
| 176 |
Match this to the `RewriteBase` in `.htaccess` (defaults to `/`). If you deploy in a subfolder, set `RewriteBase /pgh5` accordingly. |
| 177 |
|
| 178 |
### 6. Set the public origin (recommended) |
| 179 |
|
| 180 |
`SITE_URL` is the site's scheme and host with no trailing slash and no `BASE_PATH`, e.g. `define('SITE_URL', 'https://code.example.com');`. |
| 181 |
|
| 182 |
Social crawlers reject relative `og:image` / `og:url` values, so share metadata has to be absolute. Left empty, the app derives the origin from the request's `Host` header — fine locally, but set it explicitly in production, since a client can send any `Host` it likes and behind a proxy the scheme may be guessed wrong. |
| 183 |
|
| 184 |
### 7. Adjust upload limits (optional) |
| 185 |
|
| 186 |
Two places govern upload sizes: |
| 187 |
|
| 188 |
- **`config.php`** — application-level caps: |
| 189 |
- `MAX_FILE_SIZE` — per-file limit (default **25 MB**), also applied to each file extracted from a zip. |
| 190 |
- `MAX_ZIP_TOTAL` — ceiling on the total *uncompressed* size of one zip import (default **200 MB**), a guard against zip bombs. |
| 191 |
- **`.user.ini`** — PHP-level caps (`upload_max_filesize`, `post_max_size`, `max_file_uploads`). |
| 192 |
|
| 193 |
> **Note:** `.user.ini` is read by PHP-FPM / CGI / FastCGI (how cPanel typically runs PHP) but is **ignored when PHP runs as an Apache module** (common in MAMP). If your uploads are being truncated locally, set the same values in `php.ini` instead, or add `php_value` lines to `.htaccess`. |
| 194 |
|
| 195 |
Make sure `uploads/` is writable by the web server. |
| 196 |
|
| 197 |
### 8. Log in and create your first repository |
| 198 |
|
| 199 |
1. Visit `https://your-domain/admin/login.php` (or `.../pgh5/admin/login.php` in a subfolder). |
| 200 |
2. Log in with the username and password you configured. |
| 201 |
3. From the **Dashboard**, create a repository (give it a slug like `save-utils` and a name). |
| 202 |
4. Click **Upload** to add files, a folder, or a `.zip`. |
| 203 |
5. Your repo is now public at `https://your-domain/your-slug`. |
| 204 |
|
| 205 |
--- |
| 206 |
|
| 207 |
## Project layout |
| 208 |
|
| 209 |
``` |
| 210 |
. |
| 211 |
├── install.php # Guided web installer (delete after setup) |
| 212 |
├── index.php # Front controller / router |
| 213 |
├── repo.php # Public repository views (tree / blob / raw) |
| 214 |
├── config.sample.php # Safe-to-commit config template |
| 215 |
├── config.php # Your credentials & settings (keep private) |
| 216 |
├── schema.sql # Database schema (import once) |
| 217 |
├── new-password.php # Helper for generating a password hash |
| 218 |
├── .htaccess # Rewrite rules + protects config.php / schema.sql |
| 219 |
├── .user.ini # PHP upload limits (FPM/CGI hosts) |
| 220 |
├── admin/ # Password-protected admin panel |
| 221 |
│ ├── login.php |
| 222 |
│ ├── dashboard.php # Create / edit / delete repositories |
| 223 |
│ ├── upload.php # File / folder / zip uploads, folder creation |
| 224 |
│ └── delete.php |
| 225 |
├── api/ |
| 226 |
│ └── highlight.php # Dependency-free syntax highlighter |
| 227 |
├── includes/ |
| 228 |
│ ├── db.php # PDO connection + CRUD (prepared statements) |
| 229 |
│ ├── auth.php # Sessions, login, CSRF |
| 230 |
│ ├── helpers.php # URL/slug/path sanitising, language detection |
| 231 |
│ ├── tree.php # File-tree building & rendering |
| 232 |
│ ├── markdown.php # GitHub-Flavored-Markdown → HTML |
| 233 |
│ ├── og.php # Generated social preview cards (GD) |
| 234 |
│ ├── header.php |
| 235 |
│ └── footer.php |
| 236 |
├── assets/ |
| 237 |
│ ├── style.css |
| 238 |
│ ├── model-viewer.js # Dependency-free WebGL viewer for STL/3MF/OBJ/PLY/G-code |
| 239 |
│ ├── og-default.png # Share card for non-repository pages |
| 240 |
│ └── fonts/ # Bundled DejaVu faces used to draw the cards |
| 241 |
└── uploads/ # Stored repository files (writable) |
| 242 |
└── og/ # Cached social cards (auto-created, safe to delete) |
| 243 |
``` |
| 244 |
|
| 245 |
--- |
| 246 |
|
| 247 |
## Security notes |
| 248 |
|
| 249 |
The app is built with a few defensive measures worth knowing about: |
| 250 |
|
| 251 |
- **Prepared statements everywhere** — all database access uses PDO with `PDO::ATTR_EMULATE_PREPARES => false`. |
| 252 |
- **CSRF protection** on every admin POST (token compared with `hash_equals`). |
| 253 |
- **Path traversal protection** — uploaded filenames and paths are sanitized (`..`, absolute paths, and unsafe characters are stripped), and served files are re-resolved with `realpath` and confirmed to live inside the repo directory before being read. |
| 254 |
- **Safe zip extraction** — the app streams each entry through `ZipArchive::getStream()` rather than `extractTo()`, so archive-controlled paths can't escape the target folder; oversized entries and zip-bomb totals are rejected. |
| 255 |
- **Bcrypt password hashing** and **session regeneration** on login. |
| 256 |
- **`config.php` and `schema.sql` are denied** direct web access via `.htaccess`. |
| 257 |
|
| 258 |
For production, always serve over **HTTPS**, use a strong admin password, and keep `config.php` out of any public repository. |
| 259 |
|
| 260 |
--- |
| 261 |
|
| 262 |
*Built with plain PHP and MySQL — no frameworks, no Composer, no build step.* |
| 263 |
|