mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-10-27 10:30:08 +00:00
Compare commits
No commits in common. "c2d6dd923f72cb044704653fc9ce6f1dd521e156" and "fe9f0d1d0e86f55a97e3905358888f595b9bd08b" have entirely different histories.
c2d6dd923f
...
fe9f0d1d0e
17 changed files with 54 additions and 143 deletions
76
.github/workflows/release.yml
vendored
76
.github/workflows/release.yml
vendored
|
|
@ -146,79 +146,3 @@ jobs:
|
|||
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||
overwrite: true
|
||||
prerelease: true
|
||||
|
||||
# =================================
|
||||
# Windows Build
|
||||
# =================================
|
||||
build-windows:
|
||||
name: Build for Windows
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- amd64
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
- name: Build 3X-UI for Windows
|
||||
shell: pwsh
|
||||
run: |
|
||||
$env:CGO_ENABLED="1"
|
||||
$env:GOOS="windows"
|
||||
$env:GOARCH="amd64"
|
||||
go build -ldflags "-w -s" -o xui-release.exe -v main.go
|
||||
|
||||
mkdir x-ui
|
||||
Copy-Item xui-release.exe x-ui\
|
||||
mkdir x-ui\bin
|
||||
cd x-ui\bin
|
||||
|
||||
# Download Xray for Windows
|
||||
$Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v25.6.8/"
|
||||
Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
|
||||
Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
|
||||
Remove-Item "Xray-windows-64.zip"
|
||||
Remove-Item geoip.dat, geosite.dat -ErrorAction SilentlyContinue
|
||||
Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip.dat"
|
||||
Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite.dat"
|
||||
Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -OutFile "geoip_IR.dat"
|
||||
Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -OutFile "geosite_IR.dat"
|
||||
Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip_RU.dat"
|
||||
Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite_RU.dat"
|
||||
Rename-Item xray.exe xray-windows-amd64.exe
|
||||
cd ..
|
||||
Copy-Item -Path ..\windows_files\* -Destination . -Recurse
|
||||
cd ..
|
||||
|
||||
- name: Package to Zip
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path .\x-ui -DestinationPath "x-ui-windows-amd64.zip"
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: x-ui-windows-amd64
|
||||
path: ./x-ui-windows-amd64.zip
|
||||
|
||||
- name: Upload files to GH release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
if: |
|
||||
(github.event_name == 'release' && github.event.action == 'published') ||
|
||||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
file: x-ui-windows-amd64.zip
|
||||
asset_name: x-ui-windows-amd64.zip
|
||||
overwrite: true
|
||||
prerelease: true
|
||||
|
|
@ -12,11 +12,11 @@ type Protocol string
|
|||
const (
|
||||
VMESS Protocol = "vmess"
|
||||
VLESS Protocol = "vless"
|
||||
Tunnel Protocol = "tunnel"
|
||||
DOKODEMO Protocol = "dokodemo-door"
|
||||
HTTP Protocol = "http"
|
||||
Trojan Protocol = "trojan"
|
||||
Shadowsocks Protocol = "shadowsocks"
|
||||
Mixed Protocol = "mixed"
|
||||
Socks Protocol = "socks"
|
||||
WireGuard Protocol = "wireguard"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"inbounds": [
|
||||
{
|
||||
"port": 10808,
|
||||
"protocol": "mixed",
|
||||
"protocol": "socks",
|
||||
"settings": {
|
||||
"auth": "noauth",
|
||||
"udp": true,
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
],
|
||||
"enabled": true
|
||||
},
|
||||
"tag": "mixed"
|
||||
"tag": "socks"
|
||||
},
|
||||
{
|
||||
"port": 10809,
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ class DBInbound {
|
|||
return this.protocol === Protocols.SHADOWSOCKS;
|
||||
}
|
||||
|
||||
get isMixed() {
|
||||
return this.protocol === Protocols.MIXED;
|
||||
get isSocks() {
|
||||
return this.protocol === Protocols.SOCKS;
|
||||
}
|
||||
|
||||
get isHTTP() {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ const Protocols = {
|
|||
VLESS: 'vless',
|
||||
TROJAN: 'trojan',
|
||||
SHADOWSOCKS: 'shadowsocks',
|
||||
TUNNEL: 'tunnel',
|
||||
MIXED: 'mixed',
|
||||
DOKODEMO: 'dokodemo-door',
|
||||
SOCKS: 'socks',
|
||||
HTTP: 'http',
|
||||
WIREGUARD: 'wireguard',
|
||||
};
|
||||
|
|
@ -729,7 +729,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
|||
constructor(
|
||||
show = false,
|
||||
xver = 0,
|
||||
target = 'google.com:443',
|
||||
dest = 'google.com:443',
|
||||
serverNames = 'google.com,www.google.com',
|
||||
privateKey = '',
|
||||
minClientVer = '',
|
||||
|
|
@ -742,7 +742,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
|||
super();
|
||||
this.show = show;
|
||||
this.xver = xver;
|
||||
this.target = target;
|
||||
this.dest = dest;
|
||||
this.serverNames = Array.isArray(serverNames) ? serverNames.join(",") : serverNames;
|
||||
this.privateKey = privateKey;
|
||||
this.minClientVer = minClientVer;
|
||||
|
|
@ -767,7 +767,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
|||
return new RealityStreamSettings(
|
||||
json.show,
|
||||
json.xver,
|
||||
json.target,
|
||||
json.dest,
|
||||
json.serverNames,
|
||||
json.privateKey,
|
||||
json.minClientVer,
|
||||
|
|
@ -783,7 +783,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
|||
return {
|
||||
show: this.show,
|
||||
xver: this.xver,
|
||||
target: this.target,
|
||||
dest: this.dest,
|
||||
serverNames: this.serverNames.split(","),
|
||||
privateKey: this.privateKey,
|
||||
minClientVer: this.minClientVer,
|
||||
|
|
@ -1712,8 +1712,8 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||
case Protocols.VLESS: return new Inbound.VLESSSettings(protocol);
|
||||
case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol);
|
||||
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol);
|
||||
case Protocols.TUNNEL: return new Inbound.TunnelSettings(protocol);
|
||||
case Protocols.MIXED: return new Inbound.MixedSettings(protocol);
|
||||
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol);
|
||||
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
|
||||
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
||||
case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol);
|
||||
default: return null;
|
||||
|
|
@ -1726,8 +1726,8 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||
case Protocols.VLESS: return Inbound.VLESSSettings.fromJson(json);
|
||||
case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json);
|
||||
case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json);
|
||||
case Protocols.TUNNEL: return Inbound.TunnelSettings.fromJson(json);
|
||||
case Protocols.MIXED: return Inbound.MixedSettings.fromJson(json);
|
||||
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json);
|
||||
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
|
||||
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
||||
case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json);
|
||||
default: return null;
|
||||
|
|
@ -2327,7 +2327,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||
|
||||
};
|
||||
|
||||
Inbound.TunnelSettings = class extends Inbound.Settings {
|
||||
Inbound.DokodemoSettings = class extends Inbound.Settings {
|
||||
constructor(
|
||||
protocol,
|
||||
address,
|
||||
|
|
@ -2345,8 +2345,8 @@ Inbound.TunnelSettings = class extends Inbound.Settings {
|
|||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new Inbound.TunnelSettings(
|
||||
Protocols.TUNNEL,
|
||||
return new Inbound.DokodemoSettings(
|
||||
Protocols.DOKODEMO,
|
||||
json.address,
|
||||
json.port,
|
||||
XrayCommonClass.toHeaders(json.portMap),
|
||||
|
|
@ -2366,8 +2366,8 @@ Inbound.TunnelSettings = class extends Inbound.Settings {
|
|||
}
|
||||
};
|
||||
|
||||
Inbound.MixedSettings = class extends Inbound.Settings {
|
||||
constructor(protocol, auth = 'password', accounts = [new Inbound.MixedSettings.SocksAccount()], udp = false, ip = '127.0.0.1') {
|
||||
Inbound.SocksSettings = class extends Inbound.Settings {
|
||||
constructor(protocol, auth = 'password', accounts = [new Inbound.SocksSettings.SocksAccount()], udp = false, ip = '127.0.0.1') {
|
||||
super(protocol);
|
||||
this.auth = auth;
|
||||
this.accounts = accounts;
|
||||
|
|
@ -2387,11 +2387,11 @@ Inbound.MixedSettings = class extends Inbound.Settings {
|
|||
let accounts;
|
||||
if (json.auth === 'password') {
|
||||
accounts = json.accounts.map(
|
||||
account => Inbound.MixedSettings.SocksAccount.fromJson(account)
|
||||
account => Inbound.SocksSettings.SocksAccount.fromJson(account)
|
||||
)
|
||||
}
|
||||
return new Inbound.MixedSettings(
|
||||
Protocols.MIXED,
|
||||
return new Inbound.SocksSettings(
|
||||
Protocols.SOCKS,
|
||||
json.auth,
|
||||
accounts,
|
||||
json.udp,
|
||||
|
|
@ -2408,7 +2408,7 @@ Inbound.MixedSettings = class extends Inbound.Settings {
|
|||
};
|
||||
}
|
||||
};
|
||||
Inbound.MixedSettings.SocksAccount = class extends XrayCommonClass {
|
||||
Inbound.SocksSettings.SocksAccount = class extends XrayCommonClass {
|
||||
constructor(user = RandomUtil.randomSeq(10), pass = RandomUtil.randomSeq(10)) {
|
||||
super();
|
||||
this.user = user;
|
||||
|
|
@ -2416,7 +2416,7 @@ Inbound.MixedSettings.SocksAccount = class extends XrayCommonClass {
|
|||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new Inbound.MixedSettings.SocksAccount(json.user, json.pass);
|
||||
return new Inbound.SocksSettings.SocksAccount(json.user, json.pass);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const Protocols = {
|
|||
VLESS: "vless",
|
||||
Trojan: "trojan",
|
||||
Shadowsocks: "shadowsocks",
|
||||
Mixed: "mixed",
|
||||
Socks: "socks",
|
||||
HTTP: "http",
|
||||
Wireguard: "wireguard"
|
||||
};
|
||||
|
|
@ -643,7 +643,7 @@ class Outbound extends CommonClass {
|
|||
Protocols.Trojan,
|
||||
Protocols.Shadowsocks,
|
||||
Protocols.HTTP,
|
||||
Protocols.Mixed
|
||||
Protocols.Socks
|
||||
].includes(this.protocol);
|
||||
}
|
||||
|
||||
|
|
@ -652,7 +652,7 @@ class Outbound extends CommonClass {
|
|||
}
|
||||
|
||||
hasServers() {
|
||||
return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Mixed, Protocols.HTTP].includes(this.protocol);
|
||||
return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Socks, Protocols.HTTP].includes(this.protocol);
|
||||
}
|
||||
|
||||
hasAddressPort() {
|
||||
|
|
@ -662,13 +662,13 @@ class Outbound extends CommonClass {
|
|||
Protocols.VLESS,
|
||||
Protocols.Trojan,
|
||||
Protocols.Shadowsocks,
|
||||
Protocols.Mixed,
|
||||
Protocols.Socks,
|
||||
Protocols.HTTP
|
||||
].includes(this.protocol);
|
||||
}
|
||||
|
||||
hasUsername() {
|
||||
return [Protocols.Mixed, Protocols.HTTP].includes(this.protocol);
|
||||
return [Protocols.Socks, Protocols.HTTP].includes(this.protocol);
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
|
|
@ -847,7 +847,7 @@ Outbound.Settings = class extends CommonClass {
|
|||
case Protocols.VLESS: return new Outbound.VLESSSettings();
|
||||
case Protocols.Trojan: return new Outbound.TrojanSettings();
|
||||
case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
|
||||
case Protocols.Mixed: return new Outbound.MixedSettings();
|
||||
case Protocols.Socks: return new Outbound.SocksSettings();
|
||||
case Protocols.HTTP: return new Outbound.HttpSettings();
|
||||
case Protocols.Wireguard: return new Outbound.WireguardSettings();
|
||||
default: return null;
|
||||
|
|
@ -863,7 +863,7 @@ Outbound.Settings = class extends CommonClass {
|
|||
case Protocols.VLESS: return Outbound.VLESSSettings.fromJson(json);
|
||||
case Protocols.Trojan: return Outbound.TrojanSettings.fromJson(json);
|
||||
case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
|
||||
case Protocols.Mixed: return Outbound.MixedSettings.fromJson(json);
|
||||
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
|
||||
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
|
||||
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
|
||||
default: return null;
|
||||
|
|
@ -1141,7 +1141,7 @@ Outbound.ShadowsocksSettings = class extends CommonClass {
|
|||
}
|
||||
};
|
||||
|
||||
Outbound.MixedSettings = class extends CommonClass {
|
||||
Outbound.SocksSettings = class extends CommonClass {
|
||||
constructor(address, port, user, pass) {
|
||||
super();
|
||||
this.address = address;
|
||||
|
|
@ -1153,7 +1153,7 @@ Outbound.MixedSettings = class extends CommonClass {
|
|||
static fromJson(json = {}) {
|
||||
let servers = json.servers;
|
||||
if (ObjectUtil.isArrEmpty(servers)) servers = [{ users: [{}] }];
|
||||
return new Outbound.MixedSettings(
|
||||
return new Outbound.SocksSettings(
|
||||
servers[0].address,
|
||||
servers[0].port,
|
||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||
|
|
|
|||
|
|
@ -83,14 +83,14 @@
|
|||
{{template "form/shadowsocks"}}
|
||||
</template>
|
||||
|
||||
<!-- tunnel -->
|
||||
<template v-if="inbound.protocol === Protocols.TUNNEL">
|
||||
{{template "form/tunnel"}}
|
||||
<!-- dokodemo-door -->
|
||||
<template v-if="inbound.protocol === Protocols.DOKODEMO">
|
||||
{{template "form/dokodemo"}}
|
||||
</template>
|
||||
|
||||
<!-- mixed -->
|
||||
<template v-if="inbound.protocol === Protocols.MIXED">
|
||||
{{template "form/mixed"}}
|
||||
<!-- socks -->
|
||||
<template v-if="inbound.protocol === Protocols.SOCKS">
|
||||
{{template "form/socks"}}
|
||||
</template>
|
||||
|
||||
<!-- http -->
|
||||
|
|
|
|||
|
|
@ -241,9 +241,9 @@
|
|||
</template>
|
||||
</template>
|
||||
|
||||
<!-- Servers (trojan/shadowsocks/mixed/http) settings -->
|
||||
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
|
||||
<template v-if="outbound.hasServers()">
|
||||
<!-- http / mixed -->
|
||||
<!-- http / socks -->
|
||||
<template v-if="outbound.hasUsername()">
|
||||
<a-form-item label='{{ i18n "username" }}'>
|
||||
<a-input v-model.trim="outbound.settings.user"></a-input>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{{define "form/tunnel"}}
|
||||
{{define "form/dokodemo"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
||||
<a-input v-model.trim="inbound.settings.address"></a-input>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{{define "form/mixed"}}
|
||||
{{define "form/socks"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
|
||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<td width="45%">{{ i18n "username" }}</td>
|
||||
<td width="45%">{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-button icon="plus" size="small" @click="inbound.settings.addAccount(new Inbound.MixedSettings.SocksAccount())"></a-button>
|
||||
<a-button icon="plus" size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())"></a-button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Target'>
|
||||
<a-input v-model.trim="inbound.stream.reality.target"></a-input>
|
||||
<a-form-item label='Dest (Target)'>
|
||||
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='SNI'>
|
||||
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@
|
|||
<code>[[ link.link ]]</code>
|
||||
</tr-info-row>
|
||||
</template>
|
||||
<table v-if="inbound.protocol == Protocols.TUNNEL" class="tr-info-table">
|
||||
<table v-if="inbound.protocol == Protocols.DOKODEMO" class="tr-info-table">
|
||||
<tr>
|
||||
<th>{{ i18n "pages.inbounds.targetAddress" }}</th>
|
||||
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
|
||||
|
|
@ -376,7 +376,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table v-if="dbInbound.isMixed" class="tr-info-table">
|
||||
<table v-if="dbInbound.isSocks" class="tr-info-table">
|
||||
<tr>
|
||||
<th>{{ i18n "password" }} Auth</th>
|
||||
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
||||
|
|
|
|||
|
|
@ -572,7 +572,7 @@
|
|||
serverObj = o.settings.vnext;
|
||||
break;
|
||||
case Protocols.HTTP:
|
||||
case Protocols.Mixed:
|
||||
case Protocols.Socks:
|
||||
case Protocols.Shadowsocks:
|
||||
case Protocols.Trojan:
|
||||
serverObj = o.settings.servers;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
"tag": "api",
|
||||
"listen": "127.0.0.1",
|
||||
"port": 62789,
|
||||
"protocol": "tunnel",
|
||||
"protocol": "dokodemo-door",
|
||||
"settings": {
|
||||
"address": "127.0.0.1"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2129,8 +2129,8 @@ func (t *Tgbot) getInboundsAddClient() (*telego.InlineKeyboardMarkup, error) {
|
|||
}
|
||||
|
||||
excludedProtocols := map[model.Protocol]bool{
|
||||
model.Tunnel: true,
|
||||
model.Mixed: true,
|
||||
model.DOKODEMO: true,
|
||||
model.Socks: true,
|
||||
model.WireGuard: true,
|
||||
model.HTTP: true,
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,13 +0,0 @@
|
|||
you can't install fail2ban on windows
|
||||
we don't have bash menu for windows
|
||||
if you forgot your password you need to check your database with https://sqlitebrowser.org/
|
||||
the app need to be open all the time
|
||||
|
||||
default setting:
|
||||
http://localhost:2053/
|
||||
user: admin
|
||||
pass: admin
|
||||
port: 2053
|
||||
|
||||
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt
|
||||
Loading…
Reference in a new issue