Application Development
Application development on UGOS Pro system mainly includes the following steps:
- Develop and compile the application backend service
- Develop the application frontend interface using web frontend technologies
- Use the developer tool
ugclito create an application project - Write the application configuration file
project.yaml - Copy the backend service executable and frontend files to the corresponding directories in the application project
- Create the application icon and copy it to the corresponding directory in the application project
- Use the developer tool
ugclito package the application
The following guide walks you through creating a simple example application from scratch. You will learn the basic development workflow and how to use the ugcli tool.
Develop the Backend Service
Currently, UGOS Pro application backend services must be developed using compiled languages like C/C++, or Go, and built into executables that can run directly on Linux.
For this example, we will create a simple backend service in Go that provides a basic HTTP heartbeat API.
Create the backend service directory backend and write the main.go file:
Click to view code
package main
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"time"
)
// HeartbeatResponse defines the heartbeat response structure
type HeartbeatResponse struct {
Timestamp string `json:"timestamp"`
Status string `json:"status"`
}
// heartbeatHandler handles heartbeat requests
func heartbeatHandler(w http.ResponseWriter, r *http.Request) {
// Only allow GET method
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Set response headers
w.Header().Set("Content-Type", "application/json")
// Create response data
response := HeartbeatResponse{
Timestamp: time.Now().Format(time.DateTime),
Status: "healthy",
}
// Encode JSON response
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
return
}
}
func main() {
// Define command line arguments
port := flag.Int("port", 21010, "Port to listen on")
flag.Parse()
// Register routes
http.HandleFunc("/api/heartbeat", heartbeatHandler)
// Build listen address
addr := fmt.Sprintf(":%d", *port)
// Start server
fmt.Printf("Server starting on %s\n", addr)
if err := http.ListenAndServe(addr, nil); err != nil {
fmt.Printf("Server failed to start: %v\n", err)
}
}Run go build -o myapp_serv to compile and generate the backend service executable myapp_serv.
The final directory structure of backend is as follows:
backend/
├── go.mod
├── main.go
└── myapp_servDevelop the Frontend Page
You can freely choose a frontend framework such as Vue, React, etc., to develop application's interface. After development is complete, you need to place the build artifacts (HTML, CSS, JS, etc.) in the www directory of the application package.
The frontend page of this example application will be developed using HTML + CSS + JavaScript, implementing a simple user interface to display the heartbeat status of the backend service.
Create the www/index.html file with the following content:
Click to view code
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MyApp</title>
<link rel="stylesheet" href="./css/style.css">
</head>
<body>
<div class="container">
<h1>MyApp Status Monitor</h1>
<div class="auto-start">Monitoring starts automatically after page loads...</div>
<div id="status" class="status disconnected">
<div class="status-indicator"></div>
<span>Initializing...</span>
</div>
<div class="response-display" id="responseDisplay">
Monitoring data will be displayed here...
</div>
</div>
<script src="./js/app.js"></script>
</body>
</html>Create the www/js/app.js file with the following content:
Click to view code
let heartbeatInterval = null;
let requestCount = 0;
let isAutoStarted = false;
let maxEntries = 2;
// Update status display
function updateStatus(connected, message) {
const statusElement = document.getElementById('status');
const statusText = statusElement.querySelector('span');
if (connected) {
statusElement.className = 'status connected';
statusText.textContent = message || 'Connection OK';
} else {
statusElement.className = 'status disconnected';
statusText.textContent = message || 'Connection Lost';
}
}
// Format time display
function formatTime(dateString) {
const date = new Date(dateString);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
// Send heartbeat request
async function sendHeartbeat() {
const apiUrl = "/api/heartbeat";
const responseDisplay = document.getElementById('responseDisplay');
requestCount++;
try {
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
const data = await response.json();
// Update response display
const newEntry = `Request #${requestCount} - ${new Date().toLocaleTimeString()}\n` +
JSON.stringify(data, null, 2) + '\n\n';
// Keep response display area concise, show only recent records
const currentContent = responseDisplay.textContent;
const entries = currentContent.split('\n\n').filter(entry => entry.trim());
entries.unshift(newEntry.trim());
// Keep only the most recent records
if (entries.length > maxEntries) {
entries.length = maxEntries;
}
responseDisplay.textContent = entries.join('\n\n');
updateStatus(true, `Monitoring - Last update: ${new Date().toLocaleTimeString()}`);
} catch (error) {
// Update response display
const newEntry = `Request #${requestCount} - ${new Date().toLocaleTimeString()}\n` +
`Error: ${error.message}\n\n`;
const currentContent = responseDisplay.textContent;
const entries = currentContent.split('\n\n').filter(entry => entry.trim());
entries.unshift(newEntry.trim());
if (entries.length > maxEntries) {
entries.length = maxEntries;
}
responseDisplay.textContent = entries.join('\n\n');
updateStatus(false, `Connection error: ${error.message}`);
}
}
// Start heartbeat test
function startHeartbeat() {
if (heartbeatInterval) {
clearInterval(heartbeatInterval);
}
// Send a request immediately
sendHeartbeat();
// Set timer to send request every second
heartbeatInterval = setInterval(sendHeartbeat, 1000);
updateStatus(true, 'Monitoring started...');
}
// Stop heartbeat test
function stopHeartbeat() {
if (heartbeatInterval) {
clearInterval(heartbeatInterval);
heartbeatInterval = null;
}
updateStatus(false, 'Monitoring paused');
}
// Automatically start monitoring after page loads
document.addEventListener('DOMContentLoaded', function () {
// Auto start monitoring
setTimeout(() => {
startHeartbeat();
isAutoStarted = true;
}, 10);
});Create the www/css/style.css file with the following content:
Click to view code
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.status {
display: flex;
align-items: center;
margin-bottom: 20px;
padding: 10px;
border-radius: 4px;
}
.status.connected {
background-color: #d4edda;
color: #155724;
}
.status.disconnected {
background-color: #f8d7da;
color: #721c24;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 10px;
}
.status.connected .status-indicator {
background-color: #28a745;
}
.status.disconnected .status-indicator {
background-color: #dc3545;
}
.response-display {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
padding: 15px;
margin-top: 20px;
font-family: 'Courier New', monospace;
white-space: pre-wrap;
min-height: 100px;
max-height: 300px;
overflow-y: auto;
}
.timestamp {
font-size: 1.2em;
font-weight: bold;
color: #007bff;
text-align: center;
margin: 20px 0;
}
.controls {
text-align: center;
margin: 20px 0;
}
button {
padding: 10px 20px;
margin: 0 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.start-btn {
background-color: #007bff;
color: white;
}
.stop-btn {
background-color: #dc3545;
color: white;
}
.config {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"] {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.auto-start {
text-align: center;
color: #666;
font-style: italic;
margin-bottom: 10px;
}The final directory structure of www is as follows:
www
├── css
│ └── style.css
├── index.html
└── js
└── app.jsBrowser Cache Handling for css/js Files
The system web server sets Cache-Control headers when responding to css/js files to allow browser caching. To prevent browsers from still using old versions of files after a new application version is released, it is recommended to use one of the following methods:
- Filename hashing, e.g.,
app.a1b2c3d4.js, change the filename when modifying file content - Query parameter with version number, e.g.,
app.js?v=1.0.1, update the version number when releasing a new version
Create the Application Project
Run ugcli create com.mycompany.myapp to create an application project. After creation, a com.mycompany.myapp directory will be generated in the current directory with the following structure:
com.mycompany.myapp
├── project.yaml # Application configuration file
├── rootfs_amd64 # Application files for x86_64 architecture
│ └── bin
├── rootfs_arm64 # Application files for arm64 architecture
│ └── bin
├── rootfs_common # Common application files for both architectures
│ ├── icon.png # Application icon
│ └── www # Application frontend filesWrite the Application Configuration File
Modify the project.yaml file in the application project root directory to configure the basic information of the application. The main configuration items are as follows:
spec_version: "2.1" # Specification version number
app_id: com.mycompany.myapp # Unique application identifier
version: 0.1.0 # Application version number
support_arch: # Supported CPU architectures
- amd64
- arm64
start_cmd: bin/myapp_serv --port=21010 # Application backend service startup command
port: 21010 # Application backend HTTP service port
proxy_path: api # Application backend HTTP service proxy path
open_type: inner # Application frontend page open type
tag_types: # Application tags
- utility
- devtool
depend_fw_version: 1.13.0.0000
i18n: # Internationalization configuration
en-US: # Language name
name: My APP # Application display name
description: My APP Desc # Application description
author: My Company # Application developer
official: https://myapp.example.com # Application official website link
help: https://myapp.example.com/help # Application help page link
publisher: My Company # Application publisher
publisher_link: https://mycompany.example.com # Application publisher website link
zh-CN:
name: 演示应用
description: 演示应用描述
author: 演示应用开发者
official: https://myapp.example.com.cn
help: https://myapp.example.com.cn/help
publisher: 演示应用发布者
publisher_link: https://mycompany.example.com.cnCopy Application Files
Copy the application backend service executable and frontend files to the corresponding directories in the application project.
- Copy the backend service executables for both architectures to the corresponding rootfs directories, e.g., copy the amd64 architecture executable to
rootfs_amd64/bin/myapp_serv - Copy all files from the frontend www directory to the
rootfs_common/wwwdirectory.
Create the Application Icon
Please refer to the application icon design guidelines to create the application icon and replace the rootfs_common/www/icon.png file. (This step can be skipped during development, using the default icon)
Icon Design Guidelines:
- Icon size: 256*256 pixels
- Icon format: PNG
- File size: Less than 100KB
- Icon background: White or light background with 0.5 thickness 8% black stroke
- Icon shape: Square, requires smooth rounded corner template
Package the Application
Execute the ugcli pack command in the application project root directory to package the application and generate the upk installation package file.
Command example: ugcli pack --build 1, where the --build parameter is the build number, which will be combined with the version number in project.yaml to form the final application version number. For example, if the version number in project.yaml is 0.1.0, the final version number will be 0.1.0.1.
The final upk file naming format: {amd64/arm64}_{appid}_{version}.upk.