Skip to content

Prince-Patel84/zeroproxy-server

Repository files navigation

ZeroProxy Server — System Documentation

A complete guide for developers to set up, understand, and recreate the ZeroProxy Smart Attendance system on a Raspberry Pi from scratch.

Table of Contents

  1. System Architecture
  2. Installation Prerequisites
  3. Network Configuration
  4. Boot Scripts
  5. Systemd Services
  6. Database Architecture
  7. Node.js Server Setup
  8. Environment Variables (.env)
  9. Duplicating the Raspberry Pi

1. System Architecture

The Raspberry Pi acts as a dedicated server and local gateway. Here is how the network is wired:

[University Wi-Fi / Export]
          |
        wlan0  ← Pi gets internet here (for Google Sheets sync)
      [Pi 172.16.0.1]
        eth0   ← Pi hands out IPs here
          |
    [Router WAN port]    ← Cable plugged into WAN port, NOT LAN
     [TP-Link Router]
          |
    [Students' phones & Admin laptop via Wi-Fi]

Key Design Rules:

  • wlan0 is the internet uplink for the Pi only (for cloud syncing). Students do NOT get internet.
  • eth0 always has the static IP 172.16.0.1. The router's WAN port connects here and receives 172.16.0.2.
  • The router manages the student-facing Wi-Fi (its own subnet, e.g., 192.168.0.x).
  • The Node.js server is accessible at 172.16.0.1 from devices on the router's LAN.

2. Installation Prerequisites

Install these on the Raspberry Pi OS (Lite or Desktop):

# System packages
sudo apt update && sudo apt upgrade -y
sudo apt install -y hostapd dnsmasq wpa_supplicant iptables net-tools iw dhcpcd5

# Node.js (via NodeSource)
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs

# PM2 process manager
sudo npm install -g pm2

# PostgreSQL
sudo apt install -y postgresql postgresql-contrib
sudo systemctl enable postgresql

# Redis
sudo apt install -y redis-server
sudo systemctl enable redis-server

3. Network Configuration

A. dnsmasq (DHCP Server)

This assigns IP addresses to devices plugged into eth0 (i.e., the router's WAN port or a developer's laptop).

Config file: /etc/dnsmasq.conf

# Uncomment to suppress dnsmasq reading /etc/resolv.conf and from the host
# Use standard DNS
server=8.8.8.8

# DHCP for devices connected via Ethernet (eth0)
interface=eth0
dhcp-range=172.16.0.2,172.16.1.245,255.255.254.0,12h
dhcp-option=option:router,172.16.0.1

To apply changes:

sudo systemctl restart dnsmasq
sudo systemctl enable dnsmasq

B. wpa_supplicant (Wi-Fi Client)

This handles connecting the Pi's wlan0 to the university Wi-Fi for internet access.

Config file: /etc/wpa_supplicant/wpa_supplicant.conf

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=IN

# Option 1: Simple WPA2 Personal Network (e.g., a hotspot or portable router)
network={
    ssid="Export"
    psk="Net@1234"
}

# Option 2: WPA2 Enterprise (University like DAIICT)
network={
    ssid="DAIICT_Student"
    key_mgmt=WPA-EAP
    eap=PEAP
    identity="YOUR_STUDENT_ID"
    password="YOUR_PASSWORD"
    phase2="auth=MSCHAPV2"
}

⚠️ Security Note: Never commit your wpa_supplicant.conf with real credentials to a public repository. Keep it on the Pi only.


C. Router Setup (TP-Link WAN Mode)

The external router acts purely as a Wi-Fi access point for students. The Pi's dnsmasq controls the IP handed to the router itself. The router then manages a separate subnet (192.168.0.x) for students internally.

Wiring: The network cable connects from the Pi's eth0 port → the Router's WAN/Internet port (NOT a LAN port).

Critical Router Settings (Advanced → Network → LAN Settings):

TP-Link LAN DHCP Settings

  • DHCP → IP Address: 192.168.0.1
  • DHCP → IP Address Pool: 192.168.0.1 to 192.168.0.200
  • Default Gateway: 192.168.0.1
  • Primary / Secondary DNS: 0.0.0.0 (leave empty — students will use the Pi's DNS via the WAN)

Note: "Operation Mode" should be set to Router (NOT Access Point mode), which causes its WAN port to request an IP from the Pi's dnsmasq (receiving 172.16.0.2).

Wireless Settings:

  • Set SSID (e.g., ZeroproxyAP) and a strong password.
  • This is the network students will connect to.

4. Boot Scripts

These scripts live in /usr/local/bin/ and are executed by systemd at every boot.

A. ethernet-bridge.sh

The main boot-time orchestration script. It always brings up eth0 as a static LAN (172.16.0.1) first, then attempts to connect wlan0 to the internet.

Install location: /usr/local/bin/ethernet-bridge.sh

#!/bin/bash
# /usr/local/bin/ethernet-bridge.sh
# Boot-time Auto Network Decision Script for ZeroProxy
# 1. eth0 is ALWAYS a static local network (172.16.0.1).
#    It runs a DHCP server to assign an IP to a Router's WAN port or a PC.
# 2. wlan0 is ALWAYS a client attempting to get internet.
# 3. Internet is NOT shared to eth0 (no NAT). Students only reach the Node.js server.

WIFI_DEV="wlan0"
ETH_DEV="eth0"
WIFI_SSID="DAIICT_Student"
GATEWAY_IP="172.16.0.1/23"
LOG_FILE="/var/log/ethernet-bridge.log"
SCAN_ATTEMPTS=2

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
    echo "$1"
}

log "=== ZeroProxy Network Bridge Script Started ==="

# Clean up any conflicting services
sudo killall hostapd wpa_supplicant 2>/dev/null
sudo systemctl stop hostapd 2>/dev/null

# === STEP 1: INITIALIZE ETH0 (The Gateway) ===
log "Initializing $ETH_DEV as Gateway at $GATEWAY_IP..."

sudo ip link set "$ETH_DEV" up
sudo ip addr flush dev "$ETH_DEV" 2>/dev/null
sudo ip addr add $GATEWAY_IP broadcast + dev "$ETH_DEV"

sudo systemctl restart dnsmasq
log "Downstream network ready. Devices connected to eth0 will get an IP from dnsmasq."

# === STEP 2: INITIALIZE WLAN0 (The Internet Uplink) ===
log "Scanning for Wi-Fi network: '$WIFI_SSID' on $WIFI_DEV..."

sudo ip link set "$WIFI_DEV" up 2>/dev/null
sudo ip addr flush dev "$WIFI_DEV" 2>/dev/null

FOUND_EXPORT=0
for i in $(seq 1 $SCAN_ATTEMPTS); do
    SCAN_RESULT=$(timeout 5 sudo iwlist "$WIFI_DEV" scan 2>/dev/null)
    if echo "$SCAN_RESULT" | grep -q "$WIFI_SSID"; then
        FOUND_EXPORT=1
        log "Found '$WIFI_SSID' on attempt $i"
        break
    fi
    sleep 2
done

if [ $FOUND_EXPORT -eq 1 ]; then
    log "Connecting to '$WIFI_SSID' as Wi-Fi client..."
    sudo wpa_supplicant -B -c /etc/wpa_supplicant/wpa_supplicant.conf -i "$WIFI_DEV"
    sleep 3
    sudo dhclient -v "$WIFI_DEV"
    sleep 5

    if ip -4 addr show "$WIFI_DEV" | grep -q "inet "; then
        CLIENT_IP=$(ip -4 addr show "$WIFI_DEV" | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -1)
        log "SUCCESS: Connected to Internet on '$WIFI_SSID' (IP: $CLIENT_IP)"
        log "NOTICE: Internet is restricted to this Pi. Devices on eth0 cannot reach the internet."
    else
        log "WARNING: Failed to get IP on '$WIFI_SSID'."
    fi
else
    log "NOTICE: '$WIFI_SSID' not found. Letting wpa_supplicant try other saved networks..."
    sudo wpa_supplicant -B -c /etc/wpa_supplicant/wpa_supplicant.conf -i "$WIFI_DEV"
    sleep 3
    sudo dhclient -v "$WIFI_DEV"
fi

log "=== Script Complete ==="
exit 0

To install:

sudo chmod +x /usr/local/bin/ethernet-bridge.sh

B. wifi-watchdog.sh

A continuously running background daemon that monitors the wlan0 internet connection every 5 seconds. If lost, it triggers a DHCP renewal. It writes to /dev/shm/wifi-status.json (RAM) so the Node.js server can query it instantly.

Install location: /usr/local/bin/wifi-watchdog.sh

#!/bin/bash
# /usr/local/bin/wifi-watchdog.sh

INTERFACE="wlan0"
PING_TARGET="8.8.8.8"
LOG_FILE="/var/log/wifi-watchdog.log"
STATUS_FILE="/dev/shm/wifi-status.json"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
}

