Compare commits

..

8 Commits

Author SHA1 Message Date
Alireza Ahmadi
9d93468332 v1.10.1 2026-02-07 15:51:08 +01:00
Alireza Ahmadi
5e28644ec7 Merge pull request #1615 from sigseg5/main
Add CONTRIBUTING.md guideline with Docker and local development setup
2026-02-07 15:32:12 +01:00
sigseg5
9b08f181a6 add contributing guideline with docker and local developer setup 2026-02-06 22:42:51 +03:00
Alireza Ahmadi
1a0c39693e fix: trim whitespace
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
2026-02-03 20:54:42 +01:00
Alireza Ahmadi
18662b9c71 xray-core v26.2.2 2026-02-03 20:50:20 +01:00
Alireza Ahmadi
c39ad9cac1 Finalmask: Add XICMP
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
2026-02-03 20:48:08 +01:00
Alireza Ahmadi
b8f2195a3a fix timelocation for windows 2026-02-02 16:41:51 +01:00
Alireza Ahmadi
57d437dff8 corrections 2026-02-02 16:22:21 +01:00
17 changed files with 167 additions and 57 deletions

View File

@@ -86,7 +86,7 @@ jobs:
cd x-ui/bin
# Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.1.31/"
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.2.6/"
if [ "${{ matrix.platform }}" == "amd64" ]; then
wget -q ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip
@@ -182,7 +182,7 @@ jobs:
cd x-ui\bin
# Download Xray for Windows
$Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.1.31/"
$Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.2.6/"
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"

3
.gitignore vendored
View File

@@ -14,4 +14,5 @@ dist/
release/
/release.sh
/x-ui
.DS_Store
.DS_Store
/dev/

