Skip to content

Commit b494d0e

Browse files
committed
feat: add Odoo launcher script generation
Add launcher module that generates bash scripts for auto-activating venv and running Odoo. Includes: - --create-launcher flag on 'create' command to auto-generate launcher - Template-based script generation with full Odoo option support - Security checks (symlink detection, path validation) - PATH warning if launcher dir not in environment
1 parent 058708f commit b494d0e

4 files changed

Lines changed: 257 additions & 0 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,8 @@ __marimo__/
214214

215215
# Streamlit
216216
.streamlit/secrets.toml
217+
218+
.claude/
219+
.opencode/
220+
plans/
221+
release-manifest.json
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#!/bin/bash
2+
#
3+
# Odoo Launcher Script
4+
#
5+
# Auto-generated by odoo-venv. This script activates the virtual environment
6+
# and runs Odoo with the configured options.
7+
#
8+
# Configure Odoo startup by editing the variables below.
9+
# Only parameters with non-empty values will be passed to odoo.
10+
#
11+
# Usage:
12+
# 1. Edit variables below (e.g., DATABASE="mydb", DEV_MODE="all")
13+
# 2. Run: odoo-v{version}
14+
# 3. Script activates venv and builds command: odoo -d mydb --dev all
15+
16+
set -uo pipefail
17+
18+
# ============================================================================
19+
# VIRTUAL ENVIRONMENT ACTIVATION
20+
# ============================================================================
21+
22+
VENV_DIR="$VENV_DIR"
23+
export VIRTUAL_ENV="$$VENV_DIR"
24+
export PATH="$$VENV_DIR/bin:$$PATH"
25+
26+
# ============================================================================
27+
# CONFIGURATION
28+
# ============================================================================
29+
30+
# Database
31+
DATABASE="odoo_db" # -d: Database name
32+
INIT_MODULES="" # -i: Modules to install (comma-separated)
33+
UPDATE_MODULES="" # -u: Modules to update (comma-separated)
34+
ADDONS_PATH="" # --addons-path: Addons paths (comma-separated)
35+
DB_FILTER="" # --db-filter: Database filter regex
36+
37+
# Database Connection
38+
DB_USER="odoo" # -r: Database user
39+
DB_PASSWORD="odoo" # -w: Database password
40+
DB_HOST="localhost" # --db_host: Database host
41+
DB_PORT="" # --db_port: Database port
42+
43+
# Configuration
44+
CONFIG_FILE="" # -c: Configuration file path
45+
SAVE_CONFIG="" # -s: Save configuration to file
46+
STOP_AFTER_INIT="" # --stop-after-init: Stop after init (true/false)
47+
48+
# Developer Mode
49+
DEV_MODE="" # --dev: Dev mode (all, reload, qweb, werkzeug, xml)
50+
51+
# Web Server
52+
HTTP_PORT="" # --http-port: HTTP port (default: 8069)
53+
HTTP_INTERFACE="" # --http-interface: Interface to bind (default: 0.0.0.0)
54+
NO_HTTP="" # --no-http: Disable HTTP server (true/false)
55+
56+
# Testing
57+
TEST_ENABLE="" # --test-enable: Enable unit tests (true/false)
58+
TEST_TAGS="" # --test-tags: Test tags (comma-separated)
59+
60+
# Logging
61+
LOGFILE="" # --logfile: Log file path
62+
LOG_LEVEL="" # --log-level: Log level (debug, info, warn, error, critical)
63+
LOG_HANDLER="" # --log-handler: Log handler (e.g., :INFO)
64+
65+
# Performance
66+
WORKERS="0" # --workers: Number of worker processes
67+
LIMIT_TIME_CPU="3600" # --limit-time-cpu: CPU time limit (seconds)
68+
LIMIT_TIME_REAL="3600" # --limit-time-real: Real time limit (seconds)
69+
MAX_CRON_THREADS="0" # --max-cron-threads: Max cron threads
70+
PROXY_MODE="" # --proxy-mode: Enable proxy mode (true/false)
71+
72+
# ============================================================================
73+
# COMMAND BUILDER
74+
# ============================================================================
75+
76+
CMD=()
77+
78+
add_arg() { [[ -n "$$2" ]] && CMD+=("$$1" "$$2"); }
79+
add_flag() { [[ "$$2" == "true" ]] && CMD+=("$$1"); }
80+
81+
build_command() {
82+
CMD=("odoo")
83+
84+
# Database
85+
add_arg "-d" "$$DATABASE"
86+
add_arg "-i" "$$INIT_MODULES"
87+
add_arg "-u" "$$UPDATE_MODULES"
88+
add_arg "--addons-path" "$$ADDONS_PATH"
89+
add_arg "--db-filter" "$$DB_FILTER"
90+
91+
# Database connection
92+
add_arg "-r" "$$DB_USER"
93+
add_arg "-w" "$$DB_PASSWORD"
94+
add_arg "--db_host" "$$DB_HOST"
95+
add_arg "--db_port" "$$DB_PORT"
96+
97+
# Configuration
98+
add_arg "-c" "$$CONFIG_FILE"
99+
add_arg "-s" "$$SAVE_CONFIG"
100+
add_flag "--stop-after-init" "$$STOP_AFTER_INIT"
101+
102+
# Developer mode
103+
add_arg "--dev" "$$DEV_MODE"
104+
105+
# Web server
106+
add_arg "--http-port" "$$HTTP_PORT"
107+
add_arg "--http-interface" "$$HTTP_INTERFACE"
108+
add_flag "--no-http" "$$NO_HTTP"
109+
110+
# Testing
111+
add_flag "--test-enable" "$$TEST_ENABLE"
112+
add_arg "--test-tags" "$$TEST_TAGS"
113+
114+
# Logging
115+
add_arg "--logfile" "$$LOGFILE"
116+
add_arg "--log-level" "$$LOG_LEVEL"
117+
add_arg "--log-handler" "$$LOG_HANDLER"
118+
119+
# Performance
120+
add_arg "--workers" "$$WORKERS"
121+
add_arg "--limit-time-cpu" "$$LIMIT_TIME_CPU"
122+
add_arg "--limit-time-real" "$$LIMIT_TIME_REAL"
123+
add_arg "--max-cron-threads" "$$MAX_CRON_THREADS"
124+
add_flag "--proxy-mode" "$$PROXY_MODE"
125+
}
126+
127+
# ============================================================================
128+
# MAIN
129+
# ============================================================================
130+
131+
main() {
132+
build_command
133+
CMD+=("$$@")
134+
135+
echo "Executing: $${CMD[*]}"
136+
echo "---"
137+
"$${CMD[@]}"
138+
}
139+
140+
main "$$@"

