Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
a47bc58
feat: added APPS _JSON
kemboi22 Dec 23, 2025
7a861e3
feat: added dynamic building of Dockerfile
kemboi22 Dec 23, 2025
5085b7a
feat: use frappe ERPNEXT
kemboi22 Dec 23, 2025
293b15e
feat: update version
kemboi22 Dec 23, 2025
d43b8b4
feat: added jq in docker
kemboi22 Dec 23, 2025
4b86e2b
fix: remove latest tag
kemboi22 Dec 23, 2025
457d024
fix: check if folder already exists
kemboi22 Dec 23, 2025
979ad79
feat: use host networks
kemboi22 Dec 24, 2025
6c959a2
feat: use Layered Docker Build
kemboi22 Dec 26, 2025
b9dfbad
featt: remove redudant flag
kemboi22 Dec 26, 2025
5900ac7
feat: added APPS JSON in settings get config
kemboi22 Dec 26, 2025
397591f
feat: added JOBS
kemboi22 Dec 26, 2025
f2ae2be
feat: added default hasBackup
kemboi22 Dec 26, 2025
b7df648
feat: added build docker image
kemboi22 Jan 1, 2026
3171686
feat: added timeout removal
kemboi22 Jan 1, 2026
3fa2bdd
feat: updated 30build_images
kemboi22 Jan 1, 2026
740f186
feat: added build image
kemboi22 Jan 14, 2026
07c1f63
fixed permissions
kemboi22 Jan 14, 2026
f62e8e2
fix: removed docker images
kemboi22 Jan 14, 2026
f373fee
dont exit if ERPNEXT_image does not exist
kemboi22 Jan 14, 2026
dc1e611
feat: added a default image tag
kemboi22 Jan 14, 2026
b445605
feat: use comma separated that [] js array
kemboi22 Jan 15, 2026
6d7d862
feat: added transalations
kemboi22 Jan 16, 2026
a90e35a
feat: added dynamic apps
kemboi22 Jan 16, 2026
a112078
feat: added default
kemboi22 Jan 16, 2026
d2d8196
feat: added caching of the json
kemboi22 Jan 16, 2026
f3cafa6
feat: update translations
kemboi22 Jan 16, 2026
957e5ab
feta: update BuildDocker
kemboi22 Jan 16, 2026
294d382
feat: added labels
kemboi22 Jan 18, 2026
14f109b
added fetching tags
kemboi22 Jan 20, 2026
8ab88b9
feat: added version selector
kemboi22 Jan 20, 2026
40b8822
fix: update dropdown
kemboi22 Jan 21, 2026
93ab231
feat: added Settings
kemboi22 Jan 24, 2026
4e63269
refactor
kemboi22 Jan 24, 2026
e18e74e
feat: added prvent action
kemboi22 Jan 24, 2026
cd3eab6
feat: make primary button actie
kemboi22 Jan 24, 2026
8cd7fc5
fix: fixing navigation
kemboi22 Jan 24, 2026
9cc8d75
feat: added listing of podman images
kemboi22 Jan 25, 2026
d4b548a
remove unused components
kemboi22 Jan 25, 2026
8699bba
feat: few updates on podman images
kemboi22 Jan 25, 2026
02ac11c
few updates
kemboi22 Jan 25, 2026
9d36916
feat: update using tag
kemboi22 Jan 27, 2026
32f2176
few updates
kemboi22 Jan 28, 2026
86a510a
feat: few updates on cache
kemboi22 Jan 29, 2026
efd60dd
feat: update ui
kemboi22 Jan 30, 2026
323c841
feat: added ldap discovery
kemboi22 Jan 30, 2026
245255f
feat: few updates on settings
kemboi22 Jan 31, 2026
042eff5
feat: added UPGRADE instructions
kemboi22 Jan 31, 2026
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
128 changes: 128 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Upgrade to V4

## Overview

In the new ns8-erpnext we build docker at runtime where you pass in the apps.json you need to build your app.

## Architecture

The module uses a runtime Docker image building approach where:

- Custom apps are defined via JSON configuration
- Docker images are built on-demand based on the apps.json configuration
- Podman is used for container management

## Configuration Settings UI

