ZinklySocial Docs
✦ Core Platform

ZinklySocial Documentation

ZinklySocial is the self-hosted Laravel social platform behind the Zinkly ecosystem — timeline, messaging, calls, stories and a marketplace, all under one AdminCP. This guide covers what the server needs, what you're actually installing, and the exact commands to bring it online.

Check requirements Install guide Troubleshooting
PHP 8.2+ runtime
Laravel 13 framework
MySQL 8 / MariaDB 10.6 database
Redis 6+ cache & queues
The application

What's in the box

ZinklySocial is a professional Laravel 13 social / forum application with real-time features, a full admin panel, and a built-in ZIP-based update system. It's the core platform that ZinklyChat, EchoAI, NotiLive and LingoSync all plug into over its REST + WebSocket API.

Browser / apps web, ZinklyChat, admin ZinklySocial Laravel 13 · Nginx/Apache REST API + WebSocket MySQL / Redis data, cache, queues Firebase · LiveKit push & calls PayPal

What's included

Core features

  • Timeline, posts, comments, reactions
  • Follow system with privacy controls
  • Public profiles with walls
  • Real-time notifications
  • Stories — 24-hour ephemeral content
  • Reels — short videos
  • Direct messages & group chats
  • LiveKit audio / video calls
  • Marketplace with VIP packages
  • PayPal payments

Admin features

  • User management — ban, promote, restrict
  • Reports & moderation
  • Announcements
  • Static pages — Terms, Privacy
  • Settings & SEO
  • Integrations — Firebase, Google, and more
  • Update system with ZIP packages
  • Backup & rollback
  • Audit logs
  • VIP package management

Technology stack

Backend

Laravel 13 (PHP 8.2+), MySQL/MariaDB, Redis for cache & queues, Laravel Sanctum for API auth.

Frontend

Blade templates, Tailwind CSS, Alpine.js for interactivity, Vite for asset bundling.

Services

Firebase Cloud Messaging, LiveKit (WebRTC calls), GIPHY, PayPal, Google OAuth.

Before you start

Requirements

Every failed install traces back to one of these three things — check them before you upload anything.

Minimum requirements

PHP 8.2+

Composer 2.x

Node.js 18+

with npm

MySQL 8.0+

or MariaDB 10.6+

Redis 6+

queues, cache, sessions

Nginx or Apache

web server

SSL certificate

Let's Encrypt recommended

A Firebase project

free tier — for push notifications, see below

Required PHP extensions

bcmathctypecurl domfileinfogd iconvintlmbstring opensslpdo_mysqlredis xmlzip imagick (optional)

Recommended server specs

TiervCPURAMStorageRedisNotes
Small — up to 1K users24 GB50 GB SSD1 GB—
Medium — up to 10K users48 GB100 GB SSD2 GBSeparate DB server recommended
Getting started

Installation

Pick the guide that matches your access level below, then follow it start to finish — don't mix steps between guides.

What's in the download: the main app ZIP (e.g. zinkly-social-v1.0.0.zip) is the whole application — extract it on the server, never straight into a folder your web server already serves. A separate small installer-only ZIP contains just installation.php for the FTP-only path; if you have SSH, ignore it.

I have SSH access

Generic Linux/VPS steps — Composer, artisan, npm. Works on any host that gives you a shell.

SSH Installation

I'm on HestiaCP

The exact layout this app runs on in production — app in public_html, document root pointed at public_html/public.

HestiaCP Installation

No SSH at all

Shared hosting with only FTP / File Manager — still ends with the document root pointed at public/.

No SSH (FTP only)

No SSH, no artisan at all

Shared hosting with only phpMyAdmin/FTP — import the schema as raw SQL instead of running migrations.

Covered separately under AdminCP → Documentation → Manual SQL Setup once you can reach it.

Recommended

SSH Installation

The recommended way to install ZinklySocial — a full install on a fresh Linux server (Ubuntu/Debian) with SSH access.

  1. Connect and upload
    # from your machine
    ssh youruser@your-server-ip
    
    # create the app directory
    mkdir -p /home/user/web/yourdomain.com/zinkly-app
    cd /home/user/web/yourdomain.com
    
    # upload zinkly-social.zip here (scp / sftp), then:
    unzip zinkly-social.zip -d zinkly-app
    cd zinkly-app
  2. Install dependencies
    # PHP dependencies — no dev packages in production
    composer install --no-dev --optimize-autoloader
    
    # JS dependencies + build frontend assets
    npm install
    npm run build
  3. Configure environment
    cp .env.example .env
    php artisan key:generate
    nano .env   # set DB_*, APP_URL, MAIL_*, etc.
  4. Database & storage
    php artisan migrate --force --seed
    php artisan storage:link
  5. Permissions & caching
    chmod -R 775 storage bootstrap/cache
    chown -R www-data:www-data storage bootstrap/cache   # match your web server's user
    
    php artisan config:cache
    php artisan route:cache
    php artisan view:cache
  6. Point the web server — the document root must point at the app's public/ folder, never the app root.
    # example Nginx root
    root /home/user/web/yourdomain.com/zinkly-app/public;
