+AJGI is the shortform of ArOZ Javascript Gateway Interface.
+In simple words, you can add function to your system with JavaScript :)
+
+## Usages
+1. Put your js / agi file inside web/* (e.g. ./web/Dummy/backend/test.js)
+2. Load your script by calling / ajax request to ```/system/ajgi/interface?script={yourfile}.js```, (e.g. /system/ajgi/interface?script=Dummy/backend/test.js)
+3. Wait for the reponse from the script by calling sendResp in the script
+
+## Module Init Script
+To initialize a module without a main.go function call, you can create a "init.agi" script in your module root under ./web/myModule where "myModule" is your module name.
+
+To register the module, you can call to the "registerModule" function with JSON stringify module launch info following the following example JavaScript Object.
+You might also create the database table in this section of the code. For example:
+
+```
+//Create database for this module
+newDBTableIfNotExists("myModule")
+```
+
+## Application Examples
+See web/UnitTest/backend/*.js for more information on how to use AGI in webapps.
+
+For subservice, see subservice/demo/agi/ for more examples.
+
+
+### Access From Frontend
+To access server functions from front-end (e.g. You are building a serverless webapp on top of arozos), you can call to the ao_module.js function for running an agi script located under ```./web``` directory. You can find the ao_module.js wrapper under ```./web/script/```
+
+Here is an example extracted from Music module for listing files nearby the openeing music file.
+ var nearbyFiles = filelib.aglob(dirname + "/*") //aglob must be used here to prevent errors for non-unicode filename
+ var audioFiles = [];
+ var supportedFormats = [".mp3",".flac",".wav",".ogg",".aac",".webm",".mp4"];
+ //For each nearby files
+ for (var i =0; i < nearbyFiles.length; i++){
+ var thisFile = nearbyFiles[i];
+ var ext = thisFile.split(".").pop();
+ ext = "." + ext;
+ //Check if the file extension is in the supported extension list
+ for (var k = 0; k < supportedFormats.length; k++){
+ if (filelib.isDir(nearbyFiles[i]) == false && supportedFormats[k] == ext){
+ var fileExt = ext.substr(1);
+ var fileName = thisFile.split("/").pop();
+ var fileSize = filelib.filesize(thisFile);
+ var humanReadableFileSize = bytesToSize(fileSize);
+
+ var thisFileInfo = [];
+ thisFileInfo.push(fileName);
+ thisFileInfo.push(thisFile);
+ thisFileInfo.push(fileExt);
+ thisFileInfo.push(humanReadableFileSize);
+
+ audioFiles.push(thisFileInfo);
+ break;
+ }
+ }
+ }
+ sendJSONResp(JSON.stringify(audioFiles));
+}
+
+```
+
+### Access from Subservice Backend
+It is also possible to access the AGI gateway from subservice backend.
+You can include aroz library from ```./subservice/demo/aroz``` . The following is an example extracted from demo subservice that request access to your desktop filelist.
+
+```
+package main
+import (
+ aroz "your/package/name/aroz"
+)
+
+var handler *aroz.ArozHandler
+
+//...
+
+func main(){
+ //Put other flags here
+
+ //Start subservice pipeline and flag parsing (This function call will also do flag.parse())
+In order for the script to return something to the screen / caller as JSON / TEXT response,
+one of these functions has to be called.
+
+```
+sendResp(string) => Response header with text/plain header
+sendJSONResp(json_string) => Response request with JSON header
+```
+
+Customize header:
+
+You can also use customized header in return string as follow.
+
+```
+//Set Response header to html
+HTTP_HEADER = "text/html; charset=utf-8";
+
+//Send Response
+sendResp("<p>你好世界!</p>");
+```
+
+#### Register Module to module list
+You can call to the following function to register your module to the system module list. It is recommended that you register your module during the startup process (in the init.agi script located in your module root)
+
+Example Usage:
+```
+registerModule(JSON.stringify(moduleInfo));
+
+```
+
+Module Info defination
+```
+//DO NOT USE THIS IN CODE. THIS IS A DATATYPE REPRESENTATION ONLY
+//PLEASE SEE THE INIT SECTION FOR A REAL OBJECT EXAMPLE
+moduleInfo = {
+ Name string //Name of this module. e.g. "Audio"
+ Desc string //Description for this module
+ Group string //Group of the module, e.g. "system" / "media" etc
+ IconPath string //Module icon image path e.g. "Audio/img/function_icon.png"
+ Version string //Version of the module. Format: [0-9]*.[0-9][0-9].[0-9]
+ StartDir string //Default starting dir, e.g. "Audio/index.html"
+ SupportFW bool //Support floatWindow. If yes, floatWindow dir will be loaded
+ LaunchFWDir string //This link will be launched instead of 'StartDir' if fw mode
+ SupportEmb bool //Support embedded mode
+ LaunchEmb string //This link will be launched instead of StartDir / Fw if a file is opened with this module
+sendJSONResp(JSON.stringify({text: "Hello World")); //aka send Resp with JSON header
+
+```
+
+#### Database Related
+```
+newDBTableIfNotExists("tablename");
+dropDBTable("tablename");
+writeDBItem("tablename", "key", "value");
+readDBItem("tablename", "key");
+listDBTable("tablename"); //Return key value array
+deleteDBItem("tablename", "key");
+```
+
+#### Register and Packages
+```
+registerModule(JSON.stringify(moduleLaunchInfo)); //See moduleLaunchInfo in the sections above
+requirepkg("ffmpeg");
+execpkg("ffmpeg",'-i "files/users/TC/Desktop/群青.mp3" "files/users/TC/Desktop/群青.flac'); //ffmpeg must be required() before use
+
+```
+
+#### Structure & OOP
+```
+includes("hello world.js"); //Include another js / agi file within the current running one, return false if failed
+```
+
+### User Functions
+Users function are function group that only be usable when the interface is started from a user request.
+
+#### CONST
+```
+USERNAME
+USERICON
+USERQUOTA_TOTAL
+USERQUOTA_USED
+
+//Since AGI 1.3
+USER_VROOTS
+USER_MODULES //Might return ["*"] for admin permission
+```
+
+#### Filepath Virutalization
+```
+decodeVirtualPath("user:/Desktop"); //Convert virtual path (e.g. user:/Desktop) to real path (e.g. ./files/user/username/Desktop)
+decodeAbsoluteVirtualPath("user:/Desktop"); //Same as decodeVirtualPath but return in absolute path instead of relative path from the arozos binary root
+encodeRealPath("files/users/User/Desktop"); //Convert realpath into virtual path
+```
+
+#### Permission Related
+```
+getUserPermissionGroup();
+userIsAdmin(); => Return true / false
+```
+
+#### User Creation, Edit and Removal
+All the command in this section require administrator permission. To check if user is admin, use ``` userIsAdmin() ```.
+
+```
+userExists(username);
+createUser(username, password, defaultGroup); //defaultGroup must be one of the permission group that exists in the system
+removeUser(username); //Return true if success, false if failed
+```
+
+#### Library requirement
+You can request other library to be loaded and have extra functions to work with files / images.
+```
+requirelib("filelib");
+```
+
+### filelib
+filelib is the core library for users to interact with the local filesystem.
+
+To use any of the library, the agi script must call the requirelib before calling any filelib functions. Example as follows.
+http.head("http://localhost:8080/", "Content-Type"); //Get the header field "Content-Type" from the requested url, leave 2nd paramter empty to return the whole header in JSON string
+http.download("http://example.com/music.mp3", "user:/Desktop", "(Optional) My Music.mp3")
+
+```
+
+### websocket
+
+websocket library provide request upgrade from normal HTTP request to WebSocket connections.
+
+```
+//Include the library
+requirelib("websocket");
+```
+
+#### websocket functions
+
+```
+websocket.upgrade(10); //Timeout value in integer, return false if failed
+# Quick Notes for Setting Up Raspberry Pi 4 as WiFi Router
+
+This is just a quick notes for myself on how to setup a Raspberry Pi 4 with Mercury AC650M USB WiFi Adapter
+
+### Problem
+
+The current setup of the system make use of a ARGON ONE metal case which, will make the build in WiFi adapter really hard to use as an AP. Hence, we need to setup an external WiFi adapter for this purpose.
+
+### Required Parts
+
+- Mercury USB WiFi Adapter AC650M (Dual baud 5G no driver version)
+To enable systemd in your host that support aroz online system, create a bash script at your aroz online root named "start.sh"
+and fill it up with your prefered startup paratmers. The most basic one is as follow:
+
+```
+#/bin/bash
+sudo ./aroz_online_linux_amd64
+
+```
+
+And then you can create a new file called "aroz-online.service" in /etc/systemd/system with the following contents (Assume your aroz online root is at /home/pi/aroz_online)
+# Quick Notes for Setting Up Raspberry Pi 4 as WiFi Router
+
+This is just a quick notes for myself on how to setup a Raspberry Pi 4 with Mercury AC650M USB WiFi Adapter
+
+### Problem
+
+The current setup of the system make use of a ARGON ONE metal case which, will make the build in WiFi adapter really hard to use as an AP. Hence, we need to setup an external WiFi adapter for this purpose.
+
+### Required Parts
+
+- Mercury USB WiFi Adapter AC650M (Dual baud 5G no driver version)
+ log.Println("This file operation is not supported on WebSocket file operations endpoint. Please use the legacy endpoint instead. Received: ", operation)
+ w.WriteHeader(http.StatusInternalServerError)
+ w.Write([]byte("500 - Not supported operation"))
+var packageManager *apt.AptPackageManager //Manager for package auto installation
+var subserviceBasePort = 12810 //Next subservice port
+
+// =========== SYSTEM BUILD INFORMATION ==============
+var build_version = "development" //System build flag, this can be either {development / production / stable}
+var internal_version = "0.1.111" //Internal build version, please follow git commit counter for setting this value. max value \[0-9].[0-9][0-9].[0-9][0-9][0-9]\
+var deviceUUID string //The device uuid of this host
+var deviceVendor = "IMUSLAB.INC" //Vendor of the system
+var deviceVendorURL = "http://imuslab.com" //Vendor contact information
+var deviceModel = "AR100" //Hardware Model of the system
+var deviceModelDesc = "General Purpose Cloud Platform" //Device Model Description
+var show_version = flag.Bool("version", false, "Show system build version")
+var host_name = flag.String("hostname", "My ArOZ", "Default name for this host")
+var system_uuid = flag.String("uuid", "", "System UUID for clustering and distributed computing. Only need to config once for first time startup. Leave empty for auto generation.")
+var allow_upnp = flag.Bool("allow_upnp", false, "Enable uPNP service, recommended for host under NAT router")
+var allow_ssdp = flag.Bool("allow_ssdp", true, "Enable SSDP service, disable this if you do not want your device to be scanned by Windows's Network Neighborhood Page")
+var allow_mdns = flag.Bool("allow_mdns", true, "Enable MDNS service. Allow device to be scanned by nearby ArOZ Hosts")
+var disable_ip_resolve_services = flag.Bool("disable_ip_resolver", false, "Disable IP resolving if the system is running under reverse proxy environment")
+
+//Flags related to Security
+var use_tls = flag.Bool("tls", false, "Enable TLS on HTTP serving")
+var session_key = flag.String("session_key", "", "Session key, must be 16, 24 or 32 bytes long (AES-128, AES-192 or AES-256). Leave empty for auto generated.")
+var wpa_supplicant_path = flag.String("wpa_supplicant_config", "/etc/wpa_supplicant/wpa_supplicant.conf", "Path for the wpa_supplicant config")
+var wan_interface_name = flag.String("wlan_interface_name", "wlan0", "The default wireless interface for connecting to an AP")
+
+//Flags related to files and uploads
+var max_upload = flag.Int("max_upload_size", 8192, "Maxmium upload size in MB. Must not exceed the available ram on your system")
+var upload_buf = flag.Int("upload_buf", 25, "Upload buffer memory in MB. Any file larger than this size will be buffered to disk (slower).")
+var storage_config_file = flag.String("storage_config", "./system/storage.json", "File location of the storage config file")
+var tmp_directory = flag.String("tmp", "./", "Temporary storage, can be access via tmp:/. A tmp/ folder will be created in this path. Recommend fast storage devices like SSD")
+var enable_console = flag.Bool("console", false, "Enable the debugging console.")
+
+//Flags related to running on Cloud Environment or public domain
+var allow_public_registry = flag.Bool("public_reg", false, "Enable public register interface for account creation")
+var allow_autologin = flag.Bool("allow_autologin", true, "Allow RESTFUL login redirection that allow machines like billboards to login to the system on boot")
+var demo_mode = flag.Bool("demo_mode", false, "Run the system in demo mode. All directories and database are read only.")
+var allow_package_autoInstall = flag.Bool("allow_pkg_install", true, "Allow the system to install package using Advanced Package Tool (aka apt or apt-get)")
+var allow_homepage = flag.Bool("enable_homepage", false, "Redirect not logged in users to home page instead of login interface")
+
+//Scheduling and System Service Related
+var nightlyTaskRunTime = flag.Int("ntt", 3, "Nightly tasks execution time. Default 3 = 3 am in the morning")
+var maxTempFileKeepTime = flag.Int("tmp_time", 86400, "Time before tmp file will be deleted in seconds. Default 86400 seconds = 24 hours")
+ fmt.Println("Developed by tobychui and other co-developers, Licensed to " + deviceVendor)
+ //fmt.Println("THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.")
+ os.Exit(0)
+ }
+
+ //Handle flag assignments
+ max_upload_size = int64(*max_upload) << 20 //Parse the max upload size
+ if *demo_mode { //Disable hardware man under demo mode
+ log.Println("Error when trying to serve file in compatibility mode", err.Error())
+ return "", errors.New("Error when trying to serve file in compatibility mode")
+ }
+ if fileExists(possibleRealpath) {
+ realFilepath = possibleRealpath
+ log.Println("[Media Server] Serving file " + filepath.Base(possibleRealpath) + " in compatibility mode. Do not to use '&' or '+' sign in filename! ")
+ return realFilepath, nil
+ } else {
+ return "", errors.New("File not exists")
+ }
+ }
+
+ return realFilepath, nil
+}
+
+func serveMediaMime(w http.ResponseWriter, r *http.Request) {
+ This is not actually a crontab but something similar that provide
+ timered operations for executing commands in agi or bash in an interval
+ bases
+
+*/
+
+type Job struct {
+ Name string //The name of this job
+ Creator string //The creator of this job. When execute, this user permission will be used
+ Description string //Job description, can be empty
+ Admin bool //If the creator has admin permission during the creation of this job. If this doesn't match with the runtime instance, this job wille be skipped
+ ExecutionInterval int64 //Execuation interval in seconds
+ BaseTime int64 //Exeuction basetime. The next interval is calculated using (current time - base time ) % execution interval
+ ScriptFile string //The script file being called. Can be an agi script (.agi / .js) or shell script (.bat or .sh)