The Settings page (`ui/src/views/Settings.vue`) provides the following configuration options:

### 1. FQDN (Fully Qualified Domain Name)

- **Field**: Host/URL input
- **Purpose**: Set the URL where ERPNext will be accessible
- **Example**: `erpnext.example.org`
- **Validation**: Required field

### 2. TLS/SSL Configuration

- **Let's Encrypt**: Toggle to enable automatic SSL certificate generation
- **HTTP to HTTPS Redirect**: Toggle to force HTTPS redirects (enabled by default)

### 3. Frappe Version Selection

- **Options**:
- `version-15` (default)
- `version-16`
- **Purpose**: Select the Frappe framework version to use

### 4. App Management

#### Adding Custom Apps

Apps can be added via the UI modal with the following fields:

- **App Name** (required): The application identifier
- **Repository URL** (required): Git repository URL for the app
- **Branch**: Git branch to use (defaults to "main")
- **Labels**: Comma-separated labels for the app

#### App Management Features

- **Add App via Form**: Opens a modal to add new apps
- **Edit App**: Modify existing app details
- **Remove App**: Delete apps from the configuration
- **Copy JSON**: Copy the current apps.json to clipboard
- **JSON Editor**: Advanced users can directly edit the apps.json in the accordion section

#### Apps Display

Apps are displayed in a structured list showing:

- App Name
- URL
- Branch
- Labels
- Actions (Edit/Remove buttons)

### 5. ERPNext Modules Selection

- **Multi-select component** showing available modules
- **Dynamic population**: Options are generated from the apps.json configuration
- **Pre-selected values**: Previously selected modules are restored when loading configuration
- **Filtering**: When apps are removed, their modules are automatically deselected

### 6. Podman Images (Advanced)

- **View**: Lists all built Podman images with details (Repository, Tag, ID, Created, Size)
- **Refresh**: Button to refresh the images list
- **Purpose**: Monitor built Docker images

## Data Flow

1. **Configuration Loading** (`getConfiguration`):
- Fetches current configuration from backend
- Decodes base64 appJson
- Restores selected modules
- Populates all form fields

2. **App JSON Processing**:
- Stored as base64 encoded string in backend
- Parsed into structured list for display
- Used to generate multi-select options
- Filters selected modules to only include valid apps

3. **Configuration Saving** (`configureModule`):
- Validates host field
- Encodes appJson to base64
- Sends all configuration data to backend
- Triggers module reconfiguration

## JSON Format

The `app_json` field expects an array of app objects:

```json
[
{
"app_name": "my-custom-app",
"url": "https://github.com/user/repo",
"branch": "main",
"labels": "production,custom"
}
]
```

## Important Notes

1. **App Name Consistency**: The multi-select uses `app_name` or `name` field to match selected modules with available options
2. **Base64 Encoding**: The appJson is base64 encoded when sent to the backend
3. **Validation**: Apps must have at least an app_name and URL
4. **Podman Integration**: The module interfaces with Podman for container management
5. **Dynamic Options**: The ERPNext Modules multi-select options are dynamically generated from the apps.json

**NOTE**

- Always ensure apps.json is valid JSON before saving
- Selected modules are filtered to only include apps that exist in the configuration
- Podman images are specific to the configured apps and Frappe version
- Make sure you install the apps that were previously installed to avoid installtion issues
4 changes: 1 addition & 3 deletions build-images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ repobase="${REPOBASE:-ghcr.io/geniusdynamics}"
# Configure the image name
reponame="erpnext"

app_version="15.92.1"

# Create a new empty container image
container=$(buildah from scratch)

Expand Down Expand Up @@ -47,7 +45,7 @@ buildah config --entrypoint=/ \
--label="org.nethserver.authorizations=traefik@node:routeadm" \
--label="org.nethserver.tcp-ports-demand=1" \
--label="org.nethserver.rootfull=0" \
--label="org.nethserver.images=docker.io/mariadb:10.11.5 docker.io/geniusdynamics/erpnext:${app_version} docker.io/redis:6.2-alpine" \
--label="org.nethserver.images=docker.io/mariadb:10.11.5 docker.io/redis:6.2-alpine" \
"${container}"
# Commit the image
buildah commit "${container}" "${repobase}/${reponame}"
Expand Down
26 changes: 26 additions & 0 deletions imageroot/actions/build-docker-image/20configure_build_vars
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env python3

