You've already forked img-proxy-url-generator
Compare commits
16 Commits
2b1e5561a0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
d94c9bf4e6
|
|||
|
f00f9b0946
|
|||
|
b0d4f3f890
|
|||
|
e9e0d30c7a
|
|||
|
0c458dd965
|
|||
|
2b865568c0
|
|||
|
05fc22e966
|
|||
|
6159a1509d
|
|||
|
e86c892b8f
|
|||
|
61f42c5297
|
|||
|
17971f34c1
|
|||
|
d66dbe89cb
|
|||
|
9af291f4d2
|
|||
|
3a61962c04
|
|||
|
7a8d0714f5
|
|||
|
bcd287b3b3
|
46
.gitea/workflows/build-grpc.yml
Normal file
46
.gitea/workflows/build-grpc.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
on:
|
||||||
|
create:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
name: 🏗️✨ Build Workflow
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Build:
|
||||||
|
name: 🖥️ 🔨 Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 🛡️ 🔒 Add Siteworx CA Certificates
|
||||||
|
run: |
|
||||||
|
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
|
||||||
|
update-ca-certificates
|
||||||
|
|
||||||
|
- name: 📖 🔍 Checkout Repository Code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: 🔑 🔐 Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: 🏗️ 🔧 Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Replace entrypoint
|
||||||
|
run: |
|
||||||
|
sed -i 's/server/grpc/g' Dockerfile
|
||||||
|
sed -i 's/8080/9000/g' Dockerfile
|
||||||
|
|
||||||
|
- name: 🐳 🔨 Build Grpc Container
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
sbom: true
|
||||||
|
provenance: true
|
||||||
|
platforms: linux/arm64,linux/amd64
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: siteworxpro/img-proxy-url-generator:${{ gitea.ref_name }}-grpc
|
||||||
41
.gitea/workflows/build.yml
Normal file
41
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
on:
|
||||||
|
create:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
name: 🏗️✨ Build Workflow
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Build:
|
||||||
|
name: 🖥️ 🔨 Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 🛡️ 🔒 Add Siteworx CA Certificates
|
||||||
|
run: |
|
||||||
|
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
|
||||||
|
update-ca-certificates
|
||||||
|
|
||||||
|
- name: 📖 🔍 Checkout Repository Code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: 🔑 🔐 Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: 🏗️ 🔧 Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: 🐳 🔨 Build Server Container
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
sbom: true
|
||||||
|
provenance: true
|
||||||
|
platforms: linux/arm64,linux/amd64
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: siteworxpro/img-proxy-url-generator:${{ gitea.ref_name }}
|
||||||
@@ -8,22 +8,20 @@ name: 🧪 ✨ Unit Tests Workflow
|
|||||||
jobs:
|
jobs:
|
||||||
test-go:
|
test-go:
|
||||||
env:
|
env:
|
||||||
GOPRIVATE: 'git.siteworxpro.com'
|
GO_VERSION: '1.24.3'
|
||||||
GOPROXY: 'direct'
|
|
||||||
name: 🔍 🐹 Go Tests
|
name: 🔍 🐹 Go Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: 🛡️ 🔒 Add Siteworx CA Certificates
|
- name: 🛡️ 🔒 Add Siteworx CA Certificates
|
||||||
run: |
|
run: |
|
||||||
apt update && apt install -yq ca-certificates curl
|
|
||||||
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
|
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
|
||||||
update-ca-certificates
|
update-ca-certificates
|
||||||
|
|
||||||
- name: ⚙️ 🐹 Set up Go Environment
|
- name: ⚙️ 🐹 Set up Go Environment
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: '1.24.0'
|
go-version: ${{ env.GO_VERSION }}
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: 📖 🔍 Checkout Repository Code
|
- name: 📖 🔍 Checkout Repository Code
|
||||||
@@ -33,10 +31,8 @@ jobs:
|
|||||||
|
|
||||||
- name: 📦 📥 Install Dependencies
|
- name: 📦 📥 Install Dependencies
|
||||||
run: |
|
run: |
|
||||||
cd backend
|
|
||||||
go mod download
|
go mod download
|
||||||
|
|
||||||
- name: ✅ 🔍 Run Go Tests
|
- name: ✅ 🔍 Run Go Tests
|
||||||
run: |
|
run: |
|
||||||
cd backend
|
|
||||||
go test -v ./... -coverprofile=coverage.out
|
go test -v ./... -coverprofile=coverage.out
|
||||||
@@ -12,7 +12,7 @@ RUN go mod tidy && go build -o imgproxy .
|
|||||||
|
|
||||||
FROM alpine:latest AS runtime
|
FROM alpine:latest AS runtime
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 8080
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -23,4 +23,4 @@ RUN adduser -u 1001 -g appuser appuser -D && \
|
|||||||
|
|
||||||
USER 1001
|
USER 1001
|
||||||
|
|
||||||
ENTRYPOINT ["/app/imgproxy", "grpc"]
|
ENTRYPOINT ["/app/imgproxy", "server"]
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,6 +16,23 @@ func pkcs7pad(data []byte, blockSize int) []byte {
|
|||||||
return append(data, padding...)
|
return append(data, padding...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pkcs7unpad(data []byte) ([]byte, error) {
|
||||||
|
length := len(data)
|
||||||
|
if length == 0 {
|
||||||
|
return nil, errors.New("invalid padding size")
|
||||||
|
}
|
||||||
|
padLen := int(data[length-1])
|
||||||
|
if padLen > length || padLen == 0 {
|
||||||
|
return nil, errors.New("invalid padding")
|
||||||
|
}
|
||||||
|
for _, v := range data[length-padLen:] {
|
||||||
|
if int(v) != padLen {
|
||||||
|
return nil, errors.New("invalid padding")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data[:length-padLen], nil
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Generator) Decrypt(s string) (string, error) {
|
func (g *Generator) Decrypt(s string) (string, error) {
|
||||||
c, err := aes.NewCipher(g.encryptionKey)
|
c, err := aes.NewCipher(g.encryptionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -32,7 +50,12 @@ func (g *Generator) Decrypt(s string) (string, error) {
|
|||||||
|
|
||||||
cbc.CryptBlocks(cryptText, cryptText)
|
cbc.CryptBlocks(cryptText, cryptText)
|
||||||
|
|
||||||
return string(cryptText), err
|
decrypted, err := pkcs7unpad(cryptText)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(decrypted), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Generator) generateBaseAesEncUrl(file []byte) (string, error) {
|
func (g *Generator) generateBaseAesEncUrl(file []byte) (string, error) {
|
||||||
|
|||||||
@@ -41,9 +41,8 @@ func (g *Generator) GenerateUrl(file string, params []string, format Format) (st
|
|||||||
|
|
||||||
if params == nil || len(params) == 0 || params[0] == "" {
|
if params == nil || len(params) == 0 || params[0] == "" {
|
||||||
params = []string{"raw:1"}
|
params = []string{"raw:1"}
|
||||||
} else {
|
|
||||||
params = append(params, "sm:1")
|
|
||||||
}
|
}
|
||||||
|
params = append(params, "sm:1")
|
||||||
|
|
||||||
if PathPrefix != "" {
|
if PathPrefix != "" {
|
||||||
file = PathPrefix + file
|
file = PathPrefix + file
|
||||||
@@ -54,11 +53,11 @@ func (g *Generator) GenerateUrl(file string, params []string, format Format) (st
|
|||||||
var url string
|
var url string
|
||||||
var err error
|
var err error
|
||||||
if config.GetConfig().Generator.PlainUrl {
|
if config.GetConfig().Generator.PlainUrl {
|
||||||
url, _ = g.generatePlainUrl(file)
|
url, err = g.generatePlainUrl(file)
|
||||||
} else if g.encryptionKey != nil {
|
} else if g.encryptionKey != nil {
|
||||||
url, err = g.generateBaseAesEncUrl([]byte(file))
|
url, err = g.generateBaseAesEncUrl([]byte(file))
|
||||||
} else {
|
} else {
|
||||||
url, _ = g.generateBase64Url([]byte(file))
|
url, err = g.generateBase64Url([]byte(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,95 +5,104 @@ import (
|
|||||||
"github.com/charmbracelet/huh"
|
"github.com/charmbracelet/huh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
// Helper: find index of current focusField in a given field slice.
|
||||||
|
//
|
||||||
|
//goland:noinspection GoMixedReceiverTypes
|
||||||
|
func (m *Model) findCurrentFieldIndex(fields []huh.Field) int {
|
||||||
|
for i, field := range fields {
|
||||||
|
if field == m.focusField {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
if m.focusField == nil {
|
// Helper: blur current, focus next (wraps to 0)
|
||||||
m.Fields[0].Focus()
|
//
|
||||||
m.focusField = m.Fields[0]
|
//goland:noinspection GoMixedReceiverTypes
|
||||||
|
func (m *Model) focusNextField(fields []huh.Field) {
|
||||||
|
index := m.findCurrentFieldIndex(fields)
|
||||||
|
next := (index + 1) % len(fields)
|
||||||
|
m.focusField.Blur()
|
||||||
|
m.focusField = fields[next]
|
||||||
|
m.focusField.Focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: focus a specific field by index
|
||||||
|
//
|
||||||
|
//goland:noinspection GoMixedReceiverTypes
|
||||||
|
func (m *Model) focusFieldByIndex(fields []huh.Field, index int) {
|
||||||
|
if index >= 0 && index < len(fields) {
|
||||||
|
m.focusField.Blur()
|
||||||
|
m.focusField = fields[index]
|
||||||
|
m.focusField.Focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: get all selected param fields as a flat slice
|
||||||
|
//
|
||||||
|
//goland:noinspection GoMixedReceiverTypes
|
||||||
|
func (m *Model) selectedParamFields() []huh.Field {
|
||||||
|
var fields []huh.Field
|
||||||
|
for _, param := range *m.selectedParams {
|
||||||
|
fields = append(fields, param.Input()...)
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
//goland:noinspection GoMixedReceiverTypes
|
||||||
|
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
model := m // copy, since Bubble Tea prefers value receivers, but we'll operate on &model internally
|
||||||
|
|
||||||
|
if model.focusField == nil && len(model.Fields) > 0 {
|
||||||
|
model.Fields[0].Focus()
|
||||||
|
model.focusField = model.Fields[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg, ok := msg.(tea.KeyMsg); ok {
|
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
||||||
switch msg.String() {
|
switch keyMsg.String() {
|
||||||
case "tab":
|
case "tab":
|
||||||
if m.focusField != nil {
|
mainFields := model.Fields
|
||||||
|
paramFields := model.selectedParamFields()
|
||||||
|
|
||||||
index := -1
|
if model.inParamsFields && len(paramFields) > 0 {
|
||||||
|
index := model.findCurrentFieldIndex(paramFields)
|
||||||
selectedParamFields := make([]huh.Field, 0)
|
|
||||||
for _, field := range *m.selectedParams {
|
|
||||||
for _, f := range field.Input() {
|
|
||||||
selectedParamFields = append(selectedParamFields, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.inParamsFields {
|
|
||||||
for i, field := range selectedParamFields {
|
|
||||||
if field == m.focusField {
|
|
||||||
index = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for i, field := range m.Fields {
|
|
||||||
if field == m.focusField {
|
|
||||||
index = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the field is not found, return
|
|
||||||
if index == -1 {
|
if index == -1 {
|
||||||
return m, nil
|
break
|
||||||
}
|
}
|
||||||
|
if index == len(paramFields)-1 {
|
||||||
// if the field is the last one, and we have params selected go to the param fields
|
// Last param field: cycle to main fields
|
||||||
if !m.inParamsFields && index == len(m.Fields)-1 && len(selectedParamFields) > 0 {
|
model.inParamsFields = false
|
||||||
m.focusField.Blur()
|
model.focusFieldByIndex(mainFields, 0)
|
||||||
m.inParamsFields = true
|
|
||||||
paramsFields := selectedParamFields
|
|
||||||
m.focusField = paramsFields[0]
|
|
||||||
m.focusField.Focus()
|
|
||||||
|
|
||||||
// if the field is the last one, and we have params selected go to the first non params field
|
|
||||||
} else if m.inParamsFields && index == len(selectedParamFields)-1 {
|
|
||||||
m.focusField.Blur()
|
|
||||||
m.inParamsFields = false
|
|
||||||
m.focusField = m.Fields[0]
|
|
||||||
m.focusField.Focus()
|
|
||||||
|
|
||||||
// if not in the params fields and the field is the last one, go to the first one
|
|
||||||
} else if index == len(m.Fields)-1 && !m.inParamsFields {
|
|
||||||
m.focusField.Blur()
|
|
||||||
m.focusField = m.Fields[0]
|
|
||||||
m.focusField.Focus()
|
|
||||||
} else {
|
} else {
|
||||||
// otherwise, go to the next field
|
model.focusNextField(paramFields)
|
||||||
m.focusField.Blur()
|
}
|
||||||
if m.inParamsFields {
|
} else {
|
||||||
m.focusField = selectedParamFields[index+1]
|
index := model.findCurrentFieldIndex(mainFields)
|
||||||
} else {
|
if index == -1 {
|
||||||
m.focusField = m.Fields[index+1]
|
break
|
||||||
}
|
}
|
||||||
m.focusField.Focus()
|
if index == len(mainFields)-1 && len(paramFields) > 0 {
|
||||||
|
// Last main field & params exist: go to params
|
||||||
|
model.inParamsFields = true
|
||||||
|
model.focusFieldByIndex(paramFields, 0)
|
||||||
|
} else {
|
||||||
|
model.focusNextField(mainFields)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "ctrl+c", "esc":
|
case "ctrl+c", "esc":
|
||||||
return m, tea.Quit
|
return model, tea.Quit
|
||||||
case "enter":
|
case "enter":
|
||||||
return m, nil
|
return model, nil
|
||||||
default:
|
default:
|
||||||
if m.focusField != nil {
|
if model.focusField != nil {
|
||||||
md, cmd := m.focusField.(huh.Field).Update(msg)
|
md, cmd := model.focusField.(huh.Field).Update(msg)
|
||||||
|
|
||||||
if md != nil {
|
if md != nil {
|
||||||
m.focusField = md.(huh.Field)
|
model.focusField = md.(huh.Field)
|
||||||
}
|
}
|
||||||
|
return model, cmd
|
||||||
return m, cmd
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return model, nil
|
||||||
return m, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user