Skip to content

Commit edcf3f4

Browse files
committed
Add id for a running gitblit instance
1 parent f751da2 commit edcf3f4

File tree

3 files changed

+426
-1
lines changed

3 files changed

+426
-1
lines changed
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package com.gitblit.instance;
2+
3+
import com.gitblit.utils.FileUtils;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
7+
import java.io.File;
8+
import java.io.IOException;
9+
import java.net.InetAddress;
10+
import java.net.NetworkInterface;
11+
import java.net.Socket;
12+
import java.util.ArrayList;
13+
import java.util.Enumeration;
14+
import java.util.List;
15+
import java.util.UUID;
16+
17+
/**
18+
* The instance id is a unique identifier for an installed Gitblit instance.
19+
*
20+
* This is used to track the number of Gitblit instances in the field.
21+
* Its purpose is to gauge the popularity of Gitblit and to help
22+
* prioritize feature requests.
23+
*
24+
* The instance id should be unique between different instances, even
25+
* on the same machine. But it should stay the same between restarts of
26+
* the same instance. It should also stay the same between upgrades of
27+
* the same instance. Therefore, it must be stored in a file that is
28+
* not overwritten during upgrades, once it has been created.
29+
*/
30+
public class GitblitInstanceId
31+
{
32+
static final String STORAGE_FILE = "gbins";
33+
34+
private final Logger log = LoggerFactory.getLogger(getClass());
35+
36+
private final File idFileBase;
37+
38+
private UUID id;
39+
40+
41+
/**
42+
* Constructor.
43+
*/
44+
public GitblitInstanceId()
45+
{
46+
this.idFileBase = null;
47+
}
48+
49+
/**
50+
* Constructor.
51+
*/
52+
public GitblitInstanceId(File idFileBase)
53+
{
54+
this.idFileBase = idFileBase;
55+
}
56+
57+
58+
/**
59+
* Get the instance id.
60+
*
61+
* @return the instance id.
62+
*/
63+
public UUID getId() {
64+
if (this.id == null) {
65+
load();
66+
}
67+
return this.id;
68+
}
69+
70+
71+
/**
72+
* Load the instance id from the file.
73+
*/
74+
private void load()
75+
{
76+
if (this.idFileBase == null) {
77+
// Not working with stored id.
78+
log.debug("No id file base directory specified. Generated id is not persisted.");
79+
generate();
80+
return;
81+
}
82+
83+
File idFile = new File(this.idFileBase, STORAGE_FILE);
84+
if (idFile.exists()) {
85+
// Read the file
86+
String uuidString = readFromFile(idFile);
87+
88+
// Parse the UUID
89+
try {
90+
this.id = UUID.fromString(uuidString);
91+
return;
92+
}
93+
catch (IllegalArgumentException e) {
94+
log.debug("Unable to parse instance id. Will generate a new one: {}", e.getMessage(), e);
95+
}
96+
}
97+
98+
// Generate a new instance id and persist it to disk.
99+
generate();
100+
storeToFile(idFile);
101+
}
102+
103+
104+
private String readFromFile(File idfile)
105+
{
106+
// log.debug("Loading instance id from file: {}", idfile.getAbsolutePath());
107+
108+
String string = FileUtils.readContent(idfile, null).trim();
109+
String uuidString = string.replaceAll("\\s+","");
110+
return uuidString.trim();
111+
}
112+
113+
private void storeToFile(File idfile)
114+
{
115+
// Make sure that the directory exists
116+
if (!idfile.getParentFile().exists()) {
117+
if (!idfile.getParentFile().mkdirs()) {
118+
log.debug("Unable to create directory for instance id file: {}", idfile.getParentFile().getAbsolutePath());
119+
return;
120+
}
121+
}
122+
123+
// Write the UUID to the file
124+
String uuidString = this.id.toString();
125+
FileUtils.writeContent(idfile, uuidString);
126+
}
127+
128+
129+
/**
130+
* Generate a new instance id and persist it to disk.
131+
*
132+
* UUID is variant, i.e. OSF DCE, version 8, a custom format.
133+
* xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
134+
* date -rand-8rnd-8rnd- rand OUI
135+
*
136+
* The variant nibble has the variant (1) in the upper two bits as 0b10xx,
137+
* and the lower two bits are used as a version, currently 0bxx00.
138+
* Should the format of this UUID change, the version can be incremented
139+
* to 0bxx01 or 0bxx10. Further increments would set the bits in the variant
140+
* nibble to 0bxx11 and employ more bits from the next nibble for further
141+
* differentiation.
142+
*/
143+
private void generate()
144+
{
145+
// Start with a random UUID
146+
UUID id = UUID.randomUUID();
147+
long upper = id.getMostSignificantBits();
148+
long lower = id.getLeastSignificantBits();
149+
150+
151+
// Set the variant bits to 0b1000, variant 1, our version 0.
152+
lower &= 0x0FFFFFFFFFFFFFFFL; // Clear the variant bits
153+
lower |= 0x8000000000000000L; // Set the variant bits to 0b1000
154+
155+
// Set the version bits to 0b1000, version 8.
156+
upper &= 0xFFFFFFFFFFFF0FFFL; // Clear the version bits
157+
upper |= 0x0000000000008000L; // Set the version bits to 0b1000
158+
159+
160+
// Set the first four bytes to represent the date.
161+
long date = System.currentTimeMillis();
162+
date &= 0xFFFFFFFFFFFF0000L; // Clear the last two bytes, those are only a few minutes.
163+
date <<= 2 * 8; // We do not need the upper two bytes, that is too far into the future.
164+
165+
upper &= 0x00000000FFFFFFFFL; // Clear the date bits.
166+
upper |= date; // Set the date in the upper 32 bits.
167+
168+
169+
// Set the OUI in the lower three bytes.
170+
Long oui = getNodeOUI();
171+
if (oui != null) {
172+
lower &= 0xFFFFFFFFFF000000L; // Clear the OUI bits.
173+
lower |= (0x1000000L | oui); // Set the OUI in the lower three bytes. Mark as valid OUI in bit above them.
174+
}
175+
else {
176+
// Mark this as an invalid OUI, i.e. random bits, by setting the bit above the OUI bits to zero.
177+
lower &= 0xFFFFFFFFFEFFFFFFL; // Clear the valid OUI indicator bit.
178+
}
179+
180+
this.id = new UUID(upper, lower);
181+
}
182+
183+
184+
/**
185+
* Get the OUI of one NIC of this host.
186+
*
187+
* @return null if no OUI could be detected, otherwise the OUI in the lower three bytes of a Long.
188+
*/
189+
private Long getNodeOUI()
190+
{
191+
byte[] node = null;
192+
String logPrefix = "Unable to detect host. Use random value.";
193+
194+
try {
195+
InetAddress ipa = InetAddress.getLocalHost();
196+
NetworkInterface iface = NetworkInterface.getByInetAddress(ipa);
197+
if (iface != null) {
198+
node = iface.getHardwareAddress();
199+
logPrefix = "From getLocalHost:";
200+
}
201+
202+
if (node == null) {
203+
List<byte[]> macs = new ArrayList<>();
204+
List<byte[]> offmacs = new ArrayList<>();
205+
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
206+
while (interfaces.hasMoreElements()) {
207+
iface = interfaces.nextElement();
208+
byte[] mac = iface.getHardwareAddress();
209+
if (mac != null) {
210+
if (iface.isLoopback()) {
211+
continue;
212+
}
213+
if (iface.isVirtual() || iface.isPointToPoint()) {
214+
continue;
215+
}
216+
if (iface.isUp()) {
217+
macs.add(mac);
218+
}
219+
else {
220+
offmacs.add(mac);
221+
}
222+
}
223+
}
224+
225+
if (macs.size() == 1) {
226+
node = macs.get(0);
227+
logPrefix = "From up iface:";
228+
}
229+
else if (offmacs.size() == 1) {
230+
node = offmacs.get(0);
231+
logPrefix = "From down iface:";
232+
}
233+
}
234+
235+
if (node == null) {
236+
Socket socket = new Socket("www.gitblit.dev", 80);
237+
ipa = socket.getLocalAddress();
238+
socket.close();
239+
iface = NetworkInterface.getByInetAddress(ipa);
240+
if (iface != null) {
241+
node = iface.getHardwareAddress();
242+
logPrefix = "From socket:";
243+
}
244+
}
245+
246+
if (node == null) {
247+
log.debug(logPrefix);
248+
return null;
249+
}
250+
251+
if (log.isDebugEnabled()) {
252+
log.debug("{} {}", logPrefix, String.format("%02X:%02X:%02X", node[0], node[1], node[2]));
253+
}
254+
255+
long l = (((long)node[0]) << 16) & 0xff0000;
256+
l |= (((long)node[1]) << 8) & 0xff00;
257+
l |= ((long)node[2]) & 0xff;
258+
return l;
259+
}
260+
catch (IOException e) {
261+
log.debug("Exception while getting OUI: {}", e.getMessage(), e);
262+
return null;
263+
}
264+
}
265+
}

0 commit comments

Comments
 (0)