#
# Copyright (C) 2022 Nethesis S.r.l.
# SPDX-License-Identifier: GPL-3.0-or-later
#

import json
import sys
import agent

# Try to parse the stdin as JSON.
# If parsing fails, output everything to stderr
data = json.load(sys.stdin)

# This is specific to you module, so you need to change it accordingly.

ERP_NEXT_MODULES = data.get("erpSelectedModules", [])
APP_JSON = data.get("appJson")
FRAPPE_VERSION = data.get("frappeVersion", "version-15")

agent.write_envfile(
"erpnext-modules.env", {"ERP_NEXT_MODULES": ",".join(ERP_NEXT_MODULES) if ERP_NEXT_MODULES else "", "APPS_JSON": APP_JSON, "FRAPPE_VERSION": FRAPPE_VERSION}
)

agent.dump_env()
6 changes: 6 additions & 0 deletions imageroot/actions/build-docker-image/30build_docker_image
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

echo "Building Docker Image"
../configure-module/30build_docker_image

echo "Docker Image Built"
12 changes: 8 additions & 4 deletions imageroot/actions/configure-module/10configure_environment_vars
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ import agent
# If parsing fails, output everything to stderr
data = json.load(sys.stdin)

#This is specific to you module, so you need to change it accordingly.
# This is specific to you module, so you need to change it accordingly.

ERP_NEXT_MODULES = data.get("erpSelectedModules", [])
APP_JSON = data.get("appJson")
FRAPPE_VERSION = data.get("frappeVersion", "version-15")

agent.write_envfile("erpnext-modules.env", {
"ERP_NEXT_MODULES": ERP_NEXT_MODULES
})
ERP_NEXT_MODULES_STR = ",".join(ERP_NEXT_MODULES) if ERP_NEXT_MODULES else ""

agent.write_envfile(
"erpnext-modules.env", {"ERP_NEXT_MODULES": ERP_NEXT_MODULES_STR, "APPS_JSON": APP_JSON, "FRAPPE_VERSION": FRAPPE_VERSION}
)

agent.dump_env()
178 changes: 178 additions & 0 deletions imageroot/actions/configure-module/30build_docker_image
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#!/bin/bash

set -e
source erpnext-modules.env
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Function to print colored output
print_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}

print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}

print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}

# Check if podman is installed
if ! command -v podman &>/dev/null; then
print_error "podman is required but not installed. Please install podman first."
exit 1
fi

# Check if jq is installed
if ! command -v jq &>/dev/null; then
print_error "jq is required but not installed. Please install jq first."
exit 1
fi

# Function to fetch latest ERPNext tag from GitHub based on FRAPPE_VERSION
fetch_erpnext_tag() {
local major_version="${FRAPPE_VERSION##version-}"
local api_url="https://api.github.com/repos/frappe/erpnext/tags"

local tags_json
if ! tags_json=$(curl -fsSL "$api_url" 2>/dev/null); then
print_warning "Failed to fetch tags from GitHub, using default"
return 1
fi

local first_tag
if ! first_tag=$(echo "$tags_json" | jq -r ".[] | select(.name | startswith(\"v${major_version}.\")) | .name" 2>/dev/null | head -1); then
print_warning "Failed to parse tags, using default"
return 1
fi

if [ -z "$first_tag" ] || [ "$first_tag" = "null" ]; then
print_warning "No v${major_version} tags found, using default"
return 1
fi

echo "$first_tag"
return 0
}

# Cache file for app_json hash
CACHE_FILE=".app_json_cache"
CURRENT_APPS_JSON="$APPS_JSON"

# Function to compute hash of the apps JSON
compute_apps_hash() {
echo -n "$1" | sha256sum | cut -d' ' -f1
}

# Get current apps JSON hash
CURRENT_HASH=$(compute_apps_hash "$CURRENT_APPS_JSON")

