Add serverip= config option to pin the domain to a LAN IP

The "unsupported protocol" failure was never about HTTPS: the Vita
resolved the public domain to the WAN IP, the router (no NAT loopback)
answered with its own admin page, and curl reported the resulting
HTTP/0.9 garbage as CURLE_UNSUPPORTED_PROTOCOL. serverip= pins the
hostname to the LAN IP via CURLOPT_RESOLVE, keeping Host/SNI intact
for the reverse proxy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sads
2026-06-12 00:07:08 +02:00
co-authored by Claude Fable 5
parent 73835fa2f6
commit bb137f2da9
4 changed files with 51 additions and 2 deletions
+2 -1
View File
@@ -11,7 +11,7 @@ endif()
project(vitaImmich C) project(vitaImmich C)
include("${VITASDK}/share/vita.cmake" REQUIRED) include("${VITASDK}/share/vita.cmake" REQUIRED)
set(VITA_APP_NAME "Vita Immich") set(VITA_APP_NAME "Immich")
set(VITA_TITLEID "VIMM00001") set(VITA_TITLEID "VIMM00001")
set(VITA_VERSION "01.00") set(VITA_VERSION "01.00")
@@ -47,4 +47,5 @@ vita_create_self(${PROJECT_NAME}.self ${PROJECT_NAME})
vita_create_vpk(${PROJECT_NAME}.vpk ${VITA_TITLEID} ${PROJECT_NAME}.self vita_create_vpk(${PROJECT_NAME}.vpk ${VITA_TITLEID} ${PROJECT_NAME}.self
VERSION ${VITA_VERSION} VERSION ${VITA_VERSION}
NAME ${VITA_APP_NAME} NAME ${VITA_APP_NAME}
FILE ${CMAKE_SOURCE_DIR}/assets/sce_sys/icon0.png sce_sys/icon0.png
) )
+13
View File
@@ -37,6 +37,19 @@ This produces `build/vitaImmich.vpk`.
apikey=your-immich-api-key apikey=your-immich-api-key
``` ```
If your server sits behind a public domain (reverse proxy) and your
router does not support NAT loopback, add the server's LAN IP so the
domain is pinned to it while on your home network:
```
server=https://immich.example.com
apikey=your-immich-api-key
serverip=192.168.1.100
```
This keeps the Host header and TLS SNI on the domain (so the reverse
proxy still routes correctly) but connects to the LAN IP directly.
4. Relaunch the app. 4. Relaunch the app.
## Controls ## Controls
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+36 -1
View File
@@ -51,6 +51,9 @@ int _newlib_heap_size_user = 192 * 1024 * 1024;
static char g_server[512]; static char g_server[512];
static char g_apikey[256]; static char g_apikey[256];
static char g_serverip[64];
/* optional DNS pin ("host:port:ip") for routers without NAT loopback */
static struct curl_slist *g_resolve_list;
static vita2d_pgf *g_font; static vita2d_pgf *g_font;
@@ -167,7 +170,9 @@ static int load_config(void)
f = fopen(CONFIG_PATH, "w"); f = fopen(CONFIG_PATH, "w");
if (f) { if (f) {
fputs("server=http://192.168.1.100:2283\n" fputs("server=http://192.168.1.100:2283\n"
"apikey=PASTE_YOUR_IMMICH_API_KEY_HERE\n", f); "apikey=PASTE_YOUR_IMMICH_API_KEY_HERE\n"
"# serverip=192.168.1.100 (optional: LAN IP of the\n"
"# server, for routers without NAT loopback)\n", f);
fclose(f); fclose(f);
} }
return -1; return -1;
@@ -180,6 +185,8 @@ static int load_config(void)
snprintf(g_server, sizeof(g_server), "%s", clean_line(s + 7)); snprintf(g_server, sizeof(g_server), "%s", clean_line(s + 7));
else if (!strncmp(s, "apikey=", 7)) else if (!strncmp(s, "apikey=", 7))
snprintf(g_apikey, sizeof(g_apikey), "%s", clean_line(s + 7)); snprintf(g_apikey, sizeof(g_apikey), "%s", clean_line(s + 7));
else if (!strncmp(s, "serverip=", 9))
snprintf(g_serverip, sizeof(g_serverip), "%s", clean_line(s + 9));
} }
fclose(f); fclose(f);
@@ -200,6 +207,30 @@ static int load_config(void)
if (strncasecmp(g_server, "http://", 7) && strncasecmp(g_server, "https://", 8)) if (strncasecmp(g_server, "http://", 7) && strncasecmp(g_server, "https://", 8))
fatal_error(g_server, fatal_error(g_server,
"Bad server URL in config (must start with http:// or https://):"); "Bad server URL in config (must start with http:// or https://):");
/* serverip= pins the hostname to a fixed IP (keeps Host header and
* TLS SNI intact, unlike putting the IP in the URL) */
if (g_serverip[0]) {
int https = !strncasecmp(g_server, "https://", 8);
const char *host = strstr(g_server, "://") + 3;
char hostname[256];
size_t i = 0;
while (host[i] && host[i] != '/' && host[i] != ':' &&
i < sizeof(hostname) - 1) {
hostname[i] = host[i];
i++;
}
hostname[i] = '\0';
int port = https ? 443 : 80;
if (host[i] == ':')
port = atoi(host + i + 1);
char resolve[400];
snprintf(resolve, sizeof(resolve), "%s:%d:%s",
hostname, port, g_serverip);
g_resolve_list = curl_slist_append(NULL, resolve);
log_line("resolve pin: %s", resolve);
}
return 0; return 0;
} }
@@ -296,6 +327,8 @@ static CURLcode http_request(const char *url, const char *body,
/* PoC: no CA bundle on the Vita, skip TLS verification */ /* PoC: no CA bundle on the Vita, skip TLS verification */
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
if (g_resolve_list)
curl_easy_setopt(curl, CURLOPT_RESOLVE, g_resolve_list);
if (body) if (body)
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
if (g_verbose_request) { if (g_verbose_request) {
@@ -725,6 +758,8 @@ static int worker_thread(SceSize args, void *argp)
curl_easy_setopt(curl, CURLOPT_USERAGENT, "vitaImmich/0.1 (PS Vita)"); curl_easy_setopt(curl, CURLOPT_USERAGENT, "vitaImmich/0.1 (PS Vita)");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
if (g_resolve_list)
curl_easy_setopt(curl, CURLOPT_RESOLVE, g_resolve_list);
for (;;) { for (;;) {
if (g_req_state != REQ_PENDING) { if (g_req_state != REQ_PENDING) {