sterepando commited on
Commit
5480a7b
·
verified ·
1 Parent(s): 4e46096

Update Dockerfile

Browse files
Files changed (1) hide show
  1. Dockerfile +210 -67
Dockerfile CHANGED
@@ -1,10 +1,10 @@
1
  # --- START OF FILE Dockerfile ---
2
- FROM golang:latest
3
 
4
- # Обновление и зависимости
5
- RUN apt-get update && apt-get install -y wget unzip git build-essential python3
6
 
7
- # Настройка Android NDK
8
  ENV ANDROID_NDK_HOME /opt/android-ndk
9
  ENV ANDROID_NDK_VERSION r25c
10
 
@@ -13,115 +13,258 @@ RUN wget -q https://dl.google.com/android/repository/android-ndk-${ANDROID_NDK_V
13
  mv android-ndk-${ANDROID_NDK_VERSION} /opt/android-ndk && \
14
  rm android-ndk-${ANDROID_NDK_VERSION}-linux.zip
15
 
 
16
  ENV PATH ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
17
 
18
  WORKDIR /app
19
 
20
- # Настройка Go
21
- ENV GOTOOLCHAIN=auto
22
- RUN go mod init vless-client
23
 
24
- # Исправленный код main.go
25
  RUN cat <<EOF > main.go
26
  package main
27
 
28
  import "C"
29
 
30
  import (
31
- "encoding/base64"
32
- "strings"
 
 
 
 
33
  "sync"
 
 
34
 
35
- "github.com/xtls/xray-core/core"
36
- "github.com/xtls/xray-core/infra/conf/serial"
37
- _ "github.com/xtls/xray-core/main/distro/all"
38
  )
39
 
 
40
  var (
41
- instance *core.Instance
42
- mu sync.Mutex
 
 
 
 
43
  )
44
 
45
- //export StartCore
46
- func StartCore(configContent *C.char) *C.char {
47
- mu.Lock()
48
- defer mu.Unlock()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- if instance != nil {
51
- return C.CString("Core already running")
 
 
52
  }
 
53
 
54
- configStr := C.GoString(configContent)
55
-
56
- // 1. Декодируем JSON в структуру conf.Config
57
- confConfig, err := serial.DecodeJSONConfig(strings.NewReader(configStr))
58
- if err != nil {
59
- return C.CString("Config parse error: " + err.Error())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  }
 
61
 
62
- // 2. !!! ВАЖНО !!! Конвертируем conf.Config в core.Config
63
- coreConfig, err := confConfig.Build()
64
- if err != nil {
65
- return C.CString("Config build error: " + err.Error())
 
 
 
 
66
  }
67
 
68
- // 3. Создаем инстанс ядра
69
- coreInstance, err := core.New(coreConfig)
70
- if err != nil {
71
- return C.CString("Core create error: " + err.Error())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  }
73
 
74
- if err := coreInstance.Start(); err != nil {
75
- return C.CString("Core start error: " + err.Error())
 
 
 
 
76
  }
77
 
78
- instance = coreInstance
79
- return C.CString("")
80
  }
81
 
82
- //export StopCore
83
- func StopCore() {
 
 
 
 
 
84
  mu.Lock()
85
- defer mu.Unlock()
 
 
 
86
 
87
- if instance != nil {
88
- instance.Close()
89
- instance = nil
 
 
 
 
 
 
90
  }
91
  }
92
 
93
- //export Base64Decode
94
- func Base64Decode(str *C.char) *C.char {
95
- data := C.GoString(str)
96
- if l := len(data) % 4; l > 0 {
97
- data += strings.Repeat("=", 4-l)
 
 
 
 
 
 
 
98
  }
99
- decoded, err := base64.StdEncoding.DecodeString(data)
100
- if err != nil {
101
- decoded, err = base64.URLEncoding.DecodeString(data)
102
- }
103
- if err != nil {
104
- return C.CString("")
105
- }
106
- return C.CString(string(decoded))
107
  }