update_status() {
    echo "{\"online\": $1}" > "$STATUS_FILE"
}

log "=== Wi-Fi Watchdog Service Started ==="

while true; do
    IS_ONLINE="false"

    if ip link show "$INTERFACE" | grep -q "state UP"; then
        if ip -4 addr show "$INTERFACE" | grep -q "inet "; then
            if ping -c 1 -W 2 "$PING_TARGET" > /dev/null 2>&1; then
                IS_ONLINE="true"
            else
                log "Ping failed. Internet unreachable. Renewing DHCP lease..."
                sudo dhclient -r "$INTERFACE" 2>/dev/null
                sudo dhclient -v "$INTERFACE"
                log "DHCP Renewal triggered."
            fi
        fi
    fi

    update_status "$IS_ONLINE"
    sleep 5
done

To install:

sudo chmod +x /usr/local/bin/wifi-watchdog.sh

5. Systemd Services

These services ensure the correct startup order on every boot. Place them in /etc/systemd/system/.

A. ethernet-bridge.service

Runs ethernet-bridge.sh once at boot. Brings up the local eth0 network and connects wlan0 to the internet.

# /etc/systemd/system/ethernet-bridge.service
[Unit]
Description=ZeroProxy Ethernet Bridge Initialization
After=network.target sys-subsystem-net-devices-wlan0.device
Wants=network.target

