263 lines · 16 KB
Raw Download
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