# Check if cache exists and compare
if [ -f "$CACHE_FILE" ]; then
CACHED_HASH=$(cat "$CACHE_FILE")
if [ "$CURRENT_HASH" = "$CACHED_HASH" ]; then
print_warning "App configuration unchanged. Skipping Docker image build."
print_info "To force a rebuild, delete the cache file: rm $CACHE_FILE"
exit 0
else
print_info "App configuration changed. Proceeding with Docker image build..."
fi
else
print_info "No cache found. Building Docker image..."
fi

# Fetch latest ERPNext tag from GitHub based on FRAPPE_VERSION
ERPNEXT_TAG=$(fetch_erpnext_tag) || true

if [ -z "${ERPNEXT_TAG:-}" ]; then
print_warning "Could not fetch ERPNext tag, using fallback"
ERPNEXT_TAG="v${FRAPPE_VERSION##version-}.0.0"
fi

print_info "Using ERPNext tag: $ERPNEXT_TAG"

ERPNEXT_IMAGE="frappe/erpnext:${ERPNEXT_TAG}"

print_info "ERPNEXT_IMAGE forcibly set to: $ERPNEXT_IMAGE"
# Extract image name and tag from ERPNEXT_IMAGE
BASE_IMAGE="$ERPNEXT_IMAGE"
# Extract repository and tag
IMAGE_REPO=$(echo "$ERPNEXT_IMAGE" | cut -d':' -f1)
IMAGE_TAG=$(echo "$ERPNEXT_IMAGE" | cut -d':' -f2)

# Use the same tag for custom image
CUSTOM_IMAGE_NAME="${IMAGE_REPO}"
CUSTOM_IMAGE_TAG="$IMAGE_TAG"

print_info "Base Image: $BASE_IMAGE"
print_info "Custom Image will be: $CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG"

print_info "Apps to be installed:"
echo "$APPS_JSON"

FRAPPE_BRANCH="${FRAPPE_VERSION}"

print_info "Using Frappe branch: $FRAPPE_BRANCH"
print_info "Building custom ERPNext image with Podman..."

# Build the image with Podman
podman build --network=host \
--jobs=4 \
--build-arg APPS_JSON_BASE64="$APPS_JSON" \
--build-arg FRAPPE_BRANCH="$FRAPPE_BRANCH" \
-t "$CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG" \
-f ../actions/configure-module/Dockerfile \
.

if [ $? -eq 0 ]; then
print_info "Build completed successfully!"
print_info "Image tagged as: $CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG"

# Also tag as latest
podman tag "$CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG" "$CUSTOM_IMAGE_NAME:latest"

# Tag with the same name as base image (for drop-in replacement)
podman tag "$CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG" "$BASE_IMAGE"
print_info "Also tagged as: $BASE_IMAGE (drop-in replacement)"

echo ""
print_info "To push the image to a registry:"
echo " podman push $CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG"
echo " podman push $CUSTOM_IMAGE_NAME:latest"
echo " podman push $BASE_IMAGE"

echo ""
print_info "Image size:"
podman images "$CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
ENV_FILE="environment"

# Ensure env file exists
[ -f "$ENV_FILE" ] || {
print_error "Env file not found"
exit 1
}

CUSTOM_IMAGE_FULL="$CUSTOM_IMAGE_NAME:$CUSTOM_IMAGE_TAG"

# Update or add ERPNEXT_IMAGE
if grep -q '^ERPNEXT_IMAGE=' "$ENV_FILE"; then
sed -i "s|^ERPNEXT_IMAGE=.*|ERPNEXT_IMAGE=$CUSTOM_IMAGE_FULL|" "$ENV_FILE"
else
echo "ERPNEXT_IMAGE=$CUSTOM_IMAGE_FULL" >>"$ENV_FILE"
fi

print_info "Updated ERPNEXT_IMAGE in $ENV_FILE to:"
print_info "$CUSTOM_IMAGE_FULL" print_info "To use this image, it's already tagged as: $BASE_IMAGE"
print_info "Your existing docker-compose or deployment will automatically use the custom image!"

# Update the cache with the new hash
echo "$CURRENT_HASH" >"$CACHE_FILE"
print_info "Cache updated."
else
print_error "Build failed!"
exit 1
fi
Loading
Loading