[Service]
Type=oneshot
ExecStartPre=/bin/sleep 2
ExecStart=/usr/local/bin/ethernet-bridge.sh
RemainAfterExit=yes
TimeoutStartSec=60

[Install]
WantedBy=multi-user.target

B. wifi-watchdog.service

Runs wifi-watchdog.sh continuously after the bridge is established. Restarts automatically if it crashes.

# /etc/systemd/system/wifi-watchdog.service
[Unit]
Description=Wi-Fi Connectivity Watchdog
After=network.target ethernet-bridge.service

[Service]
Type=simple
ExecStart=/usr/local/bin/wifi-watchdog.sh
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

C. node-server.service

Starts the Node.js server via PM2 only after the network is up, ensuring the server is always accessible.

# /etc/systemd/system/node-server.service
[Unit]
Description=PM2 process manager for Node.js Server
After=ethernet-bridge.service
Requires=ethernet-bridge.service
Wants=ethernet-bridge.service

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/Server_Code
ExecStart=/usr/bin/pm2-runtime start /home/pi/Server_Code/main_server.js -i max --name node-server
Restart=always
Environment=NODE_ENV=production
LimitNOFILE=infinity
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

To enable all services after creating them:

sudo systemctl daemon-reload
sudo systemctl enable ethernet-bridge.service
sudo systemctl enable wifi-watchdog.service
sudo systemctl enable node-server.service

6. Database Architecture

PostgreSQL (Student Records & Attendance Buffer)

The schema is in BCNF (Boyce-Codd Normal Form). Three violations present in an earlier version were fixed:

  1. admission_year and branch_code were removed from students — they are derived from student_id (e.g. 2023CE001 → year 2023, branch CE) and computed in the server JS. Storing derived data violates BCNF.
  2. subjects TEXT[] was extracted into a dedicated enrollments table — a student:subject relationship is many-to-many and must not be collapsed into an array column.
  3. sessions now has a UNIQUE(course_id, session_date, start_time) constraint enforced at the DB level.
-- 1. Students (core identity only — no derived or multi-valued attributes)
CREATE TABLE students (
    student_id    VARCHAR(20) PRIMARY KEY,
    password_hash TEXT        NOT NULL,
    face_embedding JSONB      NOT NULL,
    image_path    TEXT,
    created_at    TIMESTAMPTZ DEFAULT NOW(),
    updated_at    TIMESTAMPTZ DEFAULT NOW()
);

-- 2. Enrollments (resolves the many-to-many: student <-> subject)
CREATE TABLE enrollments (
    student_id  VARCHAR(20) REFERENCES students(student_id) ON DELETE CASCADE,
    subject_id  VARCHAR(20) NOT NULL,
    PRIMARY KEY (student_id, subject_id)
);

