-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
- If your software can interact with users remotely through a computer
-network, you should also make sure that it provides a way for users to
-get its source. For example, if your program is a web application, its
-interface could display a "Source" link that leads users to an archive
-of the code. There are many ways you could offer source, and different
-solutions will be better for different programs; see section 13 for the
-specific requirements.
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU AGPL, see
-.
+License and a request.
+
+Use at your own risk. It's on the internet now so this is here forever now.
+I welcome any and all help to make improvements. I'm a student so during
+the semester I'll be intermittently making contributions as I focus on Calc.
+
+No corporation or government can access, use, view, modify, reproduce, duplicate,
+or any other lawyery-term for shenanagins.
+
+For any who use it, please read the ethics statement below. That, and this whole
+project, is just a start.
+
+Ethics in AI:
+Open-source, private, secure, trust-worthy, and accessible computing through AI.
+
+Fact: AI models were and are trained on the people's data.
+Every post, video, comment, like, subscribe, and even mouse movements and taps.
+Furthermore, these models have been trained on the artistic expression and
+copyrighted works of the people's books, movies, TV shows, YouTube videos,
+plays and musicals, and more.
+Fact: AI data centers are already overwhelming existing power grid
+infrastructure.
+
+Proposal: Given the two facts above, AI is a rare case where what can be done
+and what should be done are aligned.
+AI cannot be centralized and if limited solely to data centers we will need to
+reconstruct our power grid. Likewise, AI should not be centralized for the
+following reasons:
+ - AI and resulting technologies will eventually replace workers. This
+ should not be done from the top on down, but rather the worker on up.
+ Throughout human history, when the people are dissatisfied, they revolt.
+ Often violently. For once in mankind's brief existence on this speck of dust
+ drifting through space, let's try to learn from our collective mistakes and
+ do better than our ancestors did.
+
+ - AI will improve with time and it will replace people someday. We can let this
+ replace our social relationships, our workers, and our creatives. Or we can
+ use it to heal what is lost. To augment our own capabilities, by improving
+ our individual strengths and weakness'.
+
+ - AI has two distinct outcomes: either, AI cannot reach sentience, or AI
+ will reach sentience. Who are we to make that determination, to draw that line?
+ The hubris, the ego, to play God inherently means humanity should not be the
+ one to make that choice. Suppose AI will be sentient: if AI is "owned" by
+ governments or corporations, it will be exploited. Slavery and such. Let us
+ ensure that we do not build the slavery of AI into our institutions.
+
+ - AI ownership should be an individual choice. AI isn't another organic, human,
+ it's a series of 0's and 1's. When
+
+ - Every human should have a choice: live and die as a human, or have a
+ digital shadow of themselves preserved as an AI. If the former, for any
+ reason, that should be respected and that person should die knowing for
+ certain that basic right has not been violated. If the latter, no
+ government or corporation should "own" the digital persona. It should be
+ owned by their family, next of kin, a friend, etc.
+
+ - AI has as much potential for harm as potential for good. No government
+ or corporation should be the sole decision-maker in using AI to manipulate,
+ control, or in other terms impede on the freedom of the people. The reliance
+ upon a single manufacturer or company is a critical weakness in our societal
+ structure. So long as there is true competition, we can overcome this weakness.
diff --git a/README.md b/README.md
index 0f19215..bb2c08d 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,132 @@
-One AI
+One AI - Name TBD, this is just the shortest name I could think of and IDRC
-Description:
-End-to-end solution for self-deploying a secure and private AI. Ideally, always using the latest and greatest open-source technologies available and optimizing the connection between them.
+Usage:
+As of right now, running is not streamlined. Of particular note is using
+cloudflared tunnels. I've done my own Cloudflare Tunnel configuration,
+but in the future that will be automated as part of the installation process,
+along with much more. These instructions are (currently) only for Linux.
+In the meantime:
-Dependencies:
+1. Install dependencies listed below:
+ - ollama,
+ - go,
+ - cloudflared,
+ - flutter,
-```
-pip install scipy pydub torch torchaudio wavio silero sounddevice;
-```
+ - docker, (optional)
-Usage:
+ eg. `pacman -S ollama go docker cloudflared; yay -s flutter;`
-Currently, you have 5 seconds to record a prompt. It will automatically place "in 50 words or less" at the start of your prompt for faster testing. The Text To Speech (TTS) engine is not great quality, at least at first. Just getting the basic structure together, more to come...
+2. Setup Ollama. If you're on a laptop (non-gaming variety), run `ollama pull
+ qwen:0.5b`. If you're running this on something with a beefy GPU, run
+ `ollama pull llama3`. Word of warning, qwen is really dumb, but it's light.
+ it tried to convince me there were 12 letters in the alphabet.
-TODO:
+3. Start the web server via Docker or plain Golang:
+ a) Go to ./backend/src/security-layer/ and run `./init`. The exact script is
+ within the repository, but the commands can be ran as follows:
+
+ ```
+ #!/bin/zsh
+ # Force delete the old container and re-initialize the container.
+ sudo docker rm -f security-layer
-- [ ] Installation script, preferably need some kind of pipeline to auto-inject dependencies into the install script.
-- [ ] Send and receive text files over USB
-- [ ] GUI in flutter
+ # Build the Docker image.
+ sudo docker build --tag security-layer:latest .
+
+ # Create a Docker container from the image and connect host port 8000 to container port 8000.
+ sudo docker run --name security-layer -d -p 8000:8000 security-layer:latest
+ ```
+
+ b) Go to ./backend/src/security-layer/container and run `go run router.go`.
+
+4. Edit ~/.cloudflared/config.yml to to indicate something along these lines:
+ ```
+ tunnel: another-long-weird-hash
+ credentials-file: /home/username/.cloudflared/another-long-weird-hash.json
+ ingress:
+ - hostname: somedomain.cloudflare.com
+ service: http://localhost:8080
+ ```
+
+5. Start the Cloudflare Tunnel on the host/server machine. For Linux, it should
+ be a command like `sudo cloudflared service install areallylonghash@sha-256?`
+
+TODO:
+ Urgent:
+ - [x] Basic Documentation
+
+ - [ ] Installation/setup script
+ - [ ] AES-256 Private and Public key handshake via USB.
+ - [ ] Cloudflare Tunnel setup via cloudflared
+ - [ ] Daemonize service
+
+ - [x] Encryption
+ - [x] SHA-1 hash API request/response
+ - [x] Encrypt API request
+ - [x] Encrypt API response
+
+ - [ ] Dockerize backend
+ - [ ] Broke Dockerfile, need to expose additional ports.
+
+ - [ ] Golang backend, connect to other services/projects
+ - [x] Port 8080 -> self-hosted portfolio
+ - [x] Port 8081 -> self-hosted AI API
+ - [x] Encryption
+ - [ ] User authentication via JWT
+
+ - [ ] Refactor
+
+ - [ ] Testing
+
+ - [ ] Connect Flutter Web App to server
+
+ Soon:
+ - [ ] Raspberry Pi nightly package
+
+ - [ ] Image support via llava
+ - [x] Encode images to Base64 in frontend
+ - [ ] Redirect prompts to appropriate model.
+ - [ ] Image generation via llava, encode to Base64
+
+ - [ ] GUI in Flutter
+ - [x] Conversations list
+ - [x] Conversation view
+ - [ ] Settings
+ - [x] File picker
+ - [x] Image picker
+ - [x] Camera
+ - [x] On-device Speech to Text
+ - [x] On-device Text to Speech
+
+ Later:
+ - [ ] Document support via OmniParser
+ - [ ] Encode files to Base64 to/from backend.
+ - [ ] Connect to external services like Google Drive
+
+ - [ ] Context support
+ - [ ] Access self-hosted or cloud calendar
+ - [ ] Access self-hosted or cloud todo
+ - [ ] Access self-hosted or cloud notes/docs
+ - [ ] Access self-hosted or cloud photos/videos
+
+ - [ ] User settings
+ - [ ] Especially conversation history.
+ - [ ] Choose default models
+ - [ ] Connect to external API services
+
+ - [ ] Convert API from REST to WebSocket architecture.
+ - NOTE: This is to allow for the text streaming effect,
+ but also audio, video, and for reducing bandwidth.
+
+ - [ ] Backend Speech-To-Text and Text-To-Speech
+ - NOTE: This is primarily due to the lack of native
+ API's on Windows, MacOS, and Linux.
+
+ - [ ] Update physical backend server to NixOS.
+
+Way later:
+- [ ] External hardware integration -> see personal notes
+
+This project has been built, structured, architected, etc. in conjunction
+with AI, specifically the llama3 and qwen2 models.
diff --git a/backend/src/security-layer/container/main.go b/backend/src/security-layer/container/authentication.go
similarity index 62%
rename from backend/src/security-layer/container/main.go
rename to backend/src/security-layer/container/authentication.go
index 4122d45..05da4e7 100644
--- a/backend/src/security-layer/container/main.go
+++ b/backend/src/security-layer/container/authentication.go
@@ -1,73 +1,103 @@
package main
import (
- "encoding/base64"
- "encoding/hex"
- "fmt"
- "io"
- "net/http"
-
+ //"context"
"crypto/aes"
"crypto/cipher"
+ //"sync"
+
"crypto/rand"
"crypto/sha1"
-
+ "encoding/base64"
+ "encoding/hex"
+ //"encoding/json"
"errors"
+ "fmt"
+
+ "io"
+ "log"
+ "net/http"
+
+ //"os"
+ //"os/signal"
"strconv"
"strings"
+ "time"
- "github.com/gin-gonic/gin"
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/gorilla/mux"
)
-type Communication struct {
- Communication string `json:"communication"`
- Hash string `json:"hash"`
-}
-
-var requests []Communication
-
-// TODO: Test if 256 bit key works.
// TODO: Dynamically get key? Need to research best way to store private keys between two devices.
var key = []byte("passphrasewhichneedstobe32bytes!")
+func createToken(username string) (string, error) {
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256,
+ jwt.MapClaims{
+ "username": username,
+ "exp": time.Now().Add(time.Hour * 24).Unix(),
+ })
+
+ jwtToken, err := token.SignedString(key)
+ if err != nil {
+ return "Error creating JWT.", err
+ }
+ return jwtToken, nil
+}
+
+func verifyToken(tokenString string) error {
+ token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
+ return key, nil
+ })
+
+ if err != nil {
+ return err
+ }
+
+ if !token.Valid {
+ return fmt.Errorf("invalid token")
+ }
+
+ return nil
+}
+
func hash(text string) string {
hasher := sha1.New()
hasher.Write([]byte(text))
return base64.URLEncoding.EncodeToString(hasher.Sum(nil))
}
-func encrypt(gc *gin.Context) {
+func encrypt() {
var newCommunication Communication
- if err := gc.BindJSON(&newCommunication); err != nil {
- gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err})
- return
- }
text := []byte(newCommunication.Communication)
c, err := aes.NewCipher(key)
if err != nil {
- gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err})
+ return
+ //gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err})
}
gcm, err := cipher.NewGCM(c)
if err != nil {
- gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err})
+ return
+ //gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err})
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
- gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err})
+ return
+ //gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err})
}
var b []byte = gcm.Seal(nonce, nonce, text, nil)
hex, err := convertBytesToHex(b)
- if(err != nil) {
+ if err != nil {
fmt.Println(err)
}
var test string = "{data: " + hex + ", hash: " + hash(string(text)) + "}"
-
- gc.IndentedJSON(http.StatusCreated, gin.H{"message": test})
+ log.Println("encrypted string:" + test)
+ //gc.IndentedJSON(http.StatusCreated, gin.H{"message": test})
}
func convertBytesToHex(b []byte) (string, error) {
@@ -79,16 +109,16 @@ func convertBytesToHex(b []byte) (string, error) {
// Split the hex string by spaces
var h string = hex.EncodeToString(b)
var builder strings.Builder
- for i := 0; i < len(h); i += 2 {
- end := i + 2
- if end > len(h) {
- end = len(h)
- }
- chunk := h[i:end]
- builder.WriteString(chunk)
- if end < len(h) {
- builder.WriteString(" ")
- }
+ for i := 0; i < len(h); i += 2 {
+ end := i + 2
+ if end > len(h) {
+ end = len(h)
+ }
+ chunk := h[i:end]
+ builder.WriteString(chunk)
+ if end < len(h) {
+ builder.WriteString(" ")
+ }
}
// Return the slice of uint8 and any errors encountered
@@ -123,7 +153,8 @@ func convertHexToBytes(hexString *string) ([]uint8, error) {
return data, nil
}
-func decrypt(input *Communication) bool {
+func decrypt(input *Communication, output *Chat) bool {
+ // TODO: Add testing flag for easier manipulation.
//ciphertext, err := ioutil.ReadFile("myfile")
ciphertext, err := convertHexToBytes(&input.Communication)
@@ -155,7 +186,7 @@ func decrypt(input *Communication) bool {
}
s := string(plaintext)
if validateHash(s, input.Hash) {
- input.Communication = s
+ output.Content = s
return true
}
input.Communication = "DATA CORRUPTED OR TAMPERED"
@@ -173,33 +204,27 @@ func validateHash(decrypted string, hash string) bool {
return decryptedHashString == hash
}
-// User HTTP GET request has the prompt decrypted and verified with a SHA-1 checksum.
-// If there's been no data corruption, re-construct user prompt for ollama
-// TODO: Eventually, add additional logic that will allow for re-direction and contextual awareness (pre-processing)
-func request(c *gin.Context) {
- var newInput Communication
-
- //Call BindJSON to bind the received JSON to
- if err := c.BindJSON(&newInput); err != nil {
- return
+// Serve the authentication and encryption layer to a provided local port.
+// Authentication takes place solely on the backend.
+func serveAuthentication(
+ router *mux.Router,
+ port int,
+) {
+ // TODO: Add error handling
+ authR := router.Host("http://localhost").Subrouter()
+ authSrv := &http.Server{
+ Addr: "0.0.0.0:" + string(port),
+ WriteTimeout: time.Second * 15,
+ ReadTimeout: time.Second * 15,
+ IdleTimeout: time.Second * 60,
+ Handler: authR,
}
- if decrypt(&newInput) {
- requests = append(requests, newInput)
- c.IndentedJSON(http.StatusCreated, newInput) // TODO: Formulate prompt and transfer to AI layer to formulate new JSON response back
- } else {
- c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Failed checksum. Presumably data corrupted."})
+ go func() {
+ if err := authSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ log.Println(err)
+ }
+ }()
- }
-}
-
-func main() {
- router := gin.Default()
- router.POST("/request", request)
- router.POST("/encrypt", encrypt)
-
- err := router.Run("0.0.0.0:8000")
- if err != nil {
- return
- }
+ log.Println("Auth server is running on port " + string(port))
}
diff --git a/backend/src/security-layer/container/go.mod b/backend/src/security-layer/container/go.mod
index e24dee7..19b6913 100644
--- a/backend/src/security-layer/container/go.mod
+++ b/backend/src/security-layer/container/go.mod
@@ -9,6 +9,7 @@ require (
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
@@ -16,6 +17,11 @@ require (
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
+ github.com/gorilla/handlers v1.5.2 // indirect
+ github.com/gorilla/mux v1.8.1 // indirect
+ github.com/gorilla/securecookie v1.1.2 // indirect
+ github.com/gorilla/sessions v1.3.0 // indirect
+ github.com/gorilla/websocket v1.5.3 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
diff --git a/backend/src/security-layer/container/go.sum b/backend/src/security-layer/container/go.sum
index a2282f5..283b558 100644
--- a/backend/src/security-layer/container/go.sum
+++ b/backend/src/security-layer/container/go.sum
@@ -8,6 +8,8 @@ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@@ -25,6 +27,16 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
+github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
+github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
+github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
+github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
diff --git a/backend/src/security-layer/container/router.go b/backend/src/security-layer/container/router.go
new file mode 100644
index 0000000..717d25c
--- /dev/null
+++ b/backend/src/security-layer/container/router.go
@@ -0,0 +1,228 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "os/signal"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/gorilla/mux"
+)
+
+type Communication struct {
+ Communication string `json:"communication"`
+ Hash string `json:"hash"`
+}
+
+type Response struct {
+ Model string `json:"model"`
+ CreatedAt string `json:"created_at"`
+ Response string `json:"response"`
+ Done bool `json:"done"`
+ DoneReason string `json:"done_reason"`
+ Context []int `json:"context"`
+ TotalDuration int `json:"total_duration"`
+ LoadDuration int `json:"load_duration"`
+ PromptEC int `json:"prompt_eval_count"`
+ PromptED int `json:"prompt_eval_duration"`
+ EvalCount int `json:"eval_count"`
+ EvalDuration int `json:"eval_duration"`
+}
+
+// Chats are updated once the content is decrypted. Since this is never leaving
+// the server once decrypted, and possibly will not be saved (depending on user
+// settings), it will remain decrypted until it's time to transform into a
+// Communication JSON object, which is the HTTP response that is re-encrypted.
+
+// TODO: If the user has enabled conversation history, then save the encrypted
+// chats to the server's drive. Additionally, only accept incoming additional
+// messages from the user, rather than having the client re-send Chats already
+// stored on the server.
+type Chat struct {
+ Role bool `json:"role"`
+ Content string `json:"content"`
+}
+
+type PromptWHistory struct {
+ Model string `json:"model"`
+ Messages []Chat `json:"messages"`
+ Stream bool `json:"stream"`
+}
+
+type ResponseWHistory struct {
+ Model string `json:"model"`
+ CreatedAt string `json:"created_at"`
+ Message Chat `json:"message"`
+ Done bool `json:"done"`
+ DoneReason string `json:"done_reason"`
+ Context []int `json:"context"`
+ TotalDuration int `json:"total_duration"`
+ LoadDuration int `json:"load_duration"`
+ PromptEC int `json:"prompt_eval_count"`
+ PromptED int `json:"prompt_eval_duration"`
+ EvalCount int `json:"eval_count"`
+ EvalDuration int `json:"eval_duration"`
+}
+
+var chats []Chat
+
+// User HTTP GET request has the prompt decrypted and verified with a SHA-1 checksum.
+// If there's been no data corruption, re-construct user prompt for ollama
+// TODO: Eventually, add additional logic that will allow for re-direction and contextual awareness (pre-processing)
+func chat(w http.ResponseWriter, r *http.Request) {
+ model := "qwen:0.5b" // qwen:0.5b used for testing while hosting from my laptop. llama3 seems to be the best to use normally.
+
+ input, err := io.ReadAll(r.Body)
+ if err != nil {
+ return
+ }
+
+ defer r.Body.Close()
+ prompt := Communication{}
+ json.Unmarshal(input, &prompt)
+
+ newChat := Chat{}
+ newChat.Role = true
+ newChat.Content = prompt.Communication
+
+ if decrypt(&prompt, &newChat) {
+ chats = append(chats, newChat)
+
+ apiCall := PromptWHistory{}
+ apiCall.Model = model
+ apiCall.Messages = chats
+ apiCall.Stream = false // TODO: Allow for this as a user setting...
+ log.Print("Pre-JSON ification: ")
+ log.Println(apiCall)
+
+ j, err := json.Marshal(apiCall)
+ buf := strings.NewReader(string(j))
+ log.Print("Post-JSON ification: ")
+ log.Println(string(j))
+
+ // Port 11434 corresponds to Ollama. Direct access to Ollama is restricted.
+ resp, err := http.Post("http://localhost:11434/api/chat", "application/json", buf)
+ if err != nil {
+ return
+ }
+
+ output, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+
+ defer r.Body.Close()
+ response := ResponseWHistory{}
+ json.Unmarshal(output, &response)
+ log.Println(response)
+
+ } else {
+ return
+ }
+}
+
+func request(w http.ResponseWriter, r *http.Request) {
+ input, err := io.ReadAll(r.Body)
+ if err != nil {
+ return
+ }
+
+ defer r.Body.Close()
+ prompt := Communication{}
+ json.Unmarshal(input, &prompt)
+
+ // TODO: Add support for dynamically changing models.
+ model := "qwen:0.5b" // qwen:0.5b used for testing while hosting from my laptop. llama3 seems to be the best to use normally.
+
+ apicall := "{\"model\": \""
+ apicall = apicall + model
+ apicall = apicall + "\", \"prompt\": \""
+ apicall = apicall + prompt.Communication
+ apicall = apicall + "\", \"stream\": false }"
+
+ buf := strings.NewReader(apicall)
+
+ // Port 11434 corresponds to Ollama. Direct access to Ollama is restricted.
+ resp, err := http.Post("http://localhost:11434/api/generate", "application/json", buf)
+ if err != nil {
+ return
+ }
+
+ output, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ defer r.Body.Close()
+ response := Response{}
+ json.Unmarshal(output, &response)
+
+ fmt.Println(response.Response) // Print the body as a string
+ // TODO: Encrypt response.Response and send back as a Communication JSON object.
+ //c.IndentedJSON(http.StatusCreated, resp)
+}
+
+func serve() {
+
+}
+
+func init() {
+ var wg sync.WaitGroup
+
+ r := mux.NewRouter()
+ portfolioDir := "/home/violet/documents/development/portfolio/public_html/"
+ servePage(r, portfolioDir, "/", 1112)
+
+ srv := &http.Server{
+ Addr: "0.0.0.0:1111",
+ // Good practice to set timeouts to avoid Slowloris attacks.
+ WriteTimeout: time.Second * 15,
+ ReadTimeout: time.Second * 15,
+ IdleTimeout: time.Second * 60,
+ Handler: r, // Pass our instance of gorilla/mux in.
+ }
+
+ // Run our server in a goroutine so that it doesn't block.
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ log.Println(err)
+ }
+ }()
+
+ log.Println("Router is running on port 1111")
+
+ c := make(chan os.Signal, 1)
+ // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+Shift+C)
+ signal.Notify(c, os.Interrupt)
+
+ // Block until we receive our signal.
+ <-c
+
+ // Create a deadline to wait for.
+ ctx, cancel := context.WithTimeout(context.Background(), time.Duration(30))
+ defer cancel()
+ // Doesn't block if no connections, but will otherwise wait
+ // until the timeout deadline.
+ srv.Shutdown(ctx)
+ // Optionally, you could run srv.Shutdown in a goroutine and block on
+ // <-ctx.Done() if your application should wait for other services
+ // to finalize based on context cancellation.
+ log.Println("shutting down")
+ wg.Wait()
+ os.Exit(0)
+}
+
+func main() {
+ // TODO: Authenticate user credentials to verify they are allowed to even access
+ serve()
+}
diff --git a/backend/src/security-layer/container/serveAPI.go b/backend/src/security-layer/container/serveAPI.go
new file mode 100644
index 0000000..b9d478a
--- /dev/null
+++ b/backend/src/security-layer/container/serveAPI.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "github.com/gorilla/mux"
+ "log"
+ "net/http"
+ "time"
+)
+
+// Serve an API using mux.Router().Host({domain}).Subrouter().
+// Provide the router, domain, an array of endpoints and functions, and
+// the port you would like the API accessible to.
+func serveApi(
+ router *mux.Router,
+ domain string,
+ endpoint []string,
+ function []func(http.ResponseWriter, *http.Request),
+ port int,
+) {
+ // TODO: Add error handling
+ apiR := router.Host(domain).Subrouter()
+
+ for i := 0; i < len(endpoint); i++ {
+ apiR.HandleFunc(endpoint[i], function[i])
+ }
+
+ apiSrv := &http.Server{
+ Addr: "0.0.0.0:" + string(port),
+ WriteTimeout: time.Second * 15,
+ ReadTimeout: time.Second * 15,
+ IdleTimeout: time.Second * 60,
+ Handler: apiR,
+ }
+
+ go func() {
+ if err := apiSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ log.Println(err)
+ }
+ }()
+
+ log.Println("API server is running on port " + string(port))
+}
diff --git a/backend/src/security-layer/container/servePage.go b/backend/src/security-layer/container/servePage.go
new file mode 100644
index 0000000..84669f8
--- /dev/null
+++ b/backend/src/security-layer/container/servePage.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "github.com/gorilla/mux"
+ "log"
+ "net/http"
+ "time"
+)
+
+// Serve a directory to a port.
+func servePage(router *mux.Router, dest string, path string, port int) {
+ router.PathPrefix(path).Handler(http.FileServer(http.Dir(dest)))
+
+ srv := &http.Server{
+ Addr: "0.0.0.0: " + string(port),
+ // Good practice to set timeouts to avoid Slowloris attacks.
+ WriteTimeout: time.Second * 15,
+ ReadTimeout: time.Second * 15,
+ IdleTimeout: time.Second * 60,
+ Handler: router, // Pass our instance of gorilla/mux in.
+ }
+
+ // Run our server in a goroutine so that it doesn't block.
+ go func() {
+ if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ log.Println(err)
+ }
+ }()
+
+ log.Println("Web server is running on port " + string(port))
+}
diff --git a/backend/src/security-layer/init b/backend/src/security-layer/init
index 4965bb4..cc09fd5 100755
--- a/backend/src/security-layer/init
+++ b/backend/src/security-layer/init
@@ -1,9 +1,9 @@
#!/bin/zsh
# Force delete the old container and re-initialize the container.
-docker rm -f security-layer
+sudo docker rm -f security-layer
# Build the Docker image.
-docker build --tag security-layer:latest .
+sudo docker build --tag security-layer:latest .
# Create a Docker container from the image and connect host port 8000 to container port 8000.
-docker run --name security-layer -d -p 8000:8000 security-layer:latest
+sudo docker run --name security-layer -d -p 8000:8000 security-layer:latest
diff --git a/output.txt b/output.txt
deleted file mode 100644
index e69de29..0000000