# NestEggCode — a lightweight, self-hosted code browser 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. 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. --- ## Table of contents - [Features](#features) - [What makes it different from GitHub](#what-makes-it-different-from-github) - [Limitations](#limitations) - [Server requirements](#server-requirements) - [Installation](#installation) - [1. Get the files onto the server](#1-get-the-files-onto-the-server) - [2. Create the database](#2-create-the-database) - [3. Configure `config.php`](#3-configure-configphp) - [4. Set your admin password](#4-set-your-admin-password) - [5. Point the router at the right base path](#5-point-the-router-at-the-right-base-path) - [6. Set the public origin (recommended)](#6-set-the-public-origin-recommended) - [7. Adjust upload limits (optional)](#7-adjust-upload-limits-optional) - [8. Log in and create your first repository](#8-log-in-and-create-your-first-repository) - [Project layout](#project-layout) - [Security notes](#security-notes) --- ## Features **Browsing (public, no login required)** - **Repository index** at the site root listing every repository, with an auto-detected or manually set language badge and description. - **GitHub-style file tree** with breadcrumbs, folder navigation, and persistent (even empty) folders. - **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. - **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. - **Image rendering** for PNG, JPG, GIF, SVG, WebP, BMP, ICO, and AVIF, with a **Rendered / Source toggle** for SVGs. - **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**. - **Raw view and Download** buttons for every file (`/raw/` served with correct MIME type and `X-Content-Type-Options: nosniff`). - **Client-side file search** in the file view's sidebar tree. - **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. - **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. **Admin (single user, password-protected)** - **Create / edit / delete repositories**, each with a slug, name, description, and optional language label. - **Slug renaming** that moves the on-disk files and rewrites database paths in a single transaction. - **Drag-and-drop uploads** of individual files *or* whole folders (directory structure preserved via the browser's directory-upload API). - **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. - **Manual folder creation**, including nested paths (`src/utils/helpers`). - **Per-file and per-folder deletion** (folder deletion cascades to everything inside it). - **Re-upload replaces** a same-named file in place. --- ## What makes it different from GitHub | | This app | GitHub | |---|---|---| | **Purpose** | Publish & browse code you upload | Full source-control platform | | **Version control** | None — files are just stored and shown | Full Git history, branches, diffs | | **Where it runs** | Your own server / shared hosting | GitHub's cloud | | **Accounts** | One admin (you); everyone else is a read-only visitor | Multi-user, orgs, teams | | **Collaboration** | None (no PRs, issues, forks, comments) | PRs, issues, reviews, CI/CD | | **Dependencies** | Zero — plain PHP + MySQL, no Composer/npm | N/A (hosted service) | | **Data ownership** | 100% yours, on your host | Hosted by GitHub | | **Setup** | Copy files, create a DB, edit one config file | Sign up | | **Cost** | Whatever your hosting costs | Free / paid tiers | **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. --- ## Limitations Be aware of what this project intentionally does **not** do: - **No version control.** No commits, branches, history, or diffs. Uploading a file simply overwrites the previous one. - **No collaboration features.** No issues, pull requests, forks, stars, comments, or multiple accounts. - **Single admin user.** Credentials live in `config.php`; there is no user-management UI or registration. - **No Git integration.** You cannot `git push` to it. Content is added through the web upload forms (files, folders, or a `.zip`). - **Browse-only for the public.** Visitors can read and download, but never edit. - **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. - **Simplified Markdown.** The README renderer covers the common Markdown a README uses, but is not a full CommonMark implementation. - **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. - **No search across repositories.** File search is client-side and scoped to a single file view's tree. - **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. --- ## Server requirements - **PHP 7.4 or newer** (uses typed function signatures, `??`, arrow-ish syntax, etc.). PHP 8.x recommended. - **MySQL / MariaDB** with the **PDO MySQL** extension enabled. - **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`). - **PHP `zip` extension** — *optional*, required only for the "Upload a zip" feature. Everything else works without it. - **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. - Standard PHP file/session functions (`session`, `fopen`/`fwrite`, `move_uploaded_file`, `password_hash`/`password_verify`, `random_bytes`). - A writable **`uploads/`** directory. - **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. Works out of the box on typical **cPanel shared hosting** and locally with **MAMP / XAMPP / WAMP**. --- ## Installation ### Easiest: the guided installer (recommended) 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: 1. Runs **server checks** (PHP version, database driver, writable folders) and explains how to fix anything that fails. 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. 3. Shows a **post-launch checklist** to confirm everything works. 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. Prefer to do it by hand? Follow the manual steps below instead. ### Manual installation ### 1. Get the files onto the server Copy the project into your web root (or a subfolder of it). For example: - **Production (domain root):** upload everything into `public_html/`. - **Local MAMP:** drop the folder into `htdocs/` (e.g. `htdocs/pgh5`). ### 2. Create the database Create an empty MySQL database and a user with access to it (in cPanel: *MySQL Databases*; locally: phpMyAdmin). Then import the schema: ```bash mysql -u YOUR_USER -p YOUR_DB < schema.sql ``` …or import `schema.sql` through phpMyAdmin. It creates three tables: `repositories`, `files`, and `folders`. ### 3. Configure `config.php` Open `config.php` and set your real values: ```php define('DB_HOST', 'localhost'); define('DB_NAME', 'your_database_name'); define('DB_USER', 'your_database_user'); define('DB_PASS', 'your_database_password'); ``` > ⚠️ **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. ### 4. Set your admin password The admin password is stored as a **bcrypt hash**, never in plaintext. Generate one: ```bash php -r "echo password_hash('your-strong-password-here', PASSWORD_DEFAULT), PHP_EOL;" ``` Paste the result into `config.php` and pick a username: ```php define('ADMIN_USER', 'your-admin-username'); define('ADMIN_PASS_HASH', '$2y$12$...the-hash-you-generated...'); ``` > The shipped default hash is for the password `changeme` — **change it before going live.** ### 5. Point the router at the right base path `BASE_PATH` is the URL prefix the app is served under. - **Domain root:** `define('BASE_PATH', '');` - **Subfolder (e.g. local `htdocs/pgh5`):** `define('BASE_PATH', '/pgh5');` Match this to the `RewriteBase` in `.htaccess` (defaults to `/`). If you deploy in a subfolder, set `RewriteBase /pgh5` accordingly. ### 6. Set the public origin (recommended) `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');`. 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. ### 7. Adjust upload limits (optional) Two places govern upload sizes: - **`config.php`** — application-level caps: - `MAX_FILE_SIZE` — per-file limit (default **25 MB**), also applied to each file extracted from a zip. - `MAX_ZIP_TOTAL` — ceiling on the total *uncompressed* size of one zip import (default **200 MB**), a guard against zip bombs. - **`.user.ini`** — PHP-level caps (`upload_max_filesize`, `post_max_size`, `max_file_uploads`). > **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`. Make sure `uploads/` is writable by the web server. ### 8. Log in and create your first repository 1. Visit `https://your-domain/admin/login.php` (or `.../pgh5/admin/login.php` in a subfolder). 2. Log in with the username and password you configured. 3. From the **Dashboard**, create a repository (give it a slug like `save-utils` and a name). 4. Click **Upload** to add files, a folder, or a `.zip`. 5. Your repo is now public at `https://your-domain/your-slug`. --- ## Project layout ``` . ├── install.php # Guided web installer (delete after setup) ├── index.php # Front controller / router ├── repo.php # Public repository views (tree / blob / raw) ├── config.sample.php # Safe-to-commit config template ├── config.php # Your credentials & settings (keep private) ├── schema.sql # Database schema (import once) ├── new-password.php # Helper for generating a password hash ├── .htaccess # Rewrite rules + protects config.php / schema.sql ├── .user.ini # PHP upload limits (FPM/CGI hosts) ├── admin/ # Password-protected admin panel │ ├── login.php │ ├── dashboard.php # Create / edit / delete repositories │ ├── upload.php # File / folder / zip uploads, folder creation │ └── delete.php ├── api/ │ └── highlight.php # Dependency-free syntax highlighter ├── includes/ │ ├── db.php # PDO connection + CRUD (prepared statements) │ ├── auth.php # Sessions, login, CSRF │ ├── helpers.php # URL/slug/path sanitising, language detection │ ├── tree.php # File-tree building & rendering │ ├── markdown.php # GitHub-Flavored-Markdown → HTML │ ├── og.php # Generated social preview cards (GD) │ ├── header.php │ └── footer.php ├── assets/ │ ├── style.css │ ├── model-viewer.js # Dependency-free WebGL viewer for STL/3MF/OBJ/PLY/G-code │ ├── og-default.png # Share card for non-repository pages │ └── fonts/ # Bundled DejaVu faces used to draw the cards └── uploads/ # Stored repository files (writable) └── og/ # Cached social cards (auto-created, safe to delete) ``` --- ## Security notes The app is built with a few defensive measures worth knowing about: - **Prepared statements everywhere** — all database access uses PDO with `PDO::ATTR_EMULATE_PREPARES => false`. - **CSRF protection** on every admin POST (token compared with `hash_equals`). - **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. - **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. - **Bcrypt password hashing** and **session regeneration** on login. - **`config.php` and `schema.sql` are denied** direct web access via `.htaccess`. For production, always serve over **HTTPS**, use a strong admin password, and keep `config.php` out of any public repository. --- *Built with plain PHP and MySQL — no frameworks, no Composer, no build step.*