2026-04-16 17:06:36 +08:00
|
|
|
|
# IP
|
|
|
|
|
|
- TA.Netease.com
|
|
|
|
|
|
- SSH:10.145.96.67:32200
|
2026-04-16 21:10:45 +08:00
|
|
|
|
- ssh -p 32200 -i C:\Users\loujiajie\.ssh\netease_loujiajie_id_rsa loujiajie@10.145.96.67
|
2026-04-16 17:06:36 +08:00
|
|
|
|
- Artlib
|
|
|
|
|
|
- SSH:10.145.96.68:32200
|
2026-04-16 21:10:45 +08:00
|
|
|
|
- ssh -p 32200 -i C:\Users\loujiajie\.ssh\netease_loujiajie_id_rsa loujiajie@10.145.96.68
|
2026-04-16 17:06:36 +08:00
|
|
|
|
- Artlib S3
|
|
|
|
|
|
- NOS 桶信息
|
|
|
|
|
|
- 名称:matrixaita
|
|
|
|
|
|
- 项目:artct
|
|
|
|
|
|
- 成本项目:artct
|
|
|
|
|
|
- 区域:GA
|
|
|
|
|
|
- Endpoint:gzdev
|
|
|
|
|
|
- 桶用户:p-artct-matrixaita
|
|
|
|
|
|
- 域名(内网):`nos-gzdev.163nos.com`
|
2026-04-17 10:46:06 +08:00
|
|
|
|
- Access Key:57SCV9Q4MPLXQ3JCL5K9
|
|
|
|
|
|
- Secret Key:4dzMcakyxW2vEhCGjEfiXHtDgxZUiy57D9NqKOOm
|
2026-04-16 17:06:36 +08:00
|
|
|
|
|
2026-04-17 11:30:11 +08:00
|
|
|
|
VPS_SSH_KEY
|
|
|
|
|
|
|
2026-04-16 17:06:36 +08:00
|
|
|
|
## 文档
|
2026-04-16 23:51:17 +08:00
|
|
|
|
- S3:https://sa.nie.netease.com/console/webconsole/idc/
|
|
|
|
|
|
# 垃圾电脑服务
|
|
|
|
|
|
我现在想在局域网里的另一台Ubuntu电脑上部署Unreal Horde服务,存储 缓存以及管理各个worker、节点信息;本机性能强劲,作为worker进行具体工作。
|
|
|
|
|
|
- 电脑ip:10.219.36.57
|
|
|
|
|
|
- 用户名:netease
|
|
|
|
|
|
- 密码:123
|
|
|
|
|
|
|
|
|
|
|
|
官方文档(UE5.5)
|
|
|
|
|
|
- https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-in-unreal-engine?application_version=5.5
|
|
|
|
|
|
- Horde README.md https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Programs/Horde/README.md
|
|
|
|
|
|
- [**Deploying Horde**](https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Programs/Horde/Docs/Deployment.md)
|
|
|
|
|
|
- - 有关 Horde 的架构和组件的信息,以及部署它们的最佳实践。
|
|
|
|
|
|
**受众:** IT、系统管理员、打算修改 Horde 的程序员。
|
|
|
|
|
|
- [**Configuring and Operating Horde**](https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Programs/Horde/Docs/Config.md)
|
|
|
|
|
|
- - 描述如何设置和管理 Horde。
|
|
|
|
|
|
**受众:**构建/开发运营团队、管理员。
|
|
|
|
|
|
- [**Horde Internals**](https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Programs/Horde/Docs/Internals.md)
|
|
|
|
|
|
- - 描述如何构建和修改 Horde 及其架构。
|
|
|
|
|
|
**受众:**希望扩展 Horde 的开发人员。
|
|
|
|
|
|
- 视频
|
|
|
|
|
|
- [Horde and Unreal Build Accelerator: Operating at Epic Scale](https://youtu.be/ZUlwqNbYWBQ?si=DgFvrvepK67v-iTi)
|
|
|
|
|
|
|
|
|
|
|
|
## 下载地址
|
|
|
|
|
|
- 官方下载地址
|
|
|
|
|
|
- 服务端:https://github.com/EpicGames/UnrealEngine/releases/download/5.5.0-release/UnrealHordeServer.msi
|
|
|
|
|
|
|
|
|
|
|
|
# 大致步骤
|
|
|
|
|
|
1. 使用docker部署服务。
|
|
|
|
|
|
2. 服务器配置。
|
|
|
|
|
|
1. 默认端口Http 13340、Http 2 13342。
|
|
|
|
|
|
1. 默认情况下,Horde配置为使用端口5000通过未加密的HTTP提供数据。在默认情况下,代理通过端口5002使用未加密的HTTP/2上的gRPC与Horde服务器通信。这些设置在服务器启动时显示在控制台上。
|
|
|
|
|
|
2. [相关配置](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-orientation-for-unreal-engine?application_version=5.5)
|
|
|
|
|
|
- [服务器配置](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-settings-for-unreal-engine?application_version=5.5#%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%AE%BE%E7%BD%AE)将配置该服务器与其他服务器的通信,定义静态参数等。它由与该服务器一同部署的 `Server.json` 文件驱动。
|
|
|
|
|
|
- [完整配置参数清单](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-settings-for-unreal-engine?application_version=5.5#%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%AE%BE%E7%BD%AE)
|
|
|
|
|
|
- [全局配置](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-schema-for-unreal-engine?application_version=5.5#globals)在部署后控制所有面向用户的元素,该配置存于名为 `Globals.json` 的文件中。设置好部署参数后,大多数配置都在此处完成。
|
|
|
|
|
|
3. [验证相关配置](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-authentication-tutorial-for-unreal-engine?application_version=5.5)
|
|
|
|
|
|
1. [[#OIDC身份验证]]
|
|
|
|
|
|
3. 代理机配置。详见 http://10.219.103.35:13340/docs/Landing.md
|
|
|
|
|
|
1. 配置文件位置:安装目录\Agent\Defaults\agent.json
|
|
|
|
|
|
2.
|
|
|
|
|
|
# 部署笔记
|
|
|
|
|
|
|
|
|
|
|
|
## 部署流程
|
|
|
|
|
|
- [Horde服务器](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/horde-server-for-unreal-engine?application_version=5.5)
|
|
|
|
|
|
|
|
|
|
|
|
# Perforce服务器
|
|
|
|
|
|
ssl:inner02-commit.perforce.nie.netease.com:1667
|
|
|
|
|
|
|
|
|
|
|
|
# OIDC
|
|
|
|
|
|
## Netease OIDC信息
|
|
|
|
|
|
| | |
|
|
|
|
|
|
| ------------- | ---------------------------------------------------------------- |
|
|
|
|
|
|
| client id | 4884b03e951711f0ad370242ac120002 |
|
|
|
|
|
|
| client secret | f2862b8c5ab24085ab0883d119b631604884b354951711f0ad370242ac120002 |
|
|
|
|
|
|
## OIDC 参考代码
|
|
|
|
|
|
```python
|
|
|
|
|
|
#coding:UTF-8
|
|
|
|
|
|
"""
|
|
|
|
|
|
Requirements:
|
|
|
|
|
|
1. Flask >= 0.10.1
|
|
|
|
|
|
2. requests
|
|
|
|
|
|
3. jwkest >= 1.1.7
|
|
|
|
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
|
|
python oidc_code_demo.py -H {listen_address} -p {listen_port}
|
|
|
|
|
|
|
|
|
|
|
|
Help:
|
|
|
|
|
|
python oidc_code_demo.py -h
|
|
|
|
|
|
"""
|
|
|
|
|
|
import os
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
from hashlib import md5
|
|
|
|
|
|
import datetime
|
|
|
|
|
|
from urllib import urlencode
|
|
|
|
|
|
import json
|
|
|
|
|
|
import requests
|
|
|
|
|
|
from flask import Flask, request, jsonify, session, redirect
|
|
|
|
|
|
|
|
|
|
|
|
from jwkest.jwk import SYMKey
|
|
|
|
|
|
from jwkest.jws import JWS
|
|
|
|
|
|
from jwkest.jwk import load_jwks_from_url
|
|
|
|
|
|
from jwkest.jws import NoSuitableSigningKeys
|
|
|
|
|
|
|
|
|
|
|
|
__revision__ = "0.01"
|
|
|
|
|
|
__author__ = "chenxs@corp.netease.com"
|
|
|
|
|
|
|
|
|
|
|
|
OIDC_CLIENT_ID = ""
|
|
|
|
|
|
OIDC_CLIENT_SECRET = ""
|
|
|
|
|
|
OIDC_PROVIDER = "https://login.netease.com/connect"
|
|
|
|
|
|
OIDC_AUTHORIZATION_SERVER = "https://login.netease.com/connect/authorize"
|
|
|
|
|
|
OIDC_TOKEN_ENDPOINT = "https://login.netease.com/connect/token"
|
|
|
|
|
|
OIDC_USERINFO_ENDPOINT = "https://login.netease.com/connect/userinfo"
|
|
|
|
|
|
OIDC_SCOPE = "openid nickname email fullname dep title empno"
|
|
|
|
|
|
OIDC_REDIRECT_URI = "https://127.0.0.1:5000/finish"
|
|
|
|
|
|
OIDC_JWKS_URI = "https://login.netease.com/connect/jwks"
|
|
|
|
|
|
OIDC_ALG = "HS256"
|
|
|
|
|
|
|
|
|
|
|
|
PYTHON_OIDC_DEMO = Flask(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
@PYTHON_OIDC_DEMO.route("/", methods=['GET'])
|
|
|
|
|
|
def index():
|
|
|
|
|
|
"""index"""
|
|
|
|
|
|
if 'username' in session:
|
|
|
|
|
|
body = (
|
|
|
|
|
|
u"</br><h2>OpenID Connect 鐧诲綍鎴愬姛銆<E5A79B></h2></br>"
|
|
|
|
|
|
u"鎮ㄧ殑鐢ㄦ埛鍚嶆槸锛<E6A7B8>%s</br>"
|
|
|
|
|
|
u"鎮ㄧ殑鍏ㄥ悕鏄細%s</br>"
|
|
|
|
|
|
u"鎮ㄧ殑閭鏄細%s</br>"
|
|
|
|
|
|
u"鎮ㄧ殑鑱屼綅鏄細%s</br>"
|
|
|
|
|
|
u"鎮ㄧ殑閮ㄩ棬鏄細%s</br>"
|
|
|
|
|
|
u"鎮ㄧ殑宸ュ彿鏄細%s</br>") % (
|
|
|
|
|
|
session['username'], session.get('fullname', ''),
|
|
|
|
|
|
session.get('email', ''), session.get('title', ''),
|
|
|
|
|
|
session.get('dep', ''), session.get('empno', ''))
|
|
|
|
|
|
body += u"<a href='/login'>鎴虫垜閲嶆柊鐧诲綍</a>"
|
|
|
|
|
|
return body
|
|
|
|
|
|
else:
|
|
|
|
|
|
return u"<a href='/login'>鎴虫垜鐧诲綍</a>"
|
|
|
|
|
|
|
|
|
|
|
|
@PYTHON_OIDC_DEMO.route("/login", methods=['GET'])
|
|
|
|
|
|
def login():
|
|
|
|
|
|
"""AuthN Request"""
|
|
|
|
|
|
session.clear()
|
|
|
|
|
|
now = datetime.datetime.now().strftime("%s")
|
|
|
|
|
|
session['uid'] = uuid.uuid4().hex
|
|
|
|
|
|
session['state'] = session['uid']
|
|
|
|
|
|
session['nonce'] = md5(session['uid'] + now).hexdigest()
|
|
|
|
|
|
|
|
|
|
|
|
authn_request_params = {
|
|
|
|
|
|
'response_type': 'code',
|
|
|
|
|
|
'client_id': OIDC_CLIENT_ID,
|
|
|
|
|
|
'state': session['state'],
|
|
|
|
|
|
'nonce': session['nonce'],
|
|
|
|
|
|
'scope': OIDC_SCOPE,
|
|
|
|
|
|
'redirect_uri': OIDC_REDIRECT_URI,
|
|
|
|
|
|
#'prompt': 'login',
|
|
|
|
|
|
'display': 'touch',
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
redirect_url = "?".join([
|
|
|
|
|
|
OIDC_AUTHORIZATION_SERVER, urlencode(authn_request_params)])
|
|
|
|
|
|
|
|
|
|
|
|
return redirect(redirect_url)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def token_request(code):
|
|
|
|
|
|
"""2. Token Request"""
|
|
|
|
|
|
params = {
|
|
|
|
|
|
'grant_type': 'authorization_code',
|
|
|
|
|
|
'code': code,
|
|
|
|
|
|
'redirect_uri': OIDC_REDIRECT_URI,
|
|
|
|
|
|
'client_id': OIDC_CLIENT_ID,
|
|
|
|
|
|
'client_secret': OIDC_CLIENT_SECRET,
|
|
|
|
|
|
}
|
|
|
|
|
|
_resp = requests.post(OIDC_TOKEN_ENDPOINT, data=params)
|
|
|
|
|
|
return json.loads(_resp.text)
|
|
|
|
|
|
|
|
|
|
|
|
def id_token_verify(id_token, nonce=None):
|
|
|
|
|
|
"""3. id token verify"""
|
|
|
|
|
|
|
|
|
|
|
|
now = int(datetime.datetime.now().strftime("%s"))
|
|
|
|
|
|
if OIDC_ALG == "HS256":
|
|
|
|
|
|
signed_keys = [SYMKey(key=OIDC_CLIENT_SECRET)]
|
|
|
|
|
|
else:
|
|
|
|
|
|
signed_keys = load_jwks_from_url(OIDC_JWKS_URI)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
plain_id_token = JWS().verify_compact(id_token, signed_keys)
|
|
|
|
|
|
except NoSuitableSigningKeys:
|
|
|
|
|
|
# logger the id_token please
|
|
|
|
|
|
return {'error': 'can not verify the id token'}
|
|
|
|
|
|
|
|
|
|
|
|
print "idtoken: %s" % plain_id_token
|
|
|
|
|
|
if nonce:
|
|
|
|
|
|
if (not plain_id_token.has_key('nonce')) or (
|
|
|
|
|
|
plain_id_token['nonce'] != nonce):
|
|
|
|
|
|
return {'error': 'id token nonce not correct'}
|
|
|
|
|
|
if plain_id_token['iss'] != OIDC_PROVIDER:
|
|
|
|
|
|
return {'error': 'id token iss not correct'}
|
|
|
|
|
|
if plain_id_token['aud'] != OIDC_CLIENT_ID:
|
|
|
|
|
|
return {'error': 'id token aud not correct'}
|
|
|
|
|
|
if now >= int(plain_id_token['exp']):
|
|
|
|
|
|
return {'error': 'id token expired'}
|
|
|
|
|
|
|
|
|
|
|
|
return {'id_token': plain_id_token}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@PYTHON_OIDC_DEMO.route("/finish", methods=['GET'])
|
|
|
|
|
|
def finish():
|
|
|
|
|
|
"""
|
|
|
|
|
|
1. AuthN Response
|
|
|
|
|
|
2. Token Request
|
|
|
|
|
|
3. id token verify
|
|
|
|
|
|
4. userinfo request
|
|
|
|
|
|
5. login user
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
# 1. AuthN Response
|
|
|
|
|
|
try:
|
|
|
|
|
|
code = request.args.get('code')
|
|
|
|
|
|
if session['state']:
|
|
|
|
|
|
state = request.args.get('state')
|
|
|
|
|
|
if state != session['state']:
|
|
|
|
|
|
return u"闈炴硶璇锋眰"
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
return u"闈炴硶璇锋眰"
|
|
|
|
|
|
# 2. Token Request
|
|
|
|
|
|
token = token_request(code)
|
|
|
|
|
|
print "token: %s" % token
|
|
|
|
|
|
if token.has_key('error'):
|
|
|
|
|
|
return u"鍑洪敊浜嗭細%s" % str(token)
|
|
|
|
|
|
# 3. id token verify
|
|
|
|
|
|
id_token_verified = id_token_verify(token['id_token'])
|
|
|
|
|
|
if id_token_verified.has_key('error'):
|
|
|
|
|
|
return id_token_verified['error']
|
|
|
|
|
|
else:
|
|
|
|
|
|
id_token = id_token_verified['id_token']
|
|
|
|
|
|
|
|
|
|
|
|
print "id_token: %s" % id_token
|
|
|
|
|
|
|
|
|
|
|
|
# 4. userinfo request
|
|
|
|
|
|
_req_session = requests.Session()
|
|
|
|
|
|
_req_session.headers.update({
|
|
|
|
|
|
"Authorization": "Bearer %s" % token['access_token']})
|
|
|
|
|
|
userinfo_req = _req_session.get(OIDC_USERINFO_ENDPOINT)
|
|
|
|
|
|
userinfo = json.loads(userinfo_req.text)
|
|
|
|
|
|
# login the user
|
|
|
|
|
|
session['username'] = userinfo['nickname']
|
|
|
|
|
|
session['email'] = userinfo['email']
|
|
|
|
|
|
session['title'] = userinfo.get('title', '')
|
|
|
|
|
|
session['empno'] = userinfo.get('empno', '')
|
|
|
|
|
|
session['dep'] = userinfo.get('dep', '')
|
|
|
|
|
|
session['fullname'] = userinfo.get('fullname', '')
|
|
|
|
|
|
return redirect("/")
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import argparse
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
|
usage='%(prog)s [options]', version='%(prog)s ' + str(__revision__))
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
|
'-H', '--host', dest='host', type=str,
|
|
|
|
|
|
help="Specify listening adress, default is 127.0.0.1")
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
|
'-p', '--port', dest='port', type=int,
|
|
|
|
|
|
help="Specify listening port, default is 5000")
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
|
'-c', '--client_id', dest='client_id', type=str,
|
|
|
|
|
|
help="oidc client_id is required.")
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
|
'-s', '--client_secret', dest='client_secret', type=str,
|
|
|
|
|
|
help="oidc client_secret is required.")
|
|
|
|
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
|
|
host = args.host or '127.0.0.1'
|
|
|
|
|
|
port = args.port or 5000
|
|
|
|
|
|
OIDC_REDIRECT_URI = "http://%s:%s/finish" % (host, port)
|
|
|
|
|
|
OIDC_CLIENT_ID = args.client_id
|
|
|
|
|
|
OIDC_CLIENT_SECRET = args.client_secret
|
|
|
|
|
|
if not OIDC_CLIENT_ID or not OIDC_CLIENT_SECRET:
|
|
|
|
|
|
parser.print_help()
|
|
|
|
|
|
parser.exit()
|
|
|
|
|
|
|
|
|
|
|
|
PYTHON_OIDC_DEMO.secret_key = "this is a random secret"
|
|
|
|
|
|
PYTHON_OIDC_DEMO.debug = True
|
|
|
|
|
|
PYTHON_OIDC_DEMO.run(host=host, port=port)
|
|
|
|
|
|
```
|