mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-05-25 08:44:42 -04:00
Compare commits
3 Commits
fix/shared
...
unstable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5450404cb2 | ||
|
|
b7384296c1 | ||
|
|
b0dddc22a3 |
94
.github/scripts/get-version.sh
vendored
94
.github/scripts/get-version.sh
vendored
@@ -1,94 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Shared version tag generation script for GitHub Actions workflows
|
||||
# Usage: ./get-version.sh [FORMAT] [SHA_LENGTH]
|
||||
#
|
||||
# Formats:
|
||||
# docker-tag - Docker image tag (default)
|
||||
# archive - Archive filename suffix
|
||||
# all - Output all version variables for GITHUB_OUTPUT
|
||||
#
|
||||
# Environment variables:
|
||||
# GITHUB_REF - Git ref (e.g., refs/heads/master, refs/pull/123/merge)
|
||||
# GITHUB_SHA - Git commit SHA
|
||||
# GITHUB_EVENT_NAME - Event that triggered workflow (push, pull_request, etc.)
|
||||
# GITHUB_EVENT_PATH - Path to event JSON (for PR number extraction)
|
||||
# GITHUB_OUTPUT - Path to GITHUB_OUTPUT file (when format=all)
|
||||
|
||||
# Ensure we're in a git repository with source files
|
||||
cd "${GITHUB_WORKSPACE:-.}"
|
||||
|
||||
# Get version from App.php
|
||||
VERSION=$(grep "application_version" app/Config/App.php | sed "s/.*= '\(.*\)';/\1/g")
|
||||
|
||||
# Standardize SHA length (default: 7 chars)
|
||||
SHA_LENGTH="${2:-7}"
|
||||
SHA="${GITHUB_SHA:0:$SHA_LENGTH}"
|
||||
|
||||
# Initialize variables
|
||||
IMAGE_TAG=""
|
||||
BRANCH=""
|
||||
|
||||
# Detect event type and generate appropriate tag
|
||||
if [[ "$GITHUB_EVENT_NAME" == "pull_request" || "$GITHUB_EVENT_NAME" == "pull_request_review" ]]; then
|
||||
# Extract PR number from event JSON
|
||||
if [[ -f "${GITHUB_EVENT_PATH:-}" ]]; then
|
||||
PR_NUMBER=$(jq -r '.pull_request.number // .number // empty' < "$GITHUB_EVENT_PATH" 2>/dev/null || true)
|
||||
|
||||
if [[ -n "$PR_NUMBER" ]]; then
|
||||
# PR-based tag (for PR deployments)
|
||||
IMAGE_TAG="pr-${PR_NUMBER}-${SHA}"
|
||||
BRANCH="pr-${PR_NUMBER}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback if we couldn't extract PR number
|
||||
if [[ -z "$IMAGE_TAG" ]]; then
|
||||
# Try to extract from GITHUB_REF
|
||||
PR_NUMBER=$(echo "$GITHUB_REF" | grep -oP 'pull/\K[0-9]+' || true)
|
||||
if [[ -n "$PR_NUMBER" ]]; then
|
||||
IMAGE_TAG="pr-${PR_NUMBER}-${SHA}"
|
||||
BRANCH="pr-${PR_NUMBER}"
|
||||
else
|
||||
# Last resort: use SHA only
|
||||
IMAGE_TAG="${SHA}"
|
||||
BRANCH="unknown"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# Branch-based tag (for push events)
|
||||
BRANCH="${GITHUB_REF#refs/heads/}"
|
||||
BRANCH=$(echo "$BRANCH" | sed 's/feature\///' | tr '/' '_')
|
||||
|
||||
if [[ "$BRANCH" == "master" ]]; then
|
||||
# Master builds: use version as tag
|
||||
IMAGE_TAG="${VERSION}"
|
||||
else
|
||||
# Feature branch builds: version + branch + sha
|
||||
IMAGE_TAG="${VERSION}-${BRANCH}-${SHA}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Output format based on first argument
|
||||
case "${1:-docker-tag}" in
|
||||
docker-tag)
|
||||
echo "$IMAGE_TAG"
|
||||
;;
|
||||
archive)
|
||||
echo "${VERSION}.${SHA}"
|
||||
;;
|
||||
all)
|
||||
{
|
||||
echo "version=${VERSION}"
|
||||
echo "version-tag=${IMAGE_TAG}"
|
||||
echo "short-sha=${SHA}"
|
||||
echo "branch=${BRANCH}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "::debug::version=${VERSION}, version-tag=${IMAGE_TAG}, short-sha=${SHA}, branch=${BRANCH}"
|
||||
;;
|
||||
*)
|
||||
echo "::error::Unknown format: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
32
.github/workflows/build-release.yml
vendored
32
.github/workflows/build-release.yml
vendored
@@ -22,7 +22,6 @@ jobs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
version-tag: ${{ steps.version.outputs.version-tag }}
|
||||
short-sha: ${{ steps.version.outputs.short-sha }}
|
||||
branch: ${{ steps.version.outputs.branch }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -76,13 +75,16 @@ jobs:
|
||||
- name: Get version info
|
||||
id: version
|
||||
run: |
|
||||
chmod +x .github/scripts/get-version.sh
|
||||
.github/scripts/get-version.sh all 7
|
||||
VERSION=$(grep "application_version" app/Config/App.php | sed "s/.*= '\(.*\)';/\1/g")
|
||||
BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | sed 's/feature\///' | tr '/' '_')
|
||||
TAG=$(echo "${GITHUB_TAG:-$BRANCH}" | tr '/' '_')
|
||||
SHORT_SHA=$(git rev-parse --short=6 HEAD)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "version-tag=$VERSION-$BRANCH-$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
echo "short-sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
GITHUB_EVENT_PATH: ${{ github.event_path }}
|
||||
GITHUB_SHA: ${{ github.sha }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||
GITHUB_TAG: ${{ github.ref_name }}
|
||||
|
||||
- name: Create .env file
|
||||
run: |
|
||||
@@ -128,7 +130,7 @@ jobs:
|
||||
name: Build Docker Image
|
||||
runs-on: ubuntu-22.04
|
||||
needs: build
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Download build context
|
||||
@@ -152,14 +154,14 @@ jobs:
|
||||
- name: Determine Docker tags
|
||||
id: tags
|
||||
run: |
|
||||
TAG="${{ needs.build.outputs.version-tag }}"
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
echo "tags=${{ secrets.DOCKER_USERNAME }}/opensourcepos:${TAG}" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ needs.build.outputs.branch }}" = "master" ]; then
|
||||
echo "tags=${{ secrets.DOCKER_USERNAME }}/opensourcepos:${TAG},${{ secrets.DOCKER_USERNAME }}/opensourcepos:latest" >> $GITHUB_OUTPUT
|
||||
BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | tr '/' '_')
|
||||
if [ "$BRANCH" = "master" ]; then
|
||||
echo "tags=${{ secrets.DOCKER_USERNAME }}/opensourcepos:${{ needs.build.outputs.version-tag }},${{ secrets.DOCKER_USERNAME }}/opensourcepos:master" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "tags=${{ secrets.DOCKER_USERNAME }}/opensourcepos:${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "tags=${{ secrets.DOCKER_USERNAME }}/opensourcepos:${{ needs.build.outputs.version-tag }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
|
||||
- name: Build and push Docker images
|
||||
uses: docker/build-push-action@v5
|
||||
@@ -192,7 +194,7 @@ jobs:
|
||||
id: version
|
||||
run: |
|
||||
VERSION="${{ needs.build.outputs.version }}"
|
||||
SHORT_SHA="${{ needs.build.outputs.short-sha }}"
|
||||
SHORT_SHA=$(git rev-parse --short=6 HEAD)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "short-sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
13
.github/workflows/deploy-pr.yml
vendored
13
.github/workflows/deploy-pr.yml
vendored
@@ -33,15 +33,12 @@ jobs:
|
||||
|
||||
- name: Get image tag
|
||||
id: image
|
||||
run: |
|
||||
chmod +x .github/scripts/get-version.sh
|
||||
IMAGE_TAG=$(.github/scripts/get-version.sh docker-tag 7)
|
||||
echo "tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
GITHUB_EVENT_PATH: ${{ github.event_path }}
|
||||
GITHUB_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
IMAGE_TAG="pr-${PR_NUMBER}-${PR_SHA:0:7}"
|
||||
echo "tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
|
||||
|
||||
deploy:
|
||||
name: Deploy to staging
|
||||
|
||||
131
.github/workflows/install-script-test.yml
vendored
131
.github/workflows/install-script-test.yml
vendored
@@ -1,131 +0,0 @@
|
||||
name: Install Script Test
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'scripts/install-ubuntu.sh'
|
||||
- '.github/workflows/install-script-test.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'scripts/install-ubuntu.sh'
|
||||
- '.github/workflows/install-script-test.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
install-test:
|
||||
name: Test Install Script (${{ matrix.scenario }})
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- scenario: default
|
||||
db_pass: ''
|
||||
- scenario: custom-password
|
||||
db_pass: 'TestPass123!'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Make install script executable
|
||||
run: chmod +x scripts/install-ubuntu.sh
|
||||
|
||||
- name: Run install script
|
||||
env:
|
||||
DB_PASS: ${{ matrix.db_pass }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
echo "Running install script with scenario: ${{ matrix.scenario }}"
|
||||
sudo -E bash scripts/install-ubuntu.sh 2>&1 | tee install-output.log
|
||||
echo "Install completed successfully"
|
||||
|
||||
- name: Wait for services to stabilize
|
||||
run: sleep 10
|
||||
|
||||
- name: Verify Apache is running
|
||||
run: |
|
||||
echo "Checking Apache status..."
|
||||
sudo systemctl status apache2 --no-pager
|
||||
sudo systemctl is-active apache2
|
||||
|
||||
- name: Verify MariaDB is running
|
||||
run: |
|
||||
echo "Checking MariaDB status..."
|
||||
sudo systemctl status mariadb --no-pager
|
||||
sudo systemctl is-active mariadb
|
||||
|
||||
- name: Verify Apache HTTP response
|
||||
run: |
|
||||
echo "Testing HTTP response on port 80..."
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/)
|
||||
echo "HTTP Response Code: $HTTP_CODE"
|
||||
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ]; then
|
||||
echo "Apache is responding correctly"
|
||||
elif [ "$HTTP_CODE" = "500" ]; then
|
||||
echo "HTTP 500 - Application error. Checking .env configuration..."
|
||||
sudo cat /var/www/ospos/.env 2>/dev/null | grep -E "database\.default\.(hostname|database|username|password)|encryption\.key|CI_ENVIRONMENT" | head -10
|
||||
sudo cat /var/www/ospos/writable/logs/*.log 2>/dev/null | tail -20 || true
|
||||
curl -s http://localhost/ | head -50
|
||||
exit 1
|
||||
else
|
||||
echo "Unexpected HTTP code: $HTTP_CODE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify OSPOS login page
|
||||
run: |
|
||||
echo "Checking OSPOS login page..."
|
||||
curl -s http://localhost/ | grep -qi "login\|password\|username" && echo "Login page content found" || {
|
||||
echo "Login page verification failed"
|
||||
curl -s http://localhost/ | head -50
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Verify database exists
|
||||
env:
|
||||
DB_PASS: ${{ matrix.db_pass != '' && matrix.db_pass || '' }}
|
||||
run: |
|
||||
echo "Verifying database..."
|
||||
|
||||
# Extract the generated password from install output if using default
|
||||
if [ -z "${{ matrix.db_pass }}" ]; then
|
||||
GENERATED_PASS=$(grep -oP 'Database Password: \K[^\s]+' install-output.log || true)
|
||||
if [ -n "$GENERATED_PASS" ]; then
|
||||
DB_PASS="$GENERATED_PASS"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check database exists
|
||||
sudo mysql -u root -e "SHOW DATABASES LIKE 'ospos';" | grep -q ospos && echo "Database 'ospos' exists" || {
|
||||
echo "Database 'ospos' not found"
|
||||
sudo mysql -u root -e "SHOW DATABASES;"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check tables exist
|
||||
TABLE_COUNT=$(sudo mysql -u root ospos -e "SHOW TABLES;" | wc -l)
|
||||
echo "Found $TABLE_COUNT tables in database"
|
||||
if [ "$TABLE_COUNT" -gt 5 ]; then
|
||||
echo "Database tables verified"
|
||||
else
|
||||
echo "Not enough tables found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload install log
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: install-log-${{ matrix.scenario }}
|
||||
path: install-output.log
|
||||
retention-days: 7
|
||||
72
INSTALL.md
72
INSTALL.md
@@ -102,73 +102,5 @@ Do **not** use below command on live deployments unless you want to tear everyth
|
||||
|
||||
## Cloud install
|
||||
|
||||
### Recommended: DigitalOcean
|
||||
|
||||
Sign up through [our referral link](https://m.do.co/c/ac38c262507b) to get a [**$100, 60-day credit**](https://m.do.co/c/ac38c262507b).
|
||||
|
||||
1. Create an Ubuntu 20.04+ or 22.04+ droplet
|
||||
2. SSH into your server: `ssh root@<your-droplet-ip>`
|
||||
3. Run the one-line installer:
|
||||
```bash
|
||||
curl -sSL https://opensourcepos.org/install | sudo bash
|
||||
```
|
||||
|
||||
The installer will:
|
||||
- Install Apache, MariaDB, PHP 8.2 and required extensions
|
||||
- Download the **latest stable release** of OSPOS from GitHub
|
||||
- Create a database with secure random password
|
||||
- Configure OSPOS and Apache
|
||||
- **Set up SSL/TLS certificates** (interactive prompt or environment variables)
|
||||
- Display login credentials after completion
|
||||
|
||||
**Interactive Mode (Recommended for first-time users):**
|
||||
|
||||
When run without environment variables, the installer will prompt you:
|
||||
1. Whether to configure SSL (recommended for production)
|
||||
2. Your domain name (e.g., `pos.example.com`)
|
||||
3. Your email for Let's Encrypt (for production SSL)
|
||||
|
||||
```bash
|
||||
curl -sSL https://opensourcepos.org/install | sudo bash
|
||||
# Script will ask:
|
||||
# - Configure SSL? (y/n)
|
||||
# - Domain name: pos.example.com
|
||||
# - Email for Let's Encrypt: admin@example.com
|
||||
```
|
||||
|
||||
**Non-Interactive Mode (for automation):**
|
||||
|
||||
```bash
|
||||
# Development (no SSL)
|
||||
curl -sSL https://opensourcepos.org/install | APACHE_SERVER_NAME=localhost sudo -E bash
|
||||
|
||||
# Production with Let's Encrypt SSL
|
||||
curl -sSL https://opensourcepos.org/install | APACHE_SERVER_NAME=pos.example.com SSL_EMAIL=admin@example.com sudo -E bash
|
||||
|
||||
# Custom database password
|
||||
curl -sSL https://opensourcepos.org/install | DB_PASS=securepassword APACHE_SERVER_NAME=pos.example.com SSL_EMAIL=admin@example.com sudo -E bash
|
||||
```
|
||||
|
||||
**Environment variables:**
|
||||
- `DB_HOST` - Database host (default: localhost)
|
||||
- `DB_NAME` - Database name (default: ospos)
|
||||
- `DB_USER` - Database user (default: ospos)
|
||||
- `DB_PASS` - Database password (default: auto-generated)
|
||||
- `MYSQL_ROOT_PASS` - MariaDB root password (default: empty/no password)
|
||||
- `OSPOS_DIR` - Installation directory (default: /var/www/ospos)
|
||||
- `OSPOS_VERSION` - OSPOS version to install (default: latest stable release)
|
||||
- `PHP_VERSION` - PHP version (default: 8.2)
|
||||
- `APACHE_SERVER_NAME` - Server hostname (default: localhost, or set interactively)
|
||||
- `SSL_EMAIL` - Email for Let's Encrypt. When set, enables production SSL with auto-renewal
|
||||
- `SSL_DOMAIN` - Alternative to `APACHE_SERVER_NAME` for SSL certificate domain
|
||||
|
||||
> **Testing:** This installer is tested with each commit via our CI workflow. A fresh Ubuntu container is spawned, the script runs to completion, and basic sanity checks verify the installation. For production deployments, we recommend testing on a staging server first. If you encounter issues, please [open an issue](https://github.com/opensourcepos/opensourcepos/issues/new?template=bug_report.yml) with your server version and error output.
|
||||
|
||||
> **Note:** If the short URL is unavailable, use the direct GitHub URL:
|
||||
> ```bash
|
||||
> curl -sSL https://raw.githubusercontent.com/opensourcepos/opensourcepos/master/scripts/install-ubuntu.sh | sudo bash
|
||||
> ```
|
||||
|
||||
For other cloud providers or manual installation, see the [detailed installation guide](https://github.com/opensourcepos/opensourcepos/wiki/Getting-Started-installations) in the wiki.
|
||||
|
||||
**Important:** Change the default password after first login!
|
||||
If you choose DigitalOcean:
|
||||
[Through this link](https://m.do.co/c/ac38c262507b), you will get a [**free $100, 60-day credit**](https://m.do.co/c/ac38c262507b). [Check the wiki](https://github.com/opensourcepos/opensourcepos/wiki/Getting-Started-installations) for further instructions on how to install the necessary components.
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Config;
|
||||
use App\Models\Appconfig;
|
||||
use CodeIgniter\Cache\CacheInterface;
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use Config\Database;
|
||||
|
||||
/**
|
||||
* This class holds the configuration options stored from the database so that on launch those settings can be cached
|
||||
@@ -13,7 +14,7 @@ use CodeIgniter\Config\BaseConfig;
|
||||
*/
|
||||
class OSPOS extends BaseConfig
|
||||
{
|
||||
public array $settings;
|
||||
public array $settings = [];
|
||||
public string $commit_sha1 = 'dev'; // TODO: Travis scripts need to be updated to replace this with the commit hash on build
|
||||
private CacheInterface $cache;
|
||||
|
||||
@@ -33,26 +34,35 @@ class OSPOS extends BaseConfig
|
||||
|
||||
if ($cache) {
|
||||
$this->settings = decode_array($cache);
|
||||
} else {
|
||||
try {
|
||||
$appconfig = model(Appconfig::class);
|
||||
foreach ($appconfig->get_all()->getResult() as $app_config) {
|
||||
$this->settings[$app_config->key] = $app_config->value;
|
||||
}
|
||||
$this->cache->save('settings', encode_array($this->settings));
|
||||
} catch (\Exception $e) {
|
||||
// Database table doesn't exist yet (migrations haven't run)
|
||||
// or database connection failed. Return empty settings to
|
||||
// allow migration page to display. Catches mysqli_sql_exception
|
||||
// which is not a subclass of DatabaseException.
|
||||
$this->settings = [
|
||||
'language' => 'english',
|
||||
'language_code' => 'en',
|
||||
'company' => 'Home',
|
||||
'barcode_type' => 'Code39'
|
||||
];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = Database::connect();
|
||||
|
||||
if (!$db->tableExists('app_config')) {
|
||||
$this->settings = $this->getDefaultSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
$appconfig = model(Appconfig::class);
|
||||
foreach ($appconfig->get_all()->getResult() as $app_config) {
|
||||
$this->settings[$app_config->key] = $app_config->value;
|
||||
}
|
||||
$this->cache->save('settings', encode_array($this->settings));
|
||||
} catch (\Exception $e) {
|
||||
$this->settings = $this->getDefaultSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private function getDefaultSettings(): array
|
||||
{
|
||||
return [
|
||||
'language' => 'english',
|
||||
'language_code' => 'en',
|
||||
'company' => 'Home',
|
||||
'barcode_type' => 'Code39'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,4 +73,4 @@ class OSPOS extends BaseConfig
|
||||
$this->cache->delete('settings');
|
||||
$this->set_settings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,13 @@ class Login extends BaseController
|
||||
return view('login', $data);
|
||||
}
|
||||
|
||||
if (!$data['is_latest'] || $data['is_new_install']) {
|
||||
set_time_limit(3600);
|
||||
|
||||
$migration->setNamespace('App')->latest();
|
||||
return redirect()->to('login');
|
||||
}
|
||||
|
||||
$rules = ['username' => 'required|login_check[data]'];
|
||||
$messages = [
|
||||
'username' => [
|
||||
@@ -62,13 +69,6 @@ class Login extends BaseController
|
||||
|
||||
return view('login', $data);
|
||||
}
|
||||
|
||||
if (!$data['is_latest']) {
|
||||
set_time_limit(3600);
|
||||
|
||||
$migration->setNamespace('App')->latest();
|
||||
return redirect()->to('login');
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->to('home');
|
||||
@@ -79,18 +79,18 @@ class Login extends BaseController
|
||||
try {
|
||||
$migration = new MY_Migration(config('Migrations'));
|
||||
$migration->migrate_to_ci4();
|
||||
|
||||
|
||||
set_time_limit(3600);
|
||||
$migration->setNamespace('App')->latest();
|
||||
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => 'Migration completed successfully'
|
||||
]);
|
||||
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Migration failed: ' . $e->getMessage());
|
||||
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => 'Migration failed: ' . $e->getMessage()
|
||||
|
||||
@@ -161,7 +161,7 @@ class Sales extends Secure_Controller
|
||||
'only_bank_transfer'=> false,
|
||||
'only_wallet' => false,
|
||||
'only_invoices' => $this->config['invoice_enable'] && $this->request->getGet('only_invoices', FILTER_SANITIZE_NUMBER_INT),
|
||||
'is_valid_receipt' => $this->sale->is_valid_receipt($search)
|
||||
'is_valid_receipt' => $this->sale->isValidReceipt($search)
|
||||
];
|
||||
|
||||
// Check if any filter is set in the multiselect dropdown
|
||||
@@ -198,7 +198,7 @@ class Sales extends Secure_Controller
|
||||
? $this->request->getGet('term')
|
||||
: null;
|
||||
|
||||
if ($this->sale_lib->get_mode() == 'return' && $this->sale->is_valid_receipt($receipt)) {
|
||||
if ($this->sale_lib->get_mode() == 'return' && $this->sale->isValidReceipt($receipt)) {
|
||||
// If a valid receipt or invoice was found the search term will be replaced with a receipt number (POS #)
|
||||
$suggestions[] = $receipt;
|
||||
}
|
||||
@@ -525,7 +525,7 @@ class Sales extends Secure_Controller
|
||||
$quantity = ($mode == 'return') ? -$quantity : $quantity;
|
||||
$item_location = $this->sale_lib->get_sale_location();
|
||||
|
||||
if ($mode == 'return' && $this->sale->is_valid_receipt($item_id_or_number_or_item_kit_or_receipt)) {
|
||||
if ($mode == 'return' && $this->sale->isValidReceipt($item_id_or_number_or_item_kit_or_receipt)) {
|
||||
$this->sale_lib->return_entire_sale($item_id_or_number_or_item_kit_or_receipt);
|
||||
} elseif ($this->item_kit->is_valid_item_kit($item_id_or_number_or_item_kit_or_receipt)) {
|
||||
// Add kit item to order if one is assigned
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class Migration_Upgrade_To_3_1_1 extends Migration
|
||||
@@ -17,7 +18,37 @@ class Migration_Upgrade_To_3_1_1 extends Migration
|
||||
public function up(): void
|
||||
{
|
||||
helper('migration');
|
||||
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.0.2_to_3.1.1.sql');
|
||||
|
||||
// MariaDB blocks CONVERT TO CHARACTER SET on tables with FK constraints.
|
||||
// Drop all FKs across affected tables before running the SQL script, recreate after.
|
||||
$fkColumns = [
|
||||
['modules', 'module_id'],
|
||||
['stock_locations', 'location_id'],
|
||||
['permissions', 'permission_id'],
|
||||
['people', 'person_id'],
|
||||
['suppliers', 'supplier_id'],
|
||||
['items', 'item_id'],
|
||||
['item_kits', 'item_kit_id'],
|
||||
['sales', 'sale_id'],
|
||||
['receivings', 'receiving_id'],
|
||||
['employees', 'employee_id'],
|
||||
['customers', 'person_id'],
|
||||
];
|
||||
|
||||
$constraints = [];
|
||||
foreach ($fkColumns as [$table, $column]) {
|
||||
foreach (dropAllForeignKeyConstraints($table, $column) as $c) {
|
||||
$constraints[$c['constraintName']] = $c;
|
||||
}
|
||||
}
|
||||
|
||||
if (!execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.0.2_to_3.1.1.sql')) {
|
||||
throw new DatabaseException('Migration script 3.0.2_to_3.1.1.sql failed. Check logs for details.');
|
||||
}
|
||||
|
||||
$droppedTables = ['sales_suspended', 'sales_suspended_items', 'sales_suspended_items_taxes', 'sales_suspended_payments'];
|
||||
$toRecreate = array_filter($constraints, fn($c) => !in_array($c['tableName'], $droppedTables, true));
|
||||
recreateForeignKeyConstraints(array_values($toRecreate));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -327,19 +327,6 @@ INSERT INTO `ospos_sales_items` (sale_id, item_id, description, serialnumber, li
|
||||
INSERT INTO `ospos_sales_payments` (sale_id, payment_type, payment_amount) SELECT sale_id, payment_type, payment_amount FROM `ospos_sales_suspended_payments`;
|
||||
INSERT INTO `ospos_sales_items_taxes` (sale_id, item_id, line, name, percent) SELECT sale_id, item_id, line, name, percent FROM `ospos_sales_suspended_items_taxes`;
|
||||
|
||||
ALTER TABLE `ospos_sales_suspended_payments` DROP FOREIGN KEY `ospos_sales_suspended_payments_ibfk_1`;
|
||||
|
||||
ALTER TABLE `ospos_sales_suspended_items_taxes` DROP FOREIGN KEY `ospos_sales_suspended_items_taxes_ibfk_1`;
|
||||
ALTER TABLE `ospos_sales_suspended_items_taxes` DROP FOREIGN KEY `ospos_sales_suspended_items_taxes_ibfk_2`;
|
||||
|
||||
ALTER TABLE `ospos_sales_suspended_items` DROP FOREIGN KEY `ospos_sales_suspended_items_ibfk_1`;
|
||||
ALTER TABLE `ospos_sales_suspended_items` DROP FOREIGN KEY `ospos_sales_suspended_items_ibfk_2`;
|
||||
ALTER TABLE `ospos_sales_suspended_items` DROP FOREIGN KEY `ospos_sales_suspended_items_ibfk_3`;
|
||||
|
||||
ALTER TABLE `ospos_sales_suspended` DROP FOREIGN KEY `ospos_sales_suspended_ibfk_1`;
|
||||
ALTER TABLE `ospos_sales_suspended` DROP FOREIGN KEY `ospos_sales_suspended_ibfk_2`;
|
||||
ALTER TABLE `ospos_sales_suspended` DROP FOREIGN KEY `ospos_sales_suspended_ibfk_3`;
|
||||
|
||||
DROP TABLE `ospos_sales_suspended_payments`, `ospos_sales_suspended_items_taxes`, `ospos_sales_suspended_items`, `ospos_sales_suspended`;
|
||||
|
||||
--
|
||||
|
||||
@@ -140,7 +140,7 @@ CREATE TABLE IF NOT EXISTS `ospos_expense_categories` (
|
||||
`category_name` varchar(255) DEFAULT NULL,
|
||||
`category_description` varchar(255) NOT NULL,
|
||||
`deleted` int(1) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
|
||||
-- Table structure for table `ospos_expenses`
|
||||
@@ -154,7 +154,7 @@ CREATE TABLE IF NOT EXISTS `ospos_expenses` (
|
||||
`description` varchar(255) NOT NULL,
|
||||
`employee_id` int(10) NOT NULL,
|
||||
`deleted` int(1) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
|
||||
-- Indexes for table `ospos_expense_categories`
|
||||
|
||||
@@ -75,7 +75,7 @@ CREATE TABLE `ospos_cash_up` (
|
||||
`open_employee_id` int(10) NOT NULL,
|
||||
`close_employee_id` int(10) NOT NULL,
|
||||
`deleted` int(1) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
-- Indexes for table `ospos_cash_up`
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS `ospos_tax_codes` (
|
||||
`state` varchar(255) NOT NULL DEFAULT '',
|
||||
`deleted` int(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`tax_code_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
ALTER TABLE `ospos_customers`
|
||||
ADD COLUMN `tax_id` varchar(32) NOT NULL DEFAULT '' AFTER `taxable`,
|
||||
@@ -59,7 +59,7 @@ CREATE TABLE `ospos_sales_taxes` (
|
||||
`rounding_code` tinyint(2) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`sales_taxes_id`),
|
||||
KEY `print_sequence` (`sale_id`,`print_sequence`,`tax_group`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `ospos_tax_jurisdictions` (
|
||||
`jurisdiction_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
@@ -71,7 +71,7 @@ CREATE TABLE IF NOT EXISTS `ospos_tax_jurisdictions` (
|
||||
`cascade_sequence` tinyint(2) NOT NULL DEFAULT 0,
|
||||
`deleted` int(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`jurisdiction_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci AUTO_INCREMENT=1;
|
||||
|
||||
ALTER TABLE `ospos_suppliers`
|
||||
ADD COLUMN `tax_id` varchar(32) DEFAULT NULL AFTER `account_number`;
|
||||
@@ -89,7 +89,7 @@ CREATE TABLE IF NOT EXISTS `ospos_tax_rates` (
|
||||
`tax_rate` decimal(15,4) NOT NULL DEFAULT 0.0000,
|
||||
`tax_rounding_code` tinyint(2) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`tax_rate_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
-- Add support for sales tax report
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ CREATE TABLE `ospos_sales_payments` (
|
||||
`reference_code` varchar(40) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`payment_id`),
|
||||
KEY `payment_sale` (`sale_id`, `payment_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
INSERT INTO ospos_sales_payments (sale_id, payment_type, payment_amount, payment_user)
|
||||
SELECT payments.sale_id, payments.payment_type, payments.payment_amount, sales.employee_id
|
||||
|
||||
@@ -172,6 +172,7 @@ function dropAllForeignKeyConstraints(string $table, string $column): array {
|
||||
WHERE kcu.TABLE_SCHEMA = DATABASE()
|
||||
AND ((kcu.REFERENCED_TABLE_NAME = '" . $db->getPrefix() . "$table' AND kcu.REFERENCED_COLUMN_NAME = '$column')
|
||||
OR (kcu.TABLE_NAME = '" . $db->getPrefix() . "$table' AND kcu.COLUMN_NAME = '$column'))
|
||||
AND rc.CONSTRAINT_NAME IS NOT NULL
|
||||
");
|
||||
|
||||
$deletedConstraints = [];
|
||||
|
||||
@@ -25,7 +25,7 @@ class MY_Migration extends MigrationRunner
|
||||
public function get_latest_migration(): int
|
||||
{
|
||||
$migrations = $this->findMigrations();
|
||||
return basename(end($migrations)->version);
|
||||
return (int) basename(end($migrations)->version);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +41,7 @@ class MY_Migration extends MigrationRunner
|
||||
$builder = $db->table('migrations');
|
||||
$builder->select('version')->orderBy('version', 'DESC')->limit(1);
|
||||
$result = $builder->get()->getRow();
|
||||
return $result ? $result->version : 0;
|
||||
return $result ? (int) $result->version : 0;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Database not available yet (e.g. fresh install before schema).
|
||||
|
||||
@@ -327,7 +327,7 @@ class Sale extends Model
|
||||
{
|
||||
$suggestions = [];
|
||||
|
||||
if (!$this->is_valid_receipt($search)) {
|
||||
if (!$this->isValidReceipt($search)) {
|
||||
$builder = $this->db->table('sales');
|
||||
$builder->distinct()->select('first_name, last_name');
|
||||
$builder->join('people', 'people.person_id = sales.customer_id');
|
||||
@@ -408,21 +408,21 @@ class Sale extends Model
|
||||
/**
|
||||
* Checks if valid receipt
|
||||
*/
|
||||
public function is_valid_receipt(string|null &$receipt_sale_id): bool // TODO: like the others, maybe this should be an array rather than a delimited string... either that or the parameter name needs to be changed. $receipt_sale_id implies that it's an int.
|
||||
public function isValidReceipt(string|null &$receiptSaleId): bool // TODO: like the others, maybe this should be an array rather than a delimited string... either that or the parameter name needs to be changed. $receipt_sale_id implies that it's an int.
|
||||
{
|
||||
$config = config(OSPOS::class)->settings;
|
||||
|
||||
if (!empty($receipt_sale_id)) {
|
||||
if (!empty($receiptSaleId)) {
|
||||
// POS #
|
||||
$pieces = explode(' ', $receipt_sale_id);
|
||||
$pieces = explode(' ', trim($receiptSaleId));
|
||||
|
||||
if (count($pieces) == 2 && preg_match('/(POS)/i', $pieces[0])) {
|
||||
return $this->exists($pieces[1]);
|
||||
if (count($pieces) == 2 && strtoupper($pieces[0]) === 'POS' && ctype_digit($pieces[1])) {
|
||||
return $this->exists((int)$pieces[1]);
|
||||
} elseif ($config['invoice_enable']) {
|
||||
$sale_info = $this->get_sale_by_invoice_number($receipt_sale_id);
|
||||
$saleInfo = $this->get_sale_by_invoice_number($receiptSaleId);
|
||||
|
||||
if ($sale_info->getNumRows() > 0) {
|
||||
$receipt_sale_id = 'POS ' . $sale_info->getRow()->sale_id;
|
||||
if ($saleInfo->getNumRows() > 0) {
|
||||
$receiptSaleId = 'POS ' . $saleInfo->getRow()->sale_id;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,381 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
COLOR_RED='\033[0;31m'
|
||||
COLOR_GREEN='\033[0;32m'
|
||||
COLOR_YELLOW='\033[1;33m'
|
||||
COLOR_BLUE='\033[0;34m'
|
||||
COLOR_RESET='\033[0m'
|
||||
|
||||
echo -e "${COLOR_BLUE}╔══════════════════════════════════════════════════════════╗${COLOR_RESET}"
|
||||
echo -e "${COLOR_BLUE}║ Open Source Point of Sale - Ubuntu Installer ║${COLOR_RESET}"
|
||||
echo -e "${COLOR_BLUE}║ Version 3.4+ ║${COLOR_RESET}"
|
||||
echo -e "${COLOR_BLUE}╚══════════════════════════════════════════════════════════╝${COLOR_RESET}"
|
||||
echo ""
|
||||
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${COLOR_RED}Please run this script as root or with sudo${COLOR_RESET}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
DB_HOST="${DB_HOST:-localhost}"
|
||||
DB_NAME="${DB_NAME:-ospos}"
|
||||
DB_USER="${DB_USER:-ospos}"
|
||||
DB_PASS="${DB_PASS:-$(openssl rand -base64 24)}"
|
||||
OSPOS_DIR="${OSPOS_DIR:-/var/www/ospos}"
|
||||
OSPOS_VERSION="${OSPOS_VERSION:-}"
|
||||
PHP_VERSION="${PHP_VERSION:-8.2}"
|
||||
APACHE_SERVER_NAME="${APACHE_SERVER_NAME:-}"
|
||||
SSL_EMAIL="${SSL_EMAIL:-}"
|
||||
SSL_DOMAIN="${SSL_DOMAIN:-}"
|
||||
MYSQL_ROOT_PASS="${MYSQL_ROOT_PASS:-}"
|
||||
|
||||
# Validate database variables contain only safe characters (alphanumeric, underscore, hyphen, dot)
|
||||
validate_db_vars() {
|
||||
local var_name="$1"
|
||||
local var_value="$2"
|
||||
local pattern='^[a-zA-Z0-9_\-\.]+$'
|
||||
if [[ ! "$var_value" =~ $pattern ]]; then
|
||||
echo -e "${COLOR_RED}Error: ${var_name} contains invalid characters. Only alphanumeric, underscore, hyphen, and dot are allowed.${COLOR_RESET}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Validate critical database variables
|
||||
validate_db_vars "DB_NAME" "$DB_NAME"
|
||||
validate_db_vars "DB_USER" "$DB_USER"
|
||||
validate_db_vars "DB_HOST" "$DB_HOST"
|
||||
|
||||
# Check if running interactively
|
||||
INTERACTIVE=false
|
||||
if [ -t 0 ]; then
|
||||
INTERACTIVE=true
|
||||
fi
|
||||
|
||||
echo -e "${COLOR_YELLOW}Configuration:${COLOR_RESET}"
|
||||
echo -e " Database Name: ${DB_NAME}"
|
||||
echo -e " Database User: ${DB_USER}"
|
||||
echo -e " Database Host: ${DB_HOST}"
|
||||
echo -e " Install Directory: ${OSPOS_DIR}"
|
||||
echo -e " PHP Version: ${PHP_VERSION}"
|
||||
if [ -n "$OSPOS_VERSION" ]; then
|
||||
echo -e " OSPOS Version: ${OSPOS_VERSION}"
|
||||
else
|
||||
echo -e " OSPOS Version: latest"
|
||||
fi
|
||||
if [ -n "$APACHE_SERVER_NAME" ]; then
|
||||
echo -e " Server Name: ${APACHE_SERVER_NAME}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
if [ -d "$OSPOS_DIR" ]; then
|
||||
echo -e "${COLOR_RED}Installation directory $OSPOS_DIR already exists${COLOR_RESET}"
|
||||
echo -e "${COLOR_YELLOW}Remove it or set OSPOS_DIR environment variable${COLOR_RESET}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${COLOR_GREEN}[1/9] Updating system packages...${COLOR_RESET}"
|
||||
apt-get update -qq
|
||||
|
||||
echo -e "${COLOR_GREEN}[2/9] Installing Apache, PHP, and dependencies...${COLOR_RESET}"
|
||||
# Add PHP repository for newer PHP versions if not available in default repos
|
||||
if ! apt-cache policy php${PHP_VERSION} 2>/dev/null | grep -q "Candidate:"; then
|
||||
echo -e "${COLOR_YELLOW}PHP ${PHP_VERSION} not in default repos, adding ondrej/php PPA...${COLOR_RESET}"
|
||||
apt-get install -y -qq software-properties-common
|
||||
add-apt-repository -y ppa:ondrej/php
|
||||
apt-get update -qq
|
||||
fi
|
||||
|
||||
apt-get install -y -qq \
|
||||
apache2 \
|
||||
mariadb-server \
|
||||
mariadb-client \
|
||||
php${PHP_VERSION} \
|
||||
php${PHP_VERSION}-mysql \
|
||||
php${PHP_VERSION}-gd \
|
||||
php${PHP_VERSION}-bcmath \
|
||||
php${PHP_VERSION}-intl \
|
||||
php${PHP_VERSION}-mbstring \
|
||||
php${PHP_VERSION}-curl \
|
||||
php${PHP_VERSION}-xml \
|
||||
php${PHP_VERSION}-zip \
|
||||
git \
|
||||
curl \
|
||||
unzip \
|
||||
openssl
|
||||
|
||||
echo -e "${COLOR_GREEN}[3/9] Starting MariaDB...${COLOR_RESET}"
|
||||
systemctl start mariadb
|
||||
systemctl enable mariadb
|
||||
|
||||
if [ -z "$MYSQL_ROOT_PASS" ]; then
|
||||
echo -e "${COLOR_BLUE}Securing MariaDB installation...${COLOR_RESET}"
|
||||
mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '';"
|
||||
mysql -e "FLUSH PRIVILEGES;"
|
||||
else
|
||||
mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASS}';"
|
||||
fi
|
||||
|
||||
echo -e "${COLOR_GREEN}[4/9] Creating database and user...${COLOR_RESET}"
|
||||
if [ -n "$MYSQL_ROOT_PASS" ]; then
|
||||
mysql -u root -p"${MYSQL_ROOT_PASS}" <<EOF
|
||||
CREATE DATABASE IF NOT EXISTS ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER IF NOT EXISTS '${DB_USER}'@'${DB_HOST}' IDENTIFIED BY '${DB_PASS}';
|
||||
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'${DB_HOST}';
|
||||
FLUSH PRIVILEGES;
|
||||
EOF
|
||||
else
|
||||
mysql -u root <<EOF
|
||||
CREATE DATABASE IF NOT EXISTS ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER IF NOT EXISTS '${DB_USER}'@'${DB_HOST}' IDENTIFIED BY '${DB_PASS}';
|
||||
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'${DB_HOST}';
|
||||
FLUSH PRIVILEGES;
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo -e "${COLOR_GREEN}[5/9] Downloading OSPOS...${COLOR_RESET}"
|
||||
mkdir -p "$(dirname "$OSPOS_DIR")"
|
||||
cd "$(dirname "$OSPOS_DIR")"
|
||||
|
||||
if [ -z "$OSPOS_VERSION" ]; then
|
||||
OSPOS_VERSION=$(curl -sS https://api.github.com/repos/opensourcepos/opensourcepos/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||
if [ -z "$OSPOS_VERSION" ]; then
|
||||
echo -e "${COLOR_RED}Failed to get latest release version${COLOR_RESET}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${COLOR_BLUE}Downloading OSPOS version ${OSPOS_VERSION}...${COLOR_RESET}"
|
||||
ASSET_URL=$(curl -sS "https://api.github.com/repos/opensourcepos/opensourcepos/releases/tags/${OSPOS_VERSION}" | grep '"browser_download_url"' | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
|
||||
|
||||
if [ -z "$ASSET_URL" ]; then
|
||||
echo -e "${COLOR_RED}Failed to find release asset for ${OSPOS_VERSION}${COLOR_RESET}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl -sSL "$ASSET_URL" -o ospos.zip
|
||||
|
||||
if [ ! -f ospos.zip ] || [ ! -s ospos.zip ]; then
|
||||
echo -e "${COLOR_RED}Failed to download OSPOS release ${OSPOS_VERSION}${COLOR_RESET}"
|
||||
rm -f ospos.zip
|
||||
exit 1
|
||||
fi
|
||||
|
||||
unzip -q ospos.zip -d ospos-temp
|
||||
mkdir -p "${OSPOS_DIR}"
|
||||
cp -r ospos-temp/. "${OSPOS_DIR}/"
|
||||
rm -rf ospos-temp ospos.zip
|
||||
|
||||
echo -e "${COLOR_GREEN}Downloaded OSPOS ${OSPOS_VERSION}${COLOR_RESET}"
|
||||
|
||||
echo -e "${COLOR_GREEN}[6/9] Setting up OSPOS...${COLOR_RESET}"
|
||||
cd "${OSPOS_DIR}"
|
||||
|
||||
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer 2>/dev/null
|
||||
|
||||
if [ -f "composer.json" ]; then
|
||||
echo -e "${COLOR_BLUE}Installing dependencies...${COLOR_RESET}"
|
||||
composer install --no-dev --optimize-autoloader --no-interaction --quiet 2>/dev/null
|
||||
fi
|
||||
|
||||
echo -e "${COLOR_GREEN}[7/9] Configuring OSPOS...${COLOR_RESET}"
|
||||
if [ -f ".env.example" ]; then
|
||||
cp .env.example .env
|
||||
fi
|
||||
|
||||
if [ -f ".env" ]; then
|
||||
# Escape special characters in password for sed
|
||||
ESCAPED_DB_PASS=$(printf '%s\n' "$DB_PASS" | sed 's/[&/\]/\\&/g')
|
||||
|
||||
sed -i "s|database\.default\.hostname = 'localhost'|database.default.hostname = '${DB_HOST}'|" .env
|
||||
sed -i "s|database\.default\.database = 'ospos'|database.default.database = '${DB_NAME}'|" .env
|
||||
sed -i "s|database\.default\.username = 'admin'|database.default.username = '${DB_USER}'|" .env
|
||||
sed -i "s|database\.default\.password = 'pointofsale'|database.default.password = '${ESCAPED_DB_PASS}'|" .env
|
||||
sed -i "s|CI_ENVIRONMENT = development|CI_ENVIRONMENT = production|" .env
|
||||
|
||||
if grep -q "encryption\.key = ''" .env; then
|
||||
ENCRYPTION_KEY=$(openssl rand -base64 32)
|
||||
ESCAPED_KEY=$(printf '%s\n' "$ENCRYPTION_KEY" | sed 's/[&/\]/\\&/g')
|
||||
sed -i "s|encryption\.key = ''|encryption.key = '${ESCAPED_KEY}'|" .env
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${COLOR_GREEN}[8/9] Importing database schema...${COLOR_RESET}"
|
||||
if [ -n "$MYSQL_ROOT_PASS" ]; then
|
||||
mysql -u root -p"${MYSQL_ROOT_PASS}" ${DB_NAME} < app/Database/database.sql
|
||||
else
|
||||
mysql -u root ${DB_NAME} < app/Database/database.sql
|
||||
fi
|
||||
|
||||
# Interactive SSL configuration
|
||||
if $INTERACTIVE && [ -z "$SSL_EMAIL" ] && [ -z "$APACHE_SERVER_NAME" ]; then
|
||||
echo ""
|
||||
echo -e "${COLOR_BLUE}╔══════════════════════════════════════════════════════════╗${COLOR_RESET}"
|
||||
echo -e "${COLOR_BLUE}║ SSL/TLS Configuration ║${COLOR_RESET}"
|
||||
echo -e "${COLOR_BLUE}╚══════════════════════════════════════════════════════════╝${COLOR_RESET}"
|
||||
echo ""
|
||||
echo -e "${COLOR_YELLOW}SSL provides secure HTTPS access to your OSPOS installation.${COLOR_RESET}"
|
||||
echo -e "${COLOR_YELLOW}For production, we recommend Let's Encrypt (free SSL certificate).${COLOR_RESET}"
|
||||
echo ""
|
||||
|
||||
read -p "Configure SSL? (y/n) [n]: " CONFIGURE_SSL
|
||||
CONFIGURE_SSL=${CONFIGURE_SSL:-n}
|
||||
|
||||
if [[ "$CONFIGURE_SSL" =~ ^[Yy]$ ]]; then
|
||||
read -p "Enter your domain name (e.g., pos.example.com): " SSL_DOMAIN
|
||||
SSL_DOMAIN=${SSL_DOMAIN:-localhost}
|
||||
APACHE_SERVER_NAME=$SSL_DOMAIN
|
||||
|
||||
read -p "Enter your email for Let's Encrypt notifications: " SSL_EMAIL
|
||||
|
||||
if [ -z "$SSL_EMAIL" ]; then
|
||||
echo -e "${COLOR_YELLOW}No email provided. Using self-signed certificate (not recommended for production).${COLOR_RESET}"
|
||||
SSL_TYPE="self-signed"
|
||||
else
|
||||
SSL_TYPE="letsencrypt"
|
||||
fi
|
||||
else
|
||||
APACHE_SERVER_NAME="localhost"
|
||||
SSL_TYPE="none"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set default server name if not provided
|
||||
if [ -z "$APACHE_SERVER_NAME" ]; then
|
||||
APACHE_SERVER_NAME="localhost"
|
||||
fi
|
||||
|
||||
# If SSL_EMAIL is set without SSL_DOMAIN, use APACHE_SERVER_NAME
|
||||
if [ -n "$SSL_EMAIL" ] && [ -z "$SSL_DOMAIN" ] && [ "$APACHE_SERVER_NAME" != "localhost" ]; then
|
||||
SSL_DOMAIN="$APACHE_SERVER_NAME"
|
||||
fi
|
||||
|
||||
echo -e "${COLOR_GREEN}[9/9] Configuring Apache...${COLOR_RESET}"
|
||||
cat > /etc/apache2/sites-available/ospos.conf <<EOF
|
||||
<VirtualHost *:80>
|
||||
ServerName ${APACHE_SERVER_NAME}
|
||||
DocumentRoot ${OSPOS_DIR}/public
|
||||
|
||||
<Directory ${OSPOS_DIR}/public>
|
||||
Options -Indexes +FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
ErrorLog \${APACHE_LOG_DIR}/ospos_error.log
|
||||
CustomLog \${APACHE_LOG_DIR}/ospos_access.log combined
|
||||
</VirtualHost>
|
||||
EOF
|
||||
|
||||
a2enmod rewrite
|
||||
a2dissite 000-default.conf
|
||||
a2ensite ospos.conf
|
||||
|
||||
chown -R www-data:www-data "${OSPOS_DIR}"
|
||||
chmod -R 750 "${OSPOS_DIR}/writable"
|
||||
|
||||
systemctl restart apache2
|
||||
systemctl enable apache2
|
||||
|
||||
# Configure SSL if requested
|
||||
if [ -n "$SSL_EMAIL" ] && [ -n "$SSL_DOMAIN" ]; then
|
||||
# Let's Encrypt SSL
|
||||
echo -e "${COLOR_BLUE}Installing Certbot for Let's Encrypt...${COLOR_RESET}"
|
||||
apt-get install -y -qq certbot python3-certbot-apache
|
||||
|
||||
echo -e "${COLOR_BLUE}Obtaining SSL certificate for ${SSL_DOMAIN}...${COLOR_RESET}"
|
||||
certbot --apache -d ${SSL_DOMAIN} --non-interactive --agree-tos --email ${SSL_EMAIL} --redirect
|
||||
|
||||
echo -e "${COLOR_BLUE}Setting up auto-renewal...${COLOR_RESET}"
|
||||
systemctl enable certbot.timer
|
||||
systemctl start certbot.timer
|
||||
|
||||
PROTOCOL="https"
|
||||
FINAL_URL="https://${SSL_DOMAIN}/"
|
||||
elif [ -n "$SSL_DOMAIN" ]; then
|
||||
# Self-signed SSL
|
||||
echo -e "${COLOR_BLUE}Generating self-signed SSL certificate...${COLOR_RESET}"
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout /etc/ssl/private/ospos-selfsigned.key \
|
||||
-out /etc/ssl/certs/ospos-selfsigned.crt \
|
||||
-subj "/C=US/ST=State/L=City/O=Organization/CN=${SSL_DOMAIN}" 2>/dev/null
|
||||
|
||||
cat > /etc/apache2/sites-available/ospos-ssl.conf <<EOF
|
||||
<VirtualHost *:443>
|
||||
ServerName ${SSL_DOMAIN}
|
||||
DocumentRoot ${OSPOS_DIR}/public
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/ssl/certs/ospos-selfsigned.crt
|
||||
SSLCertificateKeyFile /etc/ssl/private/ospos-selfsigned.key
|
||||
|
||||
<Directory ${OSPOS_DIR}/public>
|
||||
Options -Indexes +FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
ErrorLog \${APACHE_LOG_DIR}/ospos_ssl_error.log
|
||||
CustomLog \${APACHE_LOG_DIR}/ospos_ssl_access.log combined
|
||||
</VirtualHost>
|
||||
EOF
|
||||
|
||||
a2enmod ssl
|
||||
a2ensite ospos-ssl.conf
|
||||
|
||||
cat > /etc/apache2/sites-available/ospos.conf <<EOF
|
||||
<VirtualHost *:80>
|
||||
ServerName ${SSL_DOMAIN}
|
||||
Redirect permanent / https://${SSL_DOMAIN}/
|
||||
</VirtualHost>
|
||||
EOF
|
||||
|
||||
a2dissite ospos.conf
|
||||
a2ensite ospos.conf
|
||||
|
||||
PROTOCOL="https"
|
||||
FINAL_URL="https://${SSL_DOMAIN}/"
|
||||
|
||||
echo -e "${COLOR_YELLOW}Note: Your browser will show a security warning for self-signed${COLOR_RESET}"
|
||||
echo -e "${COLOR_YELLOW} certificates. For production, re-run with an email for Let's Encrypt.${COLOR_RESET}"
|
||||
else
|
||||
PROTOCOL="http"
|
||||
FINAL_URL="http://${APACHE_SERVER_NAME}/"
|
||||
fi
|
||||
|
||||
systemctl restart apache2
|
||||
|
||||
# Configure allowed hostnames
|
||||
if [ -f "${OSPOS_DIR}/.env" ]; then
|
||||
sed -i "s|app\.allowedHostnames = ''|app.allowedHostnames = '${APACHE_SERVER_NAME}'|" ${OSPOS_DIR}/.env
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${COLOR_GREEN}╔══════════════════════════════════════════════════════════╗${COLOR_RESET}"
|
||||
echo -e "${COLOR_GREEN}║ Installation Complete! ║${COLOR_RESET}"
|
||||
echo -e "${COLOR_GREEN}╚══════════════════════════════════════════════════════════╝${COLOR_RESET}"
|
||||
echo ""
|
||||
echo -e "${COLOR_YELLOW}Database Credentials:${COLOR_RESET}"
|
||||
echo -e " Database: ${DB_NAME}"
|
||||
echo -e " Username: ${DB_USER}"
|
||||
echo -e " Password: ${DB_PASS}"
|
||||
echo ""
|
||||
echo -e "${COLOR_YELLOW}Login Credentials:${COLOR_RESET}"
|
||||
echo -e " URL: ${FINAL_URL}"
|
||||
if [ -n "$SSL_EMAIL" ]; then
|
||||
echo -e " SSL: Let's Encrypt (auto-renewal enabled)"
|
||||
elif [ -n "$SSL_DOMAIN" ]; then
|
||||
echo -e " SSL: Self-signed certificate"
|
||||
else
|
||||
echo -e " SSL: Not configured (HTTP only)"
|
||||
fi
|
||||
echo -e " Username: admin"
|
||||
echo -e " Password: pointofsale"
|
||||
echo ""
|
||||
echo -e "${COLOR_RED}IMPORTANT: Change the default password after first login!${COLOR_RESET}"
|
||||
echo ""
|
||||
echo -e "${COLOR_BLUE}Configuration file: ${OSPOS_DIR}/.env${COLOR_RESET}"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user