Skip to content

Commit 362f491

Browse files
committed
Ship one ESign-ready full IPA with dynamic tunnel bundle ID
Publish only DecoderSec.ipa (app+PacketTunnel). Discover the embedded appex id at runtime so ESign bundle remaps still connect the VPN.
1 parent 9fc9bcf commit 362f491

9 files changed

Lines changed: 98 additions & 42 deletions

File tree

‎.github/workflows/build-ipa.yml‎

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
CONFIGURATION: ${{ github.event.inputs.configuration || 'Release' }}
5050
SIGNING: ${{ github.event.inputs.signing || 'unsigned' }}
5151
LITE: '0'
52-
BOTH: '1'
52+
BOTH: '0'
5353
EVERYWHERE_CORE_MIN_VERSION: '2026.08.09'
5454

5555
steps:
@@ -113,8 +113,8 @@ jobs:
113113
run: |
114114
set -euo pipefail
115115
chmod +x Scripts/ci_export_ipa.sh Scripts/resign_ipa.sh build.sh
116-
# One archive → full VPN IPA + Lite (no PacketTunnel)
117-
BOTH=1 ./Scripts/ci_export_ipa.sh
116+
# One full VPN IPA for ESign / one-file install (app + PacketTunnel)
117+
BOTH=0 LITE=0 ./Scripts/ci_export_ipa.sh
118118
ls -lah build/ipa/
119119
120120
- name: Rename IPA with version
@@ -131,18 +131,12 @@ jobs:
131131
VER="v0.0.0-dev.${SHA_SHORT}"
132132
fi
133133
134-
FULL_SRC="$(ls build/ipa/DecoderSec-unsigned.ipa 2>/dev/null || true)"
135-
LITE_SRC="$(ls build/ipa/DecoderSec-lite-unsigned.ipa 2>/dev/null || true)"
136-
# Fallback if only one file was produced
137-
if [[ -z "$FULL_SRC" && -z "$LITE_SRC" ]]; then
138-
FULL_SRC="$(ls build/ipa/*.ipa | head -n1)"
139-
fi
140-
if [[ -n "$FULL_SRC" ]]; then
141-
mv "$FULL_SRC" "build/ipa/DecoderSec-${VER}-unsigned.ipa"
142-
fi
143-
if [[ -n "$LITE_SRC" ]]; then
144-
mv "$LITE_SRC" "build/ipa/DecoderSec-${VER}-lite-unsigned.ipa"
145-
fi
134+
SRC="$(ls build/ipa/DecoderSec-unsigned.ipa build/ipa/*.ipa 2>/dev/null | head -n1)"
135+
[[ -n "$SRC" ]] || { echo "no IPA produced"; exit 1; }
136+
# Single user-facing name + versioned copy
137+
cp "$SRC" "build/ipa/DecoderSec.ipa"
138+
mv "$SRC" "build/ipa/DecoderSec-${VER}.ipa"
139+
rm -f build/ipa/*lite* || true
146140
147141
echo "version=$VER" >> "$GITHUB_OUTPUT"
148142
PRE=false
@@ -156,7 +150,8 @@ jobs:
156150
with:
157151
name: DecoderSec-ipa-${{ steps.meta.outputs.version }}
158152
path: |
159-
build/ipa/*.ipa
153+
build/ipa/DecoderSec.ipa
154+
build/ipa/DecoderSec-*.ipa
160155
build/*.xcarchive
161156
if-no-files-found: error
162157
retention-days: 30
@@ -165,29 +160,27 @@ jobs:
165160
if: startsWith(github.ref, 'refs/tags/v')
166161
uses: softprops/action-gh-release@v2
167162
with:
168-
files: build/ipa/*.ipa
163+
files: |
164+
build/ipa/DecoderSec.ipa
165+
build/ipa/DecoderSec-${{ steps.meta.outputs.version }}.ipa
169166
generate_release_notes: true
170167
prerelease: ${{ steps.meta.outputs.prerelease == 'true' }}
171168
name: decoder sec. ${{ steps.meta.outputs.version }}
172169
body: |
173170
## decoder sec. `${{ steps.meta.outputs.version }}`
174171
175-
Two unsigned IPAs in this release (ad-hoc signed for install tools):
172+
**Один файл:** [`DecoderSec.ipa`](https://github.com/decoder-dev/decoder-sec/releases/download/${{ steps.meta.outputs.version }}/DecoderSec.ipa)
176173
177-
1. **`DecoderSec-…-lite-unsigned.ipa`** — UI only (no Packet Tunnel). Resign once for `com.decodersec.app`.
178-
2. **`DecoderSec-…-unsigned.ipa`** — full VPN. Resign with Network Extension for app + PacketTunnel (`Scripts/resign_ipa.sh`).
174+
### ESign (один раз)
175+
1. Скачай `DecoderSec.ipa`
176+
2. Открой в **ESign** → Импорт
177+
3. Подпись сертификатом с **Network Extension / VPN** (платный)
178+
4. Установи → открой → добавь конфиг → Connect
179179
180-
EverywhereCore is statically linked into the tunnel — it is **not** embedded as a Frameworks binary (that broke install on beta.5).
180+
Внутри: приложение + PacketTunnel. Без App Groups. Bundle ID extension подхватывается после ремапа ESign автоматически.
181181
182-
```bash
183-
./Scripts/resign_ipa.sh --ipa DecoderSec-…-unsigned.ipa \
184-
--profile App.mobileprovision \
185-
--identity "Apple Development: You (TEAMID)"
186-
```
187-
188-
- Bundle ID: `com.decodersec.app`
189-
- Tunnel: `com.decodersec.app.PacketTunnel`
190-
- Engines: Xray / sing-box / mihomo via EverywhereCore
182+
- Default IDs: `com.decodersec.app` + `com.decodersec.app.PacketTunnel`
183+
- Engines: Xray / sing-box / mihomo
191184
env:
192185
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
193186

@@ -199,14 +192,11 @@ jobs:
199192
name: decoder sec. ${{ steps.meta.outputs.version }}
200193
prerelease: true
201194
generate_release_notes: true
202-
files: build/ipa/*.ipa
195+
files: |
196+
build/ipa/DecoderSec.ipa
197+
build/ipa/DecoderSec-${{ steps.meta.outputs.version }}.ipa
203198
body: |
204-
## decoder sec. `${{ steps.meta.outputs.version }}` (prerelease)
205-
206-
Built from `${{ github.sha }}` via workflow_dispatch.
207-
208-
Prefer **lite** IPA if you only need the UI. Full IPA needs Network Extension when resigning.
209-
Use `Scripts/resign_ipa.sh` for one-command signing.
199+
Один файл: `DecoderSec.ipa` — ESign → подпись с Network Extension → установка.
210200
env:
211201
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
212202

‎CHANGELOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ All notable changes to **decoder sec.** are documented here.
99
- `ru` / `en` localization
1010
- Signed TestFlight builds
1111

12+
## [0.1.0-beta.7] — 2026-08-11
13+
14+
### Changed
15+
- **Один файл:** релиз публикует только полный `DecoderSec.ipa` (app + PacketTunnel) для ESign
16+
- Tunnel bundle ID читается из встроенного `.appex` — переживает ремап Bundle ID в ESign
17+
- `NSExtensionPrincipalClass` = `PacketTunnelProvider` (`@objc`) без имени модуля
18+
19+
### Notes
20+
- ESign: один импорт → одна подпись (серт с Network Extension) → установка → конфиг → Connect
21+
1222
## [0.1.0-beta.6] — 2026-08-11
1323

1424
### Fixed

‎DecoderSecTunnel/Info.plist‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<key>NSExtensionPointIdentifier</key>
1313
<string>com.apple.networkextension.packet-tunnel</string>
1414
<key>NSExtensionPrincipalClass</key>
15-
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
15+
<string>PacketTunnelProvider</string>
1616
</dict>
1717
</dict>
1818
</plist>

‎DecoderSecTunnel/PacketTunnelProvider.swift‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import EverywhereCore
1010
import Network
1111
import NetworkExtension
1212

13+
@objc(PacketTunnelProvider)
1314
final class PacketTunnelProvider: NEPacketTunnelProvider {
1415
private static let tunnelMTU = 1500
1516

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838

3939
## Downloads
4040

41-
Prerelease IPAs: **[GitHub Releases](https://github.com/decoder-dev/decoder-sec/releases)** (unsigned — resign before install).
41+
**Один файл:** [Releases](https://github.com/decoder-dev/decoder-sec/releases) → `DecoderSec.ipa`
42+
ESign: импорт → подпись (Network Extension) → установка. См. [docs/ESIGN.md](./docs/ESIGN.md).
4243

4344
## Build
4445

‎Scripts/ci_export_ipa.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ IPA_DIR="${IPA_DIR:-$ROOT/build/ipa}"
3434
SIGNING="${SIGNING:-unsigned}"
3535
EXPORT_OPTIONS="${EXPORT_OPTIONS:-$ROOT/Config/ExportOptions-ad-hoc.plist}"
3636
LITE="${LITE:-0}"
37-
BOTH="${BOTH:-1}"
37+
BOTH="${BOTH:-0}"
3838

3939
APP_ENTITLEMENTS="$ROOT/DecoderSec/DecoderSec.entitlements"
4040
TUNNEL_ENTITLEMENTS="$ROOT/DecoderSecTunnel/DecoderSecTunnel.entitlements"

‎Shared/BrandIdentity.swift‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ enum BrandIdentity {
1010
static let shortName = "decoder"
1111
static let legalName = "Decoder Sec"
1212
static let tagline = "Private tunnels. Your rules."
13+
14+
/// Default IDs used at build time. After ESign remaps the bundle ID,
15+
/// prefer ``EVCore.Identifier/networkExtension`` which reads the embedded appex.
1316
static let bundleID = "com.decodersec.app"
1417
static let networkExtensionID = "com.decodersec.app.PacketTunnel"
1518
}

‎Shared/EVCore.swift‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,40 @@ import Foundation
99

1010
enum EVCore {
1111
enum Identifier {
12-
static let bundle = BrandIdentity.bundleID
13-
static let networkExtension = BrandIdentity.networkExtensionID
12+
/// Host app bundle id (survives ESign remap).
13+
static var bundle: String {
14+
Bundle.main.bundleIdentifier ?? BrandIdentity.bundleID
15+
}
16+
17+
/// Packet Tunnel provider id. Discovers the embedded `.appex` so ESign
18+
/// can change both IDs in one sign and the app still finds the extension.
19+
static var networkExtension: String {
20+
if let embedded = embeddedTunnelBundleID() {
21+
return embedded
22+
}
23+
let base = bundle
24+
if base.contains("PacketTunnel") || base.lowercased().hasSuffix(".tunnel") || base.lowercased().hasSuffix(".ne") {
25+
return base
26+
}
27+
return base + ".PacketTunnel"
28+
}
29+
1430
static let tunnelDescription = BrandIdentity.displayName
31+
32+
private static func embeddedTunnelBundleID() -> String? {
33+
guard let plugins = Bundle.main.builtInPlugInsURL else { return nil }
34+
guard let urls = try? FileManager.default.contentsOfDirectory(
35+
at: plugins,
36+
includingPropertiesForKeys: nil,
37+
options: [.skipsHiddenFiles]
38+
) else { return nil }
39+
for url in urls where url.pathExtension == "appex" {
40+
if let id = Bundle(url: url)?.bundleIdentifier, !id.isEmpty {
41+
return id
42+
}
43+
}
44+
return nil
45+
}
1546
}
1647

1748
static let defaultDNSServers = ["1.1.1.1", "8.8.8.8"]

‎docs/ESIGN.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# ESign — один файл
2+
3+
Скачай **`DecoderSec.ipa`** из [Releases](https://github.com/decoder-dev/decoder-sec/releases) (полный VPN, не lite).
4+
5+
## Шаги
6+
7+
1. Открой **ESign** на iPhone.
8+
2. Импорт → выбери `DecoderSec.ipa`.
9+
3. Подпись **платным** сертификатом с правом **Network Extension / VPN**
10+
(бесплатный Apple ID VPN не даст — это ограничение Apple, не приложения).
11+
4. Установи → разреши VPN-профиль при первом Connect.
12+
5. В приложении добавь конфиг (или `happ://` ссылку) → **Connect**.
13+
14+
Один IPA уже содержит приложение и Packet Tunnel. App Groups не нужны.
15+
После смены Bundle ID в ESign extension находится автоматически.
16+
17+
## Если Connect не поднимает VPN
18+
19+
Сертификат без Network Extension: приложение откроется, туннель — нет.
20+
Нужен платный серт/профиль с `packet-tunnel-provider` для app + extension.

0 commit comments

Comments
 (0)