Managed hosting

HestiaCP Installation

The app lives directly inside public_html; HestiaCP's Nginx vhost is pointed at public_html/public as the document root. One folder, no file-shuffling.

  1. Create the domain and database
    • HestiaCP → Web → Add Web Domain (e.g. social.yourdomain.com)
    • Once DNS resolves to the server, enable SSL → Let's Encrypt
    • HestiaCP → DB → Add Database — note the generated name, username, password
  2. Upload the app straight into public_html
    ssh user@your-server-ip
    cd /home/user/web/social.yourdomain.com/public_html
    
    # upload zinkly-social.zip here, then:
    unzip zinkly-social.zip -d .
    rm zinkly-social.zip
    
    composer install --no-dev --optimize-autoloader
    npm install && npm run build
  3. Point the document root at public_html/public — HestiaCP serves public_html by default; Laravel must never be web-accessible at its root.

    Newer HestiaCP (UI method — no SSH needed for this step): HestiaCP → Web → your domain → Edit → check Custom document root → Point to: your domain → Directory: public. HestiaCP shows the resulting path live and saves/reloads Nginx for you:

    /home/user/web/social.yourdomain.com/public_html/public

    Older HestiaCP without that toggle — edit the vhost by hand instead:

    nano /home/user/conf/web/social.yourdomain.com/nginx.ssl.conf
    # find the "root" line and point it at:
    root /home/user/web/social.yourdomain.com/public_html/public;
    
    nginx -t && systemctl reload nginx
  4. Configure & migrate
    cd /home/user/web/social.yourdomain.com/public_html
    cp .env.example .env
    php artisan key:generate
    
    nano .env
    # DB_DATABASE, DB_USERNAME, DB_PASSWORD → from HestiaCP → DB
    # APP_URL=https://social.yourdomain.com
    
    php artisan migrate --force --seed
    php artisan storage:link
    chmod -R 775 storage bootstrap/cache
    chown -R user:user storage bootstrap/cache   # the HestiaCP system user
    
    php artisan config:cache && php artisan route:cache && php artisan view:cache

The "Custom document root" toggle has its own PHP field — don't leave it blank. HestiaCP applies the docroot to both Nginx and PHP-FPM's own working directory/open_basedir. If that PHP-side path is left empty or also gets set to just public/, PHP's working root shrinks to public/ only — Laravel then can't reach storage/, bootstrap/cache/, or vendor/ outside it, and breaks entirely (500 errors on everything). Make sure that field points at the app root (public_html), not public_html/public.

Common pitfall: editing the domain again in the HestiaCP UI regenerates the Nginx config and can silently reset the document root back to plain public_html. Re-check it after any change made there. PHP-FPM also runs as your HestiaCP login user, not www-data — chown accordingly.

Shared hosting

No SSH at all (FTP / File Manager only)

The document root still has to end up pointing at the app's public/ folder — that never goes away, SSH or not. Editing index.php's paths to fake it is fragile and breaks on the next update, so don't do that.

  1. Upload and extract the main ZIP outside your public web folder if your panel allows it (a sibling folder to public_html) — matches the SSH/HestiaCP layout, done over FTP.
  2. Point the document root at that folder's public/ subfolder, using your host's panel (usually under "Advanced" or "Domain Settings" — no SSH needed). No such field? Ask your host's support — it's a one-line change on their end.
  3. Only if truly impossible: upload the main ZIP's contents directly into public_html, then from the small installer ZIP upload just installation.php into that same folder.
  4. Add the protection block below to the top of public_html/.htaccess regardless of which option you used.
  5. Visit https://yourdomain.com/installation.php to configure the database.
  6. Then visit https://yourdomain.com/install to finish setup, run migrations, and create your admin account.

.htaccess protection (add regardless of the option above)

# top of public_html/.htaccess, above Laravel's own rules
RewriteRule ^(app|bootstrap|config|database|lang|resources|routes|storage|tests|vendor|\.env|composer\.(json|lock))(/.*)?$ - [F,L]

Option 3 still leaves the app root web-accessible at the domain root even with this rule — it only blocks a specific path list, not everything. Options 1–2 (a real document-root change) are the only way to fully avoid exposing application code. Use SSH whenever you possibly can.

Push notifications

Firebase & Push Notifications

ZinklySocial sends browser push notifications for new messages, incoming calls, likes, comments, and follows — even when the site isn't open in a tab. This needs its own Firebase project; it's a separate setup step from the server install above, and the app runs fine without it, just without push.