odoo_venv/cli/main.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import typer
77

88
from odoo_venv.exceptions import PresetNotFoundError
9+
from odoo_venv.launcher import create_launcher
910
from odoo_venv.main import create_odoo_venv
1011
from odoo_venv.utils import initialize_presets, load_presets, run_migration
1112

@@ -146,6 +147,13 @@ def create(
146147
help="Use a preset of options. Preset values can be overriden by other options.",
147148
),
148149
] = None,
150+
create_launcher_flag: Annotated[
151+
bool,
152+
typer.Option(
153+
"--create-launcher/--no-create-launcher",
154+
help="Generate a launcher script in ~/.local/bin/.",
155+
),
156+
] = False,
149157
):
150158
"""Create virtual environment to run Odoo"""
151159
if not odoo_dir:
@@ -191,3 +199,17 @@ def create(
191199
verbose=verbose,
192200
dry_run=dry_run,
193201
)
202+
203+
if create_launcher_flag:
204+
create_launcher(odoo_version, venv_dir_path, force=True)
205+
206+
207+
@app.command()
208+
def create_odoo_launcher(
209+
odoo_version: Annotated[str, typer.Argument(help="Odoo version, e.g: 19.0")],
210+
venv_dir: Annotated[str, typer.Option(help="Path to the virtual environment.")],
211+
force: Annotated[bool, typer.Option(help="Overwrite existing launcher script.")] = False,
212+
):
213+
"""Generate a launcher script in ~/.local/bin/ for the Odoo environment"""
214+
venv_dir_path = Path(venv_dir).expanduser().resolve()
215+
create_launcher(odoo_version, venv_dir_path, force=force)