108
 
109
  func main() {}
110
  EOF
111
 
112
- # Зависимости
113
- RUN go get -u github.com/xtls/xray-core@latest
114
- RUN go mod tidy
115
 
116
- # Компиляция
 
117
  RUN CGO_ENABLED=1 \
118
  GOOS=android \
119
  GOARCH=arm64 \
120
  CC=aarch64-linux-android33-clang \
121
- go build -buildmode=c-shared -o libvless.so main.go
122
 
123
- # Выдача файла
124
- RUN mkdir /output && mv libvless.so /output/
125
- WORKDIR /output
126
- CMD ["python3", "-m", "http.server", "7860"]
127
  # --- END OF FILE Dockerfile ---
 
1
  # --- START OF FILE Dockerfile ---
2
+ FROM golang:1.21
3
 
4
+ # 1. Установка необходимых инструментов
5
+ RUN apt-get update && apt-get install -y wget unzip build-essential
6
 
7
+ # 2. Настройка Android NDK (нужен для CGO кросс-компиляции)
8
  ENV ANDROID_NDK_HOME /opt/android-ndk
9
  ENV ANDROID_NDK_VERSION r25c
10
 
 
13
  mv android-ndk-${ANDROID_NDK_VERSION} /opt/android-ndk && \
14
  rm android-ndk-${ANDROID_NDK_VERSION}-linux.zip
15
 
16
+ # Добавляем тулчейны в PATH
17
  ENV PATH ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
18
 
19
  WORKDIR /app
20
 
21
+ # 3. Инициализация Go модуля
22
+ RUN go mod init mandre_server
 
23
 
24
+ # 4. Код сервера на Go (Diff-Streamer)
25
  RUN cat <<EOF > main.go
26
  package main
27
 
28
  import "C"
29
 
30
  import (
31
+ "bytes"
32
+ "fmt"
33
+ "image"
34
+ "image/color"
35
+ "image/png"
36
+ "net/http"
37
  "sync"
38
+ "time"
39
+ "unsafe"
40
 
41
+ "github.com/gorilla/websocket"
 
 
42
  )
43
 
44
+ // Глобальные переменные
45
  var (
46
+ mu sync.RWMutex
47
+ prevFrame *image.RGBA
48
+ clients = make(map[*websocket.Conn]bool)
49
+ broadcast = make(chan []byte)
50
+ upgrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
51
+ serverStarted bool
52
  )
53
 