1. Create a Firebase project

  1. Create the project at console.firebase.google.com → Add project.
  2. Register a web app — Add app → Web, name it after your site, and copy the SDK config it shows you: API key, Project ID, Auth domain, Storage bucket, Messaging sender ID, App ID.
  3. Generate a Web Push certificate — Project settings (gear icon) → Cloud Messaging tab → under "Web configuration" → generate one. This is your VAPID key.
  4. Generate a service account key — Project settings → Service accounts tab → Generate new private key. This downloads a JSON file — keep it secret, it's used server-side only.

Building a separate native mobile app (e.g. ZinklyChat)? You'd also add an Android app (for google-services.json) and/or an iOS app (for GoogleService-Info.plist) to this same Firebase project — but that only matters for that separate mobile codebase, not for this web install, and can be skipped entirely if you're not shipping a native app.

2. Configure the credentials

Go to AdminCP → Integrations and fill in the Firebase section with the web app values from step 1:

AdminCP fieldFirebase console value
Project IDProject ID
Web API keyapiKey
Auth domainauthDomain
Storage bucketstorageBucket
Messaging sender IDmessagingSenderId
App IDappId
VAPID keyWeb Push certificate key pair (step 1.3)

These save to the database and take effect immediately — no deploy or cache clear needed — and override any matching FIREBASE_WEB_* values in .env if both are set.

The service account JSON from step 1.4 is different: a server-side secret, and it is never entered into AdminCP. Upload it somewhere outside your public web root, then set one .env value:

FIREBASE_CREDENTIALS=/absolute/path/outside/public_html/firebase-service-account.json

Then pick up the new path:

php artisan config:clear && php artisan config:cache

3. How it turns on for a visitor

Nothing further to configure — this happens automatically once step 2 is saved.

  • The browser does not prompt for permission the instant someone logs in — a dismissible "Get notified here" banner appears first; the actual permission prompt only fires after the visitor clicks it.
  • Once granted, the browser registers a service worker at /firebase-messaging-sw.js (already included in the app — nothing to configure), which receives pushes while the tab is closed or backgrounded.
  • The resulting device token is saved to the device_tokens table, which is what sends pushes on the server side.

Testing & troubleshooting

Push notifications require HTTPS — they will not work over plain HTTP, even on staging. A visitor who denied the permission prompt must reset it in their browser's own site settings to be asked again; clicking the banner again won't re-trigger it.

  • Send a test push from Firebase Console → Cloud Messaging → Send test message using a registration token from the device_tokens table, before debugging app code.
  • Nothing arriving? Check storage/logs/laravel.log for "FCM send failed" — usually a missing/invalid FIREBASE_CREDENTIALS path or a revoked service account key.
  • A token FCM reports as invalid (app uninstalled, unsubscribed, site data cleared) is pruned automatically from device_tokens on the next send — no manual cleanup needed.
Common issues

Troubleshooting

Fixes for the issues that come up most, in the order they're worth checking.

SymptomLikely cause
Pages look unstyled — raw text, no colorsBrowser cache, a failed/missing npm run build, or a stale view:cache — see below
500 server error / blank white pageCheck storage/logs/laravel.log, then the web server's own error log
"Permission denied" writing to storage/Re-run the chmod/chown step from the install guide
"I changed something but nothing happened"Stale config:cache/view:cache, browser cache, or a queue worker running old code
Uploads / avatars 404 instead of loadingThe public/storage symlink is missing — run php artisan storage:link
Notifications / emails never arriveQueue worker not running, or SMTP not configured/tested
Calls won't connectLiveKit credentials or server URL wrong under AdminCP → Integrations

Pages look unstyled — step by step

This means the browser couldn't load app.css/app.js from public/build/. In order of likelihood:

  1. Stale browser cache — every npm run build generates new hashed filenames and deletes the old ones; an old cached page pointing at a deleted file 404s. Hard refresh: Ctrl+Shift+R (Cmd+Shift+R on Mac).
  2. Build never ran or failed — check public/build/manifest.json exists and is recent (ls -la public/build); re-run npm run build if missing or stale.
  3. Cached views referencing an old manifest — run php artisan view:clear (then view:cache in production) after any frontend rebuild.
  4. Nginx not serving /build/ correctly — confirm the document root actually points at public/, not the app root.

Still stuck? Take a fresh backup before trying anything destructive, then check storage/logs/laravel.log for the exact exception — the message and stack trace almost always point straight at the cause.

Once you're in

More in AdminCP

That's the surface. Once you're logged into AdminCP, System → Documentation carries this same guide plus everything else — environment variables, updates, backups, integrations — and the full version-by-version changelog.