94
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,94 @@
# Contribution guide
This document describes **development-only** setup options for contributing to the x-ui project.
Production deployments are **out of scope** for this guide.
For safety, Docker-based development is **strongly recommended**.
Local installation should be performed **only inside a VM or disposable environment**.
---
## Recommended: Docker development setup
### Prerequisites
- Docker
- Docker Compose (v2+)
- Git
### Steps
```bash
git clone https://github.com/alireza0/x-ui.git
# or:
# git clone https://github.com/<your_fork_name>/x-ui.git
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
```
### Explore panel
- Panel should be available at localhost:54321 like: [http://127.0.0.1:54321](http://127.0.0.1:54321) for example
- Default credentials: `admin` / `admin`
To stop and remove containers or `ctrl` + `C`:
```bash
docker compose down
```
### Environment
When using the Docker development setup, the [docker-compose.dev.yml](/docker-compose.dev.yml) file mounts the local database directory into the container:
```yaml
volumes:
- ./dev/db:/etc/x-ui
```
This means:
- [./dev/db](/dev/db/) on the host is used to persist panel state during development
- `/etc/x-ui` inside the container is the active database/config directory
- Data will survive container restarts but is isolated from the host system
- You can safely delete `./dev/db` to reset the development database
---
## Local development setup (unsafe on host OS)
This method runs scripts with root privileges and alters the host system.
Use **only inside a VM, container, or disposable test environment**.
### Prerequisites
- Ubuntu/Debian or Fedora (actually anything with systemd, just use proper package manager)
### Install dependencies
```bash
# Ubuntu / Debian
sudo apt update
sudo apt upgrade -y
sudo apt install -y golang unzip git wget
# Fedora
sudo dnf update -y
sudo dnf install -y golang unzip git wget
```
### Setup panel
```bash
git clone https://github.com/alireza0/x-ui.git
# or:
# git clone https://github.com/<your_fork_name>/x-ui.git
cd x-ui/
sudo bash x-ui.sh # Select -> 10. Start
```
### Explore panel
- Panel should be available at localhost:54321 like: [http://127.0.0.1:54321](http://127.0.0.1:54321) for example
- Default credentials: `admin` / `admin`

View File

@@ -23,7 +23,7 @@ case $1 in
esac
mkdir -p build/bin
cd build/bin
wget -q "https://github.com/XTLS/Xray-core/releases/download/v26.1.31/Xray-linux-${ARCH}.zip"
wget -q "https://github.com/XTLS/Xray-core/releases/download/v26.2.6/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat LICENSE README.md
mv xray "xray-linux-${FNAME}"

View File

@@ -1 +1 @@
1.10.0
1.10.1

15
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,15 @@
services:
xui:
build:
context: .
target: builder
working_dir: /app
volumes:
- .:/app
- ./dev/db:/etc/x-ui
environment:
XUI_BIN_FOLDER: /app/dev/bin
XUI_DB_FOLDER: /etc/x-ui
XUI_LOG_LEVEL: debug
XUI_DEBUG: "true"
command: ["go","run","./main.go"]

10
go.mod
View File

@@ -12,13 +12,13 @@ require (
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.4
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.25.12
github.com/xtls/xray-core v1.260131.0
github.com/shirou/gopsutil/v4 v4.26.1
github.com/xtls/xray-core v1.260202.0
go.uber.org/atomic v1.11.0
golang.org/x/text v0.33.0
google.golang.org/grpc v1.78.0
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.0
gorm.io/gorm v1.31.1
)
require (
@@ -30,13 +30,13 @@ require (
github.com/cloudflare/circl v1.6.3 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect

10
go.sum
View File

@@ -21,6 +21,8 @@ github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI=
@@ -52,6 +54,8 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -134,6 +138,8 @@ github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4
github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -161,6 +167,8 @@ github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 h1:UXjrmniKlY+ZbIqpN9
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
github.com/xtls/xray-core v1.260131.0 h1:gPBykLhUvRZ8sfubNerkwWqV3c15UtmSYQG2cgKqrV4=
github.com/xtls/xray-core v1.260131.0/go.mod h1:cxzYFZrxu1B1NtPjHsqv4UzgDvRA71mV4rXYH4KtO7Q=
github.com/xtls/xray-core v1.260202.0 h1:dYduYxGlkn/krSQJbmksbTtCdRe8OFb3YwpuXXEJG5c=
github.com/xtls/xray-core v1.260202.0/go.mod h1:cxzYFZrxu1B1NtPjHsqv4UzgDvRA71mV4rXYH4KtO7Q=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
@@ -235,6 +243,8 @@ gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=

View File

@@ -596,7 +596,6 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion = TLS_VERSION_OPTION.TLS13,
cipherSuites = '',
rejectUnknownSni = false,
pinnedPeerCertSha256 = [],
disableSystemRoot = false,
enableSessionResumption = false,
certificates = [new TlsStreamSettings.Cert()],
@@ -611,7 +610,6 @@ class TlsStreamSettings extends XrayCommonClass {
this.maxVersion = maxVersion;
this.cipherSuites = cipherSuites;
this.rejectUnknownSni = rejectUnknownSni;
this.pinnedPeerCertSha256 = pinnedPeerCertSha256;
this.disableSystemRoot = disableSystemRoot;
this.enableSessionResumption = enableSessionResumption;
this.certs = certificates;
@@ -645,7 +643,6 @@ class TlsStreamSettings extends XrayCommonClass {
json.maxVersion,
json.cipherSuites,
json.rejectUnknownSni,
json.pinnedPeerCertSha256 || [],
json.disableSystemRoot,
json.enableSessionResumption,
certs,
@@ -663,7 +660,6 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion: this.maxVersion,
cipherSuites: this.cipherSuites,
rejectUnknownSni: this.rejectUnknownSni,
pinnedPeerCertSha256: this.pinnedPeerCertSha256.length > 0 ? this.pinnedPeerCertSha256 : undefined,
disableSystemRoot: this.disableSystemRoot,
enableSessionResumption: this.enableSessionResumption,
certificates: TlsStreamSettings.toJsonArray(this.certs),
@@ -984,6 +980,8 @@ class UdpMask extends XrayCommonClass {
case 'header-dns':
case 'xdns':
return { domain: settings.domain || '' };
case 'xicmp':
return { ip: settings.ip || '', id: settings.id ?? 0 };
case 'mkcp-original':
case 'header-dtls':
case 'header-srtp':
@@ -1309,14 +1307,6 @@ class Inbound extends XrayCommonClass {
return null;
}
get kcpType() {
return this.stream.kcp.type;
}
get kcpSeed() {
return this.stream.kcp.seed;
}
get serviceName() {
return this.stream.grpc.serviceName;
}
@@ -1392,8 +1382,6 @@ class Inbound extends XrayCommonClass {
}
} else if (network === 'kcp') {
const kcp = this.stream.kcp;
obj.type = kcp.type;
obj.path = kcp.seed;
} else if (network === 'ws') {
const ws = this.stream.ws;
obj.path = ws.path;
@@ -1456,8 +1444,6 @@ class Inbound extends XrayCommonClass {
break;
case "kcp":
const kcp = this.stream.kcp;
params.set("headerType", kcp.type);
params.set("seed", kcp.seed);
break;
case "ws":
const ws = this.stream.ws;
@@ -1561,8 +1547,6 @@ class Inbound extends XrayCommonClass {
break;
case "kcp":
const kcp = this.stream.kcp;
params.set("headerType", kcp.type);
params.set("seed", kcp.seed);
break;
case "ws":
const ws = this.stream.ws;
@@ -1642,8 +1626,6 @@ class Inbound extends XrayCommonClass {
break;
case "kcp":
const kcp = this.stream.kcp;
params.set("headerType", kcp.type);
params.set("seed", kcp.seed);
break;
case "ws":
const ws = this.stream.ws;

View File

@@ -345,7 +345,8 @@ class TlsStreamSettings extends CommonClass {
fingerprint = '',
allowInsecure = false,
echConfigList = '',
verifyPeerCertByName = []
verifyPeerCertByName = 'cloudflare-dns.com',
pinnedPeerCertSha256 = '',
) {
super();
this.serverName = serverName;
@@ -353,7 +354,8 @@ class TlsStreamSettings extends CommonClass {
this.fingerprint = fingerprint;
this.allowInsecure = allowInsecure;
this.echConfigList = echConfigList;
this.verifyPeerCertByName = Array.isArray(verifyPeerCertByName) ? verifyPeerCertByName.join(",") : verifyPeerCertByName;
this.verifyPeerCertByName = verifyPeerCertByName;
this.pinnedPeerCertSha256 = pinnedPeerCertSha256;
}
static fromJson(json = {}) {
@@ -363,7 +365,8 @@ class TlsStreamSettings extends CommonClass {
json.fingerprint,
json.allowInsecure,
json.echConfigList,
json.verifyPeerCertByName
json.verifyPeerCertByName,
json.pinnedPeerCertSha256,
);
}
@@ -374,7 +377,8 @@ class TlsStreamSettings extends CommonClass {
fingerprint: this.fingerprint,
allowInsecure: this.allowInsecure,
echConfigList: this.echConfigList,
verifyPeerCertByName: this.verifyPeerCertByName.length > 0 ? this.verifyPeerCertByName.split(",") : undefined,
verifyPeerCertByName: this.verifyPeerCertByName,
pinnedPeerCertSha256: this.pinnedPeerCertSha256
};
}
}
@@ -577,6 +581,8 @@ class UdpMask extends CommonClass {
case 'header-dns':
case 'xdns':
return { domain: settings.domain || '' };
case 'xicmp':
return { ip: settings.ip || '', id: settings.id ?? 0 };
case 'mkcp-original':
case 'header-dtls':
case 'header-srtp':

View File

@@ -586,8 +586,13 @@
<a-form-item label="Allow Insecure">
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
</a-form-item>
<a-form-item label="VerifyPeerCertByNames">
<a-input v-model.trim="outbound.stream.tls.verifyPeerCertByNames"></a-input>
<a-form-item label="Verify Peer Cert By Name">
<a-input v-model.trim="outbound.stream.tls.verifyPeerCertByName"></a-input>
</a-form-item>
<a-form-item label="Pinned Peer Cert">
<a-input v-model.trim="outbound.stream.tls.pinnedPeerCertSha256"
placeholder="Enter SHA256 fingerprints (base64)">
</a-input>
</a-form-item>
</template>

View File

@@ -48,6 +48,9 @@
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp'].includes(inbound.stream.network)"
value="xdns">
xDNS (Experimental)</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="xicmp">
xICMP (Experimental)</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Password'

View File

@@ -58,12 +58,6 @@
<a-form-item label="Session Resumption">
<a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch>
</a-form-item>
<a-form-item label="Pinned Peer Cert">
<a-select mode="tags" v-model="inbound.stream.tls.pinnedPeerCertSha256"
:dropdown-class-name="themeSwitcher.currentTheme"
placeholder="Enter SHA256 fingerprints (base64)">
</a-select>
</a-form-item>
<a-divider :style="{ margin: '0' }"></a-divider>
<template v-for="cert,index in inbound.stream.tls.certs">
<a-form-item label='{{ i18n "certificate" }}'>

View File

@@ -51,12 +51,7 @@
<a-tag>[[ inbound.stream.xhttp.mode ]]</a-tag>
</td>
</tr>
</template>
<template v-if="inbound.isKcp">
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.kcpType ]]</a-tag></td></tr>
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag>[[ inbound.kcpSeed ]]</a-tag></td></tr>
</template>
</template>
<template v-if="inbound.isGrpc">
<tr><td>grpc serviceName</td><td>
<a-tooltip :title="[[ inbound.serviceName ]]">
@@ -443,4 +438,4 @@
});
</script>
{{end}}
{{end}}

View File

@@ -220,13 +220,13 @@
newRule = {};
rule.type = "field";
rule.domainMatcher = value.domainMatcher;
rule.domain = value.domain.length>0 ? value.domain.split(',') : [];
rule.ip = value.ip.length>0 ? value.ip.split(',') : [];
rule.domain = value.domain.length>0 ? value.domain.split(',').map(s => s.trim()) : [];
rule.ip = value.ip.length>0 ? value.ip.split(',').map(s => s.trim()) : [];
rule.port = value.port;
rule.sourcePort = value.sourcePort;
rule.network = value.network;
rule.source = value.source.length>0 ? value.source.split(',') : [];
rule.user = value.user.length>0 ? value.user.split(',') : [];
rule.source = value.source.length>0 ? value.source.split(',').map(s => s.trim()) : [];
rule.user = value.user.length>0 ? value.user.split(',').map(s => s.trim()) : [];
rule.inboundTag = value.inboundTag;
rule.protocol = value.protocol;
rule.attrs = Object.fromEntries(value.attrs);

View File

@@ -48,10 +48,10 @@ func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
return nil
}
func createTemplateData(params []string, seperator ...string) map[string]interface{} {
func createTemplateData(params []string, separator ...string) map[string]interface{} {
var sep string = "=="
if len(seperator) > 0 {
sep = seperator[0]
if len(separator) > 0 {
sep = separator[0]
}
templateData := make(map[string]interface{})

View File

@@ -359,7 +359,12 @@ func (s *SettingService) GetTimeLocation() (*time.Location, error) {
if err != nil {
defaultLocation := defaultValueMap["timeLocation"]
logger.Errorf("location <%v> not exist, using default location: %v", l, defaultLocation)
return time.LoadLocation(defaultLocation)
location, err = time.LoadLocation(defaultLocation)
if err != nil {
logger.Errorf("failed to load default location, using UTC: %v", err)
return time.UTC, nil
}
return location, nil
}
return location, nil
}