-- 3. Sessions (DB-enforced uniqueness)
CREATE TABLE sessions (
    id           SERIAL      PRIMARY KEY,
    course_id    VARCHAR(20) NOT NULL,
    session_date DATE        NOT NULL,
    start_time   VARCHAR(20) NOT NULL,
    created_at   TIMESTAMPTZ DEFAULT NOW(),
    CONSTRAINT   unique_session UNIQUE (course_id, session_date, start_time)
);

-- 4. Attendance (buffer — exported to Google Sheets, then cleared)
CREATE TABLE attendance (
    id         SERIAL      PRIMARY KEY,
    session_id INT         REFERENCES sessions(id)  ON DELETE CASCADE,
    student_id VARCHAR(20) REFERENCES students(student_id) ON DELETE CASCADE,
    timestamp  TIMESTAMPTZ DEFAULT NOW(),
    created_at TIMESTAMPTZ DEFAULT NOW(),
    CONSTRAINT unique_session_student UNIQUE (session_id, student_id)
);

Derived attributes (computed in server JS, not stored in DB):

const admission_year = parseInt(student_id.slice(0, 4), 10); // e.g. 2023
const branch_code    = student_id.slice(4, 6);               // e.g. CE

PostgreSQL setup commands:

sudo -u postgres psql
# Inside psql:
CREATE DATABASE zeroproxy;
CREATE USER zeroproxy_user WITH PASSWORD 'YOUR_PASSWORD';
GRANT ALL PRIVILEGES ON DATABASE zeroproxy TO zeroproxy_user;
\q

Redis (Session Cache & Security Nonces)

Used for fast in-memory operations during an active session.

Key Pattern TTL Purpose
activeSession none Currently running attendance session details
sessionStudents:[sessionId] session duration Cache of enrolled students' face embeddings
[nonce] 3 minutes Cryptographic challenge to prevent replay attacks

Redis setup:

sudo systemctl enable redis-server
# Verify Redis is running:
redis-cli ping  # Should respond: PONG

7. Node.js Server Setup

# Clone the repository onto the Pi
cd /home/pi
git clone https://github.com/YOUR_USERNAME/Zeroproxy_Server.git Server_Code
cd Server_Code

# Install dependencies
npm install

# Copy your .env file (see section 8 below)
nano .env

# Generate SSL certificates (or copy existing ones)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout server.key -out server.crt -config openssl.cnf

# Test the server manually first
node main_server.js

8. Environment Variables (.env)

Create a .env file in /home/pi/Server_Code/. Never commit this file to GitHub.

# JWT Secrets
ADMIN_TOKEN=your_admin_jwt_secret_here
STUDENT_TOKEN=your_student_jwt_secret_here
JWT_SECRET=your_main_jwt_secret_here

# Email (for OTP / notifications via Nodemailer)
SMTP_USER=your_gmail_address@gmail.com
SMTP_PASS=your_gmail_app_password

# Redis
REDIS_URL=redis://127.0.0.1:6379

# PostgreSQL
USERX=zeroproxy_user
HOST=localhost
DATABASE=zeroproxy
PASSWORD=your_db_password
PORT=5432

# Network (used for Wi-Fi BSSID validation in attendance marking)
BSSID=XX:XX:XX:XX:XX:XX

# Google Sheets Integration
GOOGLE_CREDENTIALS_PATH=/home/pi/Server_Code/smart-attendance-system.json
GOOGLE_SHEET_ID=your_google_sheet_id_here

9. Duplicating the Raspberry Pi

To set up an identical Pi for another classroom, clone the SD card directly. This copies the OS, all software, databases, and configuration with zero reinstallation.

Steps:

  1. Power down the source Raspberry Pi.
  2. Remove the MicroSD card and plug it into a computer.
  3. Create the image:
    • Windows: Use Win32 Disk Imager → Select SD card drive → Name file ZeroProxy_Master.img → Click Read.
    • Mac/Linux: sudo dd if=/dev/mmcblk0 of=ZeroProxy_Master.img bs=4M status=progress
  4. Flash to a new card:
    • Insert a blank MicroSD card (same size or larger).
    • Flash using Raspberry Pi Imager or balenaEtcher with ZeroProxy_Master.img.
  5. Boot and personalize:
    • Boot the new Pi. Then run:
      sudo raspi-config
      # → System Options → Hostname → Change to e.g. "zeroproxy-room202"
    • Update wpa_supplicant.conf if needed for the new room's Wi-Fi credentials.
    • Done! The new Pi is now a complete, independent server.

About

Raspberry Pi Node.js server — JWT auth, face verification, real-time attendance & Sheets export

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors