forked from pterodactyl/wings
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocs2.html
More file actions
1323 lines (1279 loc) · 102 KB
/
Copy pathdocs2.html
File metadata and controls
1323 lines (1279 loc) · 102 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<title>Better Files Manager Ainx guide</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<style>
::-webkit-scrollbar { height: 8px; }
::-webkit-scrollbar-track { background-color: #57534e; border-radius: 10px; }
::-webkit-scrollbar-thumb { background-color: #b8aca1; border-radius: 10px; }
.default-line { border: 1px solid #78716c; color: #e7e5e4 !important; }
</style>
</head>
<body class="bg-stone-800">
<div class="justify-center items-center flex flex-col px-3 md:px-4 pb-20 gap-4">
<h1 class="text-white font-bold text-2xl md:text-4xl mt-20">Better Files Manager - V-A.1.0.0</h1>
<p class="text-stone-400 text-[16px] font-medium">Installation guide</p>
<h1 class="mt-10 text-white font-bold text-lg">Install using install script</h1>
<div class="flex flex-col md:flex-row gap-5 w-full max-w-[1420px] justify-center">
<div class="code-block bg-emerald-600 rounded-lg p-3 flex flex-row justify-between items-center gap-2 mt-2 w-full md:w-auto">
<div class="flex items-center gap-2 min-w-0">
<span class="text-emerald-200 font-mono shrink-0">$</span>
<code class="text-white font-mono text-sm break-all">ainx install betterfilesmanager.ainx</code>
</div>
<button onclick="copyButton(this)" class="bg-emerald-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-emerald-800 transition duration-200 shrink-0">
<i class="fa-solid fa-copy"></i>
</button>
</div>
<div class="code-block bg-blue-600 rounded-lg p-3 flex flex-row justify-between items-center gap-2 mt-2 w-full md:w-auto">
<div class="flex items-center gap-2 min-w-0">
<span class="text-blue-200 font-mono shrink-0">$</span>
<code class="text-white font-mono text-sm break-all">blueprint -install betterfilesmanager</code>
</div>
<button onclick="copyButton(this)" class="bg-blue-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-blue-800 transition duration-200 shrink-0">
<i class="fa-solid fa-copy"></i>
</button>
</div>
</div>
<div class="w-full max-w-[1420px] bg-sky-500/10 border border-sky-500/50 border-l-4 rounded-lg p-4 mt-6">
<div class="flex items-start gap-3">
<i class="fa-solid fa-language text-sky-300 mt-1"></i>
<div class="min-w-0 w-full">
<h2 class="text-sky-100 font-bold text-lg">Optional Better Files translation</h2>
<p class="text-sky-100/90 mt-2">
A Better Files translation file is included at
<code class="bg-stone-900 rounded-lg px-2 py-1 text-sky-200 text-sm">translations/en/betterfilesmanager.php</code>.
Upload or place this file according to the translation instructions for the Panel theme you use, because the required location and setup can differ between themes.
</p>
</div>
</div>
</div>
<div class="flex items-center gap-4 w-[200px] mt-5">
<div class="flex-1 h-px bg-stone-500"></div>
<p class="text-stone-400 font-bold">Or</p>
<div class="flex-1 h-px bg-stone-500"></div>
</div>
<h1 class="mt-2 text-white font-bold text-lg mb-10">Using manual steps</h1>
<div class="w-full max-w-[1420px] bg-yellow-500/15 border border-yellow-500/60 border-l-4 rounded-lg p-4 mt-10">
<div class="flex items-start gap-3">
<i class="fa-solid fa-triangle-exclamation text-yellow-300 mt-1"></i>
<div>
<h2 class="text-yellow-100 font-bold text-lg">Only do this if you used the old standalone/manual install</h2>
<p class="text-yellow-100/90 mt-2">
If this is a fresh Blueprint or Ainx install, skip the cleanup section. If your Panel already has the old manual standalone version, remove those manual edits first, then install with Ainx.
</p>
</div>
</div>
</div>
<div class="w-full max-w-[1420px] bg-stone-700 rounded-lg p-4">
<h2 class="text-white font-bold text-xl">Old standalone cleanup checklist</h2>
<p class="text-stone-300 mt-3">
These are the old manual Panel edits to reverse before switching to Ainx. Red blocks are code or files to remove. Green blocks are code to restore. Each code box scrolls if the snippet is longer than the visible area.
</p>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-5">
<div class="bg-stone-800 rounded-lg p-4 min-w-0">
<h3 class="text-white font-bold">resources/scripts/routers/routes.ts</h3>
<p class="text-stone-300 mt-2">1. Remove this Better Files import if it exists:</p>
<div class="code-block bg-stone-950 rounded-lg p-3 mt-2 border-l-4 border-red-500 max-h-48 overflow-auto">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<pre><code class="text-white text-sm">import { BetterFileManagerContainer } from '@/components/server/betterfiles';</code></pre>
</div>
<p class="text-stone-300 mt-3">2. Remove this old standalone route replacement:</p>
<div class="code-block bg-stone-950 rounded-lg p-3 mt-2 border-l-4 border-red-500 max-h-56 overflow-auto">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<pre><code class="text-white text-sm">{
path: '/files',
permission: 'file.*',
name: 'Files',
component: BetterFileManagerContainer,
},</code></pre>
</div>
<p class="text-stone-300 mt-3">3. Restore the normal Pterodactyl file manager routes:</p>
<div class="code-block bg-stone-950 rounded-lg p-3 mt-2 border-l-4 border-emerald-500 max-h-64 overflow-auto">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<pre><code class="text-white text-sm">{
path: '/files',
permission: 'file.*',
name: 'Files',
component: FileManagerContainer,
},
{
path: '/files/:action(edit|new)',
permission: 'file.*',
name: undefined,
component: FileEditContainer,
},</code></pre>
</div>
</div>
<div class="bg-stone-800 rounded-lg p-4 min-w-0">
<h3 class="text-white font-bold">routes/api-client.php</h3>
<p class="text-stone-300 mt-2">Remove this manual route include if it exists:</p>
<div class="code-block bg-stone-950 rounded-lg p-3 mt-2 border-l-4 border-red-500 max-h-48 overflow-auto">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<pre><code class="text-white text-sm">require __DIR__ . '/api-better-files.php';</code></pre>
</div>
<p class="text-stone-300 mt-3">Then delete this old standalone route file if it exists:</p>
<div class="code-block bg-stone-950 rounded-lg p-3 mt-2 border-l-4 border-red-500 max-h-48 overflow-auto">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<pre><code class="text-white text-sm">routes/api-better-files.php</code></pre>
</div>
</div>
<div class="bg-stone-800 rounded-lg p-4 min-w-0">
<h3 class="text-white font-bold">routes/admin.php</h3>
<p class="text-stone-300 mt-2">Remove these old standalone admin routes if they exist:</p>
<div class="code-block bg-stone-950 rounded-lg p-3 mt-2 border-l-4 border-red-500 max-h-56 overflow-auto">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<pre><code class="text-white text-sm">Route::get('/better-files', [Admin\Settings\BetterFilesController::class, 'index'])->name('admin.settings.betterfiles');
Route::patch('/better-files', [Admin\Settings\BetterFilesController::class, 'update']);</code></pre>
</div>
</div>
<div class="bg-stone-800 rounded-lg p-4 min-w-0">
<h3 class="text-white font-bold">resources/views/partials/admin/settings/nav.blade.php</h3>
<p class="text-stone-300 mt-2">Remove this old Better Files settings navigation link if it exists:</p>
<div class="code-block bg-stone-950 rounded-lg p-3 mt-2 border-l-4 border-red-500 max-h-48 overflow-auto">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<pre><code class="text-white text-sm"><li @if($activeTab === 'better-files')class="active"@endif><a href="{{ route('admin.settings.betterfiles') }}">Better Files</a></li></code></pre>
</div>
</div>
<div class="bg-stone-800 rounded-lg p-4 min-w-0">
<h3 class="text-white font-bold">app/Console/Kernel.php</h3>
<p class="text-stone-300 mt-2">Remove this old standalone trash cleanup scheduler entry if it exists:</p>
<div class="code-block bg-stone-950 rounded-lg p-3 mt-2 border-l-4 border-red-500 max-h-48 overflow-auto">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<pre><code class="text-white text-sm">$schedule->command(\Pterodactyl\Console\Commands\BetterFilesTrashCleanupCommand::class)->daily();</code></pre>
</div>
</div>
<div class="bg-stone-800 rounded-lg p-4 min-w-0">
<h3 class="text-white font-bold">webpack.config.js</h3>
<p class="text-stone-300 mt-2">Remove the old standalone Monaco plugin import if it was added manually:</p>
<div class="code-block bg-stone-950 rounded-lg p-3 mt-2 border-l-4 border-red-500 max-h-48 overflow-auto">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<pre><code class="text-white text-sm">const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');</code></pre>
</div>
<p class="text-stone-300 mt-3">Remove the old manually added Monaco plugin entry if it exists:</p>
<div class="code-block bg-stone-950 rounded-lg p-3 mt-2 border-l-4 border-red-500 max-h-56 overflow-auto">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<pre><code class="text-white text-sm">new MonacoWebpackPlugin({
filename: isProduction ? 'monaco/[name].[contenthash:8].worker.js' : 'monaco/[name].worker.js',
publicPath: process.env.WEBPACK_PUBLIC_PATH || '/assets/',
}),</code></pre>
</div>
<p class="text-stone-300 mt-3">If you added this rule only for the standalone install, remove it too:</p>
<div class="code-block bg-stone-950 rounded-lg p-3 mt-2 border-l-4 border-red-500 max-h-48 overflow-auto">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<pre><code class="text-white text-sm">{
test: /\.(woff|woff2|ttf)$/i,
type: 'asset/resource',
},</code></pre>
</div>
</div>
<div class="bg-stone-800 rounded-lg p-4 min-w-0 lg:col-span-2">
<h3 class="text-white font-bold">Finish cleanup</h3>
<p class="text-stone-300 mt-2">After removing the old standalone edits above, clear caches and rebuild the Panel:</p>
<div class="code-block bg-stone-950 rounded-lg p-3 mt-2 border-l-4 border-emerald-500 max-h-48 overflow-auto">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<pre><code class="text-white text-sm">php artisan optimize:clear
yarn build:production
php artisan optimize</code></pre>
</div>
</div>
</div>
</div>
<div class="w-full max-w-[1420px] bg-stone-700 rounded-lg p-4 mt-6">
<h2 class="text-white font-bold text-xl">What Ainx handles</h2>
<p class="text-stone-300 mt-3">
The Ainx package handles the Panel-side addon install. Do not run the old standalone artisan installer. Do not repeat the old manual Panel file edits after installing Ainx.
</p>
<p class="text-stone-300 mt-3">
The only manual section kept below is for Wings. Use it only if you want the daemon-backed Better Files features such as live collaboration, search streaming, media streaming, archive tools, and Git daemon endpoints. NBT and database previews run through the Panel and do not need Wings edits.
</p>
</div>
<h1 class="mt-20 text-white text-2xl md:text-4xl font-bold">Wings modifications guide</h1>
<p class="text-stone-400 text-[16px] font-medium mb-10">Daemon-side changes</p>
<div class="w-full max-w-[1420px]">
<div class="bg-emerald-500/15 border border-emerald-500/60 border-l-4 rounded-lg p-4 mb-5">
<div class="flex items-start gap-3">
<i class="fa-solid fa-circle-info text-emerald-300 mt-1"></i>
<div>
<h2 class="text-emerald-100 font-bold text-lg">Recommended Wings install</h2>
<p class="text-emerald-100/90 mt-2">
We recommend using the pre-built Wings binary or the smart Better Files script from this guide. The smart script is the recommended automatic source-patching method for clean upstream Wings, repeat runs, and compatible older Better Files source installs.
</p>
<p class="text-emerald-100/90 mt-2">
Full daemon patches touch many more Wings files and have a higher chance of conflicting with other addons. Use the full patch only when you want every Tomaxikz daemon feature.
</p>
</div>
</div>
</div>
<div class="bg-red-500/10 border border-red-500/50 border-l-4 rounded-lg p-4 mb-5">
<div class="flex items-start gap-3">
<i class="fa-solid fa-shield-halved text-red-300 mt-1"></i>
<div>
<h2 class="text-red-100 font-bold text-lg">Security rebuild required</h2>
<p class="text-red-100/90 mt-2">
Build Wings with Go <code class="bg-stone-900 rounded-lg px-2 py-1 text-red-200 text-sm">1.26.7</code> or newer and <code class="bg-stone-900 rounded-lg px-2 py-1 text-red-200 text-sm">golang.org/x/text v0.39.0</code> or newer. These versions contain the runtime, archive, and Unicode fixes used by this Better Files update.
</p>
<p class="text-red-100/90 mt-2">
Updating only the Panel or source files does not patch a running Wings process. Existing source installations must download the current script or patch again, rebuild, replace <code class="bg-stone-900 rounded-lg px-2 py-1 text-red-200 text-sm">/usr/local/bin/wings</code>, and restart the service. Pre-built installations must replace the binary and restart Wings. The existing upload endpoint and request-header names remain unchanged.
</p>
<p class="text-red-100/90 mt-2">
Each resumable file must now use its own signed upload token; all chunks, retries, and <code class="bg-stone-900 rounded-lg px-2 py-1 text-red-200 text-sm">HEAD</code> checks for that token must keep the same server and file path. Git clone/pull URLs must be their final HTTPS address because redirects, proxies, and repository-local HTTP overrides now fail closed. Network transfers always use the isolated Git 2.49.1 helper; clone checkout and pull merge then run in a separate helper with networking disabled. The helper image may download on first use.
</p>
<p class="text-red-100/90 mt-2">
Resumable <code class="bg-stone-900 rounded-lg px-2 py-1 text-red-200 text-sm">PATCH</code> can now return <code class="bg-stone-900 rounded-lg px-2 py-1 text-red-200 text-sm">408</code> for an idle request body, <code class="bg-stone-900 rounded-lg px-2 py-1 text-red-200 text-sm">409</code> for an active same-file writer or an offset/length mismatch, and <code class="bg-stone-900 rounded-lg px-2 py-1 text-red-200 text-sm">429</code> when an admission limit is full. Honor <code class="bg-stone-900 rounded-lg px-2 py-1 text-red-200 text-sm">Retry-After</code> on 429. A successful <code class="bg-stone-900 rounded-lg px-2 py-1 text-red-200 text-sm">Upload-Complete: ?1</code> retires that token, so mint a fresh token for another file or any request after completion.
</p>
</div>
</div>
</div>
<div class="flex flex-col md:flex-row justify-center gap-2 bg-stone-900 rounded-lg p-2 mb-5" role="tablist" aria-label="Wings install options">
<button type="button" data-wings-tab="prebuilt" onclick="setWingsOption('prebuilt')" class="w-full md:w-auto bg-emerald-600 text-white font-bold px-4 py-2 rounded-md cursor-pointer transition duration-200 focus:outline-none focus:ring-2 focus:ring-emerald-300" role="tab" aria-selected="true" aria-controls="wings-prebuilt">
Pre-built
</button>
<button type="button" data-wings-tab="script" onclick="setWingsOption('script')" class="w-full md:w-auto bg-stone-800 text-stone-300 font-bold px-4 py-2 rounded-md cursor-pointer hover:bg-stone-600 transition duration-200 focus:outline-none focus:ring-2 focus:ring-emerald-300" role="tab" aria-selected="false" aria-controls="wings-script">
Smart script
</button>
<button type="button" data-wings-tab="patch" onclick="setWingsOption('patch')" class="w-full md:w-auto bg-stone-800 text-stone-300 font-bold px-4 py-2 rounded-md cursor-pointer hover:bg-stone-600 transition duration-200 focus:outline-none focus:ring-2 focus:ring-emerald-300" role="tab" aria-selected="false" aria-controls="wings-patch">
Patch options
</button>
<button type="button" data-wings-tab="manual" onclick="setWingsOption('manual')" class="w-full md:w-auto bg-stone-800 text-stone-300 font-bold px-4 py-2 rounded-md cursor-pointer hover:bg-stone-600 transition duration-200 focus:outline-none focus:ring-2 focus:ring-emerald-300" role="tab" aria-selected="false" aria-controls="wings-manual">
Manual edits
</button>
</div>
<div id="wings-prebuilt" data-wings-panel="prebuilt" class="bg-stone-700 rounded-lg p-4 min-w-0" role="tabpanel">
<h1 class="text-white font-bold text-xl">Install the prebuilt Wings binary</h1>
<p class="text-stone-300 mt-3">Use this if you just want the daemon already patched and built with the Better Files Wings handlers.</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">sudo mkdir -p /etc/pterodactyl
ARCH="$([[ "$(uname -m)" == "x86_64" ]] && echo "amd64" || echo "arm64")"
curl -fL --retry 3 -o /tmp/wings \
"https://github.com/Tomaxikz/daemon/releases/download/dev-latest/wings_linux_${ARCH}"
file /tmp/wings
sudo install -m 0755 /tmp/wings /usr/local/bin/wings
sudo systemctl restart wings</code></pre>
</div>
</div>
<p class="text-stone-400 mt-3 text-sm">
Release page:
<a class="text-emerald-400 hover:text-emerald-300 underline break-all" href="https://github.com/Tomaxikz/daemon/releases/tag/dev-latest">https://github.com/Tomaxikz/daemon/releases/tag/dev-latest</a>
</p>
</div>
<div id="wings-script" data-wings-panel="script" class="bg-stone-700 rounded-lg p-4 min-w-0" role="tabpanel" hidden>
<h1 class="text-white font-bold text-xl">Run the smart Better Files script</h1>
<p class="text-stone-300 mt-3">
Use this if you want to patch and build a Wings source tree yourself. The script downloads the Better Files daemon files, applies the required original-file edits by content anchors, skips edits that are already installed, formats the Go files, and builds Wings.
</p>
<p class="text-emerald-100 bg-emerald-500/10 border border-emerald-500/50 rounded-lg p-3 mt-3">
Run this from the Wings source root, the same folder that contains <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">go.mod</code>. This is more reliable than a raw patch file if your tree already has some compatible edits.
</p>
<p class="text-amber-100 bg-amber-500/10 border border-amber-500/50 rounded-lg p-3 mt-3">
Before running it, commit or back up your current source tree. The script can upgrade clean installs, repeat its current edits, and migrate source shapes it explicitly recognizes. It cannot guarantee an automatic upgrade from every old or third-party-modified Wings tree. If an expected anchor is different, it stops instead of guessing; files changed earlier in that run may still need to be restored from Git or <code class="bg-stone-900 rounded-lg px-2 py-1 text-amber-200 text-sm">.tomaxikz-betterfiles-backups</code>.
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-3 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">curl -fL -o betterfiles_patch.sh \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/betterfiles_patch.sh"
chmod +x betterfiles_patch.sh
./betterfiles_patch.sh</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3">
A successful run creates and validates a buildable source tree. Its package-wide build check does not leave a deployable binary behind, and it does not replace or restart the running Wings service automatically. After the script reports success, compile and install the binary:
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-3 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">go version # must report go1.26.7 or newer
go build -o wings .
sudo systemctl stop wings
sudo install -m 0755 ./wings /usr/local/bin/wings
sudo systemctl start wings
sudo systemctl is-active wings</code></pre>
</div>
</div>
<p class="text-stone-400 mt-3 text-sm">
Script file:
<a class="text-emerald-400 hover:text-emerald-300 underline break-all" href="https://github.com/Tomaxikz/daemon/blob/develop/betterfiles_patch.sh">https://github.com/Tomaxikz/daemon/blob/develop/betterfiles_patch.sh</a>
</p>
<p class="text-stone-400 mt-2 text-sm">
The script selects the patched Go 1.26.7 toolchain, pins <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">golang.org/x/text v0.39.0</code>, installs the native Yjs collaboration transport while retaining the legacy OT transport for older panels, pins the remaining Go dependencies, writes backups under <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">.tomaxikz-betterfiles-backups</code>, formats the changed Go files, and verifies that Wings builds. If it stops with an error, it found a source shape that needs manual review instead of guessing.
</p>
</div>
<div id="wings-patch" data-wings-panel="patch" class="bg-stone-700 rounded-lg p-4 min-w-0" role="tabpanel" hidden>
<h1 class="text-white font-bold text-xl">Apply a Wings patch</h1>
<p class="text-stone-300 mt-3">
Run these commands inside the root folder of the Wings source repo, the same folder that contains <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">go.mod</code>.
</p>
<p class="text-amber-200 bg-amber-500/10 border border-amber-500/50 rounded-lg p-3 mt-3">
Use the addon patch when you only need Better Files daemon features. Use the full patch only when you want every Tomaxikz daemon feature, because it touches more Wings files and is more likely to conflict with other addons.
</p>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-4">
<div class="bg-stone-800 rounded-lg p-4 border border-emerald-500/50">
<h2 class="text-white font-bold text-lg">Recommended: addon patch</h2>
<p class="text-stone-300 mt-2">Only applies the Better Files Wings changes.</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-3 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">curl -fL -o betterfiles.patch \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/betterfiles.patch"
git apply --3way betterfiles.patch
go version # must report go1.26.7 or newer
go build
sudo systemctl stop wings
sudo install -m 0755 wings /usr/local/bin/wings
sudo systemctl start wings</code></pre>
</div>
</div>
<p class="text-stone-400 mt-3 text-sm">
Addon patch:
<a class="text-emerald-400 hover:text-emerald-300 underline break-all" href="https://github.com/Tomaxikz/daemon/blob/develop/betterfiles.patch">https://github.com/Tomaxikz/daemon/blob/develop/betterfiles.patch</a>
</p>
</div>
<div class="bg-stone-800 rounded-lg p-4 border border-amber-500/40">
<h2 class="text-white font-bold text-lg">Full daemon patch</h2>
<p class="text-stone-300 mt-2">Applies all Tomaxikz Wings features, not only Better Files.</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-3 border-l-4 border-amber-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">curl -fL -o tomaxikz.patch \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/tomaxikz.patch"
git apply --3way tomaxikz.patch
go version # must report go1.26.7 or newer
go build
sudo systemctl stop wings
sudo install -m 0755 wings /usr/local/bin/wings
sudo systemctl start wings</code></pre>
</div>
</div>
<p class="text-stone-400 mt-3 text-sm">
Full patch:
<a class="text-emerald-400 hover:text-emerald-300 underline break-all" href="https://github.com/Tomaxikz/daemon/blob/develop/tomaxikz.patch">https://github.com/Tomaxikz/daemon/blob/develop/tomaxikz.patch</a>
</p>
</div>
</div>
</div>
<div id="wings-manual" data-wings-panel="manual" class="bg-stone-700 rounded-lg p-4 min-w-0" role="tabpanel" hidden>
<h1 class="text-white font-bold text-xl">Edit Wings by hand</h1>
<p class="text-stone-300 mt-3">
Run every command in this tab from the root of the Wings source repo, where <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">go.mod</code> exists. The manual install has three parts: download the new files, add the wiring snippets to the existing Wings files, and run the focused validation commands before installing the binary. Do not edit <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">router/router_download.go</code>; normal single-file downloads and multipart uploads stay available alongside the new routes.
</p>
<p class="text-amber-100 bg-amber-500/10 border border-amber-500/50 rounded-lg p-3 mt-3">
Commit or back up the Wings source tree first. The snippets below match the current <code class="bg-stone-900 rounded-lg px-2 py-1 text-amber-200 text-sm">develop</code> branch. If your Wings version has different surrounding code, use the addon patch as the source of truth and review each hunk instead of creating duplicate routes.
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mt-3">
<p class="text-stone-300 bg-stone-800 rounded-lg p-3">
<code class="default-line bg-stone-900 rounded px-2 py-1 text-sm">Default Wings line</code>
has a gray border and already exists; find or replace it.
</p>
<p class="text-stone-300 bg-stone-800 rounded-lg p-3 border-l-4 border-emerald-500">
<code class="bg-stone-900 rounded px-2 py-1 text-emerald-400 text-sm">New Better Files code</code>
has a green border; add it as instructed.
</p>
</div>
<div class="bg-stone-800 rounded-lg p-4 mt-4 border border-stone-600">
<h2 class="text-white font-bold text-lg">Manual edit order</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mt-3">
<p class="text-stone-300"><span class="text-emerald-400 font-bold">1.</span> Download the Go files and pin every listed dependency.</p>
<p class="text-stone-300"><span class="text-emerald-400 font-bold">2.</span> Edit <code class="bg-stone-900 rounded px-1 text-emerald-400 text-sm">router/router_server_ws.go</code> to preserve collaboration message order.</p>
<p class="text-stone-300"><span class="text-emerald-400 font-bold">3.</span> Edit <code class="bg-stone-900 rounded px-1 text-emerald-400 text-sm">router/router.go</code> for all public and server routes.</p>
<p class="text-stone-300"><span class="text-emerald-400 font-bold">4.</span> Wire the bounded operation manager into <code class="bg-stone-900 rounded px-1 text-emerald-400 text-sm">server/server.go</code>.</p>
<p class="text-stone-300"><span class="text-emerald-400 font-bold">5.</span> Add operation events in <code class="bg-stone-900 rounded px-1 text-emerald-400 text-sm">server/events.go</code> and <code class="bg-stone-900 rounded px-1 text-emerald-400 text-sm">router/websocket/listeners.go</code>.</p>
<p class="text-stone-300"><span class="text-emerald-400 font-bold">6.</span> Edit <code class="bg-stone-900 rounded px-1 text-emerald-400 text-sm">router/websocket/websocket.go</code> for live collaboration.</p>
<p class="text-stone-300"><span class="text-emerald-400 font-bold">7.</span> Edit <code class="bg-stone-900 rounded px-1 text-emerald-400 text-sm">config/config.go</code> for history and collaboration limits.</p>
<p class="text-stone-300"><span class="text-emerald-400 font-bold">8.</span> Edit <code class="bg-stone-900 rounded px-1 text-emerald-400 text-sm">router/router_server_files.go</code> for revision hooks.</p>
<p class="text-stone-300"><span class="text-emerald-400 font-bold">9.</span> Edit <code class="bg-stone-900 rounded px-1 text-emerald-400 text-sm">router/middleware/middleware.go</code> for uploads and archive streams.</p>
<p class="text-stone-300"><span class="text-emerald-400 font-bold">10.</span> Format, test, build, install, and verify Wings.</p>
</div>
</div>
<h2 class="text-white font-bold text-lg mt-6">Download the new Go files</h2>
<p class="text-stone-300 mt-3">
This creates the missing folders and downloads the Better Files, file history, archive, stream, Git, collaboration, resumable upload, safe path, search, fingerprint, operation, and Docker helper files. Keep the test files: they validate traversal protection, archive formats, resumable offsets, cancellation, Unicode search, fingerprints, and atomic copy behavior before deployment.
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">mkdir -p router/websocket router/middleware environment/docker server/filesystem
curl -L -o router/websocket/limiter.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/websocket/limiter.go"
curl -L -o router/websocket/betterfiles_collaboration.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/websocket/betterfiles_collaboration.go"
curl -L -o router/websocket/betterfiles_collaboration_ot.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/websocket/betterfiles_collaboration_ot.go"
curl -L -o router/websocket/betterfiles_collaboration_ot_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/websocket/betterfiles_collaboration_ot_test.go"
curl -L -o router/websocket/file_collaboration_yjs.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/websocket/file_collaboration_yjs.go"
curl -L -o router/websocket/file_collaboration_yjs_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/websocket/file_collaboration_yjs_test.go"
curl -L -o server/file_history.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/server/file_history.go"
curl -L -o server/file_history_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/server/file_history_test.go"
curl -L -o router/router_server_files_revisions.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_files_revisions.go"
curl -L -o router/router_server_git.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_git.go"
curl -L -o router/router_server_git_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_git_test.go"
curl -L -o router/router_server_files_search.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_files_search.go"
curl -L -o router/router_server_betterfiles_collaboration.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_betterfiles_collaboration.go"
curl -L -o router/router_server_archive_nbt.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_archive_nbt.go"
curl -L -o router/router_cdn_stream.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_cdn_stream.go"
curl -L -o router/router_download_helpers.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_download_helpers.go"
curl -L -o router/router_download_helpers_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_download_helpers_test.go"
curl -L -o router/router_system_config.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_system_config.go"
curl -L -o router/router_system_config_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_system_config_test.go"
curl -L -o environment/docker/client_accessor.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/environment/docker/client_accessor.go"
curl -L -o router/betterfiles_entry.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/betterfiles_entry.go"
curl -L -o router/betterfiles_paths.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/betterfiles_paths.go"
curl -L -o router/betterfiles_paths_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/betterfiles_paths_test.go"
curl -L -o router/betterfiles_test_helpers_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/betterfiles_test_helpers_test.go"
curl -L -o router/betterfiles_http_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/betterfiles_http_test.go"
curl -L -o router/middleware/middleware_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/middleware/middleware_test.go"
curl -L -o router/router_openapi.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_openapi.go"
curl -L -o router/router_openapi_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_openapi_test.go"
curl -L -o router/router_resumable_upload.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_resumable_upload.go"
curl -L -o router/router_resumable_upload_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_resumable_upload_test.go"
curl -L -o router/router_download_directory.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_download_directory.go"
curl -L -o router/router_download_directory_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_download_directory_test.go"
curl -L -o router/router_download_directory_http_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_download_directory_http_test.go"
curl -L -o router/router_file_operations.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_file_operations.go"
curl -L -o router/router_file_operations_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_file_operations_test.go"
curl -L -o router/router_server_files_copy_many.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_files_copy_many.go"
curl -L -o router/router_server_files_copy_many_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_files_copy_many_test.go"
curl -L -o router/router_server_files_fingerprints.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_files_fingerprints.go"
curl -L -o router/router_server_files_fingerprints_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_files_fingerprints_test.go"
curl -L -o router/router_server_files_largest.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_files_largest.go"
curl -L -o router/router_server_files_largest_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_files_largest_test.go"
curl -L -o router/router_server_files_rename.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_files_rename.go"
curl -L -o router/router_server_files_search_v2.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_files_search_v2.go"
curl -L -o router/router_server_files_search_v2_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/router_server_files_search_v2_test.go"
curl -L -o router/websocket/listeners_operation_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/router/websocket/listeners_operation_test.go"
curl -L -o server/file_operations.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/server/file_operations.go"
curl -L -o server/file_operations_test.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/server/file_operations_test.go"
curl -L -o server/filesystem/replace.go \
"https://raw.githubusercontent.com/Tomaxikz/daemon/develop/server/filesystem/replace.go"
go get golang.org/x/text@v0.39.0
go get github.com/shiv248/operational-transformation-go@v1.0.0
go get github.com/reearth/ygo@v1.31.0
go get github.com/bmatcuk/doublestar/v4@v4.9.1
go get github.com/dsnet/compress@v0.0.2-0.20230904184137-39efe44ab707
go get github.com/ulikunitz/xz@v0.5.15
go mod tidy</code></pre>
</div>
</div>
<div class="border-t border-stone-600 mt-6 pt-5">
<h2 class="text-white font-bold text-lg">Edit router/router_server_ws.go</h2>
<p class="text-stone-300 mt-3">
Apply the three manual edits below. Gray blocks are original Wings code to find; green blocks are the Better Files replacements. These edits keep authentication, legacy <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">betterfiles:collab:*</code>, and Wings-rs-compatible <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">file collab *</code> events in wire order.
</p>
<h3 class="text-white font-bold mt-5">1. Replace the WebSocket limiter setup</h3>
<p class="text-stone-300 mt-2"><span class="text-stone-200 font-bold">Original Wings code — replace this entire gray block:</span></p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-stone-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-stone-200 text-sm">// There is a separate rate limiter that applies to individual message types
// within the actual websocket logic handler. _This_ rate limiter just exists
// to avoid enormous floods of data through the socket since we need to parse
// JSON each time. This rate limit realistically should never be hit since this
// would require sending 50+ messages a second over the websocket (no more than
// 10 per 200ms).
var throttled bool
rl := rate.NewLimiter(rate.Every(time.Millisecond*200), 10)</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3"><span class="text-emerald-300 font-bold">Better Files replacement — paste this green block in the same location:</span></p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">// There is a separate rate limiter that applies to individual message types
// within the actual websocket logic handler. This limiter protects ordinary
// control traffic. Stateful collaboration events bypass both limiters and are
// processed synchronously below, matching Wings-rs behavior. Silently dropping
// one collaboration chunk would desynchronize the document.
var throttled bool
rl := rate.NewLimiter(rate.Every(time.Millisecond*200), 10)
allowOrdinaryMessage := func() bool {
if !rl.Allow() {
if !throttled {
throttled = true
_ = handler.Connection.WriteJSON(websocket.Message{Event: websocket.ThrottledEvent, Args: []string{"global"}})
}
return false
}
throttled = false
return true
}
handleMessage := func(msg websocket.Message) {
if err := handler.HandleInbound(ctx, msg); err != nil {
if errors.Is(err, server.ErrSuspended) {
cancel()
} else {
_ = handler.SendErrorJson(msg, err)
}
}
}</code></pre>
</div>
</div>
<h3 class="text-white font-bold mt-5">2. Remove the old pre-decode limiter</h3>
<p class="text-stone-300 mt-2"><span class="text-stone-200 font-bold">Original Wings code — delete this entire gray block and do not replace it here:</span></p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-stone-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-stone-200 text-sm">if !rl.Allow() {
if !throttled {
throttled = true
_ = handler.Connection.WriteJSON(websocket.Message{Event: websocket.ThrottledEvent, Args: []string{"global"}})
}
continue
}
throttled = false</code></pre>
</div>
</div>
<h3 class="text-white font-bold mt-5">3. Replace the message decode and dispatch block</h3>
<p class="text-stone-300 mt-2"><span class="text-stone-200 font-bold">Original Wings code — replace this entire gray block:</span></p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-stone-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-stone-200 text-sm">var j websocket.Message
if err := json.Unmarshal(p, &j); err != nil {
continue
}
go func(msg websocket.Message) {
if err := handler.HandleInbound(ctx, msg); err != nil {
if errors.Is(err, server.ErrSuspended) {
cancel()
} else {
_ = handler.SendErrorJson(msg, err)
}
}
}(j)</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3"><span class="text-emerald-300 font-bold">Better Files replacement — paste this green block in the same location:</span></p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">var j websocket.Message
if err := json.Unmarshal(p, &j); err != nil {
allowOrdinaryMessage()
continue
}
if !websocket.IsFileCollaborationEvent(j.Event) && !allowOrdinaryMessage() {
continue
}
// Authentication and collaboration messages are stateful protocols. Keep
// their WebSocket wire order instead of racing them in separate goroutines.
if j.Event == websocket.AuthenticationEvent || websocket.IsFileCollaborationEvent(j.Event) {
handleMessage(j)
continue
}
go handleMessage(j)</code></pre>
</div>
</div>
<p class="text-amber-100 bg-amber-500/10 border border-amber-500/50 rounded-lg p-3 mt-4">
Do not leave the original per-message goroutine in place. Authentication and collaboration messages must use the synchronous branch above; ordinary events still use <code class="bg-stone-900 rounded-lg px-2 py-1 text-amber-200 text-sm">go handleMessage(j)</code>.
</p>
</div>
<div class="border-t border-stone-600 mt-6 pt-5">
<h2 class="text-white font-bold text-lg">Edit router/router.go</h2>
<div class="flex flex-wrap gap-2 items-center mt-3">
<p class="text-stone-300">In</p>
<code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm break-all">/usr/local/dev/wings/router/router.go</code>
</div>
<p class="text-stone-300 mt-3">
First, find <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">router.Use(middleware.AttachServerManager(m), middleware.AttachApiClient(client))</code> and add the public capability document directly after it. The Panel uses this route for exact feature detection.
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">router.GET("/openapi.json", getOpenAPI)</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3">
Find <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">protected.GET("/api/system", getSystemInformation)</code> and put this capability route directly after it. This authenticated endpoint is required for the Panel to select native Yjs instead of legacy OT.
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">protected.GET("/api/system/config", getSystemConfiguration)</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3">
Find the authenticated server routes block. Put this Git block after <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">server.POST("/commands", postServerCommands)</code> and before <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">server.POST("/install", postServerInstall)</code>.
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">git := server.Group("/git")
{
git.GET("/status", getServerGitStatus)
git.POST("/install", postServerGitInstall)
git.POST("/clone", postServerGitClone)
git.POST("/pull", postServerGitPull)
git.POST("/diff", postServerGitDiff)
}</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3">
In the same file, find <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">files := server.Group("/files")</code>. Put these lines inside that block, right after <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">files.GET("/contents", getServerFileContents)</code>.
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">files.GET("/revisions", getServerFileRevisions)
files.GET("/revisions/:revision", getServerFileRevision)
files.POST("/revisions/:revision/restore", postServerFileRevisionRestore)
files.GET("/search", getServerFilesSearch)
files.POST("/search", postServerFilesSearch)
files.GET("/archive/list", getServerArchiveList)
files.POST("/archive/extract", postServerArchiveExtract)
files.GET("/list-directory", getServerListDirectory)
files.POST("/copy-many", postServerCopyMany)
files.GET("/largest-directories", getServerLargestDirectories)
files.GET("/fingerprints", getServerFileFingerprints)
files.DELETE("/operations/:operation", deleteServerFileOperation)
files.POST("/collaboration/revoke", postServerBetterFilesCollaborationRevoke)</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3">
In that same files group, replace this default Wings registration:
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-stone-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-stone-200 text-sm">files.PUT("/rename", putServerRenameFiles)</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3">
Replace it with the safe Better Files handler below. Do not add a second <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">PUT /rename</code> route. This keeps the request API unchanged while adding atomic no-overwrite and symlink protection.
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">files.PUT("/rename", putServerRenameFilesSafe)</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3">
Still in <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">router/router.go</code>, keep the existing single-file download and multipart upload routes. Add the stream and directory routes beside <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">router.GET("/download/file", getDownloadFile)</code>, then add <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">HEAD</code> and <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">PATCH</code> beside the existing <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">router.POST("/upload/file", postServerUploadFiles)</code> route.
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">router.GET("/download/stream", getDownloadStream)
router.GET("/download/directory", getDownloadDirectory)
router.HEAD("/upload/file", headServerUploadFile)
router.PATCH("/upload/file", patchServerUploadFile)</code></pre>
</div>
</div>
<p class="text-yellow-100/90 mt-3">
Leave <code class="bg-stone-900 rounded-lg px-2 py-1 text-yellow-200 text-sm">router.GET("/download/file", getDownloadFile)</code>, <code class="bg-stone-900 rounded-lg px-2 py-1 text-yellow-200 text-sm">router.POST("/upload/file", postServerUploadFiles)</code>, and <code class="bg-stone-900 rounded-lg px-2 py-1 text-yellow-200 text-sm">router/router_download.go</code> alone. The new APIs are additive: media uses <code class="bg-stone-900 rounded-lg px-2 py-1 text-yellow-200 text-sm">/download/stream</code>, directories use <code class="bg-stone-900 rounded-lg px-2 py-1 text-yellow-200 text-sm">/download/directory</code>, and resumable clients use the same signed upload URL with <code class="bg-stone-900 rounded-lg px-2 py-1 text-yellow-200 text-sm">HEAD</code>/<code class="bg-stone-900 rounded-lg px-2 py-1 text-yellow-200 text-sm">PATCH</code>.
</p>
</div>
<div class="border-t border-stone-600 mt-6 pt-5">
<h2 class="text-white font-bold text-lg">Edit server/server.go</h2>
<p class="text-stone-300 mt-3">
The new background copy API needs one bounded operation manager per server. In the <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">Server</code> struct, add the manager beside the filesystem field:
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">fileOperations *FileOperationManager</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3">
In <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">New</code>, initialize it after the resource state and before returning the server. Then cancel outstanding work in <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">CleanupForDestroy</code> immediately after <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">s.CtxCancel()</code>.
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">s.fileOperations = NewFileOperationManager(&s)
if s.fileOperations != nil {
s.fileOperations.CancelAll()
}</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3">Add this accessor outside the constructor and cleanup functions:</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">func (s *Server) FileOperations() *FileOperationManager {
return s.fileOperations
}</code></pre>
</div>
</div>
<p class="text-stone-400 mt-3 text-sm">
<code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">server/file_operations.go</code> enforces the 64-operation per-server limit, cancellation, atomic counters, safe terminal errors, and roughly one progress event per second. Do not create a global manager shared by multiple servers.
</p>
</div>
<div class="border-t border-stone-600 mt-6 pt-5">
<h2 class="text-white font-bold text-lg">Edit server/events.go and router/websocket/listeners.go</h2>
<p class="text-stone-300 mt-3">
Add these constants to the server event list. They are the exact event names expected by compatible clients.
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">OperationProgressEvent = "operation progress"
OperationErrorEvent = "operation error"
OperationCompletedEvent = "operation completed"</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3">
Append all three constants to the allowed event slice in <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">router/websocket/listeners.go</code>. In the event forwarding branch, check for a string argument list before the existing string and byte cases:
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">if args, ok := websocketEventArgs(e.Data); ok {
message.Args = args
} else if str, ok := e.Data.(string); ok {
message.Args = []string{str}
} else if b, ok := e.Data.([]byte); ok {
message.Args = []string{string(b)}
} else {
b, sendErr = json.Marshal(e.Data)
if sendErr == nil {
message.Args = []string{string(b)}
}
}</code></pre>
</div>
</div>
<p class="text-stone-300 mt-3">Finally, add this helper outside <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">listenForServerEvents</code>:</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>
<div class="overflow-x-auto">
<pre><code class="text-white text-sm">func websocketEventArgs(data interface{}) ([]string, bool) {
values, ok := data.([]interface{})
if !ok {
return nil, false
}
args := make([]string, len(values))
for i, value := range values {
arg, ok := value.(string)
if !ok {
return nil, false
}
args[i] = arg
}
return args, true
}</code></pre>
</div>
</div>
<p class="text-stone-400 mt-3 text-sm">
Preserving multiple arguments is required: progress sends <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">[operation_id, JSON snapshot]</code>, errors send <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">[operation_id, safe message]</code>, and completion sends <code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm">[operation_id]</code>.
</p>
</div>
<div class="border-t border-stone-600 mt-6 pt-5">
<h2 class="text-white font-bold text-lg">Edit router/websocket/websocket.go</h2>
<div class="flex flex-wrap gap-2 items-center mt-3">
<p class="text-stone-300">In</p>
<code class="bg-stone-900 rounded-lg px-2 py-1 text-emerald-400 text-sm break-all">/usr/local/dev/wings/router/websocket/websocket.go</code>
</div>
<p class="text-stone-300 mt-3">
Find <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">func (h *Handler) HandleInbound(ctx context.Context, m Message) error</code>. Paste this after the non-auth token validation block and before <code class="default-line bg-stone-900 rounded-lg px-2 py-1 text-sm">switch m.Event</code>.
</p>
<div class="code-block bg-stone-900 rounded-lg p-3 mt-2 border-l-4 border-emerald-500">
<div class="flex justify-end mb-2">
<button onclick="copyButton(this)" class="bg-stone-700 text-white px-3 py-1 rounded cursor-pointer hover:bg-stone-600 transition duration-200 text-sm">
<i class="fa-solid fa-copy"></i> Copy
</button>
</div>