54
+ // HTML клиент с Canvas для отрисовки Diff-кадров
55
+ const indexHTML = \`
56
+ <!DOCTYPE html>
57
+ <html>
58
+ <head>
59
+ <title>Mandre Stream (Delta)</title>
60
+ <style>
61
+ body { background: #1a1a1a; color: #eee; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; margin: 0; font-family: monospace; }
62
+ canvas { border: 2px solid #444; background: #000; max-width: 100%; max-height: 80vh; }
63
+ #status { margin-top: 10px; color: #0f0; }
64
+ </style>
65
+ </head>
66
+ <body>
67
+ <canvas id="screen"></canvas>
68
+ <div id="status">Connecting...</div>
69
+ <script>
70
+ const canvas = document.getElementById('screen');
71
+ const ctx = canvas.getContext('2d', { alpha: false }); // Optimizes compositing
72
+ const status = document.getElementById('status');
73
+
74
+ let ws = new WebSocket("ws://" + location.host + "/ws");
75
+ ws.binaryType = "arraybuffer";
76
+
77
+ ws.onopen = () => { status.innerText = "Connected (Delta Stream)"; };
78
+ ws.onclose = () => { status.innerText = "Disconnected"; setTimeout(() => location.reload(), 2000); };
79
+
80
+ ws.onmessage = (event) => {
81
+ const blob = new Blob([event.data], {type: "image/png"});
82
+ const url = URL.createObjectURL(blob);
83
+ const img = new Image();
84
+
85
+ img.onload = () => {
86
+ // Первый кадр задает размер
87
+ if (canvas.width !== img.width || canvas.height !== img.height) {
88
+ canvas.width = img.width;
89
+ canvas.height = img.height;
90
+ }
91
+ // РИСУЕМ ПОВЕРХ СТАРОГО (Прозрачные пиксели не затрут старые)
92
+ ctx.drawImage(img, 0, 0);
93
+ URL.revokeObjectURL(url);
94
+ };
95
+ img.src = url;
96
+ };
97
+ </script>
98
+ </body>
99
+ </html>
100
+ \`
101
 
102
+ //export StartServer
103
+ func StartServer(port int) {
104
+ if serverStarted {
105
+ return
106
  }
107
+ serverStarted = true
108
 
109
+ go func() {
110
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
111
+ w.Header().Set("Content-Type", "text/html")
112
+ w.Write([]byte(indexHTML))
113
+ })
114
+
115
+ http.HandleFunc("/ws", handleConnections)
116
+
117
+ addr := fmt.Sprintf(":%d", port)
118
+ fmt.Printf("[Go] Server starting on %s\n", addr)
119
+ http.ListenAndServe(addr, nil)
120
+ }()
121
+
122
+ go handleMessages()
123
+ }
124
+
125
+ //export StopServer
126
+ func StopServer() {
127
+ // В Go сложно корректно остановить http.ListenAndServe без контекста,
128
+ // но для Android плагина это обычно не критично (процесс убивается).
129
+ // Можно добавить л��гику закрытия сокетов.
130
+ }
131
+
132
+ //export UpdateFrame
133
+ func UpdateFrame(width, height int, rawPtr unsafe.Pointer, length int) {
134
+ // Превращаем C-pointer в Go slice без копирования (для чтения)
135
+ // Android Bitmap обычно ARGB_8888 (4 байта на пиксель)
136
+ rawBytes := unsafe.Slice((*byte)(rawPtr), length)
137
+
138
+ img := image.NewRGBA(image.Rect(0, 0, width, height))
139
+ // Копируем сырые данные в изображение.
140
+ // ВНИМАНИЕ: Android Bitmap.copyPixelsToBuffer дает порядок цветов, который может отличаться (RGBA vs ARGB).
141
+ // Обычно это RGBA. Если цвета перепутаны, поменяем тут.
142
+ copy(img.Pix, rawBytes)
143
+
144
+ // Генерация Delta-кадра
145
+ diffImg := generateDelta(img)
146
+
147
+ // Кодируем в PNG (Fastest compression)
148
+ var buf bytes.Buffer
149
+ enc := png.Encoder{CompressionLevel: png.NoCompression} // Скорость > Размер, т.к. мы и так шлем только разницу
150
+ if err := enc.Encode(&buf, diffImg); err == nil {
151
+ // Отправляем PNG в канал
152
+ select {
153
+ case broadcast <- buf.Bytes():
154
+ default:
155
+ // Если канал забит, пропускаем кадр (drop frame)
156
+ }
157
  }
158
+ }
159
 
160
+ func generateDelta(current *image.RGBA) image.Image {
161
+ mu.Lock()
162
+ defer mu.Unlock()
163
+
164
+ // Если это первый кадр или размер изменился - отправляем полный кадр
165
+ if prevFrame == nil || prevFrame.Bounds() != current.Bounds() {
166
+ prevFrame = current
167
+ return current
168
  }
169
 
170
+ bounds := current.Bounds()
171
+ diff := image.NewRGBA(bounds)
172
+
173
+ // Сравниваем пиксели
174
+ // current.Pix и prevFrame.Pix - это []uint8
175
+ // Формат RGBA: 4 байта на пиксель
176
+ totalPixels := len(current.Pix)
177
+
178
+ changed := false
179
+
180
+ // Проходим по байтам. Оптимизация: сравниваем uint32 было бы быстрее, но сложнее с alignment.
181
+ // Идем шагом 4 (R, G, B, A)
182
+ for i := 0; i < totalPixels; i += 4 {
183
+ // Сравниваем 4 байта пикселя
184
+ if current.Pix[i] != prevFrame.Pix[i] || // R
185
+ current.Pix[i+1] != prevFrame.Pix[i+1] || // G
186
+ current.Pix[i+2] != prevFrame.Pix[i+2] || // B
187
+ current.Pix[i+3] != prevFrame.Pix[i+3] { // A
188
+
189
+ // Пиксель изменился -> копируем его в diff
190
+ diff.Pix[i] = current.Pix[i]
191
+ diff.Pix[i+1] = current.Pix[i+1]
192
+ diff.Pix[i+2] = current.Pix[i+2]
193
+ diff.Pix[i+3] = current.Pix[i+3] // Alpha 255 (обычно)
194
+
195
+ changed = true
196
+ } else {
197
+ // Пиксель НЕ изменился -> делаем прозрачным
198
+ // В image.NewRGBA по умолчанию всё 0 (Transparent), поэтому ничего писать не надо!
199
+ // diff.Pix[i+3] = 0 // Уже 0
200
+ }
201
  }
202
 
203
+ // Обновляем предыдущий кадр
204
+ prevFrame = current
205
+
206
+ if !changed {
207
+ // Если изменений нет вообще, можно вернуть пустой 1x1 пиксель прозрачный, чтобы не слать лишнее
208
+ return image.NewRGBA(image.Rect(0, 0, 1, 1))
209
  }
210
 
211
+ return diff
 
212
  }
213
 
214
+ func handleConnections(w http.ResponseWriter, r *http.Request) {
215
+ ws, err := upgrader.Upgrade(w, r, nil)
216
+ if err != nil {
217
+ return
218
+ }
219
+ defer ws.Close()
220
+
221
  mu.Lock()
222
+ clients[ws] = true
223
+ // При подключении сбрасываем prevFrame, чтобы клиенту пришел полный кадр сразу (force I-frame)
224
+ prevFrame = nil
225
+ mu.Unlock()
226
 
227
+ for {
228
+ // Читаем сообщения (нужно для keep-alive/close), но игнорируем их
229
+ _, _, err := ws.ReadMessage()
230
+ if err != nil {
231
+ mu.Lock()
232
+ delete(clients, ws)
233
+ mu.Unlock()
234
+ break
235
+ }
236
  }
237
  }
238
 
239
+ func handleMessages() {
240
+ for {
241
+ pngData := <-broadcast
242
+ mu.RLock()
243
+ for client := range clients {
244
+ err := client.WriteMessage(websocket.BinaryMessage, pngData)
245
+ if err != nil {
246
+ client.Close()
247
+ delete(clients, client)
248
+ }
249
+ }
250
+ mu.RUnlock()
251
  }
 
 
 
 
 
 
 
 
252
  }
253
 
254
  func main() {}
255
  EOF
256
 
257
+ # 5. Установка зависимостей (WebSocket)
258
+ RUN go get github.com/gorilla/websocket
 
259
 
260
+ # 6. Компиляция shared library для Android (ARM64)
261
+ # buildmode=c-shared создает .so файл, который Python может загрузить через ctypes
262
  RUN CGO_ENABLED=1 \
263
  GOOS=android \
264
  GOARCH=arm64 \
265
  CC=aarch64-linux-android33-clang \
266
+ go build -buildmode=c-shared -o libmandre_server.so main.go
267
 
268
+ # 7. Выдача файла
269
+ CMD ["sh", "-c", "mkdir -p /output && cp libmandre_server.so /output/"]
 
 
270
  # --- END OF FILE Dockerfile ---