odoo_venv/launcher.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""Launcher script generation for Odoo environments."""
2+
3+
import os
4+
import sys
5+
from pathlib import Path
6+
from string import Template
7+
8+
import typer
9+
10+
LAUNCHER_DIR = Path("~/.local/bin").expanduser()
11+
TEMPLATE_PATH = Path(__file__).parent / "assets" / "launcher.sh.template"
12+
13+
14+
def create_launcher(odoo_version: str, venv_dir: str | Path, force: bool = False) -> Path:
15+
"""
16+
Generate a bash launcher script that auto-activates the venv and runs Odoo.
17+
18+
Args:
19+
odoo_version: Odoo version string (e.g., "19.0")
20+
venv_dir: Path to the virtual environment
21+
force: Overwrite existing launcher if True
22+
23+
Returns:
24+
Path to the created launcher script
25+
26+
Raises:
27+
SystemExit: If file exists and force=False, or on write errors
28+
"""
29+
# Extract major version for script name
30+
major_version = odoo_version.split(".")[0]
31+
script_name = f"odoo-v{major_version}"
32+
output_path = LAUNCHER_DIR / script_name
33+
34+
# Resolve venv path to absolute
35+
venv_path = Path(venv_dir).expanduser().resolve()
36+
37+
# Check if file exists
38+
if output_path.exists() and not force:
39+
typer.secho(
40+
f"Launcher script already exists: {output_path}\nUse --force to overwrite.",
41+
fg=typer.colors.YELLOW,
42+
)
43+
return output_path
44+
45+
# Create output directory if needed
46+
LAUNCHER_DIR.mkdir(parents=True, exist_ok=True)
47+
48+
# Read and render template
49+
try:
50+
template_content = TEMPLATE_PATH.read_text()
51+
rendered = Template(template_content).substitute(VENV_DIR=str(venv_path))
52+
except FileNotFoundError:
53+
typer.secho(f"Template not found: {TEMPLATE_PATH}", fg=typer.colors.RED, err=True)
54+
sys.exit(1)
55+
except (KeyError, ValueError) as e:
56+
typer.secho(f"Template substitution failed: {e}", fg=typer.colors.RED, err=True)
57+
sys.exit(1)
58+
59+
# Security: Check for symlink before writing
60+
if output_path.is_symlink():
61+
typer.secho(
62+
f"Error: {output_path} is a symbolic link. Refusing to overwrite for security reasons.",
63+
fg=typer.colors.RED,
64+
err=True,
65+
)
66+
sys.exit(1)
67+
68+
# Write script
69+
try:
70+
output_path.write_text(rendered)
71+
output_path.chmod(0o755)
72+
except PermissionError:
73+
typer.secho(
74+
f"Permission denied writing to {output_path}\nEnsure you have write access to {LAUNCHER_DIR}",
75+
fg=typer.colors.RED,
76+
err=True,
77+
)
78+
sys.exit(1)
79+
80+
# Check if launcher dir is in PATH
81+
if str(LAUNCHER_DIR) not in os.environ.get("PATH", ""):
82+
typer.secho(
83+
f"\nWarning: {LAUNCHER_DIR} is not in your PATH.\n"
84+
f"Add this to your shell profile (~/.bashrc or ~/.zshrc):\n"
85+
f' export PATH="$PATH:{LAUNCHER_DIR}"',
86+
fg=typer.colors.YELLOW,
87+
)
88+
89+
typer.secho(f"✓ Launcher created: {output_path}", fg=typer.colors.GREEN)
90+
return output_path

0 commit comments

Comments
 (0)