Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .github/assets/dashboard-products.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/assets/dashboard-reservations.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/assets/storefront.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 22 additions & 6 deletions README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

**Arrêtez de payer des abonnements SaaS coûteux. Possédez votre logiciel de location.**

[![Docker](https://img.shields.io/badge/Docker-synapsr%2Flouez-2496ED?style=for-the-badge&logo=docker)](https://hub.docker.com/r/synapsr/louez)
[![Docker Pulls](https://img.shields.io/docker/pulls/synapsr/louez?style=for-the-badge&logo=docker)](https://hub.docker.com/r/synapsr/louez)
[![GitHub Stars](https://img.shields.io/github/stars/Synapsr/Louez?style=for-the-badge&logo=github)](https://github.com/Synapsr/Louez)
[![License](https://img.shields.io/badge/License-AGPL_v3-blue?style=for-the-badge)](LICENSE)

Expand All @@ -22,13 +22,22 @@

---

## 🎬 Démo
## 🎬 Louez en action

<div align="center">

<video src="demo-fr.mp4" width="100%" autoplay loop muted playsinline></video>
<a href="https://louez.io"><img src=".github/assets/storefront.png" alt="Boutique Louez — catalogue de location avec disponibilités en temps réel" width="100%"></a>

_Louez en action — de l'installation à la première réservation_
_Votre boutique : catalogue à vos couleurs, disponibilités en temps réel, réservation en quelques clics_

<a href=".github/assets/dashboard-reservations.png"><img src=".github/assets/dashboard-reservations.png" alt="Calendrier des réservations" width="49%"></a>
<a href=".github/assets/dashboard-products.png"><img src=".github/assets/dashboard-products.png" alt="Gestion des produits" width="49%"></a>

_Votre back-office : calendrier des réservations, inventaire, clients, contrats_

[▶️ Voir la démo complète](demo-fr.mp4) • [☁️ Essayer la version hébergée](https://louez.io)

**⭐ Si Louez peut remplacer un abonnement chez vous, mettez une étoile** — c'est comme ça que le prochain loueur le découvre.

</div>

Expand Down Expand Up @@ -89,6 +98,8 @@ docker compose up -d

**C'est tout.** Ouvrez [http://localhost:3000](http://localhost:3000), créez votre compte et configurez votre boutique. Votre vitrine est servie à la racine du site ; votre tableau de bord vit sous `/dashboard`.

> Ça a marché du premier coup ? [Mettez une ⭐](https://github.com/Synapsr/Louez) — ça vous coûte un clic, et c'est comme ça que le prochain loueur trouve Louez plutôt qu'un abonnement à 99 €/mois.

Le [docker-compose.yml](docker-compose.yml) fourni est un déploiement complet et autonome :

- 🗄️ **Base de données incluse** — MySQL tourne à côté de l'application et le schéma s'installe tout seul au premier démarrage
Expand All @@ -106,9 +117,14 @@ NEXT_PUBLIC_APP_URL="https://locations.exemple.fr"
AUTH_URL="https://locations.exemple.fr"
```

### Plateformes en un clic
### Déploiement en un clic

Vous préférez un panneau d'hébergement à un terminal ? Louez démarre en un clic — l'application, la base de données et le stockage d'images ensemble :

[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/Synapsr/Louez)
[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/Synapsr/Louez)

L'image publiée `synapsr/louez` fonctionne par défaut en mode boutique unique — fournissez une base MySQL et les variables ci-dessus et elle démarre sur EasyPanel, Dokploy, Coolify, Portainer ou Railway. Voir [.env.example](.env.example) pour toute la surface de configuration (stockage S3, SMTP, Stripe, etc.).
Ou importez le `docker-compose.yml` fourni dans **EasyPanel, Dokploy, Coolify, CapRover ou Portainer**. L'image publiée `synapsr/louez` fonctionne aussi seule en mode boutique unique — fournissez une base MySQL et les variables ci-dessus et elle démarre partout. Voir [.env.example](.env.example) pour toute la surface de configuration (stockage S3, SMTP, Stripe, etc.).

### Déploiements multi-boutiques

Expand Down
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

**Stop paying for expensive SaaS. Own your rental business software.**

[![Docker](https://img.shields.io/badge/Docker-synapsr%2Flouez-2496ED?style=for-the-badge&logo=docker)](https://hub.docker.com/r/synapsr/louez)
[![Docker Pulls](https://img.shields.io/docker/pulls/synapsr/louez?style=for-the-badge&logo=docker)](https://hub.docker.com/r/synapsr/louez)
[![GitHub Stars](https://img.shields.io/github/stars/Synapsr/Louez?style=for-the-badge&logo=github)](https://github.com/Synapsr/Louez)
[![License](https://img.shields.io/badge/License-AGPL_v3-blue?style=for-the-badge)](LICENSE)

Expand All @@ -22,13 +22,22 @@

---

## 🎬 Demo
## 🎬 See it in action

<div align="center">

<video src="demo.mp4" width="100%" autoplay loop muted playsinline></video>
<a href="https://louez.io"><img src=".github/assets/storefront.png" alt="Louez storefront — rental catalog with live availability" width="100%"></a>

_See Louez in action — from setup to first booking_
_Your storefront: branded catalog, live availability, booking in a few clicks_

<a href=".github/assets/dashboard-reservations.png"><img src=".github/assets/dashboard-reservations.png" alt="Reservation calendar" width="49%"></a>
<a href=".github/assets/dashboard-products.png"><img src=".github/assets/dashboard-products.png" alt="Product management" width="49%"></a>

_Your back office: reservation calendar, inventory, customers, contracts_

[▶️ Watch the full demo](demo.mp4) • [☁️ Try the hosted version](https://louez.io)

**⭐ If Louez can replace a subscription for you, star the repo** — it is how the next rental business finds it.

</div>

Expand Down Expand Up @@ -91,6 +100,8 @@ docker compose up -d

**That's it.** Open [http://localhost:3000](http://localhost:3000), create your account, and set up your store. Your storefront goes live at the root of the site; your dashboard lives at `/dashboard`.

> Worked on the first try? [Leave a ⭐](https://github.com/Synapsr/Louez) — it costs you a click and it is how the next rental business finds Louez instead of a €99/month subscription.

The bundled [docker-compose.yml](docker-compose.yml) is a complete, self-contained deployment:

- 🗄️ **Database included** — MySQL runs alongside the app, and the schema installs itself on first boot
Expand All @@ -113,9 +124,14 @@ NEXT_PUBLIC_APP_URL="https://rentals.example.com"
AUTH_URL="https://rentals.example.com"
```

### One-click platforms
### One-click deploy

Prefer a hosting panel to a terminal? Louez launches in one click — app, database and image storage together:

[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/Synapsr/Louez)
[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/Synapsr/Louez)

Import the bundled Compose stack in EasyPanel, Dokploy, Coolify or Portainer to deploy the web app, MySQL, MinIO and the private background-removal worker together. The published image `synapsr/louez` still runs independently in single-store mode, but image isolation requires its companion `synapsr/louez-background-removal` service. See [.env.example](.env.example) for the full configuration surface (S3 storage, SMTP, Stripe, AI, and more).
Or import the bundled Compose stack in **EasyPanel, Dokploy, Coolify, CapRover or Portainer** — it deploys the web app, MySQL, MinIO and the private background-removal worker together. The published image `synapsr/louez` still runs independently in single-store mode, but image isolation requires its companion `synapsr/louez-background-removal` service. See [.env.example](.env.example) for the full configuration surface (S3 storage, SMTP, Stripe, AI, and more).

### Multi-tenant deployments

Expand Down
93 changes: 93 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{
"name": "Louez",
"description": "Open-source equipment rental platform: a branded booking storefront with live availability, plus a back office for reservations, customers, PDF contracts and revenue statistics.",
"repository": "https://github.com/Synapsr/Louez",
"website": "https://louez.io",
"logo": "https://raw.githubusercontent.com/Synapsr/Louez/main/apps/web/public/icons/icon-512.png",
"keywords": [
"rental",
"booking",
"reservations",
"inventory-management",
"storefront",
"nextjs",
"self-hosted"
],
"stack": "container",
"formation": {
"web": {
"quantity": 1,
"size": "basic"
}
},
"addons": [
{
"plan": "jawsdb",
"as": "DATABASE"
}
],
"env": {
"LOUEZ_MODE": {
"description": "standalone hosts a single store at the root of the site, with the dashboard under /dashboard.",
"value": "standalone"
},
"AUTH_URL": {
"description": "Public URL of this app — https://<the app name you chose above>.herokuapp.com, or your own domain. Sign-in is bound to it.",
"required": true
},
"AUTH_SECRET": {
"description": "Signs sessions. Generated for you.",
"generator": "secret"
},
"AUTH_TRUST_HOST": {
"description": "Required behind the Heroku router.",
"value": "true"
},
"S3_ENDPOINT": {
"description": "S3-compatible endpoint for product images (AWS S3, Cloudflare R2, Scaleway, Backblaze B2...). Image uploads stay disabled until the five S3 values are set. Everything else works without them.",
"required": false
Comment on lines +46 to +48

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Provision the storage variables required at startup

When a user accepts the default Heroku deployment and leaves these fields blank, the container cannot start: apps/web/env.ts:46-50 declares all five S3 settings as required URL/non-empty values, and runtime validation is enabled in the final image. Marking them optional while supplying only S3_PUBLIC_URL therefore produces an environment-validation failure rather than the documented deployment with uploads disabled; either provision storage or make the application schema genuinely support missing S3 configuration.

Useful? React with 👍 / 👎.

},
"S3_REGION": {
"description": "Region of your bucket, e.g. eu-west-3.",
"required": false
},
"S3_BUCKET": {
"description": "Name of your bucket.",
"required": false
},
"S3_ACCESS_KEY_ID": {
"description": "Access key with read/write access to the bucket.",
"required": false
},
"S3_SECRET_ACCESS_KEY": {
"description": "Secret key paired with the access key.",
"required": false
},
"S3_PUBLIC_URL": {
"description": "Public base URL of the bucket, or /files to serve images through the app when the bucket is private.",
"value": "/files",
"required": false
},
"SMTP_HOST": {
"description": "Optional. Without SMTP credentials you sign in with a password and Louez skips sending mail.",
"required": false
},
"SMTP_PORT": {
"description": "Optional. 587 for STARTTLS, 465 for TLS on connect.",
"value": "587",
"required": false
},
"SMTP_USER": {
"description": "Optional. SMTP username.",
"required": false
},
"SMTP_PASSWORD": {
"description": "Optional. SMTP password.",
"required": false
},
"SMTP_FROM": {
"description": "Optional. Sender address, e.g. Louez <noreply@example.com>.",
"required": false
}
}
}
20 changes: 20 additions & 0 deletions docker/Dockerfile.minio
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# syntax=docker/dockerfile:1

# =============================================================================
# Louez image store — MinIO with the product bucket pre-created
# =============================================================================
# A bucket is a top-level directory of MinIO's data drive, so creating it at
# startup replaces the one-shot `mc` job the Compose stack uses. This exists
# for platforms that run long-lived services only and cannot override a
# container's entrypoint (Render), where the upstream image would otherwise
# prepend `minio` to the shell command and refuse to start.
#
# Build: docker build -f docker/Dockerfile.minio -t louez-minio docker
# =============================================================================

FROM minio/minio:RELEASE.2025-09-07T16-13-09Z

EXPOSE 9000

ENTRYPOINT ["/bin/sh", "-c"]
CMD ["mkdir -p /data/${S3_BUCKET:-louez} && exec minio server /data"]
13 changes: 13 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
#!/bin/sh
set -eu

# Managed platforms publish the database URL and the app's public URL under
# their own variable names. Adopt them when the operator set nothing, so the
# one-click deployments in deploy/ boot without a manual configuration pass.
if [ -z "${DATABASE_URL:-}" ] && [ -n "${JAWSDB_URL:-}" ]; then
DATABASE_URL="$JAWSDB_URL"
export DATABASE_URL
fi

if [ -z "${AUTH_URL:-}" ] && [ -n "${RENDER_EXTERNAL_URL:-}" ]; then
AUTH_URL="$RENDER_EXTERNAL_URL"
export AUTH_URL
fi

# Zero-config secret for standalone installs: when the operator provides no
# AUTH_SECRET, generate one once and persist it under /app/data (mounted as a
# volume by docker-compose.yml) so sessions survive container recreation.
Expand Down
11 changes: 11 additions & 0 deletions heroku.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Heroku container build — https://devcenter.heroku.com/articles/build-docker-images-heroku-yml
#
# Builds the production image from the repository (final stage of
# docker/Dockerfile.web) and starts it through the same entrypoint the
# published image uses, so the JawsDB connection string provisioned by app.json
# is picked up as DATABASE_URL.
build:
docker:
web: docker/Dockerfile.web
run:
web: ./docker/entrypoint.sh node apps/web/server.js
104 changes: 104 additions & 0 deletions render.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Render Blueprint — https://render.com/docs/infrastructure-as-code
#
# Deploys Louez in single-store mode with its own MySQL database and a private
# MinIO store for product images. Louez installs its schema on first boot and
# derives its public URL from RENDER_EXTERNAL_URL, so the only prompt is the
# database password.
#
# All three services are paid instance types: private services and persistent
# disks are not available on Render's free tier.

services:
- type: web
name: louez
runtime: image
plan: starter
region: frankfurt
image:
url: docker.io/synapsr/louez:2
autoDeployTrigger: "off"
healthCheckPath: /api/health
envVars:
- key: LOUEZ_MODE
value: standalone
- key: AUTH_TRUST_HOST
value: "true"
# Kept out of the container's /app/data so the service needs no disk:
# Render stores the generated value with the service.
- key: AUTH_SECRET
generateValue: true
# DATABASE_URL is composed in dockerCommand below from this password —
# a Blueprint cannot concatenate values itself.
- key: MYSQL_PASSWORD
fromService:
type: pserv
name: louez-mysql
envVarKey: MYSQL_ROOT_PASSWORD
# The bucket stays private: Louez streams images same-origin under /files
# with its own credentials, so the store needs no public URL.
- key: S3_ENDPOINT
value: http://louez-minio:9000
- key: S3_REGION
value: us-east-1
- key: S3_BUCKET
value: louez
- key: S3_PUBLIC_URL
value: /files
- key: S3_ACCESS_KEY_ID
fromService:
type: pserv
name: louez-minio
envVarKey: MINIO_ROOT_USER
- key: S3_SECRET_ACCESS_KEY
fromService:
type: pserv
name: louez-minio
envVarKey: MINIO_ROOT_PASSWORD
dockerCommand: sh -c 'export DATABASE_URL="mysql://root:$MYSQL_PASSWORD@louez-mysql:3306/louez"; exec node apps/web/server.js'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Encode the prompted password before building DATABASE_URL

If the password entered in Render contains URI delimiters such as #, /, or ?, interpolating it directly into this MySQL URL changes how the value is parsed and the web service fails authentication or targets a malformed database URL. The Blueprint merely comments that users should choose alphanumeric characters, but that comment is not an enforced constraint in Render's sync: false prompt; construct the URL with percent-encoding or otherwise generate and enforce a URL-safe password.

Useful? React with 👍 / 👎.


- type: pserv
name: louez-mysql
runtime: image
plan: starter
region: frankfurt
image:
url: docker.io/library/mysql:8.4
autoDeployTrigger: "off"
disk:
name: mysql-data
mountPath: /var/lib/mysql
sizeGB: 10
envVars:
- key: MYSQL_ROOT_PASSWORD
# Prompted once when you launch the Blueprint. Use letters and digits
# only: the value is embedded in a connection URL.
sync: false
- key: MYSQL_DATABASE
value: louez
# MySQL refuses to initialize into a non-empty directory, and a fresh disk
# is not empty (lost+found), so the data lives one level down.
dockerCommand: mysqld --datadir=/var/lib/mysql/data

# MinIO with the product bucket pre-created. Render's docker command is
# passed to the image's entrypoint rather than replacing it, and MinIO's
# entrypoint prepends `minio` to whatever it receives — so the bucket step
# lives in a four-line image built from this repository instead.
- type: pserv
name: louez-minio
runtime: docker
plan: starter
region: frankfurt
dockerfilePath: ./docker/Dockerfile.minio
dockerContext: ./docker
autoDeployTrigger: "off"
disk:
name: minio-data
mountPath: /data
sizeGB: 10
envVars:
- key: MINIO_ROOT_USER
value: louez
- key: MINIO_ROOT_PASSWORD
generateValue: true
- key: S3_BUCKET